Moves push registration notification into Compose after login to make sure we use the tor configuration of the active user.
This commit is contained in:
+20
-29
@@ -27,21 +27,20 @@ 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.amethyst.ui.tor.TorManager
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.nip55AndroidSigner.NostrSignerExternal
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class RegisterAccounts(
|
||||
private val accounts: List<AccountInfo>,
|
||||
private val client: OkHttpClient,
|
||||
) {
|
||||
val tag =
|
||||
if (BuildConfig.FLAVOR == "play") {
|
||||
@@ -134,33 +133,25 @@ class RegisterAccounts(
|
||||
}
|
||||
|
||||
fun postRegistrationEvent(events: List<RelayAuthEvent>) {
|
||||
try {
|
||||
val jsonObject =
|
||||
"""{
|
||||
"events": [ ${events.joinToString(", ") { it.toJson() }} ]
|
||||
}
|
||||
"""
|
||||
|
||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||
val body = jsonObject.toRequestBody(mediaType)
|
||||
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.url("https://push.amethyst.social/register")
|
||||
.post(body)
|
||||
.build()
|
||||
|
||||
// Always try via Tor if active.
|
||||
val client = HttpClientManager.getHttpClient(TorManager.isSocksReady())
|
||||
|
||||
val isSucess = client.newCall(request).execute().use { it.isSuccessful }
|
||||
Log.i(tag, "Server registration $isSucess")
|
||||
} catch (e: java.lang.Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e(tag, "Unable to register with push server", e)
|
||||
val jsonObject =
|
||||
"""{
|
||||
"events": [ ${events.joinToString(", ") { it.toJson() }} ]
|
||||
}
|
||||
"""
|
||||
|
||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||
val body = jsonObject.toRequestBody(mediaType)
|
||||
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.url("https://push.amethyst.social/register")
|
||||
.post(body)
|
||||
.build()
|
||||
|
||||
val isSucess = client.newCall(request).execute().use { it.isSuccessful }
|
||||
Log.i(tag, "Server registration $isSucess")
|
||||
}
|
||||
|
||||
suspend fun go(notificationToken: String) {
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ class PlaybackService : MediaSessionService() {
|
||||
poolWithProxy?.let { pool ->
|
||||
// with proxy, check if the port is the same.
|
||||
val okHttp = HttpClientManager.getHttpClient(true)
|
||||
if (okHttp.proxy == pool.exoPlayerPool.builder.okHttp.proxy) {
|
||||
if (okHttp.proxy != null && okHttp.proxy == pool.exoPlayerPool.builder.okHttp.proxy) {
|
||||
return pool
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,9 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
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.service.playback.composable.DEFAULT_MUTED_SETTING
|
||||
import com.vitorpamplona.amethyst.service.playback.pip.BackgroundMedia
|
||||
@@ -122,10 +120,6 @@ class MainActivity : AppCompatActivity() {
|
||||
GlobalScope.launch(Dispatchers.IO) { Amethyst.instance.serviceManager.justStart() }
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
PushNotificationUtils.init(LocalPreferences.allSavedAccounts())
|
||||
}
|
||||
|
||||
val connectivityManager =
|
||||
(getSystemService(ConnectivityManager::class.java) as ConnectivityManager)
|
||||
connectivityManager.registerDefaultNetworkCallback(networkCallback)
|
||||
|
||||
@@ -53,8 +53,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
||||
@@ -149,6 +152,8 @@ fun LoggedInPage(
|
||||
|
||||
ListenToExternalSignerIfNeeded(accountViewModel)
|
||||
|
||||
NotificationRegistration(accountViewModel)
|
||||
|
||||
AppNavigation(
|
||||
accountViewModel = accountViewModel,
|
||||
accountStateViewModel = accountStateViewModel,
|
||||
@@ -156,6 +161,44 @@ fun LoggedInPage(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NotificationRegistration(accountViewModel: AccountViewModel) {
|
||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var job = remember<Job?> { null }
|
||||
|
||||
LaunchedEffect(accountViewModel) {
|
||||
job?.cancel()
|
||||
job =
|
||||
launch {
|
||||
val okHttpClient = HttpClientManager.getHttpClient(accountViewModel.account.shouldUseTorForTrustedRelays())
|
||||
PushNotificationUtils.checkAndInit(LocalPreferences.allSavedAccounts(), okHttpClient)
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(key1 = accountViewModel) {
|
||||
val observer =
|
||||
LifecycleEventObserver { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> {
|
||||
job?.cancel()
|
||||
job =
|
||||
scope.launch {
|
||||
val okHttpClient = HttpClientManager.getHttpClient(accountViewModel.account.shouldUseTorForTrustedRelays())
|
||||
PushNotificationUtils.checkAndInit(LocalPreferences.allSavedAccounts(), okHttpClient)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
lifeCycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
lifeCycleOwner.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ManageTorInstance(accountViewModel: AccountViewModel) {
|
||||
val torSettings by accountViewModel.account.settings.torSettings.torType
|
||||
|
||||
+6
-1
@@ -29,6 +29,8 @@ import com.google.firebase.messaging.RemoteMessage
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorManager
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -82,7 +84,10 @@ class PushNotificationReceiverService : FirebaseMessagingService() {
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token)
|
||||
Log.d("Lifetime Event", "PushNotificationReceiverService.onNewToken")
|
||||
// if the app is running, try to get tor. if not, goes open web.
|
||||
val okHttpClient = HttpClientManager.getHttpClient(TorManager.isSocksReady())
|
||||
PushNotificationUtils.checkAndInit(token, LocalPreferences.allSavedAccounts(), okHttpClient)
|
||||
notificationManager().getOrCreateZapChannel(applicationContext)
|
||||
notificationManager().getOrCreateDMChannel(applicationContext)
|
||||
}
|
||||
|
||||
+61
-14
@@ -25,24 +25,71 @@ import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.vitorpamplona.amethyst.AccountInfo
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
object PushNotificationUtils {
|
||||
var lastToken: String? = null
|
||||
var hasInit: List<AccountInfo>? = null
|
||||
|
||||
suspend fun init(accounts: List<AccountInfo>) =
|
||||
with(Dispatchers.IO) {
|
||||
if (hasInit?.equals(accounts) == true) {
|
||||
return@with
|
||||
}
|
||||
// get user notification token provided by firebase
|
||||
try {
|
||||
RegisterAccounts(accounts).go(FirebaseMessaging.getInstance().token.await())
|
||||
|
||||
hasInit = accounts.toList()
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("Firebase token", "failed to get firebase token", e)
|
||||
}
|
||||
suspend fun checkAndInit(
|
||||
accounts: List<AccountInfo>,
|
||||
okHttpClient: OkHttpClient,
|
||||
) = with(Dispatchers.IO) {
|
||||
val token = FirebaseMessaging.getInstance().token.await()
|
||||
if (hasInit?.equals(accounts) == true && lastToken == token) {
|
||||
return@with
|
||||
}
|
||||
|
||||
registerToken(token, accounts, okHttpClient)
|
||||
}
|
||||
|
||||
suspend fun checkAndInit(
|
||||
token: String,
|
||||
accounts: List<AccountInfo>,
|
||||
okHttpClient: OkHttpClient,
|
||||
) = with(Dispatchers.IO) {
|
||||
// initializes if the accounts are different or if the token has changed
|
||||
if (hasInit?.equals(accounts) == true && lastToken == token) {
|
||||
return@with
|
||||
}
|
||||
registerToken(token, accounts, okHttpClient)
|
||||
}
|
||||
|
||||
private suspend fun registerToken(
|
||||
token: String,
|
||||
accounts: List<AccountInfo>,
|
||||
okHttpClient: OkHttpClient,
|
||||
) = retryIfException("RegisterAccounts") {
|
||||
RegisterAccounts(accounts, okHttpClient).go(token)
|
||||
lastToken = token
|
||||
hasInit = accounts.toList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> retryIfException(
|
||||
debugTag: String = "RetryIfException",
|
||||
maxRetries: Int = 10,
|
||||
delayMs: Long = 1000,
|
||||
func: suspend () -> T,
|
||||
) {
|
||||
var tentative = 0
|
||||
var currentDelay = delayMs
|
||||
while (tentative < maxRetries) {
|
||||
try {
|
||||
func()
|
||||
|
||||
// if it works, finishes.
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e(debugTag, "Tentative $tentative failed", e)
|
||||
|
||||
delay(currentDelay)
|
||||
tentative++
|
||||
currentDelay = currentDelay * 2
|
||||
}
|
||||
}
|
||||
// gives up
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user