From f83956515281bb7add0f703a50ab34c0236ce9d0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 20 Dec 2024 15:36:50 -0500 Subject: [PATCH] Refactors Ammolite to remove the dependency on OkHttp to prepare for KTor and multiplatform settings. - This also reduces the Singleton coupling between Client and RelayPool. To migrate, create a NostrClient instance on your Application class and update your code to access that `client` instance. --- .../com/vitorpamplona/amethyst/Amethyst.kt | 8 +- .../com/vitorpamplona/amethyst/DebugUtils.kt | 6 +- .../vitorpamplona/amethyst/ServiceManager.kt | 16 +- .../vitorpamplona/amethyst/model/Account.kt | 228 +++++++++--------- .../service/AmethystNostrDataSource.kt | 3 +- .../amethyst/service/CashuProcessor.kt | 2 +- .../service/Nip05NostrAddressVerifier.kt | 2 +- .../service/Nip11RelayInfoRetriever.kt | 2 +- .../service/NostrAccountDataSource.kt | 5 +- .../amethyst/service/OnlineCheck.kt | 2 +- .../service/lnurl/LightningAddressResolver.kt | 2 +- .../service/notifications/RegisterAccounts.kt | 2 +- .../service/okhttp}/HttpClientManager.kt | 98 +++++--- .../service/okhttp/OkHttpWebSocket.kt | 89 +++++++ .../service/ots/OkHttpBlockstreamExplorer.kt | 2 +- .../amethyst/service/ots/OkHttpCalendar.kt | 2 +- .../service/ots/OkHttpCalendarAsyncSubmit.kt | 2 +- .../service/playback/PlaybackService.kt | 2 +- .../amethyst/service/previews/UrlPreview.kt | 2 +- .../uploads/blossom/BlossomUploader.kt | 2 +- .../service/uploads/nip96/Nip96Uploader.kt | 2 +- .../uploads/nip96/ServerInfoRetriever.kt | 2 +- .../vitorpamplona/amethyst/ui/MainActivity.kt | 2 +- .../amethyst/ui/actions/MediaSaverToDisk.kt | 2 +- .../ui/actions/uploads/ImageDownloader.kt | 2 +- .../amethyst/ui/components/VideoView.kt | 2 +- .../amethyst/ui/navigation/DrawerContent.kt | 3 +- .../ui/screen/AccountStateViewModel.kt | 11 +- .../ui/screen/loggedIn/AccountViewModel.kt | 2 + .../loggedIn/profile/gallery/GalleryThumb.kt | 2 +- .../amethyst/ui/tor/TorManager.kt | 2 +- ammolite/build.gradle | 1 - .../relays/{Client.kt => NostrClient.kt} | 85 ++++--- .../ammolite/relays/NostrDataSource.kt | 29 +-- .../vitorpamplona/ammolite/relays/Relay.kt | 48 ++-- .../ammolite/relays/RelayPool.kt | 60 ++--- .../ammolite/relays/SubscriptionManager.kt | 50 ++++ .../ammolite/sockets/WebSocket.kt | 27 +++ .../ammolite/sockets/WebSocketListener.kt | 45 ++++ .../ammolite/sockets/WebsocketBuilder.kt | 29 +++ 40 files changed, 560 insertions(+), 323 deletions(-) rename {ammolite/src/main/java/com/vitorpamplona/ammolite/service => amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp}/HttpClientManager.kt (61%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpWebSocket.kt rename ammolite/src/main/java/com/vitorpamplona/ammolite/relays/{Client.kt => NostrClient.kt} (85%) create mode 100644 ammolite/src/main/java/com/vitorpamplona/ammolite/relays/SubscriptionManager.kt create mode 100644 ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocket.kt create mode 100644 ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocketListener.kt create mode 100644 ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebsocketBuilder.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt index 8e15c295e..57aa22156 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt @@ -37,8 +37,10 @@ import coil3.memory.MemoryCache import coil3.request.crossfade import com.vitorpamplona.amethyst.service.LocationState import com.vitorpamplona.amethyst.service.notifications.PokeyReceiver +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket import com.vitorpamplona.amethyst.service.playback.VideoCache -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.ammolite.relays.NostrClient import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -54,8 +56,10 @@ import kotlin.time.measureTimedValue class Amethyst : Application() { val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val client: NostrClient = NostrClient(OkHttpWebSocket.Builder()) + // Service Manager is only active when the activity is active. - val serviceManager = ServiceManager(applicationIOScope) + val serviceManager = ServiceManager(client, applicationIOScope) val locationManager = LocationState(this, applicationIOScope) val pokeyReceiver = PokeyReceiver() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt index e6998b047..0c63f8b20 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt @@ -43,11 +43,9 @@ import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.NostrThreadDataSource import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource import com.vitorpamplona.amethyst.service.NostrVideoDataSource -import com.vitorpamplona.ammolite.relays.Client -import com.vitorpamplona.ammolite.relays.RelayPool fun debugState(context: Context) { - Client + Amethyst.instance.client .allSubscriptions() .forEach { Log.d("STATE DUMP", "${it.key} ${it.value.joinToString { it.filter.toDebugJson() }}") } @@ -89,7 +87,7 @@ fun debugState(context: Context) { Log.d("STATE DUMP", "Memory Class " + memClass + " MB (largeHeap $isLargeHeap)") } - Log.d("STATE DUMP", "Connected Relays: " + RelayPool.connectedRelays()) + Log.d("STATE DUMP", "Connected Relays: " + Amethyst.instance.client.connectedRelays()) Log.d( "STATE DUMP", diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt index 3b3d3fd0e..180809ed0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt @@ -50,12 +50,12 @@ import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.NostrThreadDataSource import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource import com.vitorpamplona.amethyst.service.NostrVideoDataSource +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.ots.OkHttpBlockstreamExplorer import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder import com.vitorpamplona.amethyst.ui.tor.TorManager import com.vitorpamplona.amethyst.ui.tor.TorType -import com.vitorpamplona.ammolite.relays.Client -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.ammolite.relays.NostrClient import com.vitorpamplona.quartz.encoders.bechToBytes import com.vitorpamplona.quartz.encoders.decodePublicKeyAsHexOrNull import com.vitorpamplona.quartz.encoders.toHexKey @@ -73,10 +73,12 @@ import kotlinx.coroutines.runBlocking @Stable class ServiceManager( + val client: NostrClient, val scope: CoroutineScope, ) { - private var isStarted: Boolean = - false // to not open amber in a loop trying to use auth relays and registering for notifications + // to not open amber in a loop trying to use auth relays and registering for notifications + private var isStarted: Boolean = false + private var account: Account? = null private var collectorJob: Job? = null @@ -156,7 +158,7 @@ class ServiceManager( if (myAccount != null) { val relaySet = myAccount.connectToRelaysWithProxy.value - Client.reconnect(relaySet) + client.reconnect(relaySet) collectorJob?.cancel() collectorJob = null @@ -165,7 +167,7 @@ class ServiceManager( myAccount.connectToRelaysWithProxy.collectLatest { delay(500) if (isStarted) { - Client.reconnect(it, onlyIfChanged = true) + client.reconnect(it, onlyIfChanged = true) } } } @@ -231,7 +233,7 @@ class ServiceManager( NostrUserProfileDataSource.stopSync() NostrVideoDataSource.stopSync() - Client.reconnect(null) + client.reconnect(null) isStarted = false } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index f290d5cb0..c59a46044 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -34,13 +34,13 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.service.LocationState import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.uploads.FileHeader import com.vitorpamplona.amethyst.tryAndWait import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType import com.vitorpamplona.amethyst.ui.tor.TorType -import com.vitorpamplona.ammolite.relays.Client import com.vitorpamplona.ammolite.relays.Constants import com.vitorpamplona.ammolite.relays.FeedType import com.vitorpamplona.ammolite.relays.Relay @@ -48,7 +48,6 @@ import com.vitorpamplona.ammolite.relays.RelaySetupInfo import com.vitorpamplona.ammolite.relays.RelaySetupInfoToConnect import com.vitorpamplona.ammolite.relays.TypedFilter import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.Dimension @@ -1129,7 +1128,7 @@ class Account( otherTags = emptyArray(), signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1146,7 +1145,7 @@ class Account( relayUse = relays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -1160,7 +1159,7 @@ class Account( signer = signer, ) { // Keep this local to avoid erasing a good contact list. - // Client.send(it) + // Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1202,7 +1201,7 @@ class Account( github = github, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } @@ -1278,7 +1277,7 @@ class Account( if (emojiUrl != null) { note.event?.let { ReactionEvent.create(emojiUrl, it, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it) } } @@ -1289,7 +1288,7 @@ class Account( note.event?.let { ReactionEvent.create(reaction, it, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it) } } @@ -1395,7 +1394,7 @@ class Account( LocalCache.consume(event, zappedNote) { it.response(signer) { onResponse(it) } } - Client.sendSingle( + Amethyst.instance.client.sendSingle( signedEvent = event, relayTemplate = RelaySetupInfoToConnect( @@ -1448,14 +1447,14 @@ class Account( note.event?.let { ReactionEvent.createWarning(it, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } note.event?.let { ReportEvent.create(it, type, signer, content) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1473,7 +1472,7 @@ class Account( } ReportEvent.create(user.pubkeyHex, type, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1490,7 +1489,7 @@ class Account( // chunks in 200 elements to avoid going over the 65KB limit for events. myNoteVersions.chunked(200).forEach { chunkedList -> DeletionEvent.create(chunkedList, signer) { deletionEvent -> - Client.send(deletionEvent) + Amethyst.instance.client.send(deletionEvent) LocalCache.justConsume(deletionEvent, null) } } @@ -1549,12 +1548,12 @@ class Account( note.event?.let { if (it.kind() == 1) { RepostEvent.create(it, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { GenericRepostEvent.create(it, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1565,7 +1564,7 @@ class Account( note.event?.let { if (it is WrappedEvent && it.host != null) { it.host?.let { - Client.sendFilterAndStopOnFirstResponse( + Amethyst.instance.client.sendFilterAndStopOnFirstResponse( filters = listOf( TypedFilter( @@ -1576,12 +1575,12 @@ class Account( ), ), onResponse = { - Client.send(it) + Amethyst.instance.client.send(it) }, ) } } else { - Client.send(it) + Amethyst.instance.client.send(it) } } } @@ -1595,7 +1594,7 @@ class Account( if (pair.value != newAttestation) { OtsEvent.create(pair.key, newAttestation, signer) { LocalCache.justConsume(it, null) - Client.send(it) + Amethyst.instance.client.send(it) settings.pendingAttestations.update { it - pair.key @@ -1626,7 +1625,7 @@ class Account( if (contactList != null) { ContactListEvent.followUser(contactList, user.pubkeyHex, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -1642,7 +1641,7 @@ class Account( }, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1655,7 +1654,7 @@ class Account( if (contactList != null) { ContactListEvent.followEvent(contactList, channel.idHex, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -1671,7 +1670,7 @@ class Account( }, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1684,7 +1683,7 @@ class Account( if (contactList != null) { ContactListEvent.followAddressableEvent(contactList, community.address, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -1701,7 +1700,7 @@ class Account( relayUse = relays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1718,7 +1717,7 @@ class Account( tag, signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -1734,7 +1733,7 @@ class Account( }, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -1770,7 +1769,7 @@ class Account( } fun onNewEventCreated(event: Event) { - Client.send(event) + Amethyst.instance.client.send(event) LocalCache.justConsume(event, null) } @@ -1888,10 +1887,10 @@ class Account( ): Note? { if (!isWriteable()) return null - Client.send(data, relayList = relayList) + Amethyst.instance.client.send(data, relayList = relayList) LocalCache.consume(data, null) - Client.send(signedEvent, relayList = relayList) + Amethyst.instance.client.send(signedEvent, relayList = relayList) LocalCache.consume(signedEvent, null) return LocalCache.getNoteIfExists(signedEvent.id) @@ -1912,8 +1911,8 @@ class Account( signedEvent: FileStorageHeaderEvent, relayList: List, ) { - Client.send(data, relayList = relayList) - Client.send(signedEvent, relayList = relayList) + Amethyst.instance.client.send(data, relayList = relayList) + Amethyst.instance.client.send(signedEvent, relayList = relayList) } fun sendHeader( @@ -1921,7 +1920,7 @@ class Account( relayList: List, onReady: (Note) -> Unit, ) { - Client.send(signedEvent, relayList = relayList) + Amethyst.instance.client.send(signedEvent, relayList = relayList) LocalCache.justConsume(signedEvent, null) LocalCache.getNoteIfExists(signedEvent.id)?.let { onReady(it) } @@ -2121,13 +2120,13 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) - replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } } + replyTo?.forEach { it.event?.let { Amethyst.instance.client.send(it, relayList = relayList) } } addresses?.forEach { LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2184,19 +2183,19 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) // broadcast replied notes replyingTo?.let { LocalCache.getNoteIfExists(replyingTo)?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } - replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } } + replyTo?.forEach { it.event?.let { Amethyst.instance.client.send(it, relayList = relayList) } } addresses?.forEach { LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2250,19 +2249,19 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) // broadcast replied notes replyingTo?.let { LocalCache.getNoteIfExists(replyingTo)?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } - replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } } + replyTo?.forEach { it.event?.let { Amethyst.instance.client.send(it, relayList = relayList) } } addresses?.forEach { LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2275,7 +2274,7 @@ class Account( val noteEvent = note.event if (noteEvent is DraftEvent) { noteEvent.createDeletedEvent(signer) { - Client.sendPrivately( + Amethyst.instance.client.sendPrivately( it, note.relays.map { it.url }.map { RelaySetupInfoToConnect( @@ -2359,11 +2358,11 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) replyingTo.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2391,11 +2390,11 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) replyingTo.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2466,11 +2465,11 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) replyingTo.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2497,7 +2496,7 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) } } @@ -2522,9 +2521,9 @@ class Account( signer = signer, ) { if (relayList.isNotEmpty()) { - Client.sendPrivately(it, relayList = relayList) + Amethyst.instance.client.sendPrivately(it, relayList = relayList) } else { - Client.send(it) + Amethyst.instance.client.send(it) } LocalCache.justConsume(it, null) } @@ -2546,9 +2545,9 @@ class Account( signer = signer, ) { if (relayList.isNotEmpty()) { - Client.sendPrivately(it, relayList = relayList) + Amethyst.instance.client.sendPrivately(it, relayList = relayList) } else { - Client.send(it) + Amethyst.instance.client.send(it) } LocalCache.justConsume(it, null) } @@ -2593,7 +2592,7 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) } } @@ -2634,7 +2633,7 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) } } @@ -2690,19 +2689,19 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) // broadcast replied notes replyingTo?.let { LocalCache.getNoteIfExists(replyingTo)?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } - replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } } + replyTo?.forEach { it.event?.let { Amethyst.instance.client.send(it, relayList = relayList) } } addresses?.forEach { LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2728,7 +2727,7 @@ class Account( signer = signer, ) { LocalCache.justConsume(it, null) - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } @@ -2782,14 +2781,14 @@ class Account( } } } else { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) LocalCache.justConsume(it, null) // Rebroadcast replies and tags to the current relay set - replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } } + replyTo?.forEach { it.event?.let { Amethyst.instance.client.send(it, relayList = relayList) } } addresses?.forEach { LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let { - Client.send(it, relayList = relayList) + Amethyst.instance.client.send(it, relayList = relayList) } } } @@ -2835,7 +2834,7 @@ class Account( } } } else { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -2881,7 +2880,7 @@ class Account( } } } else { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -2954,7 +2953,7 @@ class Account( } } } else { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } @@ -3020,9 +3019,9 @@ class Account( fun sendDraftEvent(draftEvent: DraftEvent) { val relayList = getPrivateOutBoxRelayList() if (relayList.isNotEmpty()) { - Client.sendPrivately(draftEvent, relayList) + Amethyst.instance.client.sendPrivately(draftEvent, relayList) } else { - Client.send(draftEvent) + Amethyst.instance.client.send(draftEvent) } LocalCache.justConsume(draftEvent, null) } @@ -3072,12 +3071,12 @@ class Account( } if (relayList != null) { - Client.sendPrivately(signedEvent = wrap, relayList = relayList) + Amethyst.instance.client.sendPrivately(signedEvent = wrap, relayList = relayList) } else { - Client.send(wrap) + Amethyst.instance.client.send(wrap) } } else { - Client.send(wrap) + Amethyst.instance.client.send(wrap) } } } @@ -3095,7 +3094,7 @@ class Account( picture = picture, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) LocalCache.getChannelIfExists(it.id)?.let { follow(it) } @@ -3110,7 +3109,7 @@ class Account( val oldEvent = oldStatus.event as? StatusEvent ?: return StatusEvent.update(oldEvent, newStatus, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3119,7 +3118,7 @@ class Account( if (!isWriteable()) return StatusEvent.create(newStatus, "general", expiration = null, signer) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3129,11 +3128,11 @@ class Account( val oldEvent = oldStatus.event as? StatusEvent ?: return StatusEvent.clear(oldEvent, signer) { event -> - Client.send(event) + Amethyst.instance.client.send(event) LocalCache.justConsume(event, null) DeletionEvent.createForVersionOnly(listOf(event), signer) { event2 -> - Client.send(event2) + Amethyst.instance.client.send(event2) LocalCache.justConsume(event2, null) } } @@ -3154,7 +3153,7 @@ class Account( noteEvent.taggedAddresses().filter { it != emojiListEvent.address() }, signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3172,7 +3171,7 @@ class Account( listOf(emojiListEvent.address()), signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -3187,7 +3186,7 @@ class Account( noteEvent.taggedAddresses().plus(emojiListEvent.address()), signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3218,7 +3217,7 @@ class Account( originalHash = originalHash, */ signer = signer, ) { event -> - Client.send(event) + Amethyst.instance.client.send(event) LocalCache.consume(event, null) } } @@ -3241,7 +3240,7 @@ class Account( isPrivate, signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it) } } else { @@ -3251,7 +3250,7 @@ class Account( isPrivate, signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it) } } @@ -3272,7 +3271,7 @@ class Account( isPrivate, signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it) } } else { @@ -3282,17 +3281,20 @@ class Account( isPrivate, signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it) } } } - fun createAuthEvent( + fun sendAuthEvent( relay: Relay, challenge: String, - onReady: (RelayAuthEvent) -> Unit, - ) = createAuthEvent(relay.url, challenge, onReady = onReady) + ) { + createAuthEvent(relay.url, challenge) { + Amethyst.instance.client.sendIfExists(it, relay) + } + } fun createAuthEvent( relayUrl: String, @@ -3393,7 +3395,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } else { @@ -3402,7 +3404,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } @@ -3418,7 +3420,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } @@ -3432,7 +3434,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } @@ -3448,7 +3450,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } else { @@ -3457,7 +3459,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } @@ -3473,7 +3475,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } @@ -3487,7 +3489,7 @@ class Account( isPrivate = true, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.consume(it, null) } } @@ -3517,7 +3519,7 @@ class Account( originalChannelIdHex = channel.idHex, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) follow(channel) @@ -3547,9 +3549,9 @@ class Account( } if (relayList != null) { - Client.sendPrivately(it, relayList) + Amethyst.instance.client.sendPrivately(it, relayList) } else { - Client.send(it) + Amethyst.instance.client.send(it) } LocalCache.justConsume(it, null) onReady(it) @@ -3764,7 +3766,7 @@ class Account( relays = dmRelays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -3772,7 +3774,7 @@ class Account( relays = dmRelays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3798,7 +3800,7 @@ class Account( relays = relays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -3806,7 +3808,7 @@ class Account( relays = relays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3832,7 +3834,7 @@ class Account( relays = searchRelays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -3840,7 +3842,7 @@ class Account( relays = searchRelays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3866,7 +3868,7 @@ class Account( relays = relays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -3874,7 +3876,7 @@ class Account( relays = relays, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3922,7 +3924,7 @@ class Account( relays = servers, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -3930,7 +3932,7 @@ class Account( relays = servers, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } @@ -3947,7 +3949,7 @@ class Account( relays = servers, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } else { @@ -3955,7 +3957,7 @@ class Account( relays = servers, signer = signer, ) { - Client.send(it) + Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/AmethystNostrDataSource.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/AmethystNostrDataSource.kt index 99e7d12b2..e9fbdb558 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/AmethystNostrDataSource.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/AmethystNostrDataSource.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service +import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.ammolite.relays.NostrDataSource import com.vitorpamplona.ammolite.relays.Relay @@ -28,7 +29,7 @@ import com.vitorpamplona.quartz.events.Event abstract class AmethystNostrDataSource( debugName: String, -) : NostrDataSource(debugName) { +) : NostrDataSource(Amethyst.instance.client, debugName) { override fun consume( event: Event, relay: Relay, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt index 406128116..0189c1490 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CashuProcessor.kt @@ -27,9 +27,9 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.events.Event import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt index f6fcd4b1b..85de464e5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip05NostrAddressVerifier.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt index f201464f0..3056edf9d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service import android.util.Log import android.util.LruCache -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.quartz.encoders.Nip11RelayInformation import com.vitorpamplona.quartz.encoders.RelayUrlFormatter import com.vitorpamplona.quartz.utils.TimeUtils diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 2dd202630..57ca3c884 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -25,7 +25,6 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relays.EOSEAccount import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES -import com.vitorpamplona.ammolite.relays.Client import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES import com.vitorpamplona.ammolite.relays.Relay import com.vitorpamplona.ammolite.relays.TypedFilter @@ -492,9 +491,7 @@ object NostrAccountDataSource : AmethystNostrDataSource("AccountData") { super.auth(relay, challenge) if (this::account.isInitialized) { - account.createAuthEvent(relay, challenge) { - Client.sendIfExists(it, relay) - } + account.sendAuthEvent(relay, challenge) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/OnlineCheck.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/OnlineCheck.kt index 9bf1b0bc2..be7a7cd07 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/OnlineCheck.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/OnlineCheck.kt @@ -24,7 +24,7 @@ import android.util.Log import android.util.LruCache import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.BuildConfig -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.quartz.crypto.CryptoUtils import okhttp3.EventListener import okhttp3.Protocol diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt index f9c2103ab..c6ae303eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt @@ -25,8 +25,8 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.encoders.LnInvoiceUtil import com.vitorpamplona.quartz.encoders.Lud06 import okhttp3.Request diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt index 498e00dc7..567fc7452 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt @@ -27,8 +27,8 @@ import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.launchAndWaitAll import com.vitorpamplona.amethyst.model.AccountSettings +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.tryAndWait -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.events.RelayAuthEvent import com.vitorpamplona.quartz.signers.NostrSignerExternal import kotlinx.coroutines.CancellationException diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/service/HttpClientManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/HttpClientManager.kt similarity index 61% rename from ammolite/src/main/java/com/vitorpamplona/ammolite/service/HttpClientManager.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/HttpClientManager.kt index 3a48c4fcf..8f5f9c7b4 100644 --- a/ammolite/src/main/java/com/vitorpamplona/ammolite/service/HttpClientManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/HttpClientManager.kt @@ -18,41 +18,20 @@ * 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.ammolite.service +package com.vitorpamplona.amethyst.service.okhttp import android.util.Log -import com.vitorpamplona.ammolite.service.HttpClientManager.setDefaultProxy +import com.vitorpamplona.quartz.crypto.nip17.NostrCipher import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody import java.io.IOException import java.net.InetSocketAddress import java.net.Proxy import java.time.Duration -class LoggingInterceptor : Interceptor { - @Throws(IOException::class) - override fun intercept(chain: Interceptor.Chain): Response { - val request: Request = chain.request() - val t1 = System.nanoTime() - val port = - ( - chain - .connection() - ?.route() - ?.proxy - ?.address() as? InetSocketAddress - )?.port - val response: Response = chain.proceed(request) - val t2 = System.nanoTime() - - Log.d("OkHttpLog", "Req $port ${request.url} in ${(t2 - t1) / 1e6}ms") - - return response - } -} - object HttpClientManager { val rootClient = OkHttpClient @@ -74,32 +53,32 @@ object HttpClientManager { fun setDefaultProxy(proxy: Proxy?) { if (currentProxy != proxy) { Log.d("HttpClient", "Changing proxy to: ${proxy != null}") - this.currentProxy = proxy + currentProxy = proxy // recreates singleton - this.defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) + defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) } } - fun getCurrentProxy(): Proxy? = this.currentProxy + fun getCurrentProxy(): Proxy? = currentProxy fun setDefaultTimeout(timeout: Duration) { Log.d("HttpClient", "Changing timeout to: $timeout") - if (this.defaultTimeout.seconds != timeout.seconds) { - this.defaultTimeout = timeout + if (defaultTimeout.seconds != timeout.seconds) { + defaultTimeout = timeout // recreates singleton - this.defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) - this.defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout) + defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) + defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout) } } fun setDefaultUserAgent(userAgentHeader: String) { Log.d("HttpClient", "Changing userAgent") if (userAgent != userAgentHeader) { - this.userAgent = userAgentHeader - this.defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) - this.defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout) + userAgent = userAgentHeader + defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) + defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout) } } @@ -117,6 +96,7 @@ object HttpClientManager { .writeTimeout(duration) .addInterceptor(DefaultContentTypeInterceptor(userAgent)) .addNetworkInterceptor(LoggingInterceptor()) + .addNetworkInterceptor(EncryptedBlobInterceptor()) .build() } @@ -135,6 +115,48 @@ object HttpClientManager { } } + class EncryptedBlobInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val response = chain.proceed(chain.request()) + + if (response.isSuccessful) { + val cipher = chain.request().tag(NostrCipher::class) + + println("AABBCC Cipher ${chain.request().tag(NostrCipher::class)}") + + if (cipher != null) { + val body = response.peekBody(Long.MAX_VALUE) + val decryptedBytes = cipher.decrypt(body.bytes()) + val newBody = decryptedBytes.toResponseBody(body.contentType()) + return response.newBuilder().body(newBody).build() + } + } + return response + } + } + + class LoggingInterceptor : Interceptor { + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val request: Request = chain.request() + val t1 = System.nanoTime() + val port = + ( + chain + .connection() + ?.route() + ?.proxy + ?.address() as? InetSocketAddress + )?.port + val response: Response = chain.proceed(request) + val t2 = System.nanoTime() + + Log.d("OkHttpLog", "Req $port ${request.url} in ${(t2 - t1) / 1e6}ms") + + return response + } + } + fun getCurrentProxyPort(useProxy: Boolean): Int? = if (useProxy) { (currentProxy?.address() as? InetSocketAddress)?.port @@ -144,13 +166,13 @@ object HttpClientManager { fun getHttpClient(useProxy: Boolean): OkHttpClient = if (useProxy) { - if (this.defaultHttpClient == null) { - this.defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) + if (defaultHttpClient == null) { + defaultHttpClient = buildHttpClient(currentProxy, defaultTimeout) } defaultHttpClient!! } else { - if (this.defaultHttpClientWithoutProxy == null) { - this.defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout) + if (defaultHttpClientWithoutProxy == null) { + defaultHttpClientWithoutProxy = buildHttpClient(null, defaultTimeout) } defaultHttpClientWithoutProxy!! } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpWebSocket.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpWebSocket.kt new file mode 100644 index 000000000..e2651ef63 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpWebSocket.kt @@ -0,0 +1,89 @@ +/** + * 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.amethyst.service.okhttp + +import com.vitorpamplona.ammolite.sockets.WebSocket +import com.vitorpamplona.ammolite.sockets.WebSocketListener +import com.vitorpamplona.ammolite.sockets.WebsocketBuilder +import okhttp3.Request +import okhttp3.Response + +class OkHttpWebSocket( + val url: String, + val forceProxy: Boolean, + val out: WebSocketListener, +) : WebSocket { + private val listener = OkHttpWebsocketListener() + private var socket: okhttp3.WebSocket? = null + + fun buildRequest() = Request.Builder().url(url.trim()).build() + + override fun connect() { + socket = HttpClientManager.getHttpClient(forceProxy).newWebSocket(buildRequest(), listener) + } + + inner class OkHttpWebsocketListener : okhttp3.WebSocketListener() { + override fun onOpen( + webSocket: okhttp3.WebSocket, + response: Response, + ) = out.onOpen( + response.receivedResponseAtMillis - response.sentRequestAtMillis, + response.headers.get("Sec-WebSocket-Extensions")?.contains("permessage-deflate") ?: false, + ) + + override fun onMessage( + webSocket: okhttp3.WebSocket, + text: String, + ) = out.onMessage(text) + + override fun onClosing( + webSocket: okhttp3.WebSocket, + code: Int, + reason: String, + ) = out.onClosing(code, reason) + + override fun onClosed( + webSocket: okhttp3.WebSocket, + code: Int, + reason: String, + ) = out.onClosed(code, reason) + + override fun onFailure( + webSocket: okhttp3.WebSocket, + t: Throwable, + response: Response?, + ) = out.onFailure(t, response?.message) + } + + class Builder : WebsocketBuilder { + override fun build( + url: String, + forceProxy: Boolean, + out: WebSocketListener, + ) = OkHttpWebSocket(url, forceProxy, out) + } + + override fun cancel() { + socket?.cancel() + } + + override fun send(msg: String): Boolean = socket?.send(msg) ?: false +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpBlockstreamExplorer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpBlockstreamExplorer.kt index 43faba016..18f78207a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpBlockstreamExplorer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpBlockstreamExplorer.kt @@ -24,7 +24,7 @@ import android.util.Log import android.util.LruCache import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.quartz.ots.BitcoinExplorer import com.vitorpamplona.quartz.ots.BlockHeader import com.vitorpamplona.quartz.ots.exceptions.UrlException diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt index 076977ac5..561d8fa2a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.service.ots import com.vitorpamplona.amethyst.BuildConfig -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.quartz.encoders.Hex import com.vitorpamplona.quartz.ots.ICalendar import com.vitorpamplona.quartz.ots.StreamDeserializationContext diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarAsyncSubmit.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarAsyncSubmit.kt index 1630d5251..e04092040 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarAsyncSubmit.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarAsyncSubmit.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.service.ots import com.vitorpamplona.amethyst.BuildConfig -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.quartz.ots.ICalendarAsyncSubmit import com.vitorpamplona.quartz.ots.StreamDeserializationContext import com.vitorpamplona.quartz.ots.Timestamp diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/PlaybackService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/PlaybackService.kt index f36a5a2b7..6888b24ae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/PlaybackService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/PlaybackService.kt @@ -26,7 +26,7 @@ import androidx.annotation.OptIn import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager class PlaybackService : MediaSessionService() { private var videoViewedPositionCache = VideoViewedPositionCache() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt index 99a11aa56..4f9c98c47 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreview.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.service.previews import com.vitorpamplona.amethyst.service.checkNotInMainThread -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt index bcd603766..c1c8c250d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt @@ -31,10 +31,10 @@ import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.HttpStatusMessages import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult import com.vitorpamplona.amethyst.service.uploads.nip96.randomChars import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.toHexKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt index b26201195..dbeb05e5e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt @@ -32,9 +32,9 @@ import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.HttpStatusMessages import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.encoders.Dimension import com.vitorpamplona.quartz.events.HTTPAuthorizationEvent import kotlinx.coroutines.delay diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/ServerInfoRetriever.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/ServerInfoRetriever.kt index e2a00695e..e74daf4e9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/ServerInfoRetriever.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/ServerInfoRetriever.kt @@ -24,7 +24,7 @@ import android.util.Log import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.service.checkNotInMainThread -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import kotlinx.coroutines.CancellationException import okhttp3.Request import java.net.URI diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 80d2427d6..77908f03a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.debugState import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.ui.components.DEFAULT_MUTED_SETTING import com.vitorpamplona.amethyst.ui.components.keepPlayingMutex import com.vitorpamplona.amethyst.ui.navigation.Route @@ -46,7 +47,6 @@ import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.amethyst.ui.tor.TorManager -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.encoders.Nip19Bech32 import com.vitorpamplona.quartz.encoders.Nip47WalletConnect import com.vitorpamplona.quartz.events.ChannelCreateEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/MediaSaverToDisk.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/MediaSaverToDisk.kt index f93837adb..6bf5b480d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/MediaSaverToDisk.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/MediaSaverToDisk.kt @@ -32,7 +32,7 @@ import androidx.annotation.RequiresApi import androidx.core.net.toFile import androidx.core.net.toUri import com.vitorpamplona.amethyst.BuildConfig -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import kotlinx.coroutines.CancellationException import okhttp3.Call import okhttp3.Callback diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ImageDownloader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ImageDownloader.kt index 16ac11454..684f1013c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ImageDownloader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ImageDownloader.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.actions.uploads -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index efb59a77b..99da3c2fd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -97,6 +97,7 @@ import com.linc.audiowaveform.infiniteLinearGradient import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache import com.vitorpamplona.amethyst.commons.compose.produceCachedState +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.playback.PlaybackClientController import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon @@ -117,7 +118,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.VolumeBottomIconSize import com.vitorpamplona.amethyst.ui.theme.imageModifier import com.vitorpamplona.amethyst.ui.theme.videoGalleryModifier -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.encoders.Dimension import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CancellationException diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 34e8fa0b9..8c3df6ec3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -111,7 +111,6 @@ import com.vitorpamplona.amethyst.ui.theme.drawerSpacing import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.profileContentHeaderModifier import com.vitorpamplona.amethyst.ui.tor.ConnectTorDialog -import com.vitorpamplona.ammolite.relays.RelayPool import com.vitorpamplona.ammolite.relays.RelayPoolStatus import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.HexKey @@ -563,7 +562,7 @@ fun ListContent( @Composable private fun RelayStatus(accountViewModel: AccountViewModel) { - val connectedRelaysText by RelayPool.statusFlow.collectAsStateWithLifecycle(RelayPoolStatus(0, 0)) + val connectedRelaysText by accountViewModel.relayStatusFlow().collectAsStateWithLifecycle(RelayPoolStatus(0, 0)) RenderRelayStatus(connectedRelaysText) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index 2c32d8eac..c1eb75801 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -35,7 +35,6 @@ import com.vitorpamplona.amethyst.model.DefaultSearchRelayList import com.vitorpamplona.amethyst.service.Nip05NostrAddressVerifier import com.vitorpamplona.amethyst.ui.tor.TorSettings import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow -import com.vitorpamplona.ammolite.relays.Client import com.vitorpamplona.ammolite.relays.Constants import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.KeyPair @@ -297,11 +296,11 @@ class AccountStateViewModel : ViewModel() { GlobalScope.launch(Dispatchers.IO) { delay(2000) // waits for the new user to connect to the new relays. - accountSettings.backupUserMetadata?.let { Client.send(it) } - accountSettings.backupContactList?.let { Client.send(it) } - accountSettings.backupNIP65RelayList?.let { Client.send(it) } - accountSettings.backupDMRelayList?.let { Client.send(it) } - accountSettings.backupSearchRelayList?.let { Client.send(it) } + accountSettings.backupUserMetadata?.let { Amethyst.instance.client.send(it) } + accountSettings.backupContactList?.let { Amethyst.instance.client.send(it) } + accountSettings.backupNIP65RelayList?.let { Amethyst.instance.client.send(it) } + accountSettings.backupDMRelayList?.let { Amethyst.instance.client.send(it) } + accountSettings.backupSearchRelayList?.let { Amethyst.instance.client.send(it) } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 9f06e9b26..b7e36f0b1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -1626,6 +1626,8 @@ class AccountViewModel( } } + fun relayStatusFlow() = Amethyst.instance.client.relayStatusFlow() + val draftNoteCache = CachedDraftNotes(this) class CachedDraftNotes( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt index 72071dfa1..a149d01e4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt @@ -51,6 +51,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser.Companion.isVideoUrl import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.DisplayBlurHash @@ -66,7 +67,6 @@ import com.vitorpamplona.amethyst.ui.note.elements.BannerImage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size75dp -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.quartz.events.PictureEvent import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent import com.vitorpamplona.quartz.events.VideoEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt index 5272101ee..446476e24 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorManager.kt @@ -27,7 +27,7 @@ import android.content.ServiceConnection import android.os.IBinder import android.util.Log import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE -import com.vitorpamplona.ammolite.service.HttpClientManager +import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import org.torproject.jni.TorService import org.torproject.jni.TorService.LocalBinder diff --git a/ammolite/build.gradle b/ammolite/build.gradle index c6a7101e7..ecd515719 100644 --- a/ammolite/build.gradle +++ b/ammolite/build.gradle @@ -57,7 +57,6 @@ dependencies { implementation libs.androidx.runtime.runtime implementation project(path: ':quartz') - implementation libs.okhttp testImplementation libs.junit diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/Client.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrClient.kt similarity index 85% rename from ammolite/src/main/java/com/vitorpamplona/ammolite/relays/Client.kt rename to ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrClient.kt index 47d05e51a..9d4b9009b 100644 --- a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/Client.kt +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrClient.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.ammolite.relays import android.util.Log import com.vitorpamplona.ammolite.service.checkNotInMainThread +import com.vitorpamplona.ammolite.sockets.WebsocketBuilder import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.EventInterface import kotlinx.coroutines.DelicateCoroutinesApi @@ -37,10 +38,16 @@ import java.util.concurrent.TimeUnit * The Nostr Client manages multiple personae the user may switch between. Events are received and * published through multiple relays. Events are stored with their respective persona. */ -object Client : RelayPool.Listener { +class NostrClient( + private val websocketBuilder: WebsocketBuilder, +) : RelayPool.Listener { + private val relayPool: RelayPool = RelayPool() + private val subscriptions: MutableSubscriptionManager = MutableSubscriptionManager() + private var listeners = setOf() private var relays = emptyArray() - private var subscriptions = mapOf>() + + fun buildRelay(it: RelaySetupInfoToConnect): Relay = Relay(it.url, it.read, it.write, it.forceProxy, it.feedTypes, websocketBuilder, subscriptions) @Synchronized fun reconnect( @@ -52,33 +59,33 @@ object Client : RelayPool.Listener { if (onlyIfChanged) { if (!isSameRelaySetConfig(relays)) { - if (Client.relays.isNotEmpty()) { - RelayPool.disconnect() - RelayPool.unregister(this) - RelayPool.unloadRelays() + if (this.relays.isNotEmpty()) { + relayPool.disconnect() + relayPool.unregister(this) + relayPool.unloadRelays() } if (relays != null) { - val newRelays = relays.map { Relay(it.url, it.read, it.write, it.forceProxy, it.feedTypes) } - RelayPool.register(this) - RelayPool.loadRelays(newRelays) - RelayPool.requestAndWatch() - Client.relays = newRelays.toTypedArray() + val newRelays = relays.map(::buildRelay) + relayPool.register(this) + relayPool.loadRelays(newRelays) + relayPool.requestAndWatch() + this.relays = newRelays.toTypedArray() } } } else { - if (Client.relays.isNotEmpty()) { - RelayPool.disconnect() - RelayPool.unregister(this) - RelayPool.unloadRelays() + if (this.relays.isNotEmpty()) { + relayPool.disconnect() + relayPool.unregister(this) + relayPool.unloadRelays() } if (relays != null) { - val newRelays = relays.map { Relay(it.url, it.read, it.write, it.forceProxy, it.feedTypes) } - RelayPool.register(this) - RelayPool.loadRelays(newRelays) - RelayPool.requestAndWatch() - Client.relays = newRelays.toTypedArray() + val newRelays = relays.map(::buildRelay) + relayPool.register(this) + relayPool.loadRelays(newRelays) + relayPool.requestAndWatch() + this.relays = newRelays.toTypedArray() } } } @@ -101,8 +108,8 @@ object Client : RelayPool.Listener { ) { checkNotInMainThread() - subscriptions = subscriptions + Pair(subscriptionId, filters) - RelayPool.sendFilter(subscriptionId, filters) + subscriptions.add(subscriptionId, filters) + relayPool.sendFilter(subscriptionId, filters) } fun sendFilterAndStopOnFirstResponse( @@ -129,8 +136,8 @@ object Client : RelayPool.Listener { }, ) - subscriptions = subscriptions + Pair(subscriptionId, filters) - RelayPool.sendFilter(subscriptionId, filters) + subscriptions.add(subscriptionId, filters) + relayPool.sendFilter(subscriptionId, filters) } @OptIn(DelicateCoroutinesApi::class) @@ -146,7 +153,7 @@ object Client : RelayPool.Listener { ): Boolean { checkNotInMainThread() - val size = if (relay != null) 1 else relayList?.size ?: RelayPool.availableRelays() + val size = if (relay != null) 1 else relayList?.size ?: relayPool.availableRelays() val latch = CountDownLatch(size) val relayErrors = mutableMapOf() var result = false @@ -227,8 +234,8 @@ object Client : RelayPool.Listener { ) { checkNotInMainThread() - subscriptions = subscriptions + Pair(subscriptionId, filters) - RelayPool.connectAndSendFiltersIfDisconnected() + subscriptions.add(subscriptionId, filters) + relayPool.connectAndSendFiltersIfDisconnected() } fun sendIfExists( @@ -237,7 +244,7 @@ object Client : RelayPool.Listener { ) { checkNotInMainThread() - RelayPool.getRelays(connectedRelay.url).forEach { + relayPool.getRelays(connectedRelay.url).forEach { it.send(signedEvent) } } @@ -249,14 +256,14 @@ object Client : RelayPool.Listener { ) { checkNotInMainThread() - RelayPool.getOrCreateRelay(relayTemplate, onDone) { + relayPool.runCreatingIfNeeded(buildRelay(relayTemplate), onDone = onDone) { it.send(signedEvent) } } fun send(signedEvent: EventInterface) { checkNotInMainThread() - RelayPool.send(signedEvent) + relayPool.send(signedEvent) } fun send( @@ -265,7 +272,7 @@ object Client : RelayPool.Listener { ) { checkNotInMainThread() - RelayPool.sendToSelectedRelays(relayList, signedEvent) + relayPool.sendToSelectedRelays(relayList, signedEvent) } fun sendPrivately( @@ -275,18 +282,18 @@ object Client : RelayPool.Listener { checkNotInMainThread() relayList.forEach { relayTemplate -> - RelayPool.getOrCreateRelay(relayTemplate, { }) { + relayPool.runCreatingIfNeeded(buildRelay(relayTemplate)) { it.sendOverride(signedEvent) } } } fun close(subscriptionId: String) { - RelayPool.close(subscriptionId) - subscriptions = subscriptions.minus(subscriptionId) + relayPool.close(subscriptionId) + subscriptions.remove(subscriptionId) } - fun isActive(subscriptionId: String): Boolean = subscriptions.contains(subscriptionId) + fun isActive(subscriptionId: String): Boolean = subscriptions.isActive(subscriptionId) @OptIn(DelicateCoroutinesApi::class) override fun onEvent( @@ -392,9 +399,13 @@ object Client : RelayPool.Listener { listeners = listeners.minus(listener) } - fun allSubscriptions(): Map> = subscriptions + fun allSubscriptions(): Map> = subscriptions.allSubscriptions() - fun getSubscriptionFilters(subId: String): List = subscriptions[subId] ?: emptyList() + fun getSubscriptionFilters(subId: String): List = subscriptions.getSubscriptionFilters(subId) + + fun connectedRelays() = relayPool.connectedRelays() + + fun relayStatusFlow() = relayPool.statusFlow interface Listener { /** A new message was received */ diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrDataSource.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrDataSource.kt index 44e8a2df0..b70ce6d92 100644 --- a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrDataSource.kt +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrDataSource.kt @@ -35,6 +35,7 @@ import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean abstract class NostrDataSource( + val client: NostrClient, val debugName: String, ) { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -67,7 +68,7 @@ abstract class NostrDataSource( ): Int = 31 * str1.hashCode() + str2.hashCode() private val clientListener = - object : Client.Listener { + object : NostrClient.Listener { override fun onEvent( event: Event, subscriptionId: String, @@ -139,14 +140,14 @@ abstract class NostrDataSource( init { Log.d("DataSource", "${this.javaClass.simpleName} Subscribe") - Client.subscribe(clientListener) + client.subscribe(clientListener) } fun destroy() { // makes sure to run Log.d("DataSource", "${this.javaClass.simpleName} Unsubscribe") stop() - Client.unsubscribe(clientListener) + client.unsubscribe(clientListener) scope.cancel() bundler.cancel() } @@ -170,7 +171,7 @@ abstract class NostrDataSource( GlobalScope.launch(Dispatchers.IO) { subscriptions.values.forEach { subscription -> - Client.close(subscription.id) + client.close(subscription.id) subscription.typedFilters = null } } @@ -181,7 +182,7 @@ abstract class NostrDataSource( Log.d("DataSource", "${this.javaClass.simpleName} Stop") subscriptions.values.forEach { subscription -> - Client.close(subscription.id) + client.close(subscription.id) subscription.typedFilters = null } } @@ -193,7 +194,7 @@ abstract class NostrDataSource( } fun dismissChannel(subscription: Subscription) { - Client.close(subscription.id) + client.close(subscription.id) subscriptions = subscriptions.minus(subscription.id) } @@ -231,29 +232,29 @@ abstract class NostrDataSource( subscriptions.values.forEach { updatedSubscription -> val updatedSubscriptionNewFilters = updatedSubscription.typedFilters - val isActive = Client.isActive(updatedSubscription.id) + val isActive = client.isActive(updatedSubscription.id) if (!isActive && updatedSubscriptionNewFilters != null) { // Filter was removed from the active list if (active) { - Client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters) + client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters) } } else { if (currentFilters.containsKey(updatedSubscription.id)) { if (updatedSubscriptionNewFilters == null) { // was active and is not active anymore, just close. - Client.close(updatedSubscription.id) + client.close(updatedSubscription.id) } else { // was active and is still active, check if it has changed. if (updatedSubscription.hasChangedFiltersFrom(currentFilters[updatedSubscription.id])) { - Client.close(updatedSubscription.id) + client.close(updatedSubscription.id) if (active) { - Client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters) + client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters) } } else { // hasn't changed, does nothing. if (active) { - Client.sendFilterOnlyIfDisconnected( + client.sendFilterOnlyIfDisconnected( updatedSubscription.id, updatedSubscriptionNewFilters, ) @@ -269,9 +270,9 @@ abstract class NostrDataSource( if (active) { Log.d( this@NostrDataSource.javaClass.simpleName, - "Update Filter 3 ${updatedSubscription.id} ${Client.isSubscribed(clientListener)}", + "Update Filter 3 ${updatedSubscription.id} ${client.isSubscribed(clientListener)}", ) - Client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters) + client.sendFilter(updatedSubscription.id, updatedSubscriptionNewFilters) } } } diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/Relay.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/Relay.kt index da537d4e3..cf7f13bf2 100644 --- a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/Relay.kt +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/Relay.kt @@ -22,8 +22,10 @@ package com.vitorpamplona.ammolite.relays import android.util.Log import com.vitorpamplona.ammolite.BuildConfig -import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.ammolite.service.checkNotInMainThread +import com.vitorpamplona.ammolite.sockets.WebSocket +import com.vitorpamplona.ammolite.sockets.WebSocketListener +import com.vitorpamplona.ammolite.sockets.WebsocketBuilder import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.EventInterface @@ -31,10 +33,6 @@ import com.vitorpamplona.quartz.events.RelayAuthEvent import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.bytesUsedInMemory import kotlinx.coroutines.CancellationException -import okhttp3.Request -import okhttp3.Response -import okhttp3.WebSocket -import okhttp3.WebSocketListener import java.util.concurrent.atomic.AtomicBoolean enum class FeedType { @@ -61,6 +59,8 @@ class Relay( val write: Boolean = true, val forceProxy: Boolean = false, val activeTypes: Set, + val socketBuilder: WebsocketBuilder, + val subs: SubscriptionManager, ) { companion object { // waits 3 minutes to reconnect once things fail @@ -132,13 +132,7 @@ class Relay( lastConnectTentative = TimeUtils.now() - val request = - Request - .Builder() - .url(url.trim()) - .build() - - socket = HttpClientManager.getHttpClient(forceProxy).newWebSocket(request, RelayListener(onConnected)) + socket = socketBuilder.build(url, false, RelayListener(onConnected)) } catch (e: Exception) { if (e is CancellationException) throw e @@ -153,19 +147,15 @@ class Relay( inner class RelayListener( val onConnected: (Relay) -> Unit, - ) : WebSocketListener() { + ) : WebSocketListener { override fun onOpen( - webSocket: WebSocket, - response: Response, + pingInMs: Long, + usingCompression: Boolean, ) { checkNotInMainThread() Log.d("Relay", "Connect onOpen $url $socket") - markConnectionAsReady( - pingInMs = response.receivedResponseAtMillis - response.sentRequestAtMillis, - usingCompression = - response.headers.get("Sec-WebSocket-Extensions")?.contains("permessage-deflate") ?: false, - ) + markConnectionAsReady(pingInMs, usingCompression) // Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url") onConnected(this@Relay) @@ -173,10 +163,7 @@ class Relay( listeners.forEach { it.onRelayStateChange(this@Relay, StateType.CONNECT, null) } } - override fun onMessage( - webSocket: WebSocket, - text: String, - ) { + override fun onMessage(text: String) { checkNotInMainThread() RelayStats.addBytesReceived(url, text.bytesUsedInMemory()) @@ -193,7 +180,6 @@ class Relay( } override fun onClosing( - webSocket: WebSocket, code: Int, reason: String, ) { @@ -211,7 +197,6 @@ class Relay( } override fun onClosed( - webSocket: WebSocket, code: Int, reason: String, ) { @@ -225,9 +210,8 @@ class Relay( } override fun onFailure( - webSocket: WebSocket, t: Throwable, - response: Response?, + responseMessage: String?, ) { checkNotInMainThread() @@ -235,19 +219,19 @@ class Relay( // 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"))) { - RelayStats.newError(url, response?.message ?: t.message ?: "onFailure event from server: ${t.javaClass.simpleName}") + RelayStats.newError(url, responseMessage ?: t.message ?: "onFailure event from server: ${t.javaClass.simpleName}") } // Failures disconnect the relay. markConnectionAsClosed() - Log.w("Relay", "Relay onFailure $url, ${response?.message} $response ${t.message} $socket") + Log.w("Relay", "Relay onFailure $url, $responseMessage $responseMessage ${t.message} $socket") t.printStackTrace() listeners.forEach { it.onError( this@Relay, "", - Error("WebSocket Failure. Response: $response. Exception: ${t.message}", t), + Error("WebSocket Failure. Response: $responseMessage. Exception: ${t.message}", t), ) } } @@ -461,7 +445,7 @@ class Relay( fun renewFilters() { // Force update all filters after AUTH. - Client.allSubscriptions().forEach { + subs.allSubscriptions().forEach { sendFilter(requestId = it.key, it.value) } } diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/RelayPool.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/RelayPool.kt index 4d744b195..988039aa7 100644 --- a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/RelayPool.kt +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/RelayPool.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.Immutable import com.vitorpamplona.ammolite.service.checkNotInMainThread import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.EventInterface -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.BufferOverflow @@ -37,7 +36,7 @@ import kotlinx.coroutines.launch /** * RelayPool manages the connection to multiple Relays and lets consumers deal with simple events. */ -object RelayPool : Relay.Listener { +class RelayPool : Relay.Listener { private var relays = listOf() private var listeners = setOf() @@ -57,57 +56,34 @@ object RelayPool : Relay.Listener { fun getAll() = relays - fun getOrCreateRelay( - relayTemplate: RelaySetupInfoToConnect, + fun runCreatingIfNeeded( + relay: Relay, + timeout: Long = 60000, onDone: (() -> Unit)? = null, whenConnected: (Relay) -> Unit, ) { synchronized(this) { - val matching = getRelays(relayTemplate.url) + val matching = getRelays(relay.url) if (matching.isNotEmpty()) { matching.forEach { whenConnected(it) } } else { - /** temporary connection */ - newSporadicRelay( - relayTemplate.url, - relayTemplate.read, - relayTemplate.write, - relayTemplate.forceProxy, - relayTemplate.feedTypes, - onConnected = whenConnected, - onDone = onDone, - ) - } - } - } + addRelay(relay) - @OptIn(DelicateCoroutinesApi::class) - fun newSporadicRelay( - url: String, - read: Boolean, - write: Boolean, - forceProxy: Boolean, - feedTypes: Set?, - onConnected: (Relay) -> Unit, - onDone: (() -> Unit)?, - timeout: Long = 60000, - ) { - val relay = Relay(url, read, write, forceProxy, feedTypes ?: emptySet()) - addRelay(relay) + relay.connectAndRun { + relay.renewFilters() + relay.sendOutbox() - relay.connectAndRun { - relay.renewFilters() - relay.sendOutbox() + whenConnected(relay) - onConnected(relay) + GlobalScope.launch(Dispatchers.IO) { + delay(timeout) // waits for a reply + relay.disconnect() + removeRelay(relay) - GlobalScope.launch(Dispatchers.IO) { - delay(timeout) // waits for a reply - relay.disconnect() - removeRelay(relay) - - if (onDone != null) { - onDone() + if (onDone != null) { + onDone() + } + } } } } diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/SubscriptionManager.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/SubscriptionManager.kt new file mode 100644 index 000000000..a132d299f --- /dev/null +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/relays/SubscriptionManager.kt @@ -0,0 +1,50 @@ +/** + * 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.ammolite.relays + +class MutableSubscriptionManager : SubscriptionManager { + private var subscriptions = mapOf>() + + fun add( + subscriptionId: String, + filters: List = listOf(), + ) { + subscriptions = subscriptions + Pair(subscriptionId, filters) + } + + fun remove(subscriptionId: String) { + subscriptions = subscriptions.minus(subscriptionId) + } + + override fun isActive(subscriptionId: String): Boolean = subscriptions.contains(subscriptionId) + + override fun allSubscriptions(): Map> = subscriptions + + override fun getSubscriptionFilters(subId: String): List = subscriptions[subId] ?: emptyList() +} + +interface SubscriptionManager { + fun isActive(subscriptionId: String): Boolean + + fun allSubscriptions(): Map> + + fun getSubscriptionFilters(subId: String): List +} diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocket.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocket.kt new file mode 100644 index 000000000..509e603cc --- /dev/null +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocket.kt @@ -0,0 +1,27 @@ +/** + * 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.ammolite.sockets + +interface WebSocket { + fun cancel() + + fun send(msg: String): Boolean +} diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocketListener.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocketListener.kt new file mode 100644 index 000000000..84997bfae --- /dev/null +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebSocketListener.kt @@ -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.ammolite.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?, + ) +} diff --git a/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebsocketBuilder.kt b/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebsocketBuilder.kt new file mode 100644 index 000000000..c0af7ea00 --- /dev/null +++ b/ammolite/src/main/java/com/vitorpamplona/ammolite/sockets/WebsocketBuilder.kt @@ -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.ammolite.sockets + +interface WebsocketBuilder { + fun build( + url: String, + forceProxy: Boolean, + out: WebSocketListener, + ): WebSocket +}