-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
44 changed files
with
3,405 additions
and
0 deletions.
There are no files selected for viewing
32 changes: 32 additions & 0 deletions
32
firebase-app/src/wasmJsMain/kotlin/dev/gitlive/firebase/externals/app.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
@file:JsModule("firebase/app") | ||
@file:JsNonModule | ||
|
||
package dev.gitlive.firebase.externals | ||
|
||
import kotlin.js.Promise | ||
|
||
external fun initializeApp(options: Any, name: String = definedExternally): FirebaseApp | ||
|
||
external fun getApp(name: String = definedExternally): FirebaseApp | ||
|
||
external fun getApps(): Array<FirebaseApp> | ||
|
||
external fun deleteApp(app: FirebaseApp): Promise<Unit> | ||
|
||
external interface FirebaseApp { | ||
val automaticDataCollectionEnabled: Boolean | ||
val name: String | ||
val options: FirebaseOptions | ||
} | ||
|
||
external interface FirebaseOptions { | ||
val apiKey: String | ||
val appId : String | ||
val authDomain: String? | ||
val databaseURL: String? | ||
val measurementId: String? | ||
val messagingSenderId: String? | ||
val gaTrackingId: String? | ||
val projectId: String? | ||
val storageBucket: String? | ||
} |
58 changes: 58 additions & 0 deletions
58
firebase-app/src/wasmJsMain/kotlin/dev/gitlive/firebase/firebase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package dev.gitlive.firebase | ||
|
||
import dev.gitlive.firebase.externals.deleteApp | ||
import dev.gitlive.firebase.externals.getApp | ||
import dev.gitlive.firebase.externals.getApps | ||
import dev.gitlive.firebase.externals.initializeApp | ||
import kotlin.js.json | ||
import dev.gitlive.firebase.externals.FirebaseApp as JsFirebaseApp | ||
|
||
actual val Firebase.app: FirebaseApp | ||
get() = FirebaseApp(getApp()) | ||
|
||
actual fun Firebase.app(name: String): FirebaseApp = | ||
FirebaseApp(getApp(name)) | ||
|
||
actual fun Firebase.initialize(context: Any?): FirebaseApp? = | ||
throw UnsupportedOperationException("Cannot initialize firebase without options in JS") | ||
|
||
actual fun Firebase.initialize(context: Any?, options: FirebaseOptions, name: String): FirebaseApp = | ||
FirebaseApp(initializeApp(options.toJson(), name)) | ||
|
||
actual fun Firebase.initialize(context: Any?, options: FirebaseOptions) = | ||
FirebaseApp(initializeApp(options.toJson())) | ||
|
||
actual class FirebaseApp internal constructor(val js: JsFirebaseApp) { | ||
actual val name: String | ||
get() = js.name | ||
actual val options: FirebaseOptions | ||
get() = js.options.run { | ||
FirebaseOptions(appId, apiKey, databaseURL, gaTrackingId, storageBucket, projectId, messagingSenderId, authDomain) | ||
} | ||
|
||
actual suspend fun delete() { | ||
deleteApp(js) | ||
} | ||
} | ||
|
||
actual fun Firebase.apps(context: Any?) = getApps().map { FirebaseApp(it) } | ||
|
||
private fun FirebaseOptions.toJson() = json( | ||
"apiKey" to apiKey, | ||
"appId" to applicationId, | ||
"databaseURL" to (databaseUrl ?: undefined), | ||
"storageBucket" to (storageBucket ?: undefined), | ||
"projectId" to (projectId ?: undefined), | ||
"gaTrackingId" to (gaTrackingId ?: undefined), | ||
"messagingSenderId" to (gcmSenderId ?: undefined), | ||
"authDomain" to (authDomain ?: undefined) | ||
) | ||
|
||
actual open class FirebaseException(code: String?, cause: Throwable) : Exception("$code: ${cause.message}", cause) | ||
actual open class FirebaseNetworkException(code: String?, cause: Throwable) : FirebaseException(code, cause) | ||
actual open class FirebaseTooManyRequestsException(code: String?, cause: Throwable) : FirebaseException(code, cause) | ||
actual open class FirebaseApiNotAvailableException(code: String?, cause: Throwable) : FirebaseException(code, cause) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/* | ||
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package dev.gitlive.firebase.auth | ||
|
||
import dev.gitlive.firebase.Firebase | ||
import dev.gitlive.firebase.FirebaseApp | ||
import dev.gitlive.firebase.FirebaseException | ||
import dev.gitlive.firebase.FirebaseNetworkException | ||
import dev.gitlive.firebase.auth.externals.* | ||
import kotlinx.coroutines.await | ||
import kotlinx.coroutines.channels.awaitClose | ||
import kotlinx.coroutines.flow.callbackFlow | ||
import kotlin.js.json | ||
import dev.gitlive.firebase.auth.externals.AuthResult as JsAuthResult | ||
|
||
actual val Firebase.auth | ||
get() = rethrow { FirebaseAuth(getAuth()) } | ||
|
||
actual fun Firebase.auth(app: FirebaseApp) = | ||
rethrow { FirebaseAuth(getAuth(app.js)) } | ||
|
||
actual class FirebaseAuth internal constructor(val js: Auth) { | ||
|
||
actual val currentUser: FirebaseUser? | ||
get() = rethrow { js.currentUser?.let { FirebaseUser(it) } } | ||
|
||
actual val authStateChanged get() = callbackFlow<FirebaseUser?> { | ||
val unsubscribe = js.onAuthStateChanged { | ||
trySend(it?.let { FirebaseUser(it) }) | ||
} | ||
awaitClose { unsubscribe() } | ||
} | ||
|
||
actual val idTokenChanged get() = callbackFlow<FirebaseUser?> { | ||
val unsubscribe = js.onIdTokenChanged { | ||
trySend(it?.let { FirebaseUser(it) }) | ||
} | ||
awaitClose { unsubscribe() } | ||
} | ||
|
||
actual var languageCode: String | ||
get() = js.languageCode ?: "" | ||
set(value) { js.languageCode = value } | ||
|
||
actual suspend fun applyActionCode(code: String) = rethrow { applyActionCode(js, code).await() } | ||
actual suspend fun confirmPasswordReset(code: String, newPassword: String) = rethrow { confirmPasswordReset(js, code, newPassword).await() } | ||
|
||
actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = | ||
rethrow { AuthResult(createUserWithEmailAndPassword(js, email, password).await()) } | ||
|
||
actual suspend fun fetchSignInMethodsForEmail(email: String): List<String> = rethrow { fetchSignInMethodsForEmail(js, email).await().asList() } | ||
|
||
actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) = | ||
rethrow { sendPasswordResetEmail(js, email, actionCodeSettings?.toJson()).await() } | ||
|
||
actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = | ||
rethrow { sendSignInLinkToEmail(js, email, actionCodeSettings.toJson()).await() } | ||
|
||
actual fun isSignInWithEmailLink(link: String) = rethrow { isSignInWithEmailLink(js, link) } | ||
|
||
actual suspend fun signInWithEmailAndPassword(email: String, password: String) = | ||
rethrow { AuthResult(signInWithEmailAndPassword(js, email, password).await()) } | ||
|
||
actual suspend fun signInWithCustomToken(token: String) = | ||
rethrow { AuthResult(signInWithCustomToken(js, token).await()) } | ||
|
||
actual suspend fun signInAnonymously() = | ||
rethrow { AuthResult(signInAnonymously(js).await()) } | ||
|
||
actual suspend fun signInWithCredential(authCredential: AuthCredential) = | ||
rethrow { AuthResult(signInWithCredential(js, authCredential.js).await()) } | ||
|
||
actual suspend fun signInWithEmailLink(email: String, link: String) = | ||
rethrow { AuthResult(signInWithEmailLink(js, email, link).await()) } | ||
|
||
actual suspend fun signOut() = rethrow { signOut(js).await() } | ||
|
||
actual suspend fun updateCurrentUser(user: FirebaseUser) = | ||
rethrow { updateCurrentUser(js, user.js).await() } | ||
|
||
actual suspend fun verifyPasswordResetCode(code: String): String = | ||
rethrow { verifyPasswordResetCode(js, code).await() } | ||
|
||
actual suspend fun <T : ActionCodeResult> checkActionCode(code: String): T = rethrow { | ||
val result = checkActionCode(js, 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 fun useEmulator(host: String, port: Int) = rethrow { connectAuthEmulator(js, "http://$host:$port") } | ||
} | ||
|
||
actual class AuthResult internal constructor(val js: JsAuthResult) { | ||
actual val user: FirebaseUser? | ||
get() = rethrow { js.user?.let { FirebaseUser(it) } } | ||
} | ||
|
||
actual class AuthTokenResult(val js: IdTokenResult) { | ||
// actual val authTimestamp: Long | ||
// get() = js.authTime | ||
actual val claims: Map<String, Any> | ||
get() = (js("Object").keys(js.claims) as Array<String>).mapNotNull { | ||
key -> js.claims[key]?.let { key to it } | ||
}.toMap() | ||
// actual val expirationTimestamp: Long | ||
// get() = android.expirationTime | ||
// actual val issuedAtTimestamp: Long | ||
// get() = js.issuedAtTime | ||
actual val signInProvider: String? | ||
get() = js.signInProvider | ||
actual val token: String? | ||
get() = js.token | ||
} | ||
|
||
internal fun ActionCodeSettings.toJson() = json( | ||
"url" to url, | ||
"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) | ||
actual open class FirebaseAuthEmailException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) | ||
actual open class FirebaseAuthInvalidCredentialsException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) | ||
actual open class FirebaseAuthWeakPasswordException(code: String?, cause: Throwable): FirebaseAuthInvalidCredentialsException(code, cause) | ||
actual open class FirebaseAuthInvalidUserException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) | ||
actual open class FirebaseAuthMultiFactorException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) | ||
actual open class FirebaseAuthRecentLoginRequiredException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) | ||
actual open class FirebaseAuthUserCollisionException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) | ||
actual open class FirebaseAuthWebException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) | ||
|
||
|
||
internal inline fun <T, R> T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.auth.rethrow { function() } | ||
|
||
private inline fun <R> rethrow(function: () -> R): R { | ||
try { | ||
return function() | ||
} catch (e: Exception) { | ||
throw e | ||
} catch(e: dynamic) { | ||
throw errorToException(e) | ||
} | ||
} | ||
|
||
private fun errorToException(cause: dynamic) = when(val code = cause.code?.toString()?.lowercase()) { | ||
"auth/invalid-user-token" -> FirebaseAuthInvalidUserException(code, cause) | ||
"auth/requires-recent-login" -> FirebaseAuthRecentLoginRequiredException(code, cause) | ||
"auth/user-disabled" -> FirebaseAuthInvalidUserException(code, cause) | ||
"auth/user-token-expired" -> FirebaseAuthInvalidUserException(code, cause) | ||
"auth/web-storage-unsupported" -> FirebaseAuthWebException(code, cause) | ||
"auth/network-request-failed" -> FirebaseNetworkException(code, cause) | ||
"auth/timeout" -> FirebaseNetworkException(code, cause) | ||
"auth/weak-password" -> FirebaseAuthWeakPasswordException(code, cause) | ||
"auth/invalid-credential", | ||
"auth/invalid-verification-code", | ||
"auth/missing-verification-code", | ||
"auth/invalid-verification-id", | ||
"auth/missing-verification-id" -> FirebaseAuthInvalidCredentialsException(code, cause) | ||
"auth/maximum-second-factor-count-exceeded", | ||
"auth/second-factor-already-in-use" -> FirebaseAuthMultiFactorException(code, cause) | ||
"auth/credential-already-in-use" -> FirebaseAuthUserCollisionException(code, cause) | ||
"auth/email-already-in-use" -> FirebaseAuthUserCollisionException(code, cause) | ||
"auth/invalid-email" -> FirebaseAuthEmailException(code, cause) | ||
// "auth/app-deleted" -> | ||
// "auth/app-not-authorized" -> | ||
// "auth/argument-error" -> | ||
// "auth/invalid-api-key" -> | ||
// "auth/operation-not-allowed" -> | ||
// "auth/too-many-arguments" -> | ||
// "auth/unauthorized-domain" -> | ||
else -> { | ||
println("Unknown error code in ${JSON.stringify(cause)}") | ||
FirebaseAuthException(code, cause) | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
firebase-auth/src/wasmJsMain/kotlin/auth/credentials.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package dev.gitlive.firebase.auth | ||
|
||
import dev.gitlive.firebase.auth.externals.ApplicationVerifier | ||
import dev.gitlive.firebase.auth.externals.EmailAuthProvider | ||
import dev.gitlive.firebase.auth.externals.FacebookAuthProvider | ||
import dev.gitlive.firebase.auth.externals.GithubAuthProvider | ||
import dev.gitlive.firebase.auth.externals.GoogleAuthProvider | ||
import dev.gitlive.firebase.auth.externals.PhoneAuthProvider | ||
import dev.gitlive.firebase.auth.externals.TwitterAuthProvider | ||
import kotlinx.coroutines.await | ||
import kotlin.js.json | ||
import dev.gitlive.firebase.auth.externals.AuthCredential as JsAuthCredential | ||
import dev.gitlive.firebase.auth.externals.OAuthProvider as JsOAuthProvider | ||
|
||
actual open class AuthCredential(val js: JsAuthCredential) { | ||
actual val providerId: String | ||
get() = js.providerId | ||
} | ||
|
||
actual class PhoneAuthCredential(js: JsAuthCredential) : AuthCredential(js) | ||
actual class OAuthCredential(js: JsAuthCredential) : AuthCredential(js) | ||
|
||
actual object EmailAuthProvider { | ||
actual fun credential(email: String, password: String): AuthCredential = | ||
AuthCredential(EmailAuthProvider.credential(email, password)) | ||
|
||
actual fun credentialWithLink( | ||
email: String, | ||
emailLink: String | ||
): AuthCredential = AuthCredential(EmailAuthProvider.credentialWithLink(email, emailLink)) | ||
} | ||
|
||
actual object FacebookAuthProvider { | ||
actual fun credential(accessToken: String): AuthCredential = | ||
AuthCredential(FacebookAuthProvider.credential(accessToken)) | ||
} | ||
|
||
actual object GithubAuthProvider { | ||
actual fun credential(token: String): AuthCredential = | ||
AuthCredential(GithubAuthProvider.credential(token)) | ||
} | ||
|
||
actual object GoogleAuthProvider { | ||
actual fun credential(idToken: String?, accessToken: String?): AuthCredential { | ||
require(idToken != null || accessToken != null) { | ||
"Both parameters are optional but at least one must be present." | ||
} | ||
return AuthCredential(GoogleAuthProvider.credential(idToken, accessToken)) | ||
} | ||
} | ||
|
||
actual class OAuthProvider(val js: JsOAuthProvider) { | ||
|
||
actual constructor( | ||
provider: String, | ||
scopes: List<String>, | ||
customParameters: Map<String, String>, | ||
auth: FirebaseAuth | ||
) : this(JsOAuthProvider(provider)) { | ||
rethrow { | ||
scopes.forEach { js.addScope(it) } | ||
js.setCustomParameters(customParameters) | ||
} | ||
} | ||
actual companion object { | ||
actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential = rethrow { | ||
JsOAuthProvider(providerId) | ||
.credential( | ||
json( | ||
"accessToken" to (accessToken ?: undefined), | ||
"idToken" to (idToken ?: undefined), | ||
"rawNonce" to (rawNonce ?: undefined) | ||
), | ||
accessToken ?: undefined | ||
) | ||
.let { OAuthCredential(it) } | ||
} | ||
} | ||
} | ||
|
||
actual class PhoneAuthProvider(val js: PhoneAuthProvider) { | ||
|
||
actual constructor(auth: FirebaseAuth) : this(PhoneAuthProvider(auth.js)) | ||
|
||
actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(PhoneAuthProvider.credential(verificationId, smsCode)) | ||
actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = rethrow { | ||
val verificationId = js.verifyPhoneNumber(phoneNumber, verificationProvider.verifier).await() | ||
val verificationCode = verificationProvider.getVerificationCode(verificationId) | ||
credential(verificationId, verificationCode) | ||
} | ||
} | ||
|
||
actual interface PhoneVerificationProvider { | ||
val verifier: ApplicationVerifier | ||
suspend fun getVerificationCode(verificationId: String): String | ||
} | ||
|
||
actual object TwitterAuthProvider { | ||
actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(TwitterAuthProvider.credential(token, secret)) | ||
} |
Oops, something went wrong.