Skip to content

Commit

Permalink
copy js variant to wasmJs
Browse files Browse the repository at this point in the history
  • Loading branch information
Thaerith committed May 19, 2024
1 parent 03ba32e commit 62e6920
Show file tree
Hide file tree
Showing 44 changed files with 3,405 additions and 0 deletions.
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?
}
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)
193 changes: 193 additions & 0 deletions firebase-auth/src/wasmJsMain/kotlin/auth/auth.kt
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 firebase-auth/src/wasmJsMain/kotlin/auth/credentials.kt
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))
}
Loading

0 comments on commit 62e6920

Please sign in to comment.