Moves the relay structure to quartz

This commit is contained in:
Vitor Pamplona
2025-01-14 12:00:37 -05:00
parent e36e49cc88
commit 86a9fb2af5
46 changed files with 96 additions and 89 deletions
@@ -21,7 +21,7 @@
package com.vitorpamplona.quartz.experimental.limits
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relays.Filter
import com.vitorpamplona.quartz.nip01Core.relays.filters.Filter
import com.vitorpamplona.quartz.nip13Pow.getPoWRank
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
import androidx.collection.LruCache
import com.vitorpamplona.quartz.utils.TimeUtils
class RelayStat(
var receivedBytes: Long = 0L,
var sentBytes: Long = 0L,
var spamCounter: Long = 0L,
var errorCounter: Long = 0L,
var pingInMs: Long = 0L,
) {
val messages = LruCache<RelayDebugMessage, RelayDebugMessage>(100)
fun newNotice(notice: String?) {
val debugMessage =
RelayDebugMessage(
type = RelayDebugMessageType.NOTICE,
message = notice ?: "No error message provided",
)
messages.put(debugMessage, debugMessage)
}
fun newError(error: String?) {
errorCounter++
val debugMessage =
RelayDebugMessage(
type = RelayDebugMessageType.ERROR,
message = error ?: "No error message provided",
)
messages.put(debugMessage, debugMessage)
}
fun addBytesReceived(bytesUsedInMemory: Long) {
receivedBytes += bytesUsedInMemory
}
fun addBytesSent(bytesUsedInMemory: Long) {
sentBytes += bytesUsedInMemory
}
fun newSpam(spamDescriptor: String) {
spamCounter++
val debugMessage =
RelayDebugMessage(
type = RelayDebugMessageType.SPAM,
message = spamDescriptor,
)
messages.put(debugMessage, debugMessage)
}
}
class RelayDebugMessage(
val type: RelayDebugMessageType,
val message: String,
val time: Long = TimeUtils.now(),
)
enum class RelayDebugMessageType {
SPAM,
NOTICE,
ERROR,
}
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
enum class RelayState {
// Websocket connected
CONNECTED,
// Websocket disconnecting
DISCONNECTING,
// Websocket disconnected
DISCONNECTED,
}
@@ -0,0 +1,487 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.AuthMessage
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.ClosedMessage
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.EoseMessage
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.NoticeMessage
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.NotifyMessage
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.OkMessage
import com.vitorpamplona.quartz.nip01Core.relays.commands.toClient.ToClientParser
import com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.AuthCmd
import com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.CloseCmd
import com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.CountCmd
import com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.EventCmd
import com.vitorpamplona.quartz.nip01Core.relays.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebSocket
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebSocketListener
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebsocketBuilder
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.cancellation.CancellationException
class SimpleClientRelay(
val url: String,
val socketBuilder: WebsocketBuilder,
val subs: SubscriptionCollection,
val listener: Listener,
val stats: RelayStat = RelayStat(),
) {
companion object {
// waits 3 minutes to reconnect once things fail
const val RECONNECTING_IN_SECONDS = 60 * 3
}
private var socket: WebSocket? = null
private var isReady: Boolean = false
private var usingCompression: Boolean = false
private var lastConnectTentative: Long = 0L
private var afterEOSEPerSubscription = mutableMapOf<String, Boolean>()
private val authResponseWatcher = mutableMapOf<HexKey, Boolean>()
private val authChallengesSent = mutableSetOf<String>()
/**
* Auth procedures require us to keep track of the outgoing events
* to make sure the relay waits for the auth to finish and send them.
*/
private val outboxCache = mutableMapOf<HexKey, Event>()
private var connectingMutex = AtomicBoolean()
private val parser = ToClientParser()
fun isConnectionStarted(): Boolean = socket != null
fun isConnected(): Boolean = socket != null && isReady
fun connect() = connectAndRunOverride(::sendEverything)
fun sendEverything() {
renewSubscriptions()
sendOutbox()
}
fun sendOutbox() {
synchronized(outboxCache) {
outboxCache.values.forEach {
send(it)
}
}
}
fun connectAndRunAfterSync(onConnected: () -> Unit) {
connectAndRunOverride {
sendEverything()
onConnected()
}
}
fun connectAndRunOverride(onConnected: () -> Unit) {
Log.d("Relay", "Relay.connect $url isAlreadyConnecting: ${connectingMutex.get()}")
// If there is a connection, don't wait.
if (connectingMutex.getAndSet(true)) {
return
}
try {
if (socket != null) {
connectingMutex.set(false)
return
}
lastConnectTentative = TimeUtils.now()
socket = socketBuilder.build(url, RelayListener(onConnected))
socket?.connect()
} catch (e: Exception) {
if (e is CancellationException) throw e
stats.newError(e.message ?: "Error trying to connect: ${e.javaClass.simpleName}")
markConnectionAsClosed()
e.printStackTrace()
} finally {
connectingMutex.set(false)
}
}
inner class RelayListener(
val onConnected: () -> Unit,
) : WebSocketListener {
override fun onOpen(
pingMillis: Long,
compression: Boolean,
) {
Log.d("Relay", "Connect onOpen $url $socket")
markConnectionAsReady(pingMillis, compression)
// Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url")
onConnected()
listener.onRelayStateChange(this@SimpleClientRelay, RelayState.CONNECTED)
}
override fun onMessage(text: String) {
stats.addBytesReceived(text.bytesUsedInMemory())
try {
processNewRelayMessage(text)
} catch (e: Throwable) {
if (e is CancellationException) throw e
stats.newError("Error processing: $text")
Log.e("Relay", "Error processing: $text")
listener.onError(this@SimpleClientRelay, "", Error("Error processing $text"))
}
}
override fun onClosing(
code: Int,
reason: String,
) {
Log.w("Relay", "Relay onClosing $url: $reason")
listener.onRelayStateChange(this@SimpleClientRelay, RelayState.DISCONNECTING)
}
override fun onClosed(
code: Int,
reason: String,
) {
markConnectionAsClosed()
Log.w("Relay", "Relay onClosed $url: $reason")
listener.onRelayStateChange(this@SimpleClientRelay, RelayState.DISCONNECTED)
}
override fun onFailure(
t: Throwable,
response: String?,
) {
socket?.cancel() // 1000, "Normal close"
// checks if this is an actual failure. Closing the socket generates an onFailure as well.
if (!(socket == null && (t.message == "Socket is closed" || t.message == "Socket closed"))) {
stats.newError(response ?: t.message ?: "onFailure event from server: ${t.javaClass.simpleName}")
}
// Failures disconnect the relay.
markConnectionAsClosed()
Log.w("Relay", "Relay onFailure $url, $response $response ${t.message} $socket")
t.printStackTrace()
listener.onError(
this@SimpleClientRelay,
"",
Error("WebSocket Failure. Response: $response. Exception: ${t.message}", t),
)
}
}
fun markConnectionAsReady(
pingInMs: Long,
usingCompression: Boolean,
) {
this.resetEOSEStatuses()
this.isReady = true
this.usingCompression = usingCompression
stats.pingInMs = pingInMs
}
fun markConnectionAsClosed() {
this.socket = null
this.isReady = false
this.usingCompression = false
this.resetEOSEStatuses()
}
fun processNewRelayMessage(newMessage: String) {
when (val msg = parser.parse(newMessage)) {
is EventMessage -> {
// Log.w("Relay", "Relay onEVENT $url $newMessage")
listener.onEvent(this, msg.subId, msg.event, afterEOSEPerSubscription[msg.subId] == true)
}
is EoseMessage -> {
// Log.w("Relay", "Relay onEOSE $url $newMessage")
afterEOSEPerSubscription[msg.subId] = true
listener.onEOSE(this@SimpleClientRelay, msg.subId)
}
is NoticeMessage -> {
// Log.w("Relay", "Relay onNotice $url, $newMessage")
stats.newNotice(msg.message)
listener.onError(this@SimpleClientRelay, msg.message, Error("Relay sent notice: $msg.message"))
}
is OkMessage -> {
Log.w("Relay", "Relay on OK $url, $newMessage")
// if this is the OK of an auth event, renew all subscriptions and resend all outgoing events.
if (authResponseWatcher.containsKey(msg.eventId)) {
val wasAlreadyAuthenticated = authResponseWatcher.get(msg.eventId)
authResponseWatcher.put(msg.eventId, msg.success)
if (wasAlreadyAuthenticated != true && msg.success) {
sendEverything()
}
}
// remove from cache for any error that is not an auth required error.
// for auth required, we will do the auth and try to send again.
if (outboxCache.contains(msg.eventId) && !msg.message.startsWith("auth-required")) {
synchronized(outboxCache) {
outboxCache.remove(msg.eventId)
}
}
if (!msg.success) {
stats.newNotice("Rejected event $msg.eventId: $msg.message")
}
listener.onSendResponse(this@SimpleClientRelay, msg.eventId, msg.success, msg.message)
}
is AuthMessage -> {
// Log.d("Relay", "Relay onAuth $url, $newMessage")
listener.onAuth(this@SimpleClientRelay, msg.challenge)
}
is NotifyMessage -> {
// Log.w("Relay", "Relay onNotify $url, $newMessage")
listener.onNotify(this@SimpleClientRelay, msg.message)
}
is ClosedMessage -> {
// Log.w("Relay", "Relay Closed Subscription $url, $newMessage")
listener.onClosed(this@SimpleClientRelay, msg.subscriptionId, msg.message)
}
else -> {
stats.newError("Unsupported message: $newMessage")
Log.w("Relay", "Unsupported message: $newMessage")
listener.onError(this, "", Error("Unsupported message: $newMessage"))
}
}
}
fun disconnect() {
Log.d("Relay", "Relay.disconnect $url")
lastConnectTentative = 0L // this is not an error, so prepare to reconnect as soon as requested.
socket?.cancel()
socket = null
isReady = false
usingCompression = false
resetEOSEStatuses()
}
fun resetEOSEStatuses() {
afterEOSEPerSubscription = LinkedHashMap(afterEOSEPerSubscription.size)
authResponseWatcher.clear()
authChallengesSent.clear()
}
fun sendRequest(
requestId: String,
filters: List<Filter>,
) {
if (isConnectionStarted()) {
if (isReady) {
if (filters.isNotEmpty()) {
writeToSocket(
com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.ReqCmd
.toJson(requestId, filters),
)
afterEOSEPerSubscription[requestId] = false
}
}
} else {
// waits 60 seconds to reconnect after disconnected.
if (TimeUtils.now() > lastConnectTentative + RECONNECTING_IN_SECONDS) {
// sends all filters after connection is successful.
connect()
}
}
}
fun sendCount(
requestId: String,
filters: List<Filter>,
) {
if (isConnectionStarted()) {
if (isReady) {
if (filters.isNotEmpty()) {
writeToSocket(CountCmd.toJson(requestId, filters))
afterEOSEPerSubscription[requestId] = false
}
}
} else {
// waits 60 seconds to reconnect after disconnected.
if (TimeUtils.now() > lastConnectTentative + RECONNECTING_IN_SECONDS) {
// sends all filters after connection is successful.
connect()
}
}
}
fun connectAndSendFiltersIfDisconnected() {
if (socket == null) {
// waits 60 seconds to reconnect after disconnected.
if (TimeUtils.now() > lastConnectTentative + RECONNECTING_IN_SECONDS) {
connect()
}
}
}
fun renewSubscriptions() {
// Force update all filters after AUTH.
subs.allSubscriptions().forEach {
sendRequest(requestId = it.id, it.filters)
}
}
fun send(signedEvent: Event) {
listener.onBeforeSend(this@SimpleClientRelay, signedEvent)
if (signedEvent is RelayAuthEvent) {
sendAuth(signedEvent)
} else {
sendEvent(signedEvent)
}
}
fun sendAuth(signedEvent: RelayAuthEvent) {
val challenge = signedEvent.challenge() ?: ""
// only send replies to new challenges to avoid infinite loop:
// 1. Auth is sent
// 2. auth is rejected
// 3. auth is requested
// 4. auth is sent
// ...
if (!authChallengesSent.contains(challenge)) {
authResponseWatcher[signedEvent.id] = false
authChallengesSent.add(challenge)
writeToSocket(AuthCmd.toJson(signedEvent))
}
}
fun sendEvent(signedEvent: Event) {
synchronized(outboxCache) {
outboxCache.put(signedEvent.id, signedEvent)
}
if (isConnectionStarted()) {
if (isReady) {
writeToSocket(EventCmd.toJson(signedEvent))
}
} else {
// automatically sends all filters after connection is successful.
connect()
}
}
private fun writeToSocket(str: String) {
if (socket == null) {
listener.onError(
this@SimpleClientRelay,
"",
Error("Failed to send $str. Relay is not connected."),
)
}
socket?.let {
val result = it.send(str)
listener.onSend(this@SimpleClientRelay, str, result)
stats.addBytesSent(str.bytesUsedInMemory())
Log.d("Relay", "Relay send $url (${str.length} chars) $str")
}
}
fun close(subscriptionId: String) {
writeToSocket(CloseCmd.toJson(subscriptionId))
}
interface Listener {
fun onEvent(
relay: SimpleClientRelay,
subscriptionId: String,
event: Event,
afterEOSE: Boolean,
)
fun onEOSE(
relay: SimpleClientRelay,
subscriptionId: String,
)
fun onError(
relay: SimpleClientRelay,
subscriptionId: String,
error: Error,
)
fun onAuth(
relay: SimpleClientRelay,
challenge: String,
)
fun onRelayStateChange(
relay: SimpleClientRelay,
type: RelayState,
)
fun onNotify(
relay: SimpleClientRelay,
description: String,
)
fun onClosed(
relay: SimpleClientRelay,
subscriptionId: String,
message: String,
)
fun onBeforeSend(
relay: SimpleClientRelay,
event: Event,
)
fun onSend(
relay: SimpleClientRelay,
msg: String,
success: Boolean,
)
fun onSendResponse(
relay: SimpleClientRelay,
eventId: String,
success: Boolean,
message: String,
)
}
}
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
import com.vitorpamplona.quartz.nip01Core.relays.filters.Filter
import java.util.UUID
class Subscription(
val id: String = UUID.randomUUID().toString().substring(0, 4),
val filters: List<Filter>,
)
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relays.filters.Filter
interface SubscriptionCollection {
fun isActive(subscriptionId: String): Boolean
fun getFilters(subscriptionId: String): List<Filter>
fun allSubscriptions(): List<Subscription>
fun match(
subscriptionId: String,
event: Event,
): Boolean
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.fasterxml.jackson.databind.JsonNode
class AuthMessage(
val challenge: String,
) : Message {
companion object {
const val LABEL = "AUTH"
@JvmStatic
fun parse(msgArray: JsonNode): AuthMessage =
AuthMessage(
msgArray.get(1).asText(),
)
}
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.fasterxml.jackson.databind.JsonNode
class ClosedMessage(
val subscriptionId: String,
val message: String,
) : Message {
companion object {
const val LABEL = "CLOSED"
@JvmStatic
fun parse(msgArray: JsonNode): ClosedMessage =
ClosedMessage(
msgArray[1].asText(),
msgArray[2].asText(),
)
}
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.fasterxml.jackson.databind.JsonNode
class EoseMessage(
val subId: String,
) : Message {
companion object {
const val LABEL = "EOSE"
@JvmStatic
fun parse(msgArray: JsonNode): EoseMessage =
EoseMessage(
msgArray.get(1).asText(),
)
}
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.fasterxml.jackson.databind.JsonNode
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
class EventMessage(
val subId: String,
val event: Event,
) : Message {
companion object {
const val LABEL = "EVENT"
@JvmStatic
fun parse(msgArray: JsonNode): EventMessage =
EventMessage(
msgArray.get(1).asText(),
EventMapper.fromJson(msgArray.get(2)),
)
}
}
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
interface Message
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.fasterxml.jackson.databind.JsonNode
class NoticeMessage(
val message: String,
) : Message {
companion object {
const val LABEL = "NOTICE"
@JvmStatic
fun parse(msgArray: JsonNode): NoticeMessage =
NoticeMessage(
msgArray.get(1).asText(),
)
}
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.fasterxml.jackson.databind.JsonNode
class NotifyMessage(
val message: String,
) : Message {
companion object {
const val LABEL = "NOTIFY"
@JvmStatic
fun parse(msgArray: JsonNode): NotifyMessage =
NotifyMessage(
msgArray.get(1).asText(),
)
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.fasterxml.jackson.databind.JsonNode
import com.vitorpamplona.quartz.nip01Core.HexKey
class OkMessage(
val eventId: HexKey,
val success: Boolean,
val message: String,
) : Message {
companion object {
const val LABEL = "OK"
@JvmStatic
fun parse(msgArray: JsonNode): OkMessage =
OkMessage(
msgArray[1].asText(),
msgArray[2].asBoolean(),
if (msgArray.size() > 2) msgArray[3].asText() else "",
)
}
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toClient
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
class ToClientParser {
fun parse(newMessage: String): Message? {
val msgArray = EventMapper.mapper.readTree(newMessage)
val type = msgArray.get(0).asText()
return when (type) {
EventMessage.LABEL -> EventMessage.parse(msgArray)
EoseMessage.LABEL -> EoseMessage.parse(msgArray)
NoticeMessage.LABEL -> NoticeMessage.parse(msgArray)
OkMessage.LABEL -> OkMessage.parse(msgArray)
AuthMessage.LABEL -> AuthMessage.parse(msgArray)
NotifyMessage.LABEL -> NotifyMessage.parse(msgArray)
ClosedMessage.LABEL -> ClosedMessage.parse(msgArray)
else -> null
}
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay
import com.fasterxml.jackson.databind.JsonNode
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
class AuthCmd(
val event: RelayAuthEvent,
) : Command {
companion object {
const val LABEL = "AUTH"
@JvmStatic
fun toJson(authEvent: RelayAuthEvent): String = """["AUTH",${authEvent.toJson()}]"""
@JvmStatic
fun parse(msgArray: JsonNode): AuthCmd =
AuthCmd(
EventMapper.fromJson(msgArray.get(1)) as RelayAuthEvent,
)
}
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay
import com.fasterxml.jackson.databind.JsonNode
class CloseCmd(
val subscriptionId: String,
) : Command {
companion object {
const val LABEL = "CLOSE"
@JvmStatic
fun toJson(subscriptionId: String): String = """["CLOSE","$subscriptionId"]"""
@JvmStatic
fun parse(msgArray: JsonNode): CloseCmd =
CloseCmd(
msgArray.get(1).asText(),
)
}
}
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay
interface Command
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.relays.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relays.filters.FilterDeserializer
import com.vitorpamplona.quartz.utils.joinToStringLimited
class CountCmd(
val subscriptionId: String,
val filters: List<Filter>,
) : Command {
companion object {
const val LABEL = "COUNT"
@JvmStatic
fun toJson(
requestId: String,
filters: List<Filter>,
): String =
filters.joinToStringLimited(
separator = ",",
limit = 19,
prefix = """["COUNT","$requestId",""",
postfix = "]",
) {
it.toJson()
}
@JvmStatic
fun parse(msgArray: JsonNode): CountCmd {
val filters = mutableListOf<Filter>()
for (i in 2 until msgArray.size()) {
val json = EventMapper.mapper.readTree(msgArray.get(i).asText())
if (json is ObjectNode) {
filters.add(FilterDeserializer.fromJson(json))
}
}
return CountCmd(msgArray.get(1).asText(), filters)
}
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay
import com.fasterxml.jackson.databind.JsonNode
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
class EventCmd(
val event: Event,
) : Command {
companion object {
const val LABEL = "EVENT"
@JvmStatic
fun toJson(event: Event): String = """["EVENT",${event.toJson()}]"""
@JvmStatic
fun parse(msgArray: JsonNode): EventCmd =
EventCmd(
EventMapper.fromJson(msgArray.get(1)),
)
}
}
@@ -0,0 +1,69 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.relays.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relays.filters.FilterDeserializer
import com.vitorpamplona.quartz.utils.joinToStringLimited
class ReqCmd(
val subscriptionId: String,
val filters: List<Filter>,
) : com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.Command {
companion object {
const val LABEL = "REQ"
@JvmStatic
fun toJson(
requestId: String,
filters: List<Filter>,
limit: Int = 19,
): String =
filters.joinToStringLimited(
separator = ",",
limit = limit,
prefix = """["REQ","$requestId",""",
postfix = "]",
) {
it.toJson()
}
@JvmStatic
fun parse(msgArray: JsonNode): com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.ReqCmd {
val filters = mutableListOf<Filter>()
for (i in 2 until msgArray.size()) {
val json = EventMapper.mapper.readTree(msgArray.get(i).asText())
if (json is ObjectNode) {
filters.add(FilterDeserializer.fromJson(json))
}
}
return com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.ReqCmd(
msgArray.get(1).asText(),
filters,
)
}
}
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
class ToRelayParser {
fun parse(newMessage: String): Command? {
val msgArray = EventMapper.mapper.readTree(newMessage)
val type = msgArray.get(0).asText()
return when (type) {
AuthCmd.LABEL -> AuthCmd.parse(msgArray)
CloseCmd.LABEL -> CloseCmd.parse(msgArray)
CountCmd.LABEL -> CountCmd.parse(msgArray)
EventCmd.LABEL -> EventCmd.parse(msgArray)
com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.ReqCmd.LABEL ->
com.vitorpamplona.quartz.nip01Core.relays.commands.toRelay.ReqCmd
.parse(msgArray)
else -> null
}
}
}
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
package com.vitorpamplona.quartz.nip01Core.relays.filters
import com.vitorpamplona.quartz.nip01Core.core.Event
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
package com.vitorpamplona.quartz.nip01Core.relays.filters
import com.fasterxml.jackson.databind.node.ObjectNode
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
package com.vitorpamplona.quartz.nip01Core.relays.filters
import com.vitorpamplona.quartz.nip01Core.core.Event
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays
package com.vitorpamplona.quartz.nip01Core.relays.filters
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.fasterxml.jackson.databind.node.ObjectNode
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.sockets
interface WebSocket {
fun connect()
fun cancel()
fun send(msg: String): Boolean
}
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.sockets
interface WebSocketListener {
fun onOpen(
pingMillis: Long,
compression: Boolean,
)
fun onMessage(text: String)
fun onClosing(
code: Int,
reason: String,
)
fun onClosed(
code: Int,
reason: String,
)
fun onFailure(
t: Throwable,
response: String?,
)
}
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.sockets
interface WebsocketBuilder {
fun build(
url: String,
out: WebSocketListener,
): WebSocket
}
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relays.sockets
interface WebsocketBuilderFactory {
fun build(forceProxy: Boolean): WebsocketBuilder
}