2026-04-21 22:07:29 +08:00
|
|
|
package com.xuqm.sdk.push
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
2026-04-29 09:50:09 +08:00
|
|
|
import android.util.Log
|
|
|
|
|
import com.google.firebase.messaging.FirebaseMessaging
|
|
|
|
|
import com.xuqm.sdk.XuqmLoginSession
|
2026-04-21 22:07:29 +08:00
|
|
|
import com.xuqm.sdk.XuqmSDK
|
2026-04-27 19:30:06 +08:00
|
|
|
import com.xuqm.sdk.core.ServiceEndpointRegistry
|
2026-04-21 22:07:29 +08:00
|
|
|
import com.xuqm.sdk.network.ApiClient
|
|
|
|
|
import com.xuqm.sdk.push.api.PushApi
|
2026-04-29 09:50:09 +08:00
|
|
|
import com.xuqm.sdk.push.model.PushRegistrationSnapshot
|
|
|
|
|
import com.xuqm.sdk.push.model.PushVendor
|
|
|
|
|
import com.xuqm.sdk.push.storage.PushRegistrationStore
|
2026-04-21 22:07:29 +08:00
|
|
|
import com.xuqm.sdk.utils.DeviceUtils
|
|
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
2026-04-29 09:50:09 +08:00
|
|
|
import kotlinx.coroutines.SupervisorJob
|
2026-04-21 22:07:29 +08:00
|
|
|
import kotlinx.coroutines.launch
|
2026-04-28 09:45:20 +08:00
|
|
|
import java.util.concurrent.atomic.AtomicReference
|
2026-04-21 22:07:29 +08:00
|
|
|
|
|
|
|
|
object PushSDK {
|
|
|
|
|
|
2026-04-27 19:30:06 +08:00
|
|
|
private val api: PushApi get() = ApiClient.create(PushApi::class.java, ServiceEndpointRegistry.pushBaseUrl)
|
2026-04-29 09:50:09 +08:00
|
|
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
2026-04-28 09:45:20 +08:00
|
|
|
private val registeredUserId = AtomicReference<String?>(null)
|
2026-04-21 22:07:29 +08:00
|
|
|
|
2026-04-29 09:50:09 +08:00
|
|
|
fun currentRegistration(context: Context): PushRegistrationSnapshot? {
|
2026-04-21 22:07:29 +08:00
|
|
|
XuqmSDK.requireInit()
|
2026-04-27 17:18:55 +08:00
|
|
|
val deviceId = DeviceUtils.getDeviceId(context)
|
2026-04-29 09:50:09 +08:00
|
|
|
return store(context).load(deviceId = deviceId, fallbackVendor = detectVendor())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal fun updateNativePushToken(
|
|
|
|
|
context: Context,
|
|
|
|
|
vendor: PushVendor,
|
|
|
|
|
pushToken: String,
|
|
|
|
|
) {
|
|
|
|
|
XuqmSDK.requireInit()
|
|
|
|
|
val normalizedToken = pushToken.trim()
|
|
|
|
|
require(normalizedToken.isNotBlank()) { "pushToken must not be blank" }
|
|
|
|
|
store(context).save(vendor, normalizedToken)
|
|
|
|
|
val sessionUserId = XuqmSDK.currentLoginSession?.userId
|
|
|
|
|
if (sessionUserId != null) {
|
|
|
|
|
bindImUser(context, sessionUserId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun bindImUser(context: Context, userId: String) {
|
|
|
|
|
if (!isReceivePushEnabled(context)) return
|
|
|
|
|
ensureNativePushToken(context)
|
|
|
|
|
registerDevice(context, userId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun unbindImUser(userId: String) {
|
|
|
|
|
unregisterDevice(userId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setReceivePush(
|
|
|
|
|
context: Context,
|
|
|
|
|
userId: String? = XuqmSDK.currentLoginSession?.userId,
|
|
|
|
|
enabled: Boolean,
|
|
|
|
|
) {
|
|
|
|
|
XuqmSDK.requireInit()
|
|
|
|
|
store(context).setReceivePush(enabled)
|
|
|
|
|
val resolvedUserId = userId ?: registeredUserId.get()
|
|
|
|
|
if (resolvedUserId != null) {
|
|
|
|
|
scope.launch {
|
|
|
|
|
runCatching {
|
|
|
|
|
api.setReceivePush(
|
|
|
|
|
appId = XuqmSDK.appId,
|
|
|
|
|
userId = resolvedUserId,
|
|
|
|
|
enabled = enabled,
|
|
|
|
|
)
|
|
|
|
|
if (enabled) {
|
|
|
|
|
bindImUser(context, resolvedUserId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun registerDevice(
|
|
|
|
|
context: Context,
|
|
|
|
|
userId: String,
|
|
|
|
|
) {
|
|
|
|
|
XuqmSDK.requireInit()
|
|
|
|
|
val registration = currentRegistration(context)
|
|
|
|
|
val vendor = registration?.vendor ?: detectVendor()
|
|
|
|
|
val pushToken = registration?.pushToken?.takeIf { it.isNotBlank() }
|
|
|
|
|
if (pushToken.isNullOrBlank()) {
|
|
|
|
|
Log.w("XuqmPushSDK", "Native push token not ready yet, waiting for onNewToken()")
|
|
|
|
|
ensureNativePushToken(context)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-21 22:07:29 +08:00
|
|
|
scope.launch {
|
|
|
|
|
runCatching {
|
2026-04-27 17:18:55 +08:00
|
|
|
api.registerDevice(
|
2026-04-27 19:00:54 +08:00
|
|
|
appId = XuqmSDK.appId,
|
|
|
|
|
userId = userId,
|
2026-04-29 09:50:09 +08:00
|
|
|
vendor = vendor.name,
|
|
|
|
|
token = pushToken,
|
2026-04-27 17:18:55 +08:00
|
|
|
)
|
2026-04-28 09:45:20 +08:00
|
|
|
registeredUserId.set(userId)
|
2026-04-29 09:50:09 +08:00
|
|
|
store(context).updateLastUserId(userId)
|
|
|
|
|
Log.i(
|
|
|
|
|
"XuqmPushSDK",
|
|
|
|
|
"Registered push device for userId=$userId vendor=${vendor.name}",
|
|
|
|
|
)
|
2026-04-21 22:07:29 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 17:18:55 +08:00
|
|
|
fun unregisterDevice(userId: String) {
|
2026-04-21 22:07:29 +08:00
|
|
|
XuqmSDK.requireInit()
|
|
|
|
|
scope.launch {
|
2026-04-28 09:45:20 +08:00
|
|
|
runCatching {
|
|
|
|
|
api.unregisterDevice(XuqmSDK.appId, userId)
|
|
|
|
|
registeredUserId.compareAndSet(userId, null)
|
2026-04-29 09:50:09 +08:00
|
|
|
store(XuqmSDK.appContext).updateLastUserId(null)
|
2026-04-28 09:45:20 +08:00
|
|
|
}
|
2026-04-21 22:07:29 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-28 09:45:20 +08:00
|
|
|
|
2026-04-29 09:50:09 +08:00
|
|
|
fun onSdkLogin(session: XuqmLoginSession) {
|
2026-04-28 09:45:20 +08:00
|
|
|
val context = runCatching { XuqmSDK.appContext }.getOrNull() ?: return
|
|
|
|
|
if (registeredUserId.get() == session.userId) return
|
2026-04-29 09:50:09 +08:00
|
|
|
bindImUser(context, session.userId)
|
2026-04-28 09:45:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun onSdkLogout() {
|
|
|
|
|
val userId = registeredUserId.getAndSet(null) ?: return
|
|
|
|
|
unregisterDevice(userId)
|
|
|
|
|
}
|
2026-04-29 09:50:09 +08:00
|
|
|
|
|
|
|
|
private fun detectVendor(): PushVendor = PushVendor.FCM
|
|
|
|
|
|
|
|
|
|
private fun store(context: Context): PushRegistrationStore =
|
|
|
|
|
PushRegistrationStore(context.applicationContext)
|
|
|
|
|
|
|
|
|
|
private fun isReceivePushEnabled(context: Context): Boolean =
|
|
|
|
|
store(context).load(
|
|
|
|
|
deviceId = DeviceUtils.getDeviceId(context),
|
|
|
|
|
fallbackVendor = detectVendor(),
|
|
|
|
|
)?.receivePush ?: true
|
|
|
|
|
|
|
|
|
|
private fun ensureNativePushToken(context: Context) {
|
|
|
|
|
val vendor = detectVendor()
|
|
|
|
|
if (vendor != PushVendor.FCM) return
|
|
|
|
|
val registration = currentRegistration(context)
|
|
|
|
|
if (registration?.pushToken?.isNotBlank() == true) return
|
|
|
|
|
runCatching { FirebaseMessaging.getInstance() }
|
|
|
|
|
.onSuccess { messaging ->
|
|
|
|
|
messaging.token
|
|
|
|
|
.addOnSuccessListener { token ->
|
|
|
|
|
if (token.isNotBlank()) {
|
|
|
|
|
store(context).save(PushVendor.FCM, token)
|
|
|
|
|
val sessionUserId = XuqmSDK.currentLoginSession?.userId
|
|
|
|
|
if (sessionUserId != null) {
|
|
|
|
|
registerDevice(context, sessionUserId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.addOnFailureListener { error ->
|
|
|
|
|
Log.w("XuqmPushSDK", "Unable to fetch FCM token: ${error.message}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.onFailure { error ->
|
|
|
|
|
Log.w("XuqmPushSDK", "Firebase Messaging not available: ${error.message}")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-21 22:07:29 +08:00
|
|
|
}
|