diff --git a/README.md b/README.md index e37e8c1..9c6724f 100644 --- a/README.md +++ b/README.md @@ -56,18 +56,23 @@ dependencies { XuqmSDK.initialize( context = this, appId = "ak_your_app_id", - debug = BuildConfig.DEBUG + logLevel = if (BuildConfig.DEBUG) LogLevel.DEBUG else LogLevel.WARN ) ``` ### 2. 用户登录后初始化 IM ```kotlin -// 调用业务登录接口,拿到 userSig 后再登录 IM +// 调用业务登录接口,拿到 userSig 后只需要登录一次 SDK val userSig = api.getUserSig(userId) -ImSDK.login(userId = userId, userSig = userSig) +XuqmSDK.login( + userId = userId, + userSig = userSig, + nickname = profile.nickname, + avatar = profile.avatar, +) -// Push 设备注册在 PushSDK.initialize() 内部自动完成 +// 如果工程里集成了 sdk-im,SDK 会自动完成 IM 登录 ``` --- @@ -79,18 +84,18 @@ ImSDK.login(userId = userId, userSig = userSig) | 参数 | 类型 | 说明 | |------|------|------| | `appId` | String | 应用标识(租户平台获取) | -| `debug` | Boolean | 开启日志 | +| `logLevel` | LogLevel | 日志等级 | ### TokenStore -基于 `DataStore` 持久化存储。 +基于 `EncryptedSharedPreferences` 持久化存储。 ```kotlin // 存储 -XuqmSDK.tokenStore.save("eyJ...") +XuqmSDK.tokenStore.saveToken("eyJ...") // 读取(协程) -val token = XuqmSDK.tokenStore.get() +val token = XuqmSDK.tokenStore.getToken() // 清除(登出) XuqmSDK.tokenStore.clear() diff --git a/sample-app/src/main/java/com/xuqm/sdk/sample/data/api/DemoApi.kt b/sample-app/src/main/java/com/xuqm/sdk/sample/data/api/DemoApi.kt index 27b81fd..a54fad5 100644 --- a/sample-app/src/main/java/com/xuqm/sdk/sample/data/api/DemoApi.kt +++ b/sample-app/src/main/java/com/xuqm/sdk/sample/data/api/DemoApi.kt @@ -4,6 +4,7 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import com.google.gson.annotations.SerializedName import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -36,7 +37,8 @@ data class AuthProfile( data class AuthResult( val demoToken: String, - val imToken: String? = null, + @SerializedName(value = "imToken", alternate = ["userSig"]) + val userSig: String? = null, val profile: AuthProfile, ) diff --git a/sample-app/src/main/java/com/xuqm/sdk/sample/data/repo/AuthRepository.kt b/sample-app/src/main/java/com/xuqm/sdk/sample/data/repo/AuthRepository.kt index 54fd15d..84af95b 100644 --- a/sample-app/src/main/java/com/xuqm/sdk/sample/data/repo/AuthRepository.kt +++ b/sample-app/src/main/java/com/xuqm/sdk/sample/data/repo/AuthRepository.kt @@ -3,12 +3,12 @@ package com.xuqm.sdk.sample.data.repo import android.content.Context import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys -import com.xuqm.sdk.im.ImSDK -import com.xuqm.sdk.sample.data.api.DemoApi -import com.xuqm.sdk.sample.data.api.DEMO_APP_ID -import com.xuqm.sdk.sample.data.api.DemoApiFactory +import com.xuqm.sdk.XuqmSDK import com.xuqm.sdk.sample.data.api.AuthResult import com.xuqm.sdk.sample.data.api.ChangePasswordRequest +import com.xuqm.sdk.sample.data.api.DEMO_APP_ID +import com.xuqm.sdk.sample.data.api.DemoApi +import com.xuqm.sdk.sample.data.api.DemoApiFactory import com.xuqm.sdk.sample.data.api.LoginRequest import com.xuqm.sdk.sample.data.api.RegisterRequest import com.xuqm.sdk.sample.data.api.ResetPasswordRequest @@ -42,6 +42,7 @@ class AuthRepository(context: Context) { .putString("user_id", profile.userId) .putString("nickname", profile.nickname) .putString("avatar", profile.avatar) + .putString("user_sig", result.userSig) .apply() } @@ -51,8 +52,15 @@ class AuthRepository(context: Context) { val res = api.login(LoginRequest(DEMO_APP_ID, userId, password)) val data = requireNotNull(res.data) { res.message ?: "Login failed" } saveSession(data) - data.imToken?.takeIf { it.isNotBlank() }?.let { ImSDK.loginWithToken(data.profile.userId, it) } - ?: ImSDK.login(data.profile.userId, data.profile.nickname, data.profile.avatar) + val userSig = requireNotNull(data.userSig?.takeIf { it.isNotBlank() }) { + "Login succeeded but IM credential is missing" + } + XuqmSDK.login( + userId = data.profile.userId, + userSig = userSig, + nickname = data.profile.nickname, + avatar = data.profile.avatar, + ) UserData(data.profile.userId, data.profile.nickname, data.profile.avatar, data.profile.gender) } } @@ -63,8 +71,15 @@ class AuthRepository(context: Context) { val res = api.register(RegisterRequest(DEMO_APP_ID, userId, password, nickname)) val data = requireNotNull(res.data) { res.message ?: "Register failed" } saveSession(data) - data.imToken?.takeIf { it.isNotBlank() }?.let { ImSDK.loginWithToken(data.profile.userId, it) } - ?: ImSDK.login(data.profile.userId, data.profile.nickname, data.profile.avatar) + val userSig = requireNotNull(data.userSig?.takeIf { it.isNotBlank() }) { + "Register succeeded but IM credential is missing" + } + XuqmSDK.login( + userId = data.profile.userId, + userSig = userSig, + nickname = data.profile.nickname, + avatar = data.profile.avatar, + ) UserData(data.profile.userId, data.profile.nickname, data.profile.avatar, data.profile.gender) } } @@ -94,7 +109,7 @@ class AuthRepository(context: Context) { } fun logout() { - ImSDK.disconnect() + XuqmSDK.logout() prefs.edit().clear().apply() } } diff --git a/sdk-core/src/main/java/com/xuqm/sdk/XuqmLoginSession.kt b/sdk-core/src/main/java/com/xuqm/sdk/XuqmLoginSession.kt new file mode 100644 index 0000000..663bdf8 --- /dev/null +++ b/sdk-core/src/main/java/com/xuqm/sdk/XuqmLoginSession.kt @@ -0,0 +1,9 @@ +package com.xuqm.sdk + +data class XuqmLoginSession( + val appId: String, + val userId: String, + val userSig: String, + val nickname: String? = null, + val avatar: String? = null, +) diff --git a/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt b/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt index 73e1050..436d25b 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/XuqmSDK.kt @@ -5,6 +5,8 @@ import com.xuqm.sdk.auth.TokenStore import com.xuqm.sdk.core.LogLevel import com.xuqm.sdk.core.SDKConfig import com.xuqm.sdk.network.ApiClient +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext object XuqmSDK { @@ -15,6 +17,8 @@ object XuqmSDK { private set private var initialized = false + @Volatile + private var loginSession: XuqmLoginSession? = null fun initialize( context: Context, @@ -32,4 +36,72 @@ object XuqmSDK { } val appId: String get() = config.appId + + val currentLoginSession: XuqmLoginSession? + get() = loginSession + + suspend fun login( + userId: String, + userSig: String, + nickname: String? = null, + avatar: String? = null, + ): XuqmLoginSession = withContext(Dispatchers.IO) { + requireInit() + val session = XuqmLoginSession( + appId = appId, + userId = userId, + userSig = userSig, + nickname = nickname, + avatar = avatar, + ) + loginSession = session + tokenStore.saveToken(userSig) + notifyOptionalModules("onSdkLogin", session) + session + } + + fun logout() { + val session = loginSession + loginSession = null + tokenStore.clear() + if (session != null) { + notifyOptionalModulesSync("onSdkLogout") + } + } + + private fun notifyOptionalModules(methodName: String, session: XuqmLoginSession) { + listOf( + "com.xuqm.sdk.im.ImSDK", + "com.xuqm.sdk.push.PushSDK", + "com.xuqm.sdk.update.UpdateSDK", + ).forEach { className -> + runCatching { + val clazz = Class.forName(className) + val instance = clazz.getField("INSTANCE").get(null) + val method = clazz.methods.firstOrNull { method -> + method.name == methodName && + method.parameterTypes.size == 1 && + method.parameterTypes[0].name == XuqmLoginSession::class.java.name + } ?: return@runCatching + method.invoke(instance, session) + } + } + } + + private fun notifyOptionalModulesSync(methodName: String) { + listOf( + "com.xuqm.sdk.im.ImSDK", + "com.xuqm.sdk.push.PushSDK", + "com.xuqm.sdk.update.UpdateSDK", + ).forEach { className -> + runCatching { + val clazz = Class.forName(className) + val instance = clazz.getField("INSTANCE").get(null) + val method = clazz.methods.firstOrNull { method -> + method.name == methodName && method.parameterTypes.isEmpty() + } ?: return@runCatching + method.invoke(instance) + } + } + } } diff --git a/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt b/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt index ccbda53..28da44a 100644 --- a/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt +++ b/sdk-im/src/main/java/com/xuqm/sdk/im/ImSDK.kt @@ -1,11 +1,11 @@ package com.xuqm.sdk.im +import com.xuqm.sdk.XuqmLoginSession import com.xuqm.sdk.XuqmSDK import com.xuqm.sdk.core.WS_URL import com.xuqm.sdk.im.api.AddMemberRequest import com.xuqm.sdk.im.api.CreateGroupRequest import com.xuqm.sdk.im.api.ImApi -import com.xuqm.sdk.im.api.LoginRequest import com.xuqm.sdk.im.api.SetMutedRequest import com.xuqm.sdk.im.api.SetPinnedRequest import com.xuqm.sdk.im.api.UpdateGroupRequest @@ -25,21 +25,31 @@ object ImSDK { var currentUserId: String = "" private set - suspend fun login(userId: String, nickname: String? = null, avatar: String? = null) = + init { + XuqmSDK.currentLoginSession?.let { onSdkLogin(it) } + } + + suspend fun login( + userId: String, + userSig: String, + nickname: String? = null, + avatar: String? = null, + ) = withContext(Dispatchers.IO) { + XuqmSDK.requireInit() + currentUserId = userId + connectWithToken(userSig) + } + + suspend fun loginWithUserSig(userId: String, userSig: String) = withContext(Dispatchers.IO) { XuqmSDK.requireInit() - val res = api.login(LoginRequest(XuqmSDK.appId, userId, nickname, avatar)) - val token = requireNotNull(res.data?.token) { "IM login failed: ${res.message}" } currentUserId = userId - connectWithToken(token) + connectWithToken(userSig) } + @Deprecated("Use loginWithUserSig(userId, userSig) instead.") suspend fun loginWithToken(userId: String, token: String) = - withContext(Dispatchers.IO) { - XuqmSDK.requireInit() - currentUserId = userId - connectWithToken(token) - } + loginWithUserSig(userId, token) fun sendMessage(toId: String, chatType: String, msgType: String, content: String) { client?.sendMessage(toId, chatType, msgType, content) @@ -105,10 +115,17 @@ object ImSDK { fun removeListener(listener: ImEventListener) = client?.removeListener(listener) fun disconnect() { - client?.disconnect() - client = null - currentUserId = "" - XuqmSDK.tokenStore.clear() + disconnectInternal(clearTokenStore = true) + } + + fun onSdkLogin(session: XuqmLoginSession) { + XuqmSDK.requireInit() + currentUserId = session.userId + connectWithToken(session.userSig) + } + + fun onSdkLogout() { + disconnectInternal(clearTokenStore = false) } private fun connectWithToken(token: String) { @@ -117,4 +134,13 @@ object ImSDK { client = ImClient(WS_URL, token, XuqmSDK.appId) client?.connect() } + + private fun disconnectInternal(clearTokenStore: Boolean) { + client?.disconnect() + client = null + currentUserId = "" + if (clearTokenStore) { + XuqmSDK.tokenStore.clear() + } + } }