Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wasm #511

Closed
wants to merge 3 commits into from
Closed

Wasm #511

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions firebase-app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

/*
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
Expand Down Expand Up @@ -114,6 +115,29 @@ kotlin {
}
}

@OptIn(ExperimentalWasmDsl::class)
wasmJs {
useCommonJs()
nodejs {
testTask(
Action {
useKarma {
useChromeHeadless()
}
}
)
}
browser {
testTask(
Action {
useKarma {
useChromeHeadless()
}
}
)
}
}

sourceSets {
all {
languageSettings.apply {
Expand Down
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)
24 changes: 24 additions & 0 deletions firebase-auth/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

/*
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
Expand Down Expand Up @@ -118,6 +119,29 @@ kotlin {
}
}

@OptIn(ExperimentalWasmDsl::class)
wasmJs {
useCommonJs()
nodejs {
testTask(
Action {
useKarma {
useChromeHeadless()
}
}
)
}
browser {
testTask(
Action {
useKarma {
useChromeHeadless()
}
}
)
}
}

jvm {
compilations.getByName("main") {
kotlinOptions {
Expand Down
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)
}
}
Loading
Loading