diff --git a/android/src/main/java/io/rownd/android/Rownd.kt b/android/src/main/java/io/rownd/android/Rownd.kt index b8b381a..2848d46 100644 --- a/android/src/main/java/io/rownd/android/Rownd.kt +++ b/android/src/main/java/io/rownd/android/Rownd.kt @@ -38,6 +38,7 @@ import io.rownd.android.util.InvalidRefreshTokenException import io.rownd.android.util.NoAccessTokenPresentException import io.rownd.android.util.NoRefreshTokenPresentException import io.rownd.android.util.RowndEvent +import io.rownd.android.util.RowndEventType import io.rownd.android.util.RowndException import io.rownd.android.views.HubPageSelector import io.rownd.android.views.RowndBottomSheetActivity @@ -76,6 +77,7 @@ class RowndClient( internal var eventEmitter = graph.rowndEventEmitter() internal var signInWithGoogle = graph.signInWithGoogle() internal var telemetry = graph.telemetry() + internal var superTokensSync = graph.superTokensSync() var state = stateRepo.state var user = userRepo @@ -89,6 +91,23 @@ class RowndClient( rowndContext.eventEmitter = eventEmitter rowndContext.telemetry = telemetry + eventEmitter.addListener { event -> + if (event.event != RowndEventType.SignInCompleted || event.data["user_type"] != "new_user") { + return@addListener + } + + if (!this::store.isInitialized) { + return@addListener + } + + val accessToken = store.currentState.auth.accessToken ?: return@addListener + val appInfo = config.supertokens?.appInfo ?: return@addListener + + CoroutineScope(Dispatchers.IO).launch { + superTokensSync.syncUser(accessToken = accessToken, appInfo = appInfo) + } + } + stateRepo.userRepo = userRepo stateRepo.authRepo = authRepo } @@ -563,4 +582,4 @@ enum class RowndSignInLoginStep { enum class RowndSignOutScope { all -} \ No newline at end of file +} diff --git a/android/src/main/java/io/rownd/android/di/component/RowndComponent.kt b/android/src/main/java/io/rownd/android/di/component/RowndComponent.kt index a67e507..8ae2f16 100644 --- a/android/src/main/java/io/rownd/android/di/component/RowndComponent.kt +++ b/android/src/main/java/io/rownd/android/di/component/RowndComponent.kt @@ -19,6 +19,7 @@ import io.rownd.android.util.RowndContext import io.rownd.android.util.RowndEvent import io.rownd.android.util.RowndEventEmitter import io.rownd.android.util.SignInWithGoogle +import io.rownd.android.util.SuperTokensSync import io.rownd.android.util.Telemetry import io.rownd.android.util.TokenApiClient import javax.inject.Singleton @@ -44,9 +45,10 @@ interface RowndGraph { fun rowndEventEmitter(): RowndEventEmitter fun signInWithGoogle(): SignInWithGoogle fun telemetry(): Telemetry + fun superTokensSync(): SuperTokensSync fun tokenApiClient(): TokenApiClient fun authenticatedApiClient(): AuthenticatedApiClient fun httpEngine(): HttpClientEngine fun config(): RowndConfig fun inject(rowndConfig: RowndConfig) -} \ No newline at end of file +} diff --git a/android/src/main/java/io/rownd/android/models/RowndConfig.kt b/android/src/main/java/io/rownd/android/models/RowndConfig.kt index e9484a2..f9b7eca 100644 --- a/android/src/main/java/io/rownd/android/models/RowndConfig.kt +++ b/android/src/main/java/io/rownd/android/models/RowndConfig.kt @@ -15,6 +15,31 @@ import javax.inject.Inject val json = Json { encodeDefaults = true } +@Serializable +data class SuperTokensAppInfo( + val appName: String, + val apiDomain: String, + val apiBasePath: String = "/auth" +) { + val normalizedApiDomain: String + get() = apiDomain.trimEnd('/') + + val normalizedApiBasePath: String + get() { + val basePath = apiBasePath.trim().trim('/') + return if (basePath.isEmpty()) "" else "/$basePath" + } + + fun migrationUrl(): String { + return "${normalizedApiDomain}${normalizedApiBasePath}/plugin/rownd/migrate" + } +} + +@Serializable +data class SuperTokensConfig( + val appInfo: SuperTokensAppInfo +) + @Serializable data class RowndConfig( var appKey: String? = null, @@ -34,6 +59,9 @@ data class RowndConfig( @Transient var enableSmartLinkPasteBehavior: Boolean = true, + @Transient + var supertokens: SuperTokensConfig? = null, + // Internals @Transient internal var stateFileName: String = "rownd_state.json" diff --git a/android/src/main/java/io/rownd/android/util/SuperTokensSync.kt b/android/src/main/java/io/rownd/android/util/SuperTokensSync.kt new file mode 100644 index 0000000..0ab4f9a --- /dev/null +++ b/android/src/main/java/io/rownd/android/util/SuperTokensSync.kt @@ -0,0 +1,25 @@ +package io.rownd.android.util + +import android.util.Log +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.rownd.android.models.SuperTokensAppInfo +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SuperTokensSync @Inject constructor( + private val apiClient: KtorApiClient, +) { + suspend fun syncUser(accessToken: String, appInfo: SuperTokensAppInfo) { + val migrationUrl = appInfo.migrationUrl() + + try { + apiClient.client.post(migrationUrl) { + header("Authorization", "Bearer $accessToken") + } + } catch (e: Exception) { + Log.e("Rownd.ST", "[Rownd->ST] migrate failed (non-fatal): ${e.message}") + } + } +}