diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index 0470977bcd..96322c0a4f 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -185,6 +185,13 @@ android:foregroundServiceType="mediaPlayback|microphone|camera|remoteMessaging" /> + + + + + + onVerification(pn.verification) + } + // TODO: Start same job than the periodic service ? + // Receiving the push notif is enough to wake the app and fetch msgs + // But it may not be enough when the phone is in doze, or with some + // vendors + } + + override fun onNewEndpoint(endpoint: PushEndpoint, instance: String) { + Log.d(TAG, "onNewEndpoint") + endpoint.pubKeySet ?: run { + // Should not happen + Log.w(TAG, "Missing pubKeySet") + return + } + CoroutineScope(Dispatchers.Default).launch { + chatModel.controller.sendCmd( + null, + CC.APIRegisterWebPush(endpoint.url, endpoint.pubKeySet!!.auth, endpoint.pubKeySet!!.pubKey), + log = true + ) + } + } + + override fun onRegistrationFailed(reason: FailedReason, instance: String) { + Log.d(TAG, "onRegistrationFailed: $reason") + // TODO: notification to inform about failed registration + } + + override fun onUnregistered(instance: String) { + Log.d(TAG, "onUnregistered") + // TODO: notification to inform about unregistration + CoroutineScope(Dispatchers.Default).launch { + chatModel.controller.sendCmd( + null, + CC.APIDeleteSavedNtf(), + log = true + ) + } + } + + companion object { + private const val TAG = "PushService" + } +} \ No newline at end of file diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PushManager.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PushManager.kt index 2c94373073..a96c4a435f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PushManager.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/platform/PushManager.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.getUserServers import chat.simplex.common.platform.Log +import chat.simplex.common.platform.chatModel import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.networkAndServers.showAddServerDialog import chat.simplex.res.MR @@ -39,10 +40,11 @@ object PushManager { * Else alert about missing service */ suspend fun initUnifiedPush(context: Context, scope: CoroutineScope, onSuccess: () -> Unit) { - val userServers = getUserServers(null) ?: listOf() + val rh = chatModel.remoteHostId() + val userServers = getUserServers(rh) ?: listOf() if (!userServers.hasNtfServer()) { Log.d(TAG, "User doesn't have any NTF server") - showMissingNTFDialog(scope, userServers) + showMissingNTFDialog(scope, rh, userServers) // After coming back from the server view, users will have to click on "Instant" again return } @@ -64,7 +66,7 @@ object PushManager { showSelectPushServiceDialog(context, distributors) { UnifiedPush.saveDistributor(context, it) register(context) - onSuccess + onSuccess() } } } @@ -81,13 +83,13 @@ object PushManager { */ private fun List.hasNtfServer(): Boolean { // TODO: check if ntf server has a VAPID key - return this.any { it.ntfServers.any() } + return this.any { it.ntfServers.any { s -> s.enabled } } } /** * Show a dialog to inform about missing NTF server */ - private fun showMissingNTFDialog(scope: CoroutineScope, userServers: List) = AlertManager.shared.showAlert { + private fun showMissingNTFDialog(scope: CoroutineScope, rh: Long?, userServers: List) = AlertManager.shared.showAlert { AlertDialog( onDismissRequest = AlertManager.shared::hideAlert, title = { @@ -109,7 +111,7 @@ object PushManager { confirmButton = { TextButton(onClick = { AlertManager.shared.hideAlert() - showAddServerDialog(scope, userServers) + showAddServerDialog(scope, rh, userServers) }) { Text(stringResource(MR.strings.smp_servers_add)) } }, // Ignore @@ -123,10 +125,10 @@ object PushManager { /** * Dialog to add a server, manually or with a QR code */ - private fun showAddServerDialog(scope: CoroutineScope, userServers: List) { + private fun showAddServerDialog(scope: CoroutineScope, rh: Long?, userServers: List) { val userServersState = mutableStateOf(userServers) val serverErrors = mutableStateOf(listOf()) - showAddServerDialog(scope, userServersState, serverErrors, null) + showAddServerDialog(scope, userServersState, serverErrors, rh) } /** diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 646004db68..a7badd03a4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3464,6 +3464,9 @@ sealed class CC { class ResetAgentServersStats(): CC() class GetAgentSubsTotal(val userId: Long): CC() class GetAgentServersSummary(val userId: Long): CC() + class APIRegisterWebPush(val endpoint: String, val auth: String, val p256dh: String): CC() + class APIVerifySavedNtf(val code: String): CC() + class APIDeleteSavedNtf(): CC() val cmdString: String get() = when (this) { is Console -> cmd @@ -3656,6 +3659,9 @@ sealed class CC { is ResetAgentServersStats -> "/reset servers stats" is GetAgentSubsTotal -> "/get subs total $userId" is GetAgentServersSummary -> "/get servers summary $userId" + is APIRegisterWebPush -> "/_ntf register webpush $endpoint $auth $p256dh INSTANT" + is APIDeleteSavedNtf -> "/_ntf delete saved" + is APIVerifySavedNtf -> "/_ntf verify $code" } val cmdType: String get() = when (this) { @@ -3814,6 +3820,9 @@ sealed class CC { is ResetAgentServersStats -> "resetAgentServersStats" is GetAgentSubsTotal -> "getAgentSubsTotal" is GetAgentServersSummary -> "getAgentServersSummary" + is APIRegisterWebPush -> "apiRegisterWebPush" + is APIDeleteSavedNtf -> "apiDeleteSavedNtf" + is APIVerifySavedNtf -> "apiVerifySavedNtf" } data class ItemRange(val from: Long, val to: Long) @@ -6952,7 +6961,7 @@ sealed class AgentErrorType { is CMD -> "CMD ${cmdErr.string} $errContext" is CONN -> "CONN ${connErr.string}" is SMP -> "SMP ${smpErr.string}" - // is NTF -> "NTF ${ntfErr.string}" + is NTF -> "NTF ${ntfErr.string}" is XFTP -> "XFTP ${xftpErr.string}" is PROXY -> "PROXY $proxyServer $relayServer ${proxyErr.string}" is RCP -> "RCP ${rcpErr.string}" @@ -6965,7 +6974,7 @@ sealed class AgentErrorType { @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType, val errContext: String): AgentErrorType() @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType() @Serializable @SerialName("SMP") class SMP(val serverAddress: String, val smpErr: SMPErrorType): AgentErrorType() - // @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() + @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() @Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType() @Serializable @SerialName("PROXY") class PROXY(val proxyServer: String, val relayServer: String, val proxyErr: ProxyClientError): AgentErrorType() @Serializable @SerialName("RCP") class RCP(val rcpErr: RCErrorType): AgentErrorType() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index baa667bb8d..3220f66868 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -101,27 +101,26 @@ fun navigateToProtocolView( userServers = userServers, serverErrors = serverErrors, onDelete = { - if (protocol == ServerProtocol.SMP) { - deleteSMPServer(userServers, operatorIndex, serverIndex) - } else { - deleteXFTPServer(userServers, operatorIndex, serverIndex) + when (protocol) { + ServerProtocol.NTF -> deleteNTFServer(userServers, operatorIndex, serverIndex) + ServerProtocol.SMP -> deleteSMPServer(userServers, operatorIndex, serverIndex) + ServerProtocol.XFTP -> deleteXFTPServer(userServers, operatorIndex, serverIndex) } close() }, onUpdate = { updatedServer -> userServers.value = userServers.value.toMutableList().apply { - this[operatorIndex] = this[operatorIndex].copy( - smpServers = if (protocol == ServerProtocol.SMP) { - this[operatorIndex].smpServers.toMutableList().apply { - this[serverIndex] = updatedServer - } - } else this[operatorIndex].smpServers, - xftpServers = if (protocol == ServerProtocol.XFTP) { - this[operatorIndex].xftpServers.toMutableList().apply { - this[serverIndex] = updatedServer - } - } else this[operatorIndex].xftpServers - ) + if (platform.supportsPushNotifications && protocol == ServerProtocol.NTF && updatedServer.enabled) { + // We keep a single ntf server, if the updatedServer is enabled, we disable all other ntf servers first + this.replaceAll { op -> + op.copy(ntfServers = op.ntfServers.map { server -> server.copy(enabled = false).also { s -> Log.d(TAG, "ntf: $s")} }) + } + } + this[operatorIndex] = when (protocol) { + ServerProtocol.NTF -> this[operatorIndex].copy(ntfServers = this[operatorIndex].ntfServers.toMutableList().apply { this[serverIndex] = updatedServer }) + ServerProtocol.SMP -> this[operatorIndex].copy(smpServers = this[operatorIndex].smpServers.toMutableList().apply { this[serverIndex] = updatedServer }) + ServerProtocol.XFTP -> this[operatorIndex].copy(xftpServers = this[operatorIndex].xftpServers.toMutableList().apply { this[serverIndex] = updatedServer }) + } } }, close = close, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt index bebc96a28c..9f5ebb34b2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt @@ -115,7 +115,11 @@ private fun ProtocolServerLayout( onDelete: () -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.XFTP) MR.strings.xftp_server else MR.strings.smp_server)) + AppBarTitle(stringResource(when (serverProtocol) { + ServerProtocol.NTF -> MR.strings.ntf_server + ServerProtocol.XFTP -> MR.strings.xftp_server + ServerProtocol.SMP -> MR.strings.smp_server + })) if (server.value.preset) { PresetServer(server, testing, testServer) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt index 4276df28a6..9d3d307fc8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt @@ -389,6 +389,32 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUp return fs } +fun deleteNTFServer( + userServers: MutableState>, + operatorServersIndex: Int, + serverIndex: Int +) { + val serverIsSaved = userServers.value[operatorServersIndex].ntfServers[serverIndex].serverId != null + + if (serverIsSaved) { + userServers.value = userServers.value.toMutableList().apply { + this[operatorServersIndex] = this[operatorServersIndex].copy( + ntfServers = this[operatorServersIndex].ntfServers.toMutableList().apply { + this[serverIndex] = this[serverIndex].copy(deleted = true) + } + ) + } + } else { + userServers.value = userServers.value.toMutableList().apply { + this[operatorServersIndex] = this[operatorServersIndex].copy( + ntfServers = this[operatorServersIndex].ntfServers.toMutableList().apply { + this.removeAt(serverIndex) + } + ) + } + } +} + fun deleteXFTPServer( userServers: MutableState>, operatorServersIndex: Int, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index cbd701f907..39daacf355 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2642,6 +2642,7 @@ Starting from %s. SMP server XFTP server + NTF server Reconnect attempts Sent directly diff --git a/cabal.project b/cabal.project index 48b75a86cd..9b82856f8e 100644 --- a/cabal.project +++ b/cabal.project @@ -11,8 +11,13 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git - location: https://github.com/simplex-chat/simplexmq.git - tag: d352d518c2b3a42bc7a298954dde799422e1457f + location: https://codeberg.org/s1m/sxmq.git + tag: f5720a254104d70b33ac1479ffa9d24ba9988b59 + +-- source-repository-package +-- type: git +-- location: https://github.com/simplex-chat/simplexmq.git +-- tag: d352d518c2b3a42bc7a298954dde799422e1457f source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 68a6054ef0..ffdf834552 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,4 +1,5 @@ { + "https://codeberg.org/s1m/sxmq.git"."f5720a254104d70b33ac1479ffa9d24ba9988b59" = "cf9a74de0d05afa60a74aa8aa54205c718286b9c16c5f0f398a24d7fa7f7b1ff"; "https://github.com/simplex-chat/simplexmq.git"."d352d518c2b3a42bc7a298954dde799422e1457f" = "1rha84pfpaqx3mf218szkfra334vhijqf17hanxqmp1sicfbf1x3"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";