diff --git a/demo-app/build.gradle.kts b/demo-app/build.gradle.kts index d69dbbc09eb..63edad05124 100644 --- a/demo-app/build.gradle.kts +++ b/demo-app/build.gradle.kts @@ -292,6 +292,9 @@ dependencies { implementation(libs.audioswitch) + // Logging + implementation(libs.okhttp.logging) + // Also Leak Canary added in the previous block // Instrumentation tests diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt index eb2262c0032..38964adfdb2 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/services/stream/StreamService.kt @@ -17,11 +17,14 @@ package io.getstream.video.android.data.services.stream import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import io.getstream.log.streamLog import io.getstream.video.android.model.User import io.getstream.video.android.models.UserCredentials import io.getstream.video.android.models.builtInCredentials import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.create import retrofit2.http.GET @@ -38,10 +41,19 @@ fun interface StreamService { private const val BASE_URL = "https://pronto.getstream.io/" private val json = Json { ignoreUnknownKeys = true } - + private val okHttpClient = OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor { + streamLog(tag = "Video:Http") { it } + }.apply { + level = HttpLoggingInterceptor.Level.BODY + }, + ) + .build() private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .client(okHttpClient) .build() private val serviceInstance = retrofit.create() diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index f8cb6ea6b0c..42f2d090878 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -61,6 +61,7 @@ import io.getstream.video.android.util.config.AppConfig import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.runBlocking public enum class InitializedState { NOT_STARTED, RUNNING, FINISHED, FAILED @@ -197,7 +198,18 @@ object StreamVideoInitHelper { chatClient.connectUser( user = chatUser, - token = token, + tokenProvider = object : io.getstream.chat.android.client.token.TokenProvider { + override fun loadToken(): String { + return runBlocking { + val email = user.custom?.get("email") + val authData = StreamService.instance.getAuthData( + environment = AppConfig.currentEnvironment.value!!.env, + userId = email, + ) + authData.token + } + } + }, ).enqueue() } @@ -303,10 +315,12 @@ object StreamVideoInitHelper { ), tokenProvider = object : TokenProvider { override suspend fun loadToken(): String { - val email = user.custom?.get("email") + val userEmail = user.custom?.get("email") + val userId = user.id + val userIdForTokenRenewal = if (userEmail.isNullOrEmpty()) userId else userEmail val authData = StreamService.instance.getAuthData( environment = AppConfig.currentEnvironment.value!!.env, - userId = email, + userId = userIdForTokenRenewal, ) return authData.token } diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 33372d00a4c..f4a89cf5d4f 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -13118,9 +13118,17 @@ public abstract interface class io/getstream/video/android/core/socket/common/to public abstract fun loadToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/getstream/video/android/core/socket/common/token/TokenRepository { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getToken ()Ljava/lang/String; + public final fun updateToken (Ljava/lang/String;)V +} + public class io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { - public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun connect (Lio/getstream/video/android/model/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; @@ -13327,8 +13335,8 @@ public final class io/getstream/video/android/core/socket/coordinator/state/Vide public final class io/getstream/video/android/core/socket/sfu/SfuSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { public static final field Companion Lio/getstream/video/android/core/socket/sfu/SfuSocketConnection$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/socket/common/token/TokenRepository;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connect (Lstream/video/sfu/event/JoinRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index cc9dad27aed..be76766b99a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -37,8 +37,9 @@ import io.getstream.video.android.core.permission.android.DefaultStreamPermissio import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope -import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.RepositoryTokenProvider import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.sounds.RingingCallVibrationConfig import io.getstream.video.android.core.sounds.Sounds import io.getstream.video.android.core.sounds.defaultResourcesRingingConfig @@ -95,6 +96,7 @@ import java.net.ConnectException * @see ClientState.connection * */ + public class StreamVideoBuilder @JvmOverloads constructor( context: Context, private val apiKey: ApiKey, @@ -106,7 +108,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( object : TokenProvider { override suspend fun loadToken(): String = legacy.invoke(null) } - } ?: ConstantTokenProvider(token), + } ?: RepositoryTokenProvider(tokenRepository), private val loggingLevel: LoggingLevel = LoggingLevel(), private val notificationConfig: NotificationConfig = NotificationConfig(), private val ringNotification: ((call: Call) -> Notification?)? = null, @@ -214,7 +216,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( // Android JSR-310 backport backport AndroidThreeTen.init(context) - + tokenRepository.updateToken(token) // This connection module class exposes the connections to the various retrofit APIs. val coordinatorConnectionModule = CoordinatorConnectionModule( context = context, @@ -225,9 +227,9 @@ public class StreamVideoBuilder @JvmOverloads constructor( loggingLevel = loggingLevel, user = user, apiKey = apiKey, - userToken = token, tokenProvider = tokenProvider, lifecycle = lifecycle, + tokenRepository = tokenRepository, ) val deviceTokenStorage = DeviceTokenStorage(context) @@ -272,6 +274,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( vibrationConfig = vibrationConfig, enableStereoForSubscriber = enableStereoForSubscriber, telecomConfig = telecomConfig, + tokenRepository = tokenRepository, ) if (user.type == UserType.Guest) { @@ -348,6 +351,11 @@ public class StreamVideoBuilder @JvmOverloads constructor( } } +/** + * Refactor Later + */ +internal val tokenRepository = TokenRepository("") + sealed class GEO { /** Run calls over our global edge network, this is the default and right for most applications */ object GlobalEdgeNetwork : GEO() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index eb536dde76b..35665871dc5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -107,8 +107,9 @@ import io.getstream.video.android.core.permission.android.DefaultStreamPermissio import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.ErrorResponse import io.getstream.video.android.core.socket.common.scope.ClientScope -import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.RepositoryTokenProvider import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.sounds.CallSoundAndVibrationPlayer import io.getstream.video.android.core.sounds.RingingCallVibrationConfig @@ -161,7 +162,8 @@ internal class StreamVideoClient internal constructor( internal var token: String, private val lifecycle: Lifecycle, internal val coordinatorConnectionModule: CoordinatorConnectionModule, - internal val tokenProvider: TokenProvider = ConstantTokenProvider(token), + internal val tokenRepository: TokenRepository, + internal val tokenProvider: TokenProvider = RepositoryTokenProvider(tokenRepository), internal val streamNotificationManager: StreamNotificationManager, internal val enableCallNotificationUpdates: Boolean, internal val callServiceConfigRegistry: CallServiceConfigRegistry = CallServiceConfigRegistry(), @@ -273,6 +275,7 @@ internal class StreamVideoClient internal constructor( // Retry once with a new token if the token is expired if (e.isAuthError()) { val newToken = tokenProvider.loadToken() + tokenRepository.updateToken(newToken) token = newToken coordinatorConnectionModule.updateToken(newToken) apiCall() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 6fec1eab39e..daf2a60d7bc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -78,6 +78,7 @@ import io.getstream.video.android.core.model.VideoTrack import io.getstream.video.android.core.model.toPeerType import io.getstream.video.android.core.socket.common.VideoParser import io.getstream.video.android.core.socket.common.parser2.MoshiVideoParser +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.sfu.state.SfuSocketState import io.getstream.video.android.core.toJson import io.getstream.video.android.core.trace.PeerConnectionTraceKey @@ -236,12 +237,12 @@ public class RtcSession internal constructor( apiUrl = sfuUrl, wssUrl = sfuWsUrl, connectionTimeoutInMs = 2000L, - userToken = sfuToken, lifecycle = lifecycle, onSignalingLost = { error -> call.debug.fastReconnect() }, tracer = sfuTracer, + tokenRepository = TokenRepository(sfuToken), ) }, ) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt index 98ac4d93838..999a73deb19 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt @@ -76,11 +76,6 @@ internal interface ConnectionModuleDeclaration { // Internals - private val authInterceptor = CoordinatorAuthInterceptor(apiKey, userToken) + private val authInterceptor = CoordinatorAuthInterceptor(apiKey, tokenRepository) private val retrofit: Retrofit by lazy { Retrofit.Builder().baseUrl(apiUrl) .addConverterFactory(ScalarsConverterFactory.create()) @@ -73,8 +75,8 @@ internal class CoordinatorConnectionModule( HeadersInterceptor(HeadersUtil()), ) .addInterceptor(authInterceptor).addInterceptor( - HttpLoggingInterceptor { - streamLog(tag = "Video:Http") { it } + HttpLoggingInterceptor { message -> + println(message) }.apply { level = loggingLevel.httpLoggingLevel.level }, @@ -96,17 +98,17 @@ internal class CoordinatorConnectionModule( apiKey = apiKey, url = wssUrl, user = user, - token = userToken, + token = tokenRepository.getToken(), httpClient = http, networkStateProvider = networkStateProvider, - scope = scope, + scope = UserScope(context = scope.coroutineContext + Dispatchers.IO.limitedParallelism(1)), lifecycle = lifecycle, tokenProvider = tokenProvider, + tokenRepository = tokenRepository, ) override fun updateToken(token: UserToken) { - socketConnection.updateToken(token) - authInterceptor.token = token + tokenRepository.updateToken(token) } override fun updateAuthType(authType: String) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt index e0a95525ee2..708707f9594 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt @@ -23,6 +23,7 @@ import io.getstream.video.android.core.api.SignalServerService import io.getstream.video.android.core.call.utils.SignalLostSignalingServiceDecorator import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.sfu.SfuSocketConnection import io.getstream.video.android.core.trace.Tracer import io.getstream.video.android.core.trace.tracedWith @@ -37,11 +38,11 @@ import java.util.concurrent.TimeUnit internal class SfuConnectionModule( context: Context, + val tokenRepository: TokenRepository, override val apiKey: ApiKey, override val apiUrl: String, override val wssUrl: String, override val connectionTimeoutInMs: Long, - override val userToken: SfuToken, override val lifecycle: Lifecycle, override val tracer: Tracer, val onSignalingLost: (Error) -> Unit, @@ -57,7 +58,7 @@ internal class SfuConnectionModule( private fun buildSfuOkHttpClient(): OkHttpClient { val connectionTimeoutInMs = 10000L // create a new OkHTTP client and set timeouts - val authInterceptor = CoordinatorAuthInterceptor(apiKey, userToken) + val authInterceptor = CoordinatorAuthInterceptor(apiKey, tokenRepository) return OkHttpClient.Builder().addInterceptor(authInterceptor).addInterceptor( HttpLoggingInterceptor().apply { level = loggingLevel.httpLoggingLevel.level @@ -93,13 +94,15 @@ internal class SfuConnectionModule( apiKey = apiKey, scope = scope, httpClient = http, - tokenProvider = ConstantTokenProvider(userToken), + tokenProvider = ConstantTokenProvider(tokenRepository.getToken()), lifecycle = lifecycle, networkStateProvider = networkStateProvider, + tokenRepository = tokenRepository, ) override val socketConnection: SfuSocketConnection = _internalSocketConnection override fun updateToken(token: SfuToken) { + tokenRepository.updateToken(token) _internalSocketConnection.updateToken(token) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt index 76b476cd88e..2570bd9be50 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt @@ -41,6 +41,7 @@ interface SocketActions { /** * Send raw data to the socket. If you already have a parsed event that can be sent. + * Refactor later to return bool */ fun sendData(data: String) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt index 39d127234ac..5f7d092a53f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt @@ -22,8 +22,10 @@ package io.getstream.video.android.core.socket.common.token * * @property tokenProvider The [TokenProvider] used to obtain new tokens. */ -internal class CacheableTokenProvider(private val tokenProvider: TokenProvider) : TokenProvider { - private var cachedToken = "" +internal class CacheableTokenProvider( + private val tokenProvider: TokenProvider, +) : TokenProvider { + internal var cachedToken = "" override suspend fun loadToken(): String = tokenProvider.loadToken().also { cachedToken = it } /** diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt new file mode 100644 index 00000000000..38e8199a4fd --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/PersistingTokenProvider.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014-2025 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +internal class PersistingTokenProvider( + private val tokenProvider: TokenProvider, + private val tokenRepository: TokenRepository, +) : TokenProvider { + override suspend fun loadToken(): String = tokenProvider.loadToken().also { + tokenRepository.updateToken(it) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/RepositoryTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/RepositoryTokenProvider.kt new file mode 100644 index 00000000000..54e6a1c1c8e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/RepositoryTokenProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014-2025 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +internal class RepositoryTokenProvider(val tokenRepository: TokenRepository) : TokenProvider { + override suspend fun loadToken(): String { + return tokenRepository.getToken() + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt index abd71237c98..e7b11fdcc19 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt @@ -51,6 +51,8 @@ internal interface TokenManager { */ fun setTokenProvider(provider: CacheableTokenProvider) + fun setTokenProvider(provider: PersistingTokenProvider) + /** * Obtain last token loaded. * diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt index e4c1bca4e62..c5fef2d54ec 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt @@ -16,13 +16,12 @@ package io.getstream.video.android.core.socket.common.token -internal class TokenManagerImpl : TokenManager { - @Volatile - private var token: String = EMPTY_TOKEN +internal class TokenManagerImpl(private val tokenRepository: TokenRepository) : TokenManager { + private lateinit var provider: TokenProvider override fun updateToken(token: String) { - this.token = token + this.tokenRepository.updateToken(token) } override suspend fun ensureTokenLoaded() { @@ -33,27 +32,30 @@ internal class TokenManagerImpl : TokenManager { override suspend fun loadSync(): String { return provider.loadToken().also { - this.token = it + this.tokenRepository.updateToken(it) } } override fun setTokenProvider(provider: CacheableTokenProvider) { this.provider = provider - this.token = provider.getCachedToken() + } + + override fun setTokenProvider(provider: PersistingTokenProvider) { + this.provider = provider } override fun hasTokenProvider(): Boolean { return this::provider.isInitialized } - override fun getToken(): String = token + override fun getToken(): String = tokenRepository.getToken() override fun hasToken(): Boolean { - return token != EMPTY_TOKEN + return tokenRepository.getToken() != EMPTY_TOKEN } override fun expireToken() { - token = EMPTY_TOKEN + tokenRepository.updateToken(EMPTY_TOKEN) } companion object { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt new file mode 100644 index 00000000000..f9036448822 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenRepository.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +import io.getstream.video.android.core.socket.common.token.TokenManagerImpl.Companion.EMPTY_TOKEN + +class TokenRepository(@Volatile private var token: String = EMPTY_TOKEN) { + + fun updateToken(token: String) { + this.token = token + } + + fun getToken(): String { + return token + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt index 7e939e92951..890e82b359a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -278,11 +278,20 @@ internal open class CoordinatorSocket( tokenManager.expireToken() } + when (error.serverErrorCode) { + VideoErrorCode.TOKEN_EXPIRED.code, + -> { + tokenManager.expireToken() + val token = tokenManager.loadSync() + tokenManager.updateToken(token) + } + else -> {} + } + when (error.serverErrorCode) { VideoErrorCode.UNDEFINED_TOKEN.code, VideoErrorCode.INVALID_TOKEN.code, VideoErrorCode.API_KEY_NOT_FOUND.code, - VideoErrorCode.VALIDATION_ERROR.code, -> { logger.d { "One unrecoverable error happened. Error: $error. Error code: ${error.serverErrorCode}" @@ -290,7 +299,9 @@ internal open class CoordinatorSocket( coordinatorSocketStateService.onUnrecoverableError(error) } - else -> coordinatorSocketStateService.onNetworkError(error) + else -> { + coordinatorSocketStateService.onNetworkError(error) + } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt index a81c9ae669d..93b48979551 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -35,9 +35,10 @@ import io.getstream.video.android.core.socket.common.VideoParser import io.getstream.video.android.core.socket.common.parser2.MoshiVideoParser import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope -import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.PersistingTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManagerImpl import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.utils.isWhitespaceOnly import io.getstream.video.android.core.utils.mapState @@ -63,6 +64,8 @@ import okhttp3.OkHttpClient * - Raises the error if there is a permanent failure * - Flow to avoid concurrency related bugs * - Ability to wait till the socket is connected (important to prevent race conditions) + * + * This should be internal */ public open class CoordinatorSocketConnection( private val apiKey: ApiKey, @@ -71,6 +74,10 @@ public open class CoordinatorSocketConnection( /** The user to connect. */ private val user: User, /** The initial token. */ + @Deprecated( + "token is not used", + ReplaceWith("Use tokenManager or tokenRepository.getToken() instead"), + ) private val token: String, /** Inject your http client */ private val httpClient: OkHttpClient, @@ -82,12 +89,13 @@ public open class CoordinatorSocketConnection( private val lifecycle: Lifecycle, /** Token provider */ private val tokenProvider: TokenProvider, + private val tokenRepository: TokenRepository, ) : SocketListener(), SocketActions { // Private state private val parser: VideoParser = MoshiVideoParser() - private val tokenManager = TokenManagerImpl() + private val tokenManager = TokenManagerImpl(tokenRepository) // Internal state private val logger by taggedLogger("Video:Socket") @@ -121,7 +129,7 @@ public open class CoordinatorSocketConnection( // Init init { - tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider)) + tokenManager.setTokenProvider(PersistingTokenProvider(tokenProvider, tokenRepository)) } // Extension opportunity for subclasses @@ -130,12 +138,12 @@ public open class CoordinatorSocketConnection( logger.d { "[onCreated] Socket is created" } scope.launch { logger.d { "[onConnected] Video socket created, user: $user" } - if (token.isEmpty()) { + if (tokenManager.getToken().isEmpty()) { logger.e { "[onConnected] Token is empty. Disconnecting." } disconnect() } else { val authRequest = WSAuthMessageRequest( - token = token, + token = tokenManager.getToken().ifEmpty { tokenRepository.getToken() }, userDetails = ConnectUserDetailsRequest( id = user.id, name = user.name.takeUnless { it.isWhitespaceOnly() }, @@ -189,7 +197,9 @@ public open class CoordinatorSocketConnection( override fun onDisconnected(cause: DisconnectCause) { super.onDisconnected(cause) connectionId.value = null - logger.d { "[onDisconnected] Socket disconnected. Cause: $cause" } + logger.d { + "[onDisconnected] Socket disconnected. Cause: ${(cause as? DisconnectCause.Error)?.error}" + } } // API diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt index 07693ed8019..73fef0b76d5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt @@ -31,9 +31,10 @@ import io.getstream.video.android.core.socket.common.SocketListener import io.getstream.video.android.core.socket.common.StreamWebSocketEvent import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope -import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.PersistingTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManagerImpl import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.common.token.TokenRepository import io.getstream.video.android.core.socket.sfu.state.SfuSocketState import io.getstream.video.android.core.utils.mapState import io.getstream.video.android.model.ApiKey @@ -62,6 +63,7 @@ class SfuSocketConnection( private val lifecycle: Lifecycle, /** Token provider */ private val tokenProvider: TokenProvider, + private val tokenRepository: TokenRepository, ) : SocketListener(), SocketActions { @@ -70,7 +72,7 @@ class SfuSocketConnection( } private val logger by taggedLogger("Video:SfuSocket") - private val tokenManager = TokenManagerImpl() + private val tokenManager = TokenManagerImpl(tokenRepository) private val internalSocket: SfuSocket = SfuSocket( wssUrl = url, apiKey = apiKey, @@ -100,7 +102,7 @@ class SfuSocketConnection( // Initialization init { - tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider)) + tokenManager.setTokenProvider(PersistingTokenProvider(tokenProvider, tokenRepository)) } override fun onCreated() { diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt index 4b98f23ae42..5258888ff94 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt @@ -67,7 +67,10 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine object IntegrationTestState { + @Volatile var client: StreamVideo? = null + + @Volatile var call: Call? = null } @@ -99,7 +102,7 @@ open class IntegrationTestBase(val connectCoordinatorWS: Boolean = true) : TestB geo = GEO.GlobalEdgeNetwork, user = testData.users["thierry"]!!, token = authData?.token!!, - loggingLevel = LoggingLevel(Priority.DEBUG, HttpLoggingLevel.BASIC), + loggingLevel = LoggingLevel(Priority.DEBUG, HttpLoggingLevel.BODY), ) // if (BuildConfig.CORE_TEST_LOCAL == "1") { // builder.videoDomain = "localhost" diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt index 3b8b62cdf0c..b5ddc139e82 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt @@ -17,6 +17,7 @@ package io.getstream.video.android.core.token import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.PersistingTokenProvider import io.getstream.video.android.core.socket.common.token.TokenManager internal class FakeTokenManager( @@ -34,6 +35,10 @@ internal class FakeTokenManager( // empty } + override fun setTokenProvider(provider: PersistingTokenProvider) { + // empty + } + override fun hasTokenProvider(): Boolean { return true }