diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f19c050c3..a5bfdc1a9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,8 +24,6 @@ jobs: arguments: :updateVersions - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Install Homebrew - run: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - name: Install Carthage run: brew install carthage - name: Publish diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 619fb3670..ea96223f1 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -20,8 +20,6 @@ jobs: java-version: 1.8 - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Install Homebrew - run: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - name: Install Carthage run: brew install carthage - name: Assemble diff --git a/README.md b/README.md index 83ae86865..4472f08f6 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ The following libraries are available for the various Firebase products. | Service or Product | Gradle Dependency | API Coverage | | ------------------------------------------------------------------------------------ | :-----------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Authentication](https://firebase.google.com/docs/auth#kotlin-android) | [`dev.gitlive:firebase-auth:0.2.0`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/0.2.0/pom) | [![40%](https://img.shields.io/badge/-50%25-red?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | -| [Realtime Database](https://firebase.google.com/docs/database#kotlin-android) | [`dev.gitlive:firebase-database:0.2.0`](https://search.maven.org/artifact/dev.gitlive/firebase-database/0.2.0/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | -| [Cloud Firestore](https://firebase.google.com/docs/firestore#kotlin-android) | [`dev.gitlive:firebase-firestore:0.2.0`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/0.2.0/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | -| [Cloud Functions](https://firebase.google.com/docs/functions/callable#kotlin-android)| [`dev.gitlive:firebase-functions:0.2.0`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/0.2.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | -| [Cloud Messaging](https://firebase.google.com/docs/messaging#kotlin-android) | [`dev.gitlive:firebase-messaging:0.2.0`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/0.2.0/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | -| [Cloud Storage](https://firebase.google.com/docs/storage#kotlin-android) | [`dev.gitlive:firebase-storage:0.2.0`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/0.2.0/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | +| [Authentication](https://firebase.google.com/docs/auth#kotlin-android) | [`dev.gitlive:firebase-auth:0.4.0`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/0.4.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) | +| [Realtime Database](https://firebase.google.com/docs/database#kotlin-android) | [`dev.gitlive:firebase-database:0.4.0`](https://search.maven.org/artifact/dev.gitlive/firebase-database/0.4.0/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) | +| [Cloud Firestore](https://firebase.google.com/docs/firestore#kotlin-android) | [`dev.gitlive:firebase-firestore:0.4.0`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/0.4.0/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) | +| [Cloud Functions](https://firebase.google.com/docs/functions/callable#kotlin-android)| [`dev.gitlive:firebase-functions:0.4.0`](https://search.maven.org/artifact/dev.gitlive/firebase-functions/0.4.0/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt) | +| [Cloud Messaging](https://firebase.google.com/docs/messaging#kotlin-android) | [`dev.gitlive:firebase-messaging:0.4.0`](https://search.maven.org/artifact/dev.gitlive/firebase-messaging/0.4.0/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | +| [Cloud Storage](https://firebase.google.com/docs/storage#kotlin-android) | [`dev.gitlive:firebase-storage:0.4.0`](https://search.maven.org/artifact/dev.gitlive/firebase-storage/0.4.0/pom) | ![0%](https://img.shields.io/badge/-0%25-lightgrey?style=flat-square) | Is the Firebase library or API you need missing? [Create an issue](https://github.com/GitLiveApp/firebase-kotlin-sdk/issues/new?labels=API+coverage&template=increase-api-coverage.md&title=Add+%5Bclass+name%5D.%5Bfunction+name%5D+to+%5Blibrary+name%5D+for+%5Bplatform+names%5D) to request additional API coverage or be awesome and [submit a PR](https://github.com/GitLiveApp/firebase-kotlin-sdk/fork) @@ -138,12 +138,12 @@ If you are building a Kotlin multiplatform library which will be consumed from J ```json "dependencies": { - "@gitlive/firebase-auth": "0.2.0", - "@gitlive/firebase-database": "0.2.0", - "@gitlive/firebase-firestore": "0.2.0", - "@gitlive/firebase-functions": "0.2.0", - "@gitlive/firebase-storage": "0.2.0", - "@gitlive/firebase-messaging": "0.2.0" + "@gitlive/firebase-auth": "0.4.0", + "@gitlive/firebase-database": "0.4.0", + "@gitlive/firebase-firestore": "0.4.0", + "@gitlive/firebase-functions": "0.4.0", + "@gitlive/firebase-storage": "0.4.0", + "@gitlive/firebase-messaging": "0.4.0" } ``` diff --git a/build.gradle.kts b/build.gradle.kts index 2ce3ddc38..fb36169db 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,8 +56,7 @@ subprojects { tasks.withType().configureEach { - onlyIf { !project.gradle.startParameter.taskNames.contains("publishToMavenLocal") - } + onlyIf { !project.gradle.startParameter.taskNames.contains("publishToMavenLocal") } } diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt index fb91d250a..ad37b3a0c 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -7,6 +7,7 @@ package dev.gitlive.firebase.auth import com.google.firebase.auth.ActionCodeEmailInfo import com.google.firebase.auth.ActionCodeMultiFactorInfo +import com.google.firebase.auth.ActionCodeResult.* import com.google.firebase.auth.FirebaseAuth.AuthStateListener import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp @@ -44,19 +45,18 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase. set(value) { android.setLanguageCode(value) } actual suspend fun applyActionCode(code: String) = android.applyActionCode(code).await().run { Unit } - actual suspend fun checkActionCode(code: String): ActionCodeResult = ActionCodeResult(android.checkActionCode(code).await()) actual suspend fun confirmPasswordReset(code: String, newPassword: String) = android.confirmPasswordReset(code, newPassword).await().run { Unit } actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = AuthResult(android.createUserWithEmailAndPassword(email, password).await()) - actual suspend fun fetchSignInMethodsForEmail(email: String): SignInMethodQueryResult = SignInMethodQueryResult(android.fetchSignInMethodsForEmail(email).await()) + actual suspend fun fetchSignInMethodsForEmail(email: String): List = android.fetchSignInMethodsForEmail(email).await().signInMethods.orEmpty() actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { - android.sendPasswordResetEmail(email, actionCodeSettings?.android).await() + android.sendPasswordResetEmail(email, actionCodeSettings?.toAndroid()).await() } - actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = android.sendSignInLinkToEmail(email, actionCodeSettings.android).await().run { Unit } + actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = android.sendSignInLinkToEmail(email, actionCodeSettings.toAndroid()).await().run { Unit } actual suspend fun signInWithEmailAndPassword(email: String, password: String) = AuthResult(android.signInWithEmailAndPassword(email, password).await()) @@ -73,6 +73,28 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase. actual suspend fun updateCurrentUser(user: FirebaseUser) = android.updateCurrentUser(user.android).await().run { Unit } actual suspend fun verifyPasswordResetCode(code: String): String = android.verifyPasswordResetCode(code).await() + + actual suspend fun checkActionCode(code: String): T { + val result = android.checkActionCode(code).await() + @Suppress("UNCHECKED_CAST") + return when(result.operation) { + ERROR -> ActionCodeResult.Error + SIGN_IN_WITH_EMAIL_LINK -> ActionCodeResult.SignInWithEmailLink + VERIFY_EMAIL -> ActionCodeResult.VerifyEmail(result.info!!.email) + PASSWORD_RESET -> ActionCodeResult.PasswordReset(result.info!!.email) + RECOVER_EMAIL -> (result.info as ActionCodeEmailInfo).run { + ActionCodeResult.RecoverEmail(email, previousEmail) + } + VERIFY_BEFORE_CHANGE_EMAIL -> (result.info as ActionCodeEmailInfo).run { + ActionCodeResult.VerifyBeforeChangeEmail(email, previousEmail) + } + REVERT_SECOND_FACTOR_ADDITION -> (result.info as ActionCodeMultiFactorInfo).run { + ActionCodeResult.RevertSecondFactorAddition(email, MultiFactorInfo(multiFactorInfo)) + } + else -> throw UnsupportedOperationException(result.operation.toString()) + } as T + } + } actual class AuthResult internal constructor(val android: com.google.firebase.auth.AuthResult) { @@ -80,68 +102,13 @@ actual class AuthResult internal constructor(val android: com.google.firebase.au get() = android.user?.let { FirebaseUser(it) } } -actual class ActionCodeResult(val android: com.google.firebase.auth.ActionCodeResult) { - actual val operation: Operation - get() = when (android.operation) { - com.google.firebase.auth.ActionCodeResult.PASSWORD_RESET -> Operation.PasswordReset(this) - com.google.firebase.auth.ActionCodeResult.VERIFY_EMAIL -> Operation.VerifyEmail(this) - com.google.firebase.auth.ActionCodeResult.RECOVER_EMAIL -> Operation.RecoverEmail(this) - com.google.firebase.auth.ActionCodeResult.ERROR -> Operation.Error - com.google.firebase.auth.ActionCodeResult.SIGN_IN_WITH_EMAIL_LINK -> Operation.SignInWithEmailLink - com.google.firebase.auth.ActionCodeResult.VERIFY_BEFORE_CHANGE_EMAIL -> Operation.VerifyBeforeChangeEmail(this) - com.google.firebase.auth.ActionCodeResult.REVERT_SECOND_FACTOR_ADDITION -> Operation.RevertSecondFactorAddition(this) - else -> Operation.Error - } -} - -internal actual sealed class ActionCodeDataType { - - actual abstract fun dataForResult(result: ActionCodeResult): T - - actual object Email : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): String = result.android.info!!.email - } - actual object PreviousEmail : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): String = (result.android.info as ActionCodeEmailInfo).previousEmail - } - actual object MultiFactor : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): MultiFactorInfo? = (result.android.info as? ActionCodeMultiFactorInfo)?.multiFactorInfo?.let { MultiFactorInfo(it) } - } -} - -actual class SignInMethodQueryResult(val android: com.google.firebase.auth.SignInMethodQueryResult) { - actual val signInMethods: List - get() = android.signInMethods ?: emptyList() -} - -actual class ActionCodeSettings private constructor(val android: com.google.firebase.auth.ActionCodeSettings) { - - actual constructor(url: String, - androidPackageName: AndroidPackageName?, - dynamicLinkDomain: String?, - canHandleCodeInApp: Boolean, - iOSBundleId: String? - ) : this(com.google.firebase.auth.ActionCodeSettings.newBuilder().apply { - this.url = url - androidPackageName?.let { - this.setAndroidPackageName(it.androidPackageName, it.installIfNotAvailable, it.minimumVersion) - } - this.dynamicLinkDomain = dynamicLinkDomain - this.handleCodeInApp = canHandleCodeInApp - this.iosBundleId = iosBundleId - }.build()) - - actual val canHandleCodeInApp: Boolean - get() = android.canHandleCodeInApp() - actual val androidPackageName: AndroidPackageName? - get() = android.androidPackageName?.let { - AndroidPackageName(it, android.androidInstallApp, android.androidMinimumVersion) - } - actual val iOSBundle: String? - get() = android.iosBundle - actual val url: String - get() = android.url -} +internal fun ActionCodeSettings.toAndroid() = com.google.firebase.auth.ActionCodeSettings.newBuilder() + .setUrl(url) + .also { androidPackageName?.run { it.setAndroidPackageName(packageName, installIfNotAvailable, minimumVersion) } } + .also { dynamicLinkDomain?.run { it.setDynamicLinkDomain(this) } } + .setHandleCodeInApp(canHandleCodeInApp) + .also { iOSBundleId?.run { it.setIOSBundleId(this) } } + .build() actual typealias FirebaseAuthException = com.google.firebase.auth.FirebaseAuthException actual typealias FirebaseAuthActionCodeException = com.google.firebase.auth.FirebaseAuthActionCodeException diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index c1e1340d1..fbd387321 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -6,6 +6,7 @@ package dev.gitlive.firebase.auth import android.app.Activity import com.google.firebase.FirebaseException +import com.google.firebase.auth.OAuthProvider import com.google.firebase.auth.PhoneAuthProvider import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.coroutineScope @@ -45,24 +46,12 @@ actual class OAuthProvider(val android: com.google.firebase.auth.OAuthProvider.B actual constructor(provider: String, auth: FirebaseAuth) : this(com.google.firebase.auth.OAuthProvider.newBuilder(provider, auth.android), auth) actual companion object { - actual fun credentials(type: OAuthCredentialsType): AuthCredential { - val credential = com.google.firebase.auth.OAuthProvider.newCredentialBuilder(type.providerId).apply { - when (type) { - is OAuthCredentialsType.AccessToken -> accessToken = type.accessToken - is OAuthCredentialsType.IdAndAccessToken -> { - accessToken = type.accessToken - setIdToken(type.idToken) - } - is OAuthCredentialsType.IdAndAccessTokenAndRawNonce -> { - accessToken = type.accessToken - setIdTokenWithRawNonce(type.idToken, type.rawNonce) - } - is OAuthCredentialsType.IdTokenAndRawNonce -> { - setIdTokenWithRawNonce(type.idToken, type.rawNonce) - } - } - }.build() - return (credential as? com.google.firebase.auth.OAuthCredential)?.let { OAuthCredential(it) } ?: AuthCredential(credential) + actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { + val builder = OAuthProvider.newCredentialBuilder(providerId) + accessToken?.let { builder.accessToken = it } + idToken?.let { builder.idToken = it } + rawNonce?.let { builder.setIdTokenWithRawNonce(idToken!!, it) } + return OAuthCredential(builder.build() as com.google.firebase.auth.OAuthCredential) } } @@ -99,9 +88,7 @@ actual class PhoneAuthProvider(val android: com.google.firebase.auth.PhoneAuthPr launch { val code = verificationProvider.getVerificationCode() try { - val credentials = - credential(verificationId, code) - response.complete(Result.success(credentials)) + response.complete(Result.success(credential(verificationId, code))) } catch (e: Exception) { response.complete(Result.failure(e)) } diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt index 20aa61055..e8b6297b3 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt @@ -38,8 +38,8 @@ actual class FirebaseUser internal constructor(val android: com.google.firebase. actual suspend fun reauthenticate(credential: AuthCredential) = android.reauthenticate(credential.android).await().run { Unit } actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = AuthResult(android.reauthenticateAndRetrieveData(credential.android).await()) actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) { - val request = actionCodeSettings?.android?.let { android.sendEmailVerification(it) } ?: android.sendEmailVerification() - request.await().run { Unit } + val request = actionCodeSettings?.let { android.sendEmailVerification(it.toAndroid()) } ?: android.sendEmailVerification() + request.await() } actual suspend fun unlink(provider: String): FirebaseUser? = android.unlink(provider).await().user?.let { FirebaseUser(it) } actual suspend fun updateEmail(email: String) = android.updateEmail(email).await().run { Unit } @@ -52,7 +52,8 @@ actual class FirebaseUser internal constructor(val android: com.google.firebase. }.build() android.updateProfile(request).await() } - actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = android.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.android).await().run { Unit } + actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = + android.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.toAndroid()).await().run { Unit } } actual class UserInfo(val android: com.google.firebase.auth.UserInfo) { diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt index ea28b1bd2..f75461106 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -21,10 +21,10 @@ expect class FirebaseAuth { val idTokenChanged: Flow var languageCode: String suspend fun applyActionCode(code: String) - suspend fun checkActionCode(code: String): ActionCodeResult + suspend fun checkActionCode(code: String): T suspend fun confirmPasswordReset(code: String, newPassword: String) suspend fun createUserWithEmailAndPassword(email: String, password: String): AuthResult - suspend fun fetchSignInMethodsForEmail(email: String): SignInMethodQueryResult + suspend fun fetchSignInMethodsForEmail(email: String): List suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings? = null) suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult @@ -40,66 +40,25 @@ expect class AuthResult { val user: FirebaseUser? } -expect class ActionCodeResult { - val operation: Operation +sealed class ActionCodeResult { + object Error : ActionCodeResult() + object SignInWithEmailLink : ActionCodeResult() + class PasswordReset internal constructor(val email: String) : ActionCodeResult() + class VerifyEmail internal constructor(val email: String) : ActionCodeResult() + class RecoverEmail internal constructor(val email: String, val previousEmail: String) : ActionCodeResult() + class VerifyBeforeChangeEmail internal constructor(val email: String, val previousEmail: String) : ActionCodeResult() + class RevertSecondFactorAddition internal constructor(val email: String, val multiFactorInfo: MultiFactorInfo?) : ActionCodeResult() } -fun > ActionCodeResult.getData(type: A): T? { - return type.dataForResult(this) -} - -expect class SignInMethodQueryResult { - val signInMethods: List -} - -sealed class Operation { - class PasswordReset(result: ActionCodeResult) : Operation() { - val email: String = ActionCodeDataType.Email.dataForResult(result) - } - class VerifyEmail(result: ActionCodeResult) : Operation() { - val email: String = ActionCodeDataType.Email.dataForResult(result) - } - class RecoverEmail(result: ActionCodeResult) : Operation() { - val email: String = ActionCodeDataType.Email.dataForResult(result) - val previousEmail: String = ActionCodeDataType.PreviousEmail.dataForResult(result) - } - object Error : Operation() - object SignInWithEmailLink : Operation() - class VerifyBeforeChangeEmail(result: ActionCodeResult) : Operation() { - val email: String = ActionCodeDataType.Email.dataForResult(result) - val previousEmail: String = ActionCodeDataType.PreviousEmail.dataForResult(result) - } - class RevertSecondFactorAddition(result: ActionCodeResult) : Operation() { - val email: String = ActionCodeDataType.Email.dataForResult(result) - val multiFactorInfo: MultiFactorInfo? = ActionCodeDataType.MultiFactor.dataForResult(result) - } -} - -internal expect sealed class ActionCodeDataType { - - abstract fun dataForResult(result: ActionCodeResult): T - - object Email : ActionCodeDataType - object PreviousEmail : ActionCodeDataType - object MultiFactor : ActionCodeDataType -} - -expect class ActionCodeSettings { - constructor( - url: String, - androidPackageName: AndroidPackageName? = null, - dynamicLinkDomain: String? = null, - canHandleCodeInApp: Boolean = false, - iOSBundleId: String? = null - ) - - val canHandleCodeInApp: Boolean - val androidPackageName: AndroidPackageName? - val iOSBundle: String? - val url: String -} +data class ActionCodeSettings( + val url: String, + val androidPackageName: AndroidPackageName? = null, + val dynamicLinkDomain: String? = null, + val canHandleCodeInApp: Boolean = false, + val iOSBundleId: String? = null +) -data class AndroidPackageName(val androidPackageName: String, val installIfNotAvailable: Boolean, val minimumVersion: String?) +data class AndroidPackageName(val packageName: String, val installIfNotAvailable: Boolean, val minimumVersion: String?) expect open class FirebaseAuthException : FirebaseException expect class FirebaseAuthActionCodeException : FirebaseAuthException diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 6146f5348..320cad47f 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -5,8 +5,6 @@ package dev.gitlive.firebase.auth import dev.gitlive.firebase.Firebase -import kotlin.jvm.JvmName -import kotlin.js.JsName expect open class AuthCredential { val providerId: String @@ -31,18 +29,9 @@ expect object GoogleAuthProvider { fun credential(idToken: String, accessToken: String): AuthCredential } -sealed class OAuthCredentialsType { - abstract val providerId: String - data class AccessToken(val accessToken: String, override val providerId: String) : OAuthCredentialsType() - data class IdAndAccessToken(val idToken: String, val accessToken: String, override val providerId: String) : OAuthCredentialsType() - data class IdAndAccessTokenAndRawNonce(val idToken: String, val accessToken: String, val rawNonce: String, override val providerId: String) : OAuthCredentialsType() - data class IdTokenAndRawNonce(val idToken: String, val rawNonce: String, override val providerId: String) : OAuthCredentialsType() - -} - expect class OAuthProvider constructor(provider: String, auth: FirebaseAuth = Firebase.auth) { companion object { - fun credentials(type: OAuthCredentialsType): AuthCredential + fun credential(providerId: String, accessToken: String? = null, idToken: String? = null, rawNonce: String? = null): OAuthCredential } fun addScope(vararg scope: String) @@ -56,7 +45,6 @@ expect class SignInProvider expect class PhoneAuthProvider constructor(auth: FirebaseAuth = Firebase.auth) { fun credential(verificationId: String, smsCode: String): PhoneAuthCredential suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential - } expect interface PhoneVerificationProvider diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt index dac2dcbde..823ead25b 100644 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -54,10 +54,10 @@ class FirebaseAuthTest { fun testFetchSignInMethods() = runTest { val email = "test+${Random.nextInt(100000)}@test.com" var signInMethodResult = Firebase.auth.fetchSignInMethodsForEmail(email) - assertEquals(emptyList(), signInMethodResult.signInMethods) + assertEquals(emptyList(), signInMethodResult) Firebase.auth.createUserWithEmailAndPassword(email, "test123") signInMethodResult = Firebase.auth.fetchSignInMethodsForEmail(email) - assertEquals(listOf("password"), signInMethodResult.signInMethods) + assertEquals(listOf("password"), signInMethodResult) Firebase.auth.signInWithEmailAndPassword(email, "test123").user!!.delete() } diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 423bed6c2..337fe1668 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -9,6 +9,7 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.offerOrNull +import dev.gitlive.firebase.auth.ActionCodeResult.* import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose @@ -42,39 +43,52 @@ actual class FirebaseAuth internal constructor(val ios: FIRAuth) { set(value) { ios.setLanguageCode(value) } actual suspend fun applyActionCode(code: String) = ios.await { applyActionCode(code, it) }.run { Unit } - actual suspend fun checkActionCode(code: String): ActionCodeResult = ActionCodeResult(ios.awaitExpectedResult { checkActionCode(code, it) }) actual suspend fun confirmPasswordReset(code: String, newPassword: String) = ios.await { confirmPasswordResetWithCode(code, newPassword, it) }.run { Unit } actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = - AuthResult(ios.awaitExpectedResult { createUserWithEmail(email = email, password = password, completion = it) }) + AuthResult(ios.awaitResult { createUserWithEmail(email = email, password = password, completion = it) }) - actual suspend fun fetchSignInMethodsForEmail(email: String): SignInMethodQueryResult { - val signInMethods: List<*>? = ios.awaitResult { fetchSignInMethodsForEmail(email, it) } - return SignInMethodQueryResult(signInMethods?.mapNotNull { it as String } ?: emptyList()) - } + @Suppress("UNCHECKED_CAST") + actual suspend fun fetchSignInMethodsForEmail(email: String) = + ios.awaitResult?> { fetchSignInMethodsForEmail(email, it) }.orEmpty() as List actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { - ios.await { actionCodeSettings?.let { actionSettings -> sendPasswordResetWithEmail(email, actionSettings.ios, it) } ?: sendPasswordResetWithEmail(email = email, completion = it) } + ios.await { actionCodeSettings?.let { actionSettings -> sendPasswordResetWithEmail(email, actionSettings.toIos(), it) } ?: sendPasswordResetWithEmail(email = email, completion = it) } } - actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = ios.await { sendSignInLinkToEmail(email, actionCodeSettings.ios, it) }.run { Unit } + actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = ios.await { sendSignInLinkToEmail(email, actionCodeSettings.toIos(), it) }.run { Unit } actual suspend fun signInWithEmailAndPassword(email: String, password: String) = - AuthResult(ios.awaitExpectedResult { signInWithEmail(email = email, password = password, completion = it) }) + AuthResult(ios.awaitResult { signInWithEmail(email = email, password = password, completion = it) }) actual suspend fun signInWithCustomToken(token: String) = - AuthResult(ios.awaitExpectedResult { signInWithCustomToken(token, it) }) + AuthResult(ios.awaitResult { signInWithCustomToken(token, it) }) actual suspend fun signInAnonymously() = - AuthResult(ios.awaitExpectedResult { signInAnonymouslyWithCompletion(it) }) + AuthResult(ios.awaitResult { signInAnonymouslyWithCompletion(it) }) actual suspend fun signInWithCredential(authCredential: AuthCredential) = - AuthResult(ios.awaitExpectedResult { signInWithCredential(authCredential.ios, it) }) + AuthResult(ios.awaitResult { signInWithCredential(authCredential.ios, it) }) actual suspend fun signOut() = ios.throwError { signOut(it) }.run { Unit } actual suspend fun updateCurrentUser(user: FirebaseUser) = ios.await { updateCurrentUser(user.ios, it) }.run { Unit } - actual suspend fun verifyPasswordResetCode(code: String): String = ios.awaitExpectedResult { verifyPasswordResetCode(code, it) } + actual suspend fun verifyPasswordResetCode(code: String): String = ios.awaitResult { verifyPasswordResetCode(code, it) } + + actual suspend fun checkActionCode(code: String): T { + val result: FIRActionCodeInfo = ios.awaitResult { checkActionCode(code, it) } + @Suppress("UNCHECKED_CAST") + return when(result.operation) { + FIRActionCodeOperationUnknown -> Error + FIRActionCodeOperationEmailLink -> SignInWithEmailLink + FIRActionCodeOperationVerifyEmail -> VerifyEmail(result.email!!) + FIRActionCodeOperationPasswordReset -> PasswordReset(result.email!!) + FIRActionCodeOperationRecoverEmail -> RecoverEmail(result.email!!, result.previousEmail!!) + FIRActionCodeOperationVerifyAndChangeEmail -> VerifyBeforeChangeEmail(result.email!!, result.previousEmail!!) + FIRActionCodeOperationRevertSecondFactorAddition -> RevertSecondFactorAddition(result.email!!, null) + else -> throw UnsupportedOperationException(result.operation.toString()) + } as T + } } actual class AuthResult internal constructor(val ios: FIRAuthDataResult) { @@ -82,64 +96,12 @@ actual class AuthResult internal constructor(val ios: FIRAuthDataResult) { get() = FirebaseUser(ios.user) } -actual class ActionCodeResult(val ios: FIRActionCodeInfo) { - actual val operation: Operation - get() = when (ios.operation) { - FIRActionCodeOperationPasswordReset -> Operation.PasswordReset(this) - FIRActionCodeOperationVerifyEmail -> Operation.VerifyEmail(this) - FIRActionCodeOperationRecoverEmail -> Operation.RecoverEmail(this) - FIRActionCodeOperationUnknown-> Operation.Error - FIRActionCodeOperationEmailLink -> Operation.SignInWithEmailLink - FIRActionCodeOperationVerifyAndChangeEmail -> Operation.VerifyBeforeChangeEmail(this) - FIRActionCodeOperationRevertSecondFactorAddition -> Operation.RevertSecondFactorAddition(this) - else -> Operation.Error - } -} - -internal actual sealed class ActionCodeDataType { - - actual abstract fun dataForResult(result: ActionCodeResult): T - - actual object Email : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): String = result.ios.email!! - } - actual object PreviousEmail : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): String = result.ios.previousEmail!! - } - actual object MultiFactor : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): MultiFactorInfo? = null - } -} - -actual class SignInMethodQueryResult(actual val signInMethods: List) - -actual class ActionCodeSettings private constructor(val ios: FIRActionCodeSettings) { - - actual constructor(url: String, - androidPackageName: AndroidPackageName?, - dynamicLinkDomain: String?, - canHandleCodeInApp: Boolean, - iOSBundleId: String? - ) : this(FIRActionCodeSettings().apply { - this.URL = NSURL.URLWithString(url) - androidPackageName?.let { - this.setAndroidPackageName(it.androidPackageName, it.installIfNotAvailable, it.minimumVersion) - } - this.dynamicLinkDomain = dynamicLinkDomain - this.handleCodeInApp = canHandleCodeInApp - iOSBundleId?.let { setIOSBundleID(it) } - }) - - actual val canHandleCodeInApp: Boolean - get() = ios.handleCodeInApp() - actual val androidPackageName: AndroidPackageName? - get() = ios.androidPackageName?.let { - AndroidPackageName(it, ios.androidInstallIfNotAvailable, ios.androidMinimumVersion) - } - actual val iOSBundle: String? - get() = ios.iOSBundleID - actual val url: String - get() = ios.URL?.absoluteString ?: "" +internal fun ActionCodeSettings.toIos() = FIRActionCodeSettings().also { + it.URL = NSURL.URLWithString(url) + androidPackageName?.run { it.setAndroidPackageName(packageName, installIfNotAvailable, minimumVersion) } + it.dynamicLinkDomain = dynamicLinkDomain + it.handleCodeInApp = canHandleCodeInApp + iOSBundleId?.run { it.setIOSBundleID(this) } } actual open class FirebaseAuthException(message: String): FirebaseException(message) @@ -152,10 +114,6 @@ actual open class FirebaseAuthRecentLoginRequiredException(message: String): Fir actual open class FirebaseAuthUserCollisionException(message: String): FirebaseAuthException(message) actual open class FirebaseAuthWebException(message: String): FirebaseAuthException(message) -class UnexpectedNullResultException : Exception() { - override val message: String? = "The API encountered an unexpected null result" -} - internal fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { memScoped { val errorPointer: CPointer> = alloc>().ptr @@ -168,47 +126,19 @@ internal fun T.throwError(block: T.(errorPointer: CPointer T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R? { +internal suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { val job = CompletableDeferred() function { result, error -> - if(error != null) { - job.completeExceptionally(error.toException()) - } else { - job.complete(result) - } - } - return job.await() -} - -internal suspend fun T.awaitResult(default: R, function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { - val job = CompletableDeferred() - function { result, error -> - if(result != null) { + if(error == null) { job.complete(result) - } else if(error != null) { - job.completeExceptionally(error.toException()) } else { - job.complete(default) - } - } - return job.await() -} - -internal suspend fun T.awaitExpectedResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { - val job = CompletableDeferred() - function { result, error -> - if(result != null) { - job.complete(result) - } else if(error != null) { job.completeExceptionally(error.toException()) - } else { - job.completeExceptionally(UnexpectedNullResultException()) } } - return job.await() + return job.await() as R } -internal suspend fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { +internal suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { val job = CompletableDeferred() function { error -> if(error == null) { diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 88b8312cf..546ccf991 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -38,13 +38,14 @@ actual class OAuthProvider(val ios: FIROAuthProvider, private val auth: Firebase actual constructor(provider: String, auth: FirebaseAuth) : this(FIROAuthProvider.providerWithProviderID(provider, auth.ios), auth) actual companion object { - actual fun credentials(type: OAuthCredentialsType): AuthCredential { - return AuthCredential(when (type) { - is OAuthCredentialsType.AccessToken -> FIROAuthProvider.credentialWithProviderID(type.providerId, type.accessToken) - is OAuthCredentialsType.IdAndAccessToken -> FIROAuthProvider.credentialWithProviderID(providerID = type.providerId, IDToken = type.idToken, accessToken = type.accessToken) - is OAuthCredentialsType.IdAndAccessTokenAndRawNonce -> FIROAuthProvider.credentialWithProviderID(providerID = type.providerId, IDToken = type.idToken, rawNonce = type.rawNonce, accessToken = type.accessToken) - is OAuthCredentialsType.IdTokenAndRawNonce -> FIROAuthProvider.credentialWithProviderID(providerID = type.providerId, IDToken = type.idToken, rawNonce = type.rawNonce) - }) + actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { + val credential = when { + idToken == null -> FIROAuthProvider.credentialWithProviderID(providerID = providerId, accessToken = accessToken!!) + accessToken == null -> FIROAuthProvider.credentialWithProviderID(providerID = providerId, IDToken = idToken, rawNonce = rawNonce!!) + rawNonce == null -> FIROAuthProvider.credentialWithProviderID(providerID = providerId, IDToken = idToken, accessToken = accessToken) + else -> FIROAuthProvider.credentialWithProviderID(providerID = providerId, IDToken = idToken, rawNonce = rawNonce, accessToken = accessToken) + } + return OAuthCredential(credential) } } @@ -52,6 +53,7 @@ actual class OAuthProvider(val ios: FIROAuthProvider, private val auth: Firebase val scopes = ios.scopes?.mapNotNull { it as? String } ?: emptyList() ios.setScopes(scopes + scope.asList()) } + actual fun setCustomParameters(parameters: Map) { ios.setCustomParameters(emptyMap() + parameters) } @@ -67,7 +69,7 @@ actual class OAuthProvider(val ios: FIROAuthProvider, private val auth: Firebase null}.toMap() } - actual suspend fun signIn(signInProvider: SignInProvider): AuthResult = AuthResult(ios.awaitExpectedResult { auth.ios.signInWithProvider(ios, signInProvider.delegate, it) }) + actual suspend fun signIn(signInProvider: SignInProvider): AuthResult = AuthResult(ios.awaitResult { auth.ios.signInWithProvider(ios, signInProvider.delegate, it) }) } actual class SignInProvider(val delegate: FIRAuthUIDelegateProtocol) @@ -78,7 +80,7 @@ actual class PhoneAuthProvider(val ios: FIRPhoneAuthProvider) { actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(ios.credentialWithVerificationID(verificationId, smsCode)) actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential { - val verificationId: String = ios.awaitExpectedResult { ios.verifyPhoneNumber(phoneNumber, verificationProvider.delegate, it) } + val verificationId: String = ios.awaitResult { ios.verifyPhoneNumber(phoneNumber, verificationProvider.delegate, it) } val verificationCode = verificationProvider.getVerificationCode() return credential(verificationId, verificationCode) } diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt index add3a8e98..2822e7406 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt @@ -10,7 +10,7 @@ actual class MultiFactor(val ios: FIRMultiFactor) { actual val enrolledFactors: List get() = ios.enrolledFactors.mapNotNull { info -> (info as? FIRMultiFactorInfo)?.let{ MultiFactorInfo(it) } } actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) = ios.await { enrollWithAssertion(multiFactorAssertion.ios, displayName, it) }.run { Unit } - actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(ios.awaitExpectedResult { getSessionWithCompletion(completion = it) }) + actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(ios.awaitResult { getSessionWithCompletion(completion = it) }) actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) = ios.await { unenrollWithInfo(multiFactorInfo.ios, it) }.run { Unit } actual suspend fun unenroll(factorUid: String) = ios.await { unenrollWithFactorUID(factorUid, it) }.run { Unit } } @@ -38,5 +38,5 @@ actual class MultiFactorResolver(val ios: FIRMultiFactorResolver) { actual val hints: List = ios.hints.mapNotNull { hint -> (hint as? FIRMultiFactorInfo)?.let { MultiFactorInfo(it) } } actual val session: MultiFactorSession = MultiFactorSession(ios.session) - actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(ios.awaitExpectedResult { resolveSignInWithAssertion(assertion.ios, it) }) + actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(ios.awaitResult { resolveSignInWithAssertion(assertion.ios, it) }) } \ No newline at end of file diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt index 1432cd749..f75678485 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt @@ -30,18 +30,28 @@ actual class FirebaseUser internal constructor(val ios: FIRUser) { get() = ios.providerData.mapNotNull { provider -> (provider as? FIRUserInfoProtocol)?.let { UserInfo(it) } } actual val providerId: String get() = ios.providerID + actual suspend fun delete() = ios.await { deleteWithCompletion(it) }.run { Unit } + actual suspend fun reload() = ios.await { reloadWithCompletion(it) }.run { Unit } - actual suspend fun getIdToken(forceRefresh: Boolean): String? = ios.awaitResult { getIDTokenForcingRefresh(forceRefresh, it) } - actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = AuthResult(ios.awaitExpectedResult { linkWithCredential(credential.ios, it) }) - actual suspend fun reauthenticate(credential: AuthCredential) { - val result: FIRAuthDataResult = ios.awaitExpectedResult { reauthenticateWithCredential(credential.ios, it) } - } - actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = AuthResult(ios.awaitExpectedResult { reauthenticateWithCredential(credential.ios, it) }) + + actual suspend fun getIdToken(forceRefresh: Boolean): String? = + ios.awaitResult { getIDTokenForcingRefresh(forceRefresh, it) } + + actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = + AuthResult(ios.awaitResult { linkWithCredential(credential.ios, it) }) + + actual suspend fun reauthenticate(credential: AuthCredential) = + ios.awaitResult { reauthenticateWithCredential(credential.ios, it) }.run { Unit } + + actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = + AuthResult(ios.awaitResult { reauthenticateAndRetrieveDataWithCredential(credential.ios, it) }) actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) = ios.await { - actionCodeSettings?.let { actionSettings -> sendEmailVerificationWithActionCodeSettings(actionSettings.ios, it) } ?: sendEmailVerificationWithCompletion(it) - }.run { Unit } + actionCodeSettings?.let { settings -> sendEmailVerificationWithActionCodeSettings(settings.toIos(), it) } + ?: sendEmailVerificationWithCompletion(it) + } + actual suspend fun unlink(provider: String): FirebaseUser? { val user: FIRUser? = ios.awaitResult { unlinkFromProvider(provider, it) } return user?.let { @@ -59,7 +69,7 @@ actual class FirebaseUser internal constructor(val ios: FIRUser) { ios.await { request.commitChangesWithCompletion(it) } } actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = ios.await { - actionCodeSettings?.let { actionSettings -> sendEmailVerificationBeforeUpdatingEmail(newEmail, actionSettings.ios, it) } ?: sendEmailVerificationBeforeUpdatingEmail(newEmail, it) + actionCodeSettings?.let { actionSettings -> sendEmailVerificationBeforeUpdatingEmail(newEmail, actionSettings.toIos(), it) } ?: sendEmailVerificationBeforeUpdatingEmail(newEmail, it) }.run { Unit } } diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 023fc3b46..6b587545a 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -8,6 +8,7 @@ import dev.gitlive.firebase.* import kotlinx.coroutines.await import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow +import kotlin.js.json actual val Firebase.auth get() = rethrow { dev.gitlive.firebase.auth; FirebaseAuth(firebase.auth()) } @@ -39,28 +40,27 @@ actual class FirebaseAuth internal constructor(val js: firebase.auth.Auth) { set(value) { js.languageCode = value } actual suspend fun applyActionCode(code: String) = rethrow { js.applyActionCode(code).await() } - actual suspend fun checkActionCode(code: String): ActionCodeResult = rethrow { ActionCodeResult(js.checkActionCode(code).await()) } actual suspend fun confirmPasswordReset(code: String, newPassword: String) = rethrow { js.confirmPasswordReset(code, newPassword).await() } actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = rethrow { AuthResult(js.createUserWithEmailAndPassword(email, password).await()) } - actual suspend fun fetchSignInMethodsForEmail(email: String): SignInMethodQueryResult = rethrow { SignInMethodQueryResult(js.fetchSignInMethodsForEmail(email).await().asList()) } + actual suspend fun fetchSignInMethodsForEmail(email: String): List = rethrow { js.fetchSignInMethodsForEmail(email).await().asList() } actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) = - rethrow { js.sendPasswordResetEmail(email, actionCodeSettings?.js).await() } + rethrow { js.sendPasswordResetEmail(email, actionCodeSettings?.toJson()).await() } actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = - rethrow { js.sendSignInLinkToEmail(email, actionCodeSettings.js).await() } + rethrow { js.sendSignInLinkToEmail(email, actionCodeSettings.toJson()).await() } actual suspend fun signInWithEmailAndPassword(email: String, password: String) = rethrow { AuthResult(js.signInWithEmailAndPassword(email, password).await()) } - actual suspend fun signInWithCustomToken(token: String) - = rethrow { AuthResult(js.signInWithCustomToken(token).await()) } + actual suspend fun signInWithCustomToken(token: String) = + rethrow { AuthResult(js.signInWithCustomToken(token).await()) } - actual suspend fun signInAnonymously() - = rethrow { AuthResult(js.signInAnonymously().await()) } + actual suspend fun signInAnonymously() = + rethrow { AuthResult(js.signInAnonymously().await()) } actual suspend fun signInWithCredential(authCredential: AuthCredential) = rethrow { AuthResult(js.signInWithCredential(authCredential.js).await()) } @@ -68,14 +68,30 @@ actual class FirebaseAuth internal constructor(val js: firebase.auth.Auth) { actual suspend fun signOut() = rethrow { js.signOut().await() } actual suspend fun updateCurrentUser(user: FirebaseUser) = - rethrow { - js.updateCurrentUser(user.js).await() - } - actual suspend fun verifyPasswordResetCode(code: String): String = - rethrow { - js.verifyPasswordResetCode(code).await() - } + rethrow { js.updateCurrentUser(user.js).await() } + actual suspend fun verifyPasswordResetCode(code: String): String = + rethrow { js.verifyPasswordResetCode(code).await() } + + actual suspend fun checkActionCode(code: String): T = rethrow { + val result = js.checkActionCode(code).await() + @Suppress("UNCHECKED_CAST") + return when(result.operation) { + "EMAIL_SIGNIN" -> ActionCodeResult.SignInWithEmailLink + "VERIFY_EMAIL" -> ActionCodeResult.VerifyEmail(result.data.email!!) + "PASSWORD_RESET" -> ActionCodeResult.PasswordReset(result.data.email!!) + "RECOVER_EMAIL" -> ActionCodeResult.RecoverEmail(result.data.email!!, result.data.previousEmail!!) + "VERIFY_AND_CHANGE_EMAIL" -> ActionCodeResult.VerifyBeforeChangeEmail( + result.data.email!!, + result.data.previousEmail!! + ) + "REVERT_SECOND_FACTOR_ADDITION" -> ActionCodeResult.RevertSecondFactorAddition( + result.data.email!!, + result.data.multiFactorInfo?.let { MultiFactorInfo(it) } + ) + else -> throw UnsupportedOperationException(result.operation) + } as T + } } actual class AuthResult internal constructor(val js: firebase.auth.AuthResult) { @@ -83,70 +99,12 @@ actual class AuthResult internal constructor(val js: firebase.auth.AuthResult) { get() = rethrow { js.user?.let { FirebaseUser(it) } } } -actual class ActionCodeResult(val js: firebase.auth.ActionCodeInfo) { - actual val operation: Operation - get() = when (js.operation) { - "PASSWORD_RESET" -> Operation.PasswordReset(this) - "VERIFY_EMAIL" -> Operation.VerifyEmail(this) - "RECOVER_EMAIL" -> Operation.RecoverEmail(this) - "EMAIL_SIGNIN" -> Operation.SignInWithEmailLink - "VERIFY_AND_CHANGE_EMAIL" -> Operation.VerifyBeforeChangeEmail(this) - "REVERT_SECOND_FACTOR_ADDITION" -> Operation.RevertSecondFactorAddition(this) - else -> Operation.Error - } -} - -internal actual sealed class ActionCodeDataType { - - actual abstract fun dataForResult(result: ActionCodeResult): T - - actual object Email : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): String = result.js.data.email!! - } - actual object PreviousEmail : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): String = result.js.data.previousEmail!! - } - actual object MultiFactor : ActionCodeDataType() { - override fun dataForResult(result: ActionCodeResult): MultiFactorInfo? = result.js.data.multiFactorInfo?.let { MultiFactorInfo(it) } - } -} - -actual class SignInMethodQueryResult(actual val signInMethods: List) - -actual class ActionCodeSettings private constructor(val js: firebase.auth.ActionCodeSettings) { - - actual constructor(url: String, - androidPackageName: AndroidPackageName?, - dynamicLinkDomain: String?, - canHandleCodeInApp: Boolean, - iOSBundleId: String? - ) : this(object : firebase.auth.ActionCodeSettings { - override val android: firebase.auth.AndroidActionCodeSettings? = androidPackageName?.let { androidPackageName -> object : firebase.auth.AndroidActionCodeSettings { - override val installApp: Boolean get() = androidPackageName.installIfNotAvailable - override val minimumVersion: String? get() = androidPackageName.minimumVersion - override val packageName: String get() = androidPackageName.androidPackageName - - } } - override val dynamicLinkDomain: String? = dynamicLinkDomain - override val handleCodeInApp: Boolean? = canHandleCodeInApp - override val iOS: firebase.auth.iOSActionCodeSettings? = iOSBundleId?.let { iOSBundleId -> - object : firebase.auth.iOSActionCodeSettings { - override val bundleId: String? - get() = iOSBundleId - } - } - override val url: String = url - }) - - actual val canHandleCodeInApp: Boolean - get() = js.handleCodeInApp ?: false - actual val androidPackageName: AndroidPackageName? - get() = js.android?.let { AndroidPackageName(it.packageName, it.installApp ?: false, it.minimumVersion) } - actual val iOSBundle: String? - get() = js.iOS?.bundleId - actual val url: String - get() = js.url -} +internal fun ActionCodeSettings.toJson() = json( + "android" to (androidPackageName?.run { json("installApp" to installIfNotAvailable, "minimumVersion" to minimumVersion, "packageName" to packageName) } ?: undefined), + "dynamicLinkDomain" to (dynamicLinkDomain ?: undefined), + "handleCodeInApp" to canHandleCodeInApp, + "ios" to (iOSBundleId?.run { json("bundleId" to iOSBundleId) } ?: undefined) +) actual open class FirebaseAuthException(code: String?, cause: Throwable): FirebaseException(code, cause) actual open class FirebaseAuthActionCodeException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 09ce0c990..15baf5eb6 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -2,6 +2,8 @@ package dev.gitlive.firebase.auth import dev.gitlive.firebase.firebase import kotlinx.coroutines.await +import kotlin.js.Json +import kotlin.js.json actual open class AuthCredential(val js: firebase.auth.AuthCredential) { actual val providerId: String @@ -12,49 +14,40 @@ actual class PhoneAuthCredential(js: firebase.auth.AuthCredential) : AuthCredent actual class OAuthCredential(js: firebase.auth.AuthCredential) : AuthCredential(js) actual object EmailAuthProvider { - actual fun credential( - email: String, - password: String - ): AuthCredential = AuthCredential(firebase.auth.EmailAuthProvider.credential(email, password)) + actual fun credential(email: String, password: String): AuthCredential = + AuthCredential(firebase.auth.EmailAuthProvider.credential(email, password)) } actual object FacebookAuthProvider { - actual fun credential(accessToken: String): AuthCredential = AuthCredential(firebase.auth.FacebookAuthProvider.credential(accessToken)) + actual fun credential(accessToken: String): AuthCredential = + AuthCredential(firebase.auth.FacebookAuthProvider.credential(accessToken)) } actual object GithubAuthProvider { - actual fun credential(token: String): AuthCredential = AuthCredential(firebase.auth.GithubAuthProvider.credential(token)) + actual fun credential(token: String): AuthCredential = + AuthCredential(firebase.auth.GithubAuthProvider.credential(token)) } actual object GoogleAuthProvider { - actual fun credential(idToken: String, accessToken: String): AuthCredential = AuthCredential(firebase.auth.GoogleAuthProvider.credential(idToken, accessToken)) + actual fun credential(idToken: String, accessToken: String): AuthCredential = + AuthCredential(firebase.auth.GoogleAuthProvider.credential(idToken, accessToken)) } actual class OAuthProvider(val js: firebase.auth.OAuthProvider, private val auth: FirebaseAuth) { actual constructor(provider: String, auth: FirebaseAuth) : this(firebase.auth.OAuthProvider(provider), auth) actual companion object { - - actual fun credentials(type: OAuthCredentialsType): AuthCredential { - return when (type) { - is OAuthCredentialsType.AccessToken -> getCredentials(type.providerId, accessToken = type.accessToken) - is OAuthCredentialsType.IdAndAccessToken -> getCredentials(type.providerId, idToken = type.idToken, accessToken = type.accessToken) - is OAuthCredentialsType.IdAndAccessTokenAndRawNonce -> getCredentials(type.providerId, idToken = type.idToken, rawNonce = type.rawNonce, accessToken = type.accessToken) - is OAuthCredentialsType.IdTokenAndRawNonce -> getCredentials(type.providerId, idToken = type.idToken, rawNonce = type.rawNonce) - } - } - - private fun getCredentials(providerId: String, accessToken: String? = null, idToken: String? = null, rawNonce: String? = null): AuthCredential = rethrow { - val acT = accessToken - val idT = idToken - val rno = rawNonce - val provider = firebase.auth.OAuthProvider(providerId) - val credentials = provider.credential(object : firebase.auth.OAuthCredentialOptions { - override val accessToken: String? = acT - override val idToken: String? = idT - override val rawNonce: String? = rno - }, accessToken) - return AuthCredential(credentials) + actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential = rethrow { + firebase.auth.OAuthProvider(providerId) + .credential( + json( + "accessToken" to (accessToken ?: undefined), + "idToken" to (idToken ?: undefined), + "rawNonce" to (rawNonce ?: undefined) + ), + accessToken ?: undefined + ) + .let { OAuthCredential(it) } } } diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/user.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/user.kt index 580d15dd4..0a0463231 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/user.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/user.kt @@ -37,7 +37,7 @@ actual class FirebaseUser internal constructor(val js: firebase.user.User) { } actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = rethrow { AuthResult(js.reauthenticateWithCredential(credential.js).await()) } - actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) = rethrow { js.sendEmailVerification(actionCodeSettings?.js).await() } + actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) = rethrow { js.sendEmailVerification(actionCodeSettings?.toJson()).await() } actual suspend fun unlink(provider: String): FirebaseUser? = rethrow { FirebaseUser(js.unlink(provider).await()) } actual suspend fun updateEmail(email: String) = rethrow { js.updateEmail(email).await() } actual suspend fun updatePassword(password: String) = rethrow { js.updatePassword(password).await() } @@ -49,7 +49,7 @@ actual class FirebaseUser internal constructor(val js: firebase.user.User) { } js.updateProfile(request).await() } - actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = rethrow { js.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.js).await() } + actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = rethrow { js.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.toJson()).await() } } actual class UserInfo(val js: firebase.user.UserInfo) { diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt index 3ae30e9b6..d8c587411 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt @@ -63,8 +63,8 @@ external object firebase { fun confirmPasswordReset(code: String, newPassword: String): Promise fun createUserWithEmailAndPassword(email: String, password: String): Promise fun fetchSignInMethodsForEmail(email: String): Promise> - fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?): Promise - fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings?): Promise + fun sendPasswordResetEmail(email: String, actionCodeSettings: Any?): Promise + fun sendSignInLinkToEmail(email: String, actionCodeSettings: Any?): Promise fun signInWithEmailAndPassword(email: String, password: String): Promise fun signInWithCustomToken(token: String): Promise fun signInAnonymously(): Promise @@ -147,7 +147,7 @@ external object firebase { open class OAuthProvider(providerId: String) : AuthProvider { val providerId: String - fun credential(optionsOrIdToken: OAuthCredentialOptions?, accessToken: String?): AuthCredential + fun credential(optionsOrIdToken: Any?, accessToken: String?): AuthCredential fun addScope(scope: String) fun setCustomParameters(customOAuthParameters: Map) @@ -201,13 +201,13 @@ external object firebase { fun linkWithCredential(credential: auth.AuthCredential): Promise fun reauthenticateWithCredential(credential: auth.AuthCredential): Promise fun reload(): Promise - fun sendEmailVerification(actionCodeSettings: auth.ActionCodeSettings?): Promise + fun sendEmailVerification(actionCodeSettings: Any?): Promise fun unlink(providerId: String): Promise fun updateEmail(newEmail: String): Promise fun updatePassword(newPassword: String): Promise fun updatePhoneNumber(phoneCredential: auth.AuthCredential): Promise fun updateProfile(profile: ProfileUpdateRequest): Promise - fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: auth.ActionCodeSettings?): Promise + fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: Any?): Promise } abstract class UserMetadata { diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index 14b9b4b06..cdef91ec6 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -178,19 +178,19 @@ actual class OnDisconnect internal constructor( actual class DatabaseException(message: String) : RuntimeException(message) -private suspend fun T.awaitResult(whileOnline: Boolean, function: T.(callback: (NSError?, R?) -> Unit) -> Unit): R { - val job = CompletableDeferred() +private suspend inline fun T.awaitResult(whileOnline: Boolean, function: T.(callback: (NSError?, R?) -> Unit) -> Unit): R { + val job = CompletableDeferred() function { error, result -> - if(result != null) { + if(error == null) { job.complete(result) - } else if(error != null) { + } else { job.completeExceptionally(DatabaseException(error.toString())) } } - return job.run { if(whileOnline) awaitWhileOnline() else await() } + return job.run { if(whileOnline) awaitWhileOnline() else await() } as R } -suspend fun T.await(whileOnline: Boolean, function: T.(callback: (NSError?, FIRDatabaseReference?) -> Unit) -> Unit) { +private suspend inline fun T.await(whileOnline: Boolean, function: T.(callback: (NSError?, FIRDatabaseReference?) -> Unit) -> Unit) { val job = CompletableDeferred() function { error, _ -> if(error == null) { diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 4b05a609d..a6aad9170 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -330,19 +330,19 @@ private fun T.throwError(block: T.(errorPointer: CPointer awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T { - val job = CompletableDeferred() +internal suspend inline fun awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T { + val job = CompletableDeferred() function { result, error -> - if(result != null) { + if(error == null) { job.complete(result) - } else if(error != null) { + } else { job.completeExceptionally(error.toException()) } } - return job.await() + return job.await() as T } -suspend fun await(function: (callback: (NSError?) -> Unit) -> T): T { +internal suspend inline fun await(function: (callback: (NSError?) -> Unit) -> T): T { val job = CompletableDeferred() val result = function { error -> if(error == null) { diff --git a/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt index bc129e00d..41e44ac54 100644 --- a/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -55,7 +55,7 @@ actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { actual class FirebaseFunctionsException(message: String): FirebaseException(message) -private suspend fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { +private suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { val job = CompletableDeferred() function { error -> if(error == null) { @@ -67,14 +67,14 @@ private suspend fun T.await(function: T.(callback: (NSError?) -> Unit) -> Un job.await() } -suspend fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { - val job = CompletableDeferred() +private suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { + val job = CompletableDeferred() function { result, error -> - if(result != null) { + if(error == null) { job.complete(result) - } else if(error != null) { + } else { job.completeExceptionally(FirebaseFunctionsException(error.toString())) } } - return job.await() + return job.await() as R } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e4dba01e3..52744b5e9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,9 +6,9 @@ android.useAndroidX=true testOptions.unitTests.isIncludeAndroidResources = true # Versions: -firebase-app.version=0.3.1 -firebase-auth.version=0.3.1 -firebase-common.version=0.3.1 -firebase-database.version=0.3.1 -firebase-firestore.version=0.3.1 -firebase-functions.version=0.3.1 +firebase-app.version=0.4.0 +firebase-auth.version=0.4.0 +firebase-common.version=0.4.0 +firebase-database.version=0.4.0 +firebase-firestore.version=0.4.0 +firebase-functions.version=0.4.0