session-android/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt

112 lines
4.6 KiB
Kotlin
Raw Normal View History

2023-06-21 02:31:35 +02:00
package org.thoughtcrime.securesms.notifications
import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.interfaces.Sign
import com.goterl.lazysodium.utils.KeyPair
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.map
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody
2023-07-26 03:18:20 +02:00
import org.session.libsession.messaging.sending_receiving.notifications.*
2023-06-21 02:31:35 +02:00
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.Version
2023-07-26 07:06:06 +02:00
import org.session.libsession.utilities.Device
2023-06-21 02:31:35 +02:00
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.retryIfNeeded
2023-07-26 03:18:20 +02:00
import javax.inject.Inject
import javax.inject.Singleton
2023-06-21 02:31:35 +02:00
private const val TAG = "PushManagerV2"
2023-07-26 03:18:20 +02:00
private const val maxRetryCount = 4
2023-06-21 02:31:35 +02:00
2023-07-26 03:18:20 +02:00
@Singleton
class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) {
2023-06-21 02:31:35 +02:00
private val sodium = LazySodiumAndroid(SodiumAndroid())
fun register(
2023-07-26 07:06:06 +02:00
device: Device,
2023-06-21 02:31:35 +02:00
token: String,
publicKey: String,
userEd25519Key: KeyPair,
namespaces: List<Int>
): Promise<SubscriptionResponse, Exception> {
2023-07-25 07:19:41 +02:00
val pnKey = pushHandler.getOrCreateNotificationKey()
2023-06-21 02:31:35 +02:00
val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s
// if we want to support passing namespace list, here is the place to do it
val sigData = "MONITOR${publicKey}${timestamp}1${namespaces.joinToString(separator = ",")}".encodeToByteArray()
val signature = ByteArray(Sign.BYTES)
sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEd25519Key.secretKey.asBytes)
val requestParameters = SubscriptionRequest(
pubkey = publicKey,
session_ed25519 = userEd25519Key.publicKey.asHexString,
namespaces = listOf(Namespace.DEFAULT),
data = true, // only permit data subscription for now (?)
2023-07-26 07:06:06 +02:00
service = device.service,
2023-06-21 02:31:35 +02:00
sig_ts = timestamp,
signature = Base64.encodeBytes(signature),
service_info = mapOf("token" to token),
enc_key = pnKey.asHexString,
).let(Json::encodeToString)
return retryResponseBody<SubscriptionResponse>("subscribe", requestParameters) success {
2023-06-23 02:59:49 +02:00
Log.d(TAG, "registerV2 success")
2023-06-21 02:31:35 +02:00
}
}
fun unregister(
2023-07-26 07:06:06 +02:00
device: Device,
2023-06-21 02:31:35 +02:00
token: String,
userPublicKey: String,
userEdKey: KeyPair
): Promise<UnsubscribeResponse, Exception> {
val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s
// if we want to support passing namespace list, here is the place to do it
val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray()
val signature = ByteArray(Sign.BYTES)
sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes)
val requestParameters = UnsubscriptionRequest(
pubkey = userPublicKey,
session_ed25519 = userEdKey.publicKey.asHexString,
2023-07-26 07:06:06 +02:00
service = device.service,
2023-06-21 02:31:35 +02:00
sig_ts = timestamp,
signature = Base64.encodeBytes(signature),
service_info = mapOf("token" to token),
).let(Json::encodeToString)
return retryResponseBody<UnsubscribeResponse>("unsubscribe", requestParameters) success {
2023-06-23 02:59:49 +02:00
Log.d(TAG, "unregisterV2 success")
2023-06-21 02:31:35 +02:00
}
}
private inline fun <reified T: Response> retryResponseBody(path: String, requestParameters: String): Promise<T, Exception> =
2023-07-26 03:18:20 +02:00
retryIfNeeded(maxRetryCount) { getResponseBody(path, requestParameters) }
2023-06-21 02:31:35 +02:00
private inline fun <reified T: Response> getResponseBody(path: String, requestParameters: String): Promise<T, Exception> {
2023-06-23 02:59:49 +02:00
val server = Server.LATEST
val url = "${server.url}/$path"
2023-06-21 02:31:35 +02:00
val body = RequestBody.create(MediaType.get("application/json"), requestParameters)
val request = Request.Builder().url(url).post(body).build()
return OnionRequestAPI.sendOnionRequest(
request,
2023-06-23 02:59:49 +02:00
server.url,
server.publicKey,
2023-06-21 02:31:35 +02:00
Version.V4
).map { response ->
response.body!!.inputStream()
.let { Json.decodeFromStream<T>(it) }
.also { if (it.isFailure()) throw Exception("error: ${it.message}.") }
}
}
2023-06-23 02:59:49 +02:00
}