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

wasmJs prototype fix #515

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions firebase-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ kotlin {
}
}

wasmJs()

js(IR) {
useCommonJs()
nodejs {
Expand Down
4 changes: 2 additions & 2 deletions firebase-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gitlive/firebase-app",
"version": "1.12.0",
"version": "1.13.0-wasmJsFix",
"description": "Wrapper around firebase for usage in Kotlin Multiplatform projects",
"main": "firebase-app.js",
"scripts": {
Expand All @@ -23,7 +23,7 @@
},
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
"dependencies": {
"@gitlive/firebase-common": "1.12.0",
"@gitlive/firebase-common": "1.13.0-wasmJsFix",
"firebase": "9.19.1",
"kotlin": "1.8.20",
"kotlinx-coroutines-core": "1.6.4"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@file:JsModule("firebase/app")

package dev.gitlive.firebase.externals

import kotlin.js.Promise

external fun initializeApp(options: JsAny, name: String = definedExternally): FirebaseApp

external fun getApp(name: String = definedExternally): FirebaseApp

external fun getApps(): JsArray<FirebaseApp>

external fun deleteApp(app: FirebaseApp): Promise<JsAny?>

external interface FirebaseApp: JsAny {
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?
}
101 changes: 101 additions & 0 deletions firebase-app/src/wasmJsMain/kotlin/dev/gitlive/firebase/firebase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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 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() {
"".toJsReference()
deleteApp(js)
}
}

actual fun Firebase.apps(context: Any?) = getApps().asSequence().filterNotNull().map { FirebaseApp(it) }.toList()

@JsName("Object")
external class JsObject : JsAny {
operator fun get(key: JsString): JsAny?
operator fun set(key: JsString, value: JsAny?)
}

fun json(params: List<Pair<String, Any?>>): JsObject {
return json(*params.toTypedArray())
}

fun json(params: Map<String, Any?>): JsObject {
return json(*params.entries.map { it.key to it.value }.toTypedArray())
}

fun json(vararg params: Pair<String, Any?>): JsObject {
return JsObject().apply {
params.forEach {
val key = it.first.toJsString()
when (val value = it.second) {
is String -> set(key, value.toJsString())
is Boolean -> set(key, value.toJsBoolean())
is Int -> set(key, value.toJsNumber())
is JsObject -> set(key, value)
is JsString -> set(key, value)
is JsBoolean -> set(key, value)
is JsNumber -> set(key, value)
is JsArray<*> -> set(key, value)
else -> error("Unknown param $it")
}
}
}
}

private fun FirebaseOptions.toJson() = JsObject().apply {
set("apiKey".toJsString(), apiKey.toJsString())
set("appId".toJsString(), applicationId.toJsString())
set("databaseURL".toJsString(), (databaseUrl?.toJsString()))
set("storageBucket".toJsString(), (storageBucket?.toJsString()))
set("projectId".toJsString(), (projectId?.toJsString()))
set("gaTrackingId".toJsString(), (gaTrackingId?.toJsString()))
set("messagingSenderId".toJsString(), (gcmSenderId?.toJsString()))
set("authDomain".toJsString(), (authDomain?.toJsString()))
}

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)


fun <T: JsAny> JsArray<T>.asSequence(): Sequence<T?> {
var i = 0
return sequence {
while (i++ < length) {
yield(get(i))
}
}
}
2 changes: 2 additions & 0 deletions firebase-auth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ kotlin {
}
}

wasmJs()

jvm {
compilations.getByName("main") {
kotlinOptions {
Expand Down
4 changes: 2 additions & 2 deletions firebase-auth/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gitlive/firebase-auth",
"version": "1.12.0",
"version": "1.13.0-wasmJsFix",
"description": "Wrapper around firebase for usage in Kotlin Multiplatform projects",
"main": "firebase-auth.js",
"scripts": {
Expand All @@ -23,7 +23,7 @@
},
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
"dependencies": {
"@gitlive/firebase-app": "1.12.0",
"@gitlive/firebase-app": "1.13.0-wasmJsFix",
"firebase": "9.19.1",
"kotlin": "1.8.20",
"kotlinx-coroutines-core": "1.6.4"
Expand Down
210 changes: 210 additions & 0 deletions firebase-auth/src/wasmJsMain/kotlin/dev/gitlive/firebase/auth/auth.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* 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.JsObject
import dev.gitlive.firebase.asSequence
import dev.gitlive.firebase.auth.externals.*
import dev.gitlive.firebase.json
import kotlinx.coroutines.await
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
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): Unit = rethrow { applyActionCode(js, code).await() }
actual suspend fun confirmPasswordReset(code: String, newPassword: String): Unit = 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<JsArray<JsString>>().asSequence().map { it.toString() }.toList() }

actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?): Unit =
rethrow { sendPasswordResetEmail(js, email, actionCodeSettings?.toJson()).await() }

actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings): Unit =
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(): Unit = rethrow { signOut(js).await() }

actual suspend fun updateCurrentUser(user: FirebaseUser): Unit =
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: ActionCodeInfo = 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 tokenResult: IdTokenResult) {
// actual val authTimestamp: Long
// get() = js.authTime
actual val claims: Map<String, Any>
get() = JsObjectKeys(tokenResult.claims).asSequence().mapNotNull { key ->
val keyJsString = key as? JsString ?: return@mapNotNull null
val value = tokenResult.claims[keyJsString] ?: return@mapNotNull null
val keyString = keyJsString.toString()
keyString to value
}.toList().toMap()
// actual val expirationTimestamp: Long
// get() = android.expirationTime
// actual val issuedAtTimestamp: Long
// get() = js.issuedAtTime
actual val signInProvider: String?
get() = tokenResult.signInProvider
actual val token: String?
get() = tokenResult.token
}

internal fun ActionCodeSettings.toJson() = json(
"url" to url,
"android" to (androidPackageName?.run { json("installApp" to installIfNotAvailable, "minimumVersion" to minimumVersion, "packageName" to packageName) }),
"dynamicLinkDomain" to (dynamicLinkDomain ?: null),
"handleCodeInApp" to canHandleCodeInApp,
"ios" to (iOSBundleId?.run { json("bundleId" to iOSBundleId) } ?: null)
)

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
}
}

private fun errorToException(o: JsObject): FirebaseException {
val cause = Exception(o.toString())
return when(val code = o["code".toJsString()]?.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 -> {
FirebaseAuthException(code, cause)
}
}
}

@JsFun("(output) => Object.keys(output)")
external fun JsObjectKeys(vararg output: JsObject?): JsArray<JsAny?>

fun <T: JsAny?> JsArray<T>.asSequence(): Sequence<T?> {
var i = 0
return sequence {
while (i++ < length) {
yield(get(i))
}
}
}
Loading
Loading