Fix push device registration

This commit is contained in:
sevenhill
2026-07-05 16:07:44 +03:00
parent bb92651714
commit 81dc918a50
5 changed files with 147 additions and 14 deletions
+2 -2
View File
@@ -22,8 +22,8 @@ android {
applicationId = "xyz.kusoft.qmax"
minSdk = 26
targetSdk = 36
versionCode = 48
versionName = "0.1.47"
versionCode = 49
versionName = "0.1.48"
buildConfigField("String", "QMAX_DEFAULT_SERVER_URL", "\"https://qmax.kusoft.xyz\"")
buildConfigField("String", "QMAX_DEFAULT_PAIRING_CODE", "\"qmax-MxRq4h2HQBEIFs6k\"")
@@ -6,6 +6,7 @@ import android.content.ClipData
import android.content.ActivityNotFoundException
import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import androidx.core.content.FileProvider
import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.coroutines.flow.Flow
@@ -268,23 +269,27 @@ class QMaxRepository(
return withFreshSession(session) { api.submitMaxCode(it.serverUrl, it.accessToken, code) }
}
suspend fun registerCurrentPushDevice(session: QMaxSession) {
suspend fun registerCurrentPushDevice(session: QMaxSession): Boolean {
if (!FirebaseBootstrap.ensureInitialized(context)) {
return
Log.w(PushLogTag, "Firebase is not initialized; push device registration skipped")
return false
}
val token = FirebaseMessaging.getInstance().token.await()
registerPushDevice(session, token)
return registerPushDevice(session, token)
}
suspend fun registerPushDevice(session: QMaxSession, firebaseToken: String) {
suspend fun registerPushDevice(session: QMaxSession, firebaseToken: String): Boolean {
if (firebaseToken.isBlank()) {
return
Log.w(PushLogTag, "Firebase returned a blank FCM token")
return false
}
withFreshSession(session) {
api.registerPushDevice(it.serverUrl, it.accessToken, firebaseToken)
}
Log.i(PushLogTag, "Push device token registered with QMAX API")
return true
}
private suspend fun sharePayload(session: QMaxSession, text: String?, attachments: List<AttachmentDto>) {
@@ -494,6 +499,8 @@ class QMaxRepository(
}
}
private const val PushLogTag = "QMaxPush"
private data class SharedAttachment(
val attachment: AttachmentDto,
val file: File,
@@ -70,6 +70,8 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
private var chatSearchJob: Job? = null
private var autoLoginJob: Job? = null
private var pushRegisteredForToken: String? = null
private var pushRegistrationJob: Job? = null
private var pushRegistrationInFlightForToken: String? = null
private var autoLoginAttempted = false
private var pendingOpenChatId: String? = null
private val locallyReadChatFingerprints = mutableMapOf<String, String>()
@@ -97,6 +99,9 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
realtimeConnectJob?.cancel()
messagePollJob?.cancel()
presencePollJob?.cancel()
pushRegistrationJob?.cancel()
pushRegistrationInFlightForToken = null
pushRegisteredForToken = null
realtime.disconnect()
if (!autoLoginAttempted && state.value.pairingCode.isNotBlank()) {
autoLoginAttempted = true
@@ -843,11 +848,46 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
if (pushRegisteredForToken == registrationKey) {
return
}
if (pushRegistrationJob?.isActive == true && pushRegistrationInFlightForToken == registrationKey) {
return
}
pushRegisteredForToken = registrationKey
viewModelScope.launch {
runCatching {
repository.registerCurrentPushDevice(session)
pushRegistrationJob?.cancel()
pushRegistrationInFlightForToken = registrationKey
pushRegistrationJob = viewModelScope.launch {
try {
var retryDelayMs = 0L
for (attempt in 1..PushRegistrationAttempts) {
if (retryDelayMs > 0) {
delay(retryDelayMs)
}
val registered = try {
repository.registerCurrentPushDevice(session)
} catch (error: CancellationException) {
throw error
} catch (error: Throwable) {
Log.w(LogTag, "push registration failed, attempt=$attempt", error)
false
}
if (registered) {
pushRegisteredForToken = registrationKey
Log.i(LogTag, "push registration completed")
return@launch
}
Log.w(LogTag, "push registration was not completed, attempt=$attempt")
retryDelayMs = if (retryDelayMs == 0L) {
InitialPushRegistrationRetryMs
} else {
(retryDelayMs * 2).coerceAtMost(MaxPushRegistrationRetryMs)
}
}
} finally {
if (pushRegistrationInFlightForToken == registrationKey) {
pushRegistrationInFlightForToken = null
}
}
}
}
@@ -1036,5 +1076,8 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
const val ChatListTimeoutMs = 45_000L
const val MessageSyncTimeoutMs = 120_000L
const val AutoLoginDelayMs = 1_000L
const val PushRegistrationAttempts = 6
const val InitialPushRegistrationRetryMs = 15_000L
const val MaxPushRegistrationRetryMs = 120_000L
}
}