diff --git a/firebase-app/package.json b/firebase-app/package.json index 056be9c9d..ad462e03f 100644 --- a/firebase-app/package.json +++ b/firebase-app/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-app", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-app.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-common": "1.9.0", + "@gitlive/firebase-common": "1.10.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/externals/app.kt b/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/externals/app.kt new file mode 100644 index 000000000..ab2066cf9 --- /dev/null +++ b/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/externals/app.kt @@ -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 + +external fun deleteApp(app: FirebaseApp): Promise + +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? +} diff --git a/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/firebase.kt index 1d911c290..6bffaa7be 100644 --- a/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/firebase.kt @@ -4,35 +4,42 @@ 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(firebase.app()) + get() = FirebaseApp(getApp()) actual fun Firebase.app(name: String): FirebaseApp = - FirebaseApp(firebase.app(name)) + 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(firebase.initializeApp(options.toJson(), name)) + FirebaseApp(initializeApp(options.toJson(), name)) actual fun Firebase.initialize(context: Any?, options: FirebaseOptions) = - FirebaseApp(firebase.initializeApp(options.toJson())) + FirebaseApp(initializeApp(options.toJson())) -actual class FirebaseApp internal constructor(val js: firebase.App) { +actual class FirebaseApp internal constructor(val js: JsFirebaseApp) { actual val name: String get() = js.name actual val options: FirebaseOptions get() = js.options.run { - FirebaseOptions(applicationId, apiKey, databaseUrl, gaTrackingId, storageBucket, projectId, messagingSenderId, authDomain) + FirebaseOptions(appId, apiKey, databaseURL, gaTrackingId, storageBucket, projectId, messagingSenderId, authDomain) } - actual fun delete() = js.delete() + actual fun delete() { + deleteApp(js) + } } -actual fun Firebase.apps(context: Any?) = firebase.apps.map { FirebaseApp(it) } +actual fun Firebase.apps(context: Any?) = getApps().map { FirebaseApp(it) } private fun FirebaseOptions.toJson() = json( "apiKey" to apiKey, diff --git a/firebase-auth/karma.config.d/karma.conf.js b/firebase-auth/karma.config.d/karma.conf.js new file mode 100644 index 000000000..e2301eddd --- /dev/null +++ b/firebase-auth/karma.config.d/karma.conf.js @@ -0,0 +1,8 @@ +// Some tests are fluky in GitHub Actions, so we increase the timeout. +config.set({ + client: { + mocha: { + timeout: 5000 + } + }, +}); diff --git a/firebase-auth/package.json b/firebase-auth/package.json index 2cc003be7..fe344e504 100644 --- a/firebase-auth/package.json +++ b/firebase-auth/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-auth", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-auth.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" 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 12250e31e..1635540c0 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 @@ -5,12 +5,13 @@ package dev.gitlive.firebase.auth import dev.gitlive.firebase.* +import kotlinx.coroutines.test.TestResult import kotlin.random.Random import kotlin.test.* expect val emulatorHost: String expect val context: Any -expect fun runTest(test: suspend () -> Unit) +expect fun runTest(test: suspend () -> Unit): TestResult class FirebaseAuthTest { 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 dfa7ea0b6..a6499a5e7 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 @@ -5,18 +5,21 @@ package dev.gitlive.firebase.auth import dev.gitlive.firebase.* +import dev.gitlive.firebase.FirebaseApp +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 { dev.gitlive.firebase.auth; FirebaseAuth(firebase.auth()) } + get() = rethrow { FirebaseAuth(getAuth()) } actual fun Firebase.auth(app: FirebaseApp) = - rethrow { dev.gitlive.firebase.auth; FirebaseAuth(firebase.auth(app.js)) } + rethrow { FirebaseAuth(getAuth(app.js)) } -actual class FirebaseAuth internal constructor(val js: firebase.auth.Auth) { +actual class FirebaseAuth internal constructor(val js: Auth) { actual val currentUser: FirebaseUser? get() = rethrow { js.currentUser?.let { FirebaseUser(it) } } @@ -39,47 +42,47 @@ actual class FirebaseAuth internal constructor(val js: firebase.auth.Auth) { get() = js.languageCode ?: "" set(value) { js.languageCode = value } - actual suspend fun applyActionCode(code: String) = rethrow { js.applyActionCode(code).await() } - actual suspend fun confirmPasswordReset(code: String, newPassword: String) = rethrow { js.confirmPasswordReset(code, newPassword).await() } + 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(js.createUserWithEmailAndPassword(email, password).await()) } + rethrow { AuthResult(createUserWithEmailAndPassword(js, email, password).await()) } - actual suspend fun fetchSignInMethodsForEmail(email: String): List = rethrow { js.fetchSignInMethodsForEmail(email).await().asList() } + actual suspend fun fetchSignInMethodsForEmail(email: String): List = rethrow { fetchSignInMethodsForEmail(js, email).await().asList() } actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) = - rethrow { js.sendPasswordResetEmail(email, actionCodeSettings?.toJson()).await() } + rethrow { sendPasswordResetEmail(js, email, actionCodeSettings?.toJson()).await() } actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = - rethrow { js.sendSignInLinkToEmail(email, actionCodeSettings.toJson()).await() } + rethrow { sendSignInLinkToEmail(js, email, actionCodeSettings.toJson()).await() } - actual fun isSignInWithEmailLink(link: String) = rethrow { js.isSignInWithEmailLink(link) } + actual fun isSignInWithEmailLink(link: String) = rethrow { isSignInWithEmailLink(js, link) } actual suspend fun signInWithEmailAndPassword(email: String, password: String) = - rethrow { AuthResult(js.signInWithEmailAndPassword(email, password).await()) } + rethrow { AuthResult(signInWithEmailAndPassword(js, email, password).await()) } actual suspend fun signInWithCustomToken(token: String) = - rethrow { AuthResult(js.signInWithCustomToken(token).await()) } + rethrow { AuthResult(signInWithCustomToken(js, token).await()) } actual suspend fun signInAnonymously() = - rethrow { AuthResult(js.signInAnonymously().await()) } + rethrow { AuthResult(signInAnonymously(js).await()) } actual suspend fun signInWithCredential(authCredential: AuthCredential) = - rethrow { AuthResult(js.signInWithCredential(authCredential.js).await()) } + rethrow { AuthResult(signInWithCredential(js, authCredential.js).await()) } actual suspend fun signInWithEmailLink(email: String, link: String) = - rethrow { AuthResult(js.signInWithEmailLink(email, link).await()) } + rethrow { AuthResult(signInWithEmailLink(js, email, link).await()) } - actual suspend fun signOut() = rethrow { js.signOut().await() } + actual suspend fun signOut() = rethrow { signOut(js).await() } actual suspend fun updateCurrentUser(user: FirebaseUser) = - rethrow { js.updateCurrentUser(user.js).await() } + rethrow { updateCurrentUser(js, user.js).await() } actual suspend fun verifyPasswordResetCode(code: String): String = - rethrow { js.verifyPasswordResetCode(code).await() } + rethrow { verifyPasswordResetCode(js, code).await() } actual suspend fun checkActionCode(code: String): T = rethrow { - val result = js.checkActionCode(code).await() + val result = checkActionCode(js, code).await() @Suppress("UNCHECKED_CAST") return when(result.operation) { "EMAIL_SIGNIN" -> ActionCodeResult.SignInWithEmailLink @@ -98,15 +101,15 @@ actual class FirebaseAuth internal constructor(val js: firebase.auth.Auth) { } as T } - actual fun useEmulator(host: String, port: Int) = rethrow { js.useEmulator("http://$host:$port") } + actual fun useEmulator(host: String, port: Int) = rethrow { connectAuthEmulator(js, "http://$host:$port") } } -actual class AuthResult internal constructor(val js: firebase.auth.AuthResult) { +actual class AuthResult internal constructor(val js: JsAuthResult) { actual val user: FirebaseUser? get() = rethrow { js.user?.let { FirebaseUser(it) } } } -actual class AuthTokenResult(val js: firebase.auth.IdTokenResult) { +actual class AuthTokenResult(val js: IdTokenResult) { // actual val authTimestamp: Long // get() = js.authTime actual val claims: Map 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 13db5dc70..eb6fb9efe 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 @@ -1,36 +1,43 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.firebase +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 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: firebase.auth.AuthCredential) { +actual open class AuthCredential(val js: JsAuthCredential) { actual val providerId: String get() = js.providerId } -actual class PhoneAuthCredential(js: firebase.auth.AuthCredential) : AuthCredential(js) -actual class OAuthCredential(js: firebase.auth.AuthCredential) : AuthCredential(js) +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(firebase.auth.EmailAuthProvider.credential(email, password)) + AuthCredential(EmailAuthProvider.credential(email, password)) actual fun credentialWithLink( email: String, emailLink: String - ): AuthCredential = AuthCredential(firebase.auth.EmailAuthProvider.credentialWithLink(email, emailLink)) + ): AuthCredential = AuthCredential(EmailAuthProvider.credentialWithLink(email, emailLink)) } actual object FacebookAuthProvider { actual fun credential(accessToken: String): AuthCredential = - AuthCredential(firebase.auth.FacebookAuthProvider.credential(accessToken)) + AuthCredential(FacebookAuthProvider.credential(accessToken)) } actual object GithubAuthProvider { actual fun credential(token: String): AuthCredential = - AuthCredential(firebase.auth.GithubAuthProvider.credential(token)) + AuthCredential(GithubAuthProvider.credential(token)) } actual object GoogleAuthProvider { @@ -38,18 +45,18 @@ actual object GoogleAuthProvider { require(idToken != null || accessToken != null) { "Both parameters are optional but at least one must be present." } - return AuthCredential(firebase.auth.GoogleAuthProvider.credential(idToken, accessToken)) + return AuthCredential(GoogleAuthProvider.credential(idToken, accessToken)) } } -actual class OAuthProvider(val js: firebase.auth.OAuthProvider) { +actual class OAuthProvider(val js: JsOAuthProvider) { actual constructor( provider: String, scopes: List, customParameters: Map, auth: FirebaseAuth - ) : this(firebase.auth.OAuthProvider(provider)) { + ) : this(JsOAuthProvider(provider)) { rethrow { scopes.forEach { js.addScope(it) } js.setCustomParameters(customParameters) @@ -57,7 +64,7 @@ actual class OAuthProvider(val js: firebase.auth.OAuthProvider) { } actual companion object { actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential = rethrow { - firebase.auth.OAuthProvider(providerId) + JsOAuthProvider(providerId) .credential( json( "accessToken" to (accessToken ?: undefined), @@ -71,11 +78,11 @@ actual class OAuthProvider(val js: firebase.auth.OAuthProvider) { } } -actual class PhoneAuthProvider(val js: firebase.auth.PhoneAuthProvider) { +actual class PhoneAuthProvider(val js: PhoneAuthProvider) { - actual constructor(auth: FirebaseAuth) : this(firebase.auth.PhoneAuthProvider(auth.js)) + actual constructor(auth: FirebaseAuth) : this(PhoneAuthProvider(auth.js)) - actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(firebase.auth.PhoneAuthProvider.credential(verificationId, smsCode)) + 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) @@ -84,10 +91,10 @@ actual class PhoneAuthProvider(val js: firebase.auth.PhoneAuthProvider) { } actual interface PhoneVerificationProvider { - val verifier: firebase.auth.ApplicationVerifier + val verifier: ApplicationVerifier suspend fun getVerificationCode(verificationId: String): String } actual object TwitterAuthProvider { - actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(firebase.auth.TwitterAuthProvider.credential(token, secret)) + actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(TwitterAuthProvider.credential(token, secret)) } diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/externals/auth.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/externals/auth.kt new file mode 100644 index 000000000..0681ee8a6 --- /dev/null +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/externals/auth.kt @@ -0,0 +1,286 @@ +@file:JsModule("firebase/auth") +@file:JsNonModule + +package dev.gitlive.firebase.auth.externals + +import dev.gitlive.firebase.Unsubscribe +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Json +import kotlin.js.Promise + +external fun applyActionCode(auth: Auth, code: String): Promise + +external fun checkActionCode(auth: Auth, code: String): Promise + +external fun confirmPasswordReset(auth: Auth, code: String, newPassword: String): Promise + +external fun connectAuthEmulator(auth: Auth, url: String, options: Any? = definedExternally) + +external fun createUserWithEmailAndPassword( + auth: Auth, + email: String, + password: String +): Promise + +external fun deleteUser(user: User): Promise + +external fun fetchSignInMethodsForEmail(auth: Auth, email: String): Promise> + +external fun getAuth(app: FirebaseApp? = definedExternally): Auth + +external fun getIdToken(user: User, forceRefresh: Boolean?): Promise + +external fun getIdTokenResult(user: User, forceRefresh: Boolean?): Promise + +external fun isSignInWithEmailLink(auth: Auth, link: String): Boolean + +external fun linkWithCredential(user: User, credential: AuthCredential): Promise + +external fun multiFactor(user: User): MultiFactorUser + +external fun onAuthStateChanged(auth: Auth, nextOrObserver: (User?) -> Unit): Unsubscribe + +external fun onIdTokenChanged(auth: Auth, nextOrObserver: (User?) -> Unit): Unsubscribe + +external fun sendEmailVerification(user: User, actionCodeSettings: Any?): Promise + +external fun reauthenticateWithCredential( + user: User, + credential: AuthCredential +): Promise + +external fun reload(user: User): Promise + +external fun sendPasswordResetEmail( + auth: Auth, + email: String, + actionCodeSettings: Any? +): Promise + +external fun sendSignInLinkToEmail( + auth: Auth, + email: String, + actionCodeSettings: Any? +): Promise + +external fun signInAnonymously(auth: Auth): Promise + +external fun signInWithCredential(auth: Auth, authCredential: AuthCredential): Promise + +external fun signInWithCustomToken(auth: Auth, token: String): Promise + +external fun signInWithEmailAndPassword( + auth: Auth, + email: String, + password: String +): Promise + +external fun signInWithEmailLink(auth: Auth, email: String, link: String): Promise + +external fun signInWithPopup(auth: Auth, provider: AuthProvider): Promise + +external fun signInWithRedirect(auth: Auth, provider: AuthProvider): Promise + +external fun getRedirectResult(auth: Auth): Promise + +external fun signOut(auth: Auth): Promise + +external fun unlink(user: User, providerId: String): Promise + +external fun updateCurrentUser(auth: Auth, user: User?): Promise + +external fun updateEmail(user: User, newEmail: String): Promise + +external fun updatePassword(user: User, newPassword: String): Promise + +external fun updatePhoneNumber(user: User, phoneCredential: AuthCredential): Promise + +external fun updateProfile(user: User, profile: Json): Promise + +external fun verifyBeforeUpdateEmail( + user: User, + newEmail: String, + actionCodeSettings: Any? +): Promise + +external fun verifyPasswordResetCode(auth: Auth, code: String): Promise + +external interface Auth { + val currentUser: User? + var languageCode: String? + + fun onAuthStateChanged(nextOrObserver: (User?) -> Unit): Unsubscribe + fun onIdTokenChanged(nextOrObserver: (User?) -> Unit): Unsubscribe + fun signOut(): Promise + fun updateCurrentUser(user: User?): Promise +} + +external interface UserInfo { + val displayName: String? + val email: String? + val phoneNumber: String? + val photoURL: String? + val providerId: String + val uid: String +} + +external interface User : UserInfo { + val emailVerified: Boolean + val isAnonymous: Boolean + val metadata: UserMetadata + val providerData: Array + val refreshToken: String + val tenantId: String? + + fun delete(): Promise + fun getIdToken(forceRefresh: Boolean?): Promise + fun getIdTokenResult(forceRefresh: Boolean?): Promise + fun reload(): Promise +} + +external interface UserMetadata { + val creationTime: String? + val lastSignInTime: String? +} + +external interface IdTokenResult { + val authTime: String + val claims: Json + val expirationTime: String + val issuedAtTime: String + val signInProvider: String? + val signInSecondFactor: String? + val token: String +} + +external interface ActionCodeInfo { + val operation: String + val data: ActionCodeData +} + +external interface ActionCodeData { + val email: String? + val multiFactorInfo: MultiFactorInfo? + val previousEmail: String? +} + +external interface AuthResult { + val credential: AuthCredential? + val operationType: String? + val user: User? +} + +external interface AuthCredential { + val providerId: String + val signInMethod: String +} + +external interface OAuthCredential : AuthCredential { + val accessToken: String? + val idToken: String? + val secret: String? +} + +external interface UserCredential { + val operationType: String + val providerId: String? + val user: User +} + +external interface ProfileUpdateRequest { + val displayName: String? + val photoURL: String? +} + +external interface MultiFactorUser { + val enrolledFactors: Array + + fun enroll(assertion: MultiFactorAssertion, displayName: String?): Promise + fun getSession(): Promise + fun unenroll(option: MultiFactorInfo): Promise + fun unenroll(option: String): Promise +} + +external interface MultiFactorInfo { + val displayName: String? + val enrollmentTime: String + val factorId: String + val uid: String +} + +external interface MultiFactorAssertion { + val factorId: String +} + +external interface MultiFactorSession + +external interface MultiFactorResolver { + val auth: Auth + val hints: Array + val session: MultiFactorSession + + fun resolveSignIn(assertion: MultiFactorAssertion): Promise +} + +external interface AuthProvider + +external interface AuthError + +external object EmailAuthProvider : AuthProvider { + fun credential(email: String, password: String): AuthCredential + fun credentialWithLink(email: String, emailLink: String): AuthCredential +} + +external object FacebookAuthProvider : AuthProvider { + fun credential(token: String): AuthCredential +} + +external object GithubAuthProvider : AuthProvider { + fun credential(token: String): AuthCredential +} + +external class GoogleAuthProvider : AuthProvider { + fun addScope(scope: String) + companion object { + fun credential(idToken: String?, accessToken: String?): AuthCredential + fun credentialFromResult(userCredential: UserCredential): OAuthCredential? + fun credentialFromError(error: AuthError): OAuthCredential? + } +} + +external class OAuthProvider(providerId: String) : AuthProvider { + val providerId: String + fun credential(optionsOrIdToken: Any?, accessToken: String?): AuthCredential + + fun addScope(scope: String) + fun setCustomParameters(customOAuthParameters: Map) +} + +external interface OAuthCredentialOptions { + val accessToken: String? + val idToken: String? + val rawNonce: String? +} + +external class PhoneAuthProvider(auth: Auth?) : AuthProvider { + companion object { + fun credential( + verificationId: String, + verificationCode: String + ): AuthCredential + } + + fun verifyPhoneNumber( + phoneInfoOptions: String, + applicationVerifier: ApplicationVerifier + ): Promise +} + +external interface ApplicationVerifier { + val type: String + fun verify(): Promise +} + +external object TwitterAuthProvider : AuthProvider { + fun credential(token: String, secret: String): AuthCredential +} diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt index 41cd7edbf..d4ad636a7 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt @@ -1,10 +1,14 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.auth.externals.MultiFactorUser import kotlinx.coroutines.await import kotlin.js.Date +import dev.gitlive.firebase.auth.externals.MultiFactorAssertion as JsMultiFactorAssertion +import dev.gitlive.firebase.auth.externals.MultiFactorInfo as JsMultiFactorInfo +import dev.gitlive.firebase.auth.externals.MultiFactorResolver as JsMultiFactorResolver +import dev.gitlive.firebase.auth.externals.MultiFactorSession as JsMultiFactorSession -actual class MultiFactor(val js: firebase.multifactor.MultiFactorUser) { +actual class MultiFactor(val js: MultiFactorUser) { actual val enrolledFactors: List get() = rethrow { js.enrolledFactors.map { MultiFactorInfo(it) } } actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) = @@ -17,7 +21,7 @@ actual class MultiFactor(val js: firebase.multifactor.MultiFactorUser) { rethrow { js.unenroll(factorUid).await() } } -actual class MultiFactorInfo(val js: firebase.multifactor.MultiFactorInfo) { +actual class MultiFactorInfo(val js: JsMultiFactorInfo) { actual val displayName: String? get() = rethrow { js.displayName } actual val enrollmentTime: Double @@ -28,17 +32,17 @@ actual class MultiFactorInfo(val js: firebase.multifactor.MultiFactorInfo) { get() = rethrow { js.uid } } -actual class MultiFactorAssertion(val js: firebase.multifactor.MultiFactorAssertion) { +actual class MultiFactorAssertion(val js: JsMultiFactorAssertion) { actual val factorId: String get() = rethrow { js.factorId } } -actual class MultiFactorSession(val js: firebase.multifactor.MultiFactorSession) +actual class MultiFactorSession(val js: JsMultiFactorSession) -actual class MultiFactorResolver(val js: firebase.multifactor.MultifactorResolver) { +actual class MultiFactorResolver(val js: JsMultiFactorResolver) { actual val auth: FirebaseAuth = rethrow { FirebaseAuth(js.auth) } actual val hints: List = rethrow { js.hints.map { MultiFactorInfo(it) } } actual val session: MultiFactorSession = rethrow { MultiFactorSession(js.session) } actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = rethrow { AuthResult(js.resolveSignIn(assertion.js).await()) } -} \ No newline at end of file +} 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 ec0070a9e..41570f0c2 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 @@ -1,11 +1,12 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.auth.externals.* import kotlinx.coroutines.await import kotlin.js.Date +import dev.gitlive.firebase.auth.externals.UserInfo as JsUserInfo import kotlin.js.json -actual class FirebaseUser internal constructor(val js: firebase.user.User) { +actual class FirebaseUser internal constructor(val js: User) { actual val uid: String get() = rethrow { js.uid } actual val displayName: String? @@ -23,7 +24,7 @@ actual class FirebaseUser internal constructor(val js: firebase.user.User) { actual val metaData: UserMetaData? get() = rethrow { UserMetaData(js.metadata) } actual val multiFactor: MultiFactor - get() = rethrow { MultiFactor(js.multiFactor) } + get() = rethrow { MultiFactor(multiFactor(js)) } actual val providerData: List get() = rethrow { js.providerData.map { UserInfo(it) } } actual val providerId: String @@ -31,30 +32,30 @@ actual class FirebaseUser internal constructor(val js: firebase.user.User) { actual suspend fun delete() = rethrow { js.delete().await() } actual suspend fun reload() = rethrow { js.reload().await() } actual suspend fun getIdToken(forceRefresh: Boolean): String? = rethrow { js.getIdToken(forceRefresh).await() } - actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = rethrow { AuthTokenResult(js.getIdTokenResult(forceRefresh).await()) } - actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = rethrow { AuthResult(js.linkWithCredential(credential.js).await()) } + actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = rethrow { AuthTokenResult(getIdTokenResult(js, forceRefresh).await()) } + actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = rethrow { AuthResult( linkWithCredential(js, credential.js).await()) } actual suspend fun reauthenticate(credential: AuthCredential) = rethrow { - js.reauthenticateWithCredential(credential.js).await() + reauthenticateWithCredential(js, credential.js).await() Unit } - actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = rethrow { AuthResult(js.reauthenticateWithCredential(credential.js).await()) } + actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = rethrow { AuthResult(reauthenticateWithCredential(js, credential.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() } - actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential) = rethrow { js.updatePhoneNumber(credential.js).await() } - actual suspend fun updateProfile(displayName: String?, photoUrl: String?) = rethrow { + actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) = rethrow { sendEmailVerification(js, actionCodeSettings?.toJson()).await() } + actual suspend fun unlink(provider: String): FirebaseUser? = rethrow { FirebaseUser(unlink(js, provider).await()) } + actual suspend fun updateEmail(email: String) = rethrow { updateEmail(js, email).await() } + actual suspend fun updatePassword(password: String) = rethrow { updatePassword(js, password).await() } + actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential) = rethrow { updatePhoneNumber(js, credential.js).await() } + actual suspend fun updateProfile(displayName: String?, photoUrl: String?): Unit = rethrow { val request = listOfNotNull( displayName.takeUnless { it === UNCHANGED }?.let { "displayName" to it }, photoUrl.takeUnless { it === UNCHANGED }?.let { "photoURL" to it } ) - js.updateProfile(json(*request.toTypedArray())).await() + updateProfile(js, json(*request.toTypedArray())).await() } - actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = rethrow { js.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.toJson()).await() } + actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = rethrow { verifyBeforeUpdateEmail(js, newEmail, actionCodeSettings?.toJson()).await() } } -actual class UserInfo(val js: firebase.user.UserInfo) { +actual class UserInfo(val js: JsUserInfo) { actual val displayName: String? get() = rethrow { js.displayName } actual val email: String? @@ -69,7 +70,7 @@ actual class UserInfo(val js: firebase.user.UserInfo) { get() = rethrow { js.uid } } -actual class UserMetaData(val js: firebase.user.UserMetadata) { +actual class UserMetaData(val js: UserMetadata) { actual val creationTime: Double? get() = rethrow {js.creationTime?.let { (Date(it).getTime() / 1000.0) } } actual val lastSignInTime: Double? diff --git a/firebase-auth/src/jsTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/jsTest/kotlin/dev/gitlive/firebase/auth/auth.kt index be90b4c54..58c87cded 100644 --- a/firebase-auth/src/jsTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/jsTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -9,6 +9,4 @@ actual val emulatorHost: String = "localhost" actual val context: Any = Unit -actual fun runTest(test: suspend () -> Unit) { - kotlinx.coroutines.test.runTest { test() } -} +actual fun runTest(test: suspend () -> Unit) = kotlinx.coroutines.test.runTest { test() } diff --git a/firebase-common/package.json b/firebase-common/package.json index 9c2e46c2d..ad254de8f 100644 --- a/firebase-common/package.json +++ b/firebase-common/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-common", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-common.js", "scripts": { diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/Unsubscribe.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/Unsubscribe.kt new file mode 100644 index 000000000..087d4f86b --- /dev/null +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/Unsubscribe.kt @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase + +typealias Unsubscribe = () -> Unit diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt deleted file mode 100644 index 3e2b39eb7..000000000 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt +++ /dev/null @@ -1,683 +0,0 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:JsModule("firebase/compat/app") - -package dev.gitlive.firebase - -import kotlinx.serialization.StringFormat -import org.khronos.webgl.Uint8Array -import org.w3c.files.Blob -import org.w3c.files.File -import kotlin.js.Json -import kotlin.js.Promise - -@JsName("default") -external object firebase { - - open class App { - val name: String - val options: Options - fun delete() - fun functions(region: String? = definedExternally): functions.Functions - fun database(url: String? = definedExternally): database.Database - fun firestore(): firestore.Firestore - fun storage(): storage.Storage - } - - interface Options { - val applicationId: String - val apiKey: String - val databaseUrl: String? - val gaTrackingId: String? - val storageBucket: String? - val projectId: String? - val messagingSenderId: String? - val authDomain: String? - } - - val apps : Array - fun app(name: String? = definedExternally): App - fun initializeApp(options: Any, name: String? = definedExternally) : App - - interface FirebaseError { - var code: String - var message: String - var name: String - } - - fun auth(app: App? = definedExternally): auth.Auth - - object auth { - open class Auth { - val currentUser: user.User? - var languageCode: String? - - fun useEmulator(url: String) - fun applyActionCode(code: String): Promise - fun checkActionCode(code: String): Promise - fun confirmPasswordReset(code: String, newPassword: String): Promise - fun createUserWithEmailAndPassword(email: String, password: String): Promise - fun fetchSignInMethodsForEmail(email: String): Promise> - fun sendPasswordResetEmail(email: String, actionCodeSettings: Any?): Promise - fun sendSignInLinkToEmail(email: String, actionCodeSettings: Any?): Promise - fun isSignInWithEmailLink(link: String): Boolean - fun signInWithEmailAndPassword(email: String, password: String): Promise - fun signInWithCustomToken(token: String): Promise - fun signInAnonymously(): Promise - fun signInWithCredential(authCredential: AuthCredential): Promise - fun signInWithPopup(provider: AuthProvider): Promise - fun signInWithRedirect(provider: AuthProvider): Promise - fun signInWithEmailLink(email: String, link: String): Promise - fun getRedirectResult(): Promise - fun signOut(): Promise - fun updateCurrentUser(user: user.User?): Promise - fun verifyPasswordResetCode(code: String): Promise - - fun onAuthStateChanged(nextOrObserver: (user.User?) -> Unit): () -> Unit - fun onIdTokenChanged(nextOrObserver: (user.User?) -> Unit): () -> Unit - } - - abstract class IdTokenResult { - val authTime: String - val claims: Json - val expirationTime: String - val issuedAtTime: String - val signInProvider: String? - val signInSecondFactor: String? - val token: String - } - - abstract class AuthResult { - val credential: AuthCredential? - val operationType: String? - val user: user.User? - } - - abstract class AuthCredential { - val providerId: String - val signInMethod: String - } - - abstract class ActionCodeInfo { - val operation: String - val data: ActionCodeData - } - - abstract class ActionCodeData { - val email: String? - val multiFactorInfo: multifactor.MultiFactorInfo? - val previousEmail: String? - } - - interface ActionCodeSettings { - val android: AndroidActionCodeSettings? - val dynamicLinkDomain: String? - val handleCodeInApp: Boolean? - val iOS: iOSActionCodeSettings? - val url: String - } - - interface AndroidActionCodeSettings { - val installApp: Boolean? - val minimumVersion: String? - val packageName: String - } - - interface iOSActionCodeSettings { - val bundleId: String? - } - - interface AuthProvider - - class EmailAuthProvider : AuthProvider { - companion object { - fun credential(email : String, password : String): AuthCredential - fun credentialWithLink(email: String, emailLink: String): AuthCredential - } - } - - class FacebookAuthProvider : AuthProvider { - companion object { - fun credential(token: String): AuthCredential - } - } - - class GithubAuthProvider : AuthProvider { - companion object { - fun credential(token: String): AuthCredential - } - } - - class GoogleAuthProvider : AuthProvider{ - companion object { - fun credential(idToken: String?, accessToken: String?): AuthCredential - } - } - - open class OAuthProvider(providerId: String) : AuthProvider { - val providerId: String - fun credential(optionsOrIdToken: Any?, accessToken: String?): AuthCredential - - fun addScope(scope: String) - fun setCustomParameters(customOAuthParameters: Map) - } - - interface OAuthCredentialOptions { - val accessToken: String? - val idToken: String? - val rawNonce: String? - } - - class PhoneAuthProvider(auth: Auth?) : AuthProvider { - companion object { - fun credential(verificationId: String, verificationCode: String): AuthCredential - } - fun verifyPhoneNumber(phoneInfoOptions: String, applicationVerifier: ApplicationVerifier): Promise - } - - abstract class ApplicationVerifier { - val type: String - fun verify(): Promise - } - - class TwitterAuthProvider : AuthProvider { - companion object { - fun credential (token: String, secret: String): AuthCredential - } - } - } - - fun User(a: Any,b: Any,c: Any): user.User - - object user { - abstract class User { - val uid: String - val displayName: String? - val email: String? - val emailVerified: Boolean - val metadata: UserMetadata - val multiFactor: multifactor.MultiFactorUser - val phoneNumber: String? - val photoURL: String? - val providerData: Array - val providerId: String - val refreshToken: String - val tenantId: String? - val isAnonymous: Boolean - - fun delete(): Promise - fun getIdToken(forceRefresh: Boolean?): Promise - fun getIdTokenResult(forceRefresh: Boolean?): Promise - fun linkWithCredential(credential: auth.AuthCredential): Promise - fun reauthenticateWithCredential(credential: auth.AuthCredential): Promise - fun reload(): 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: Json): Promise - fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: Any?): Promise - } - - abstract class UserMetadata { - val creationTime: String? - val lastSignInTime: String? - } - - abstract class UserInfo { - val displayName: String? - val email: String? - val phoneNumber: String? - val photoURL: String? - val providerId: String - val uid: String - } - - interface ProfileUpdateRequest { - val displayName: String? - val photoURL: String? - } - - } - - object multifactor { - abstract class MultiFactorUser { - val enrolledFactors: Array - - fun enroll(assertion: MultiFactorAssertion, displayName: String?): Promise - fun getSession(): Promise - fun unenroll(option: MultiFactorInfo): Promise - fun unenroll(option: String): Promise - } - - abstract class MultiFactorInfo { - val displayName: String? - val enrollmentTime: String - val factorId: String - val uid: String - } - - abstract class MultiFactorAssertion { - val factorId: String - } - - interface MultiFactorSession - - abstract class MultifactorResolver { - - val auth: auth.Auth - val hints: Array - val session: MultiFactorSession - - fun resolveSignIn(assertion: MultiFactorAssertion): Promise - } - } - - fun functions(app: App? = definedExternally): functions.Functions - - object functions { - class Functions { - fun httpsCallable(name: String, options: Json?): HttpsCallable - fun useEmulator(host: String, port: Int) - } - interface HttpsCallableResult { - val data: Any? - } - interface HttpsCallable { - } - - } - - fun database(app: App? = definedExternally): database.Database - - object database { - fun enableLogging(logger: Boolean?, persistent: Boolean? = definedExternally) - - open class Database { - fun ref(path: String? = definedExternally): Reference - fun useEmulator(host: String, port: Int) - } - open class ThenableReference : Reference - - - open class Query { - fun on(eventType: String?, callback: SnapshotCallback, cancelCallbackOrContext: (error: Error) -> Unit? = definedExternally, context: Any? = definedExternally): SnapshotCallback - fun off(eventType: String?, callback: SnapshotCallback?, context: Any? = definedExternally) - fun once(eventType: String, callback: SnapshotCallback, failureCallbackOrContext: (error: Error) -> Unit? = definedExternally, context: Any? = definedExternally): SnapshotCallback - fun orderByChild(path: String): Query - fun orderByKey(): Query - fun orderByValue(): Query - fun startAt(value: Any, key: String? = definedExternally): Query - fun endAt(value: Any, key: String? = definedExternally): Query - fun equalTo(value: Any, key: String? = definedExternally): Query - fun limitToFirst(limit: Int): Query - fun limitToLast(limit: Int): Query - } - - open class Reference: Query { - val key: String? - fun child(path: String): Reference - fun remove(): Promise - fun onDisconnect(): OnDisconnect - fun update(value: Any?): Promise - fun set(value: Any?): Promise - fun push(): ThenableReference - fun transaction( - transactionUpdate: (currentData: T) -> T, - onComplete: (error: Error?, committed: Boolean, snapshot: DataSnapshot?) -> Unit, - applyLocally: Boolean? - ): Promise - } - - open class DataSnapshot { - val key: String? - val ref: Reference - fun `val`(): Any? - fun exists(): Boolean - fun hasChildren(): Boolean - fun forEach(action: (a: DataSnapshot) -> Boolean): Boolean - fun numChildren(): Int - fun child(path: String): DataSnapshot - } - - open class OnDisconnect { - fun update(value: Any?): Promise - fun remove(): Promise - fun cancel(): Promise - fun set(value: Any?): Promise - } - - object ServerValue { - val TIMESTAMP: Any - fun increment (delta: Double): Any - } - } - - fun firestore(): firestore.Firestore - - object firestore { - fun setLogLevel(level: String) - - open class PersistenceSettings { - var experimentalTabSynchronization: Boolean - } - - open class Firestore { - fun runTransaction(func: (transaction: Transaction) -> Promise): Promise - fun batch(): WriteBatch - fun collection(collectionPath: String): CollectionReference - fun collectionGroup(collectionId: String): Query - fun doc(documentPath: String): DocumentReference - fun settings(settings: Json) - fun enablePersistence(): Promise - fun clearPersistence(): Promise - fun useEmulator(host: String, port: Int) - fun disableNetwork(): Promise - fun enableNetwork(): Promise - } - - open class Timestamp(seconds: Double, nanoseconds: Double) { - companion object { - fun now(): Timestamp - } - - val seconds: Double - val nanoseconds: Double - fun toMillis(): Double - - fun isEqual(other: Timestamp): Boolean - } - - open class Query { - fun get(options: Any? = definedExternally): Promise - fun where(field: String, opStr: String, value: Any?): Query - fun where(field: FieldPath, opStr: String, value: Any?): Query - fun onSnapshot(next: (snapshot: QuerySnapshot) -> Unit, error: (error: Error) -> Unit): () -> Unit - fun onSnapshot(options: Json, next: (snapshot: QuerySnapshot) -> Unit, error: (error: Error) -> Unit): () -> Unit - fun limit(limit: Double): Query - fun orderBy(field: String, direction: Any): Query - fun orderBy(field: FieldPath, direction: Any): Query - fun startAfter(document: DocumentSnapshot): Query - fun startAfter(vararg fieldValues: Any): Query - fun startAt(document: DocumentSnapshot): Query - fun startAt(vararg fieldValues: Any): Query - fun endBefore(document: DocumentSnapshot): Query - fun endBefore(vararg fieldValues: Any): Query - fun endAt(document: DocumentSnapshot): Query - fun endAt(vararg fieldValues: Any): Query - } - - open class CollectionReference : Query { - val path: String - val parent: DocumentReference? - fun doc(path: String = definedExternally): DocumentReference - fun add(data: Any): Promise - } - - open class QuerySnapshot { - val docs: Array - fun docChanges(): Array - val empty: Boolean - val metadata: SnapshotMetadata - } - - open class DocumentChange { - val doc: DocumentSnapshot - val newIndex: Int - val oldIndex: Int - val type: String - } - - open class DocumentSnapshot { - val id: String - val ref: DocumentReference - val exists: Boolean - val metadata: SnapshotMetadata - fun data(options: Any? = definedExternally): Any? - fun get(fieldPath: String, options: Any? = definedExternally): Any? - fun get(fieldPath: FieldPath, options: Any? = definedExternally): Any? - } - - open class SnapshotMetadata { - val hasPendingWrites: Boolean - val fromCache: Boolean - } - - open class DocumentReference { - val id: String - val path: String - val parent: CollectionReference - - fun collection(path: String): CollectionReference - fun get(options: Any? = definedExternally): Promise - fun set(data: Any, options: Any? = definedExternally): Promise - fun update(data: Any): Promise - fun update(field: String, value: Any?, vararg moreFieldsAndValues: Any?): Promise - fun update(field: FieldPath, value: Any?, vararg moreFieldsAndValues: Any?): Promise - fun delete(): Promise - fun onSnapshot(options: Json, next: (snapshot: DocumentSnapshot) -> Unit, error: (error: Error) -> Unit): ()->Unit - fun isEqual(other: DocumentReference): Boolean - } - - open class WriteBatch { - fun commit(): Promise - fun delete(documentReference: DocumentReference): WriteBatch - fun set(documentReference: DocumentReference, data: Any, options: Any? = definedExternally): WriteBatch - fun update(documentReference: DocumentReference, data: Any): WriteBatch - fun update(documentReference: DocumentReference, field: String, value: Any?, vararg moreFieldsAndValues: Any?): WriteBatch - fun update(documentReference: DocumentReference, field: FieldPath, value: Any?, vararg moreFieldsAndValues: Any?): WriteBatch - } - - open class Transaction { - fun get(documentReference: DocumentReference): Promise - fun set(documentReference: DocumentReference, data: Any, options: Any? = definedExternally): Transaction - fun update(documentReference: DocumentReference, data: Any): Transaction - fun update(documentReference: DocumentReference, field: String, value: Any?, vararg moreFieldsAndValues: Any?): Transaction - fun update(documentReference: DocumentReference, field: FieldPath, value: Any?, vararg moreFieldsAndValues: Any?): Transaction - fun delete(documentReference: DocumentReference): Transaction - } - - open class FieldPath(vararg fieldNames: String) { - companion object { - val documentId: FieldPath - } - - fun isEqual(other: FieldPath): Boolean - } - - abstract class FieldValue { - companion object { - fun serverTimestamp(): FieldValue - fun delete(): FieldValue - fun increment(value: Int): FieldValue - fun arrayRemove(vararg elements: Any): FieldValue - fun arrayUnion(vararg elements: Any): FieldValue - } - - fun isEqual(other: FieldValue): Boolean - } - - open class GeoPoint(latitude: Double, longitude: Double) { - val latitude: Double - val longitude: Double - - fun isEqual(other: GeoPoint): Boolean - } - } - - fun storage(): storage.Storage - - object storage { - - open class Storage { - val maxOperationRetryTime: Double - val maxUploadRetryTime: Double - - fun setMaxOperationRetryTime(time: Double): Unit - fun setMaxUploadRetryTime(time: Double): Unit - fun useEmulator(host: String, port: Int) - fun ref(): Reference - } - - open class Reference { - val bucket: String - val fullPath: String - val name: String - val parent: Reference? - val root: Reference - val storage: Storage - - fun child(path: String): Reference - fun delete(): Promise - fun getDownloadURL(): Promise - fun getMetadata(): Promise - fun list(options: ListOptions? = definedExternally): Promise - fun listAll(): Promise - fun put(data: Blob, metadata: UploadMetadata? = definedExternally): UploadTask - fun put(data: Uint8Array, metadata: UploadMetadata? = definedExternally): UploadTask - fun put(data: File, metadata: UploadMetadata? = definedExternally): UploadTask - fun putString(data: String, format: StringFormat? = definedExternally, metadata: UploadMetadata? = definedExternally): UploadTask - fun updateMetadata(metadata: SettableMetadata): Promise - } - - open class FullMetadata { - val bucket: String - val fullPath: String - val generation: String - val metageneration: String - val name: String - val size: Number - val timeCreated: String - val updated: String - val customMetadata: Json - } - - open class ListOptions { - val maxResults: Number - val pageToken: String - } - - open class ListResult { - val items: Array - val nextPageToken: String - val prefixes: Array - } - - open class SettableMetadata { - var cacheControl: String? - var contentDisposition: String? - var contentEncoding: String? - var contentLanguage: String? - var contentType: String? - var customMetadata: Json? - } - - open class UploadMetadata { - var cacheControl: String? - var contentDisposition: String? - var contentEncoding: String? - var contentLanguage: String? - var contentType: String? - var customMetadata: Json? - } - - open class UploadTask { - val snapshot: UploadTaskSnapshot - - fun cancel(): Boolean - fun pause(): Boolean - fun resume(): Boolean - fun on(event: String, next: (snapshot: UploadTaskSnapshot) -> Unit, error: (a: FirebaseStorageError) -> Unit, complete: () -> Unit): () -> Unit - } - - open class UploadTaskSnapshot { - val bytesTransferred: Number - val metadata: FullMetadata - val ref: Reference - val state: String - val task: UploadTask - val totalBytes: Number - } - - open class FirebaseStorageError { - val code: String - val message: String - val name: String - val stack: String? - } - - } - - fun remoteConfig(app: App? = definedExternally): remoteConfig.RemoteConfig - - object remoteConfig { - interface RemoteConfig { - var defaultConfig: Any - var fetchTimeMillis: Long - var lastFetchStatus: String - val settings: Settings - fun activate(): Promise - fun ensureInitialized(): Promise - fun fetch(): Promise - fun fetchAndActivate(): Promise - fun getAll(): Json - fun getBoolean(key: String): Boolean - fun getNumber(key: String): Number - fun getString(key: String): String? - fun getValue(key: String): Value - } - - interface Settings { - var fetchTimeoutMillis: Number - var minimumFetchIntervalMillis: Number - } - - interface Value { - fun asBoolean(): Boolean - fun asNumber(): Number - fun asString(): String? - fun getSource(): String - } - } - - fun installations(app: App? = definedExternally): installations.Installations - - object installations { - interface Installations { - fun delete(): Promise - fun getId(): Promise - fun getToken(forceRefresh: Boolean): Promise - } - } - - fun performance(app: App? = definedExternally): performance - - object performance { - - var dataCollectionEnabled: Boolean - var instrumentationEnabled: Boolean - - fun trace( - name: String - ): PerformanceTrace - - open class Performance { - - } - - open class PerformanceTrace { - fun start() - fun stop() - - fun getAttribute(attr: String): String? - fun getMetric(metricName: String): Number - fun incrementMetric(metricName: String, num: Number) - fun putAttribute(attr: String, value: String) - fun putMetric(metricName: String, num: Number) - fun removeAttribute(attr: String) - } - } -} diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals2.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals2.kt deleted file mode 100644 index 0f5c2b6c4..000000000 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals2.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.gitlive.firebase - -import kotlin.js.Promise - -@JsModule("firebase/compat/auth") -@JsName("default") -external object auth - -@JsModule("firebase/compat/database") -@JsName("default") -external object database - -@JsModule("firebase/compat/firestore") -@JsName("default") -external object firestore - -@JsModule("firebase/compat/functions") -@JsName("default") -external object functions - -external object installations - -@JsModule("firebase/compat/remote-config") -@JsName("default") -external object remoteConfig - -@JsModule("firebase/compat/performance") -@JsName("default") -external object performance - -@JsModule("firebase/compat/storage") -@JsName("default") -external object storage - -typealias SnapshotCallback = (data: firebase.database.DataSnapshot, b: String?) -> Unit - -operator fun firebase.functions.HttpsCallable.invoke() = asDynamic()() as Promise -operator fun firebase.functions.HttpsCallable.invoke(data: Any?) = asDynamic()(data) as Promise - diff --git a/firebase-config/package.json b/firebase-config/package.json index ab01b3209..195116866 100644 --- a/firebase-config/package.json +++ b/firebase-config/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-config", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-config.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-config/src/commonTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt b/firebase-config/src/commonTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt index 8c1751c3d..6ae20572a 100644 --- a/firebase-config/src/commonTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt +++ b/firebase-config/src/commonTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt @@ -8,6 +8,7 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize +import kotlinx.coroutines.test.TestResult import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Ignore @@ -15,7 +16,7 @@ import kotlin.test.Test import kotlin.test.assertEquals expect val context: Any -expect fun runTest(test: suspend () -> Unit) +expect fun runTest(test: suspend () -> Unit): TestResult class FirebaseRemoteConfigTest { private val defaults = arrayOf( diff --git a/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt b/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt index b1b19825f..bba268c12 100644 --- a/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt +++ b/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt @@ -3,44 +3,40 @@ package dev.gitlive.firebase.remoteconfig import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.remoteconfig.externals.* import kotlinx.coroutines.await import kotlin.js.json actual val Firebase.remoteConfig: FirebaseRemoteConfig - get() = rethrow { - dev.gitlive.firebase.remoteConfig - FirebaseRemoteConfig(firebase.remoteConfig()) - } + get() = rethrow { FirebaseRemoteConfig(getRemoteConfig()) } actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig = rethrow { - dev.gitlive.firebase.remoteConfig - FirebaseRemoteConfig(firebase.remoteConfig(app.js)) + FirebaseRemoteConfig(getRemoteConfig(app.js)) } -actual class FirebaseRemoteConfig internal constructor(val js: firebase.remoteConfig.RemoteConfig) { +actual class FirebaseRemoteConfig internal constructor(val js: RemoteConfig) { actual val all: Map get() = rethrow { getAllKeys().map { Pair(it, getValue(it)) }.toMap() } actual val info: FirebaseRemoteConfigInfo get() = rethrow { FirebaseRemoteConfigInfo( - configSettings = js.settings.toSettings(), + configSettings = js.settings.toFirebaseRemoteConfigSettings(), fetchTimeMillis = js.fetchTimeMillis, lastFetchStatus = js.lastFetchStatus.toFetchStatus() ) } - actual suspend fun activate(): Boolean = rethrow { js.activate().await() } - actual suspend fun ensureInitialized(): Unit = rethrow { js.activate().await() } + actual suspend fun activate(): Boolean = rethrow { activate(js).await() } + actual suspend fun ensureInitialized(): Unit = rethrow { ensureInitialized(js).await() } actual suspend fun fetch(minimumFetchIntervalInSeconds: Long?): Unit = - rethrow { js.fetch().await() } + rethrow { fetchConfig(js).await() } - actual suspend fun fetchAndActivate(): Boolean = rethrow { js.fetchAndActivate().await() } + actual suspend fun fetchAndActivate(): Boolean = rethrow { fetchAndActivate(js).await() } actual fun getValue(key: String): FirebaseRemoteConfigValue = rethrow { - FirebaseRemoteConfigValue(js.getValue(key)) + FirebaseRemoteConfigValue(getValue(js, key)) } actual fun getKeysByPrefix(prefix: String): Set { @@ -49,7 +45,7 @@ actual class FirebaseRemoteConfig internal constructor(val js: firebase.remoteCo private fun getAllKeys(): Set { val objectKeys = js("Object.keys") - return objectKeys(js.getAll()).unsafeCast>().toSet() + return objectKeys(getAll(js)).unsafeCast>().toSet() } actual suspend fun reset() { @@ -68,7 +64,7 @@ actual class FirebaseRemoteConfig internal constructor(val js: firebase.remoteCo js.defaultConfig = json(*defaults) } - private fun firebase.remoteConfig.Settings.toSettings(): FirebaseRemoteConfigSettings { + private fun Settings.toFirebaseRemoteConfigSettings(): FirebaseRemoteConfigSettings { return FirebaseRemoteConfigSettings( fetchTimeoutInSeconds = fetchTimeoutMillis.toLong() / 1000, minimumFetchIntervalInSeconds = minimumFetchIntervalMillis.toLong() / 1000 diff --git a/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt b/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt index 7465ff24d..2891f926a 100644 --- a/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt +++ b/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt @@ -1,8 +1,8 @@ package dev.gitlive.firebase.remoteconfig -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.remoteconfig.externals.Value -actual class FirebaseRemoteConfigValue(val js: firebase.remoteConfig.Value) { +actual class FirebaseRemoteConfigValue(val js: Value) { actual fun asBoolean(): Boolean = rethrow { js.asBoolean() } actual fun asByteArray(): ByteArray = rethrow { js.asString()?.encodeToByteArray() ?: byteArrayOf() } actual fun asDouble(): Double = rethrow { js.asNumber().toDouble() } diff --git a/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/externals/remoteconfig.kt b/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/externals/remoteconfig.kt new file mode 100644 index 000000000..b2a42dc84 --- /dev/null +++ b/firebase-config/src/jsMain/kotlin/dev/gitlive/firebase/remoteconfig/externals/remoteconfig.kt @@ -0,0 +1,47 @@ +@file:JsModule("firebase/remote-config") +@file:JsNonModule + +package dev.gitlive.firebase.remoteconfig.externals + +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Json +import kotlin.js.Promise + +external fun activate(remoteConfig: RemoteConfig): Promise + +external fun ensureInitialized(remoteConfig: RemoteConfig): Promise + +external fun fetchAndActivate(remoteConfig: RemoteConfig): Promise + +external fun fetchConfig(remoteConfig: RemoteConfig): Promise + +external fun getAll(remoteConfig: RemoteConfig): Json + +external fun getBoolean(remoteConfig: RemoteConfig, key: String): Boolean + +external fun getNumber(remoteConfig: RemoteConfig, key: String): Number + +external fun getRemoteConfig(app: FirebaseApp? = definedExternally): RemoteConfig + +external fun getString(remoteConfig: RemoteConfig, key: String): String? + +external fun getValue(remoteConfig: RemoteConfig, key: String): Value + +external interface RemoteConfig { + var defaultConfig: Any + var fetchTimeMillis: Long + var lastFetchStatus: String + val settings: Settings +} + +external interface Settings { + var fetchTimeoutMillis: Number + var minimumFetchIntervalMillis: Number +} + +external interface Value { + fun asBoolean(): Boolean + fun asNumber(): Number + fun asString(): String? + fun getSource(): String +} diff --git a/firebase-config/src/jsTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt b/firebase-config/src/jsTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt index 74650729c..585e73bc4 100644 --- a/firebase-config/src/jsTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt +++ b/firebase-config/src/jsTest/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt @@ -3,6 +3,4 @@ package dev.gitlive.firebase.remoteconfig actual val context: Any = Unit -actual fun runTest(test: suspend () -> Unit) { - kotlinx.coroutines.test.runTest { test() } -} +actual fun runTest(test: suspend () -> Unit) = kotlinx.coroutines.test.runTest { test() } diff --git a/firebase-crashlytics/package.json b/firebase-crashlytics/package.json index 457b15af2..1a32ade20 100644 --- a/firebase-crashlytics/package.json +++ b/firebase-crashlytics/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-crashlytics", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-crashlytics.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-crashlytics/src/commonTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/commonTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index 92ac6d480..c1e46c6ec 100644 --- a/firebase-crashlytics/src/commonTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/commonTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -9,14 +9,14 @@ import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.TestResult import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertFalse -import kotlin.test.assertNotNull expect val emulatorHost: String expect val context: Any -expect fun runTest(test: suspend CoroutineScope.() -> Unit) +expect fun runTest(test: suspend CoroutineScope.() -> Unit): TestResult class FirebaseCrashlyticsTest { diff --git a/firebase-database/build.gradle.kts b/firebase-database/build.gradle.kts index 9a10f96d1..e3640473c 100644 --- a/firebase-database/build.gradle.kts +++ b/firebase-database/build.gradle.kts @@ -21,6 +21,7 @@ android { defaultConfig { minSdk = property("minSdkVersion") as Int targetSdk = property("targetSdkVersion") as Int + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } sourceSets { getByName("main") { @@ -82,15 +83,15 @@ kotlin { useCommonJs() nodejs { testTask { - useMocha { - timeout = "5s" + useKarma { + useChromeHeadless() } } } browser { testTask { - useMocha { - timeout = "5s" + useKarma { + useChromeHeadless() } } } @@ -159,4 +160,3 @@ signing { useInMemoryPgpKeys(signingKey, signingPassword) sign(publishing.publications) } - diff --git a/firebase-database/karma.config.d/karma.conf.js b/firebase-database/karma.config.d/karma.conf.js new file mode 100644 index 000000000..e2301eddd --- /dev/null +++ b/firebase-database/karma.config.d/karma.conf.js @@ -0,0 +1,8 @@ +// Some tests are fluky in GitHub Actions, so we increase the timeout. +config.set({ + client: { + mocha: { + timeout: 5000 + } + }, +}); diff --git a/firebase-database/package.json b/firebase-database/package.json index 5041fbb15..966515c71 100644 --- a/firebase-database/package.json +++ b/firebase-database/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-database", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-database.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-database/src/androidAndroidTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidAndroidTest/kotlin/dev/gitlive/firebase/database/database.kt index 91653371b..0745baa19 100644 --- a/firebase-database/src/androidAndroidTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidAndroidTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -6,6 +6,7 @@ package dev.gitlive.firebase.database import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.test.TestResult actual val emulatorHost: String = "10.0.2.2" diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt index 787280153..e586ccf99 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -270,4 +270,3 @@ actual class OnDisconnect internal constructor( } actual typealias DatabaseException = com.google.firebase.database.DatabaseException - diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt index 64dcfc3b4..d5bbf6aee 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -98,4 +98,3 @@ expect class OnDisconnect { suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean = true) suspend fun updateChildren(update: Map, encodeDefaults: Boolean = true) } - diff --git a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt index 4b747df92..90ad13212 100644 --- a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -2,12 +2,13 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.* import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.TestResult import kotlinx.serialization.* import kotlin.test.* expect val emulatorHost: String expect val context: Any -expect fun runTest(test: suspend () -> Unit) +expect fun runTest(test: suspend () -> Unit): TestResult class FirebaseDatabaseTest { diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt index 5625a6dac..cfb14b69b 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt @@ -1,9 +1,8 @@ package dev.gitlive.firebase.database -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.database.externals.serverTimestamp import kotlinx.serialization.Serializable - -private typealias NativeServerValue = firebase.database.ServerValue +import dev.gitlive.firebase.database.externals.increment as jsIncrement /** Represents a Firebase ServerValue. */ @Serializable(with = ServerValueSerializer::class) @@ -11,8 +10,8 @@ actual class ServerValue internal actual constructor( internal actual val nativeValue: Any ){ actual companion object { - actual val TIMESTAMP: ServerValue get() = ServerValue(NativeServerValue.TIMESTAMP) - actual fun increment(delta: Double): ServerValue = ServerValue(NativeServerValue.increment(delta)) + actual val TIMESTAMP: ServerValue get() = ServerValue(serverTimestamp()) + actual fun increment(delta: Double): ServerValue = ServerValue(jsIncrement(delta)) } override fun equals(other: Any?): Boolean = diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt index de6caef76..d4d47b6be 100644 --- a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -5,6 +5,8 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.* +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.database.externals.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow @@ -15,142 +17,153 @@ import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy import kotlin.js.Promise +import kotlin.js.json +import dev.gitlive.firebase.database.externals.DataSnapshot as JsDataSnapshot +import dev.gitlive.firebase.database.externals.DatabaseReference as JsDatabaseReference +import dev.gitlive.firebase.database.externals.OnDisconnect as JsOnDisconnect +import dev.gitlive.firebase.database.externals.Query as JsQuery +import dev.gitlive.firebase.database.externals.endAt as jsEndAt +import dev.gitlive.firebase.database.externals.equalTo as jsEqualTo +import dev.gitlive.firebase.database.externals.limitToFirst as jsLimitToFirst +import dev.gitlive.firebase.database.externals.limitToLast as jsLimitToLast +import dev.gitlive.firebase.database.externals.orderByChild as jsOrderByChild +import dev.gitlive.firebase.database.externals.orderByKey as jsOrderByKey +import dev.gitlive.firebase.database.externals.orderByValue as jsOrderByValue +import dev.gitlive.firebase.database.externals.startAt as jsStartAt actual val Firebase.database - get() = rethrow { dev.gitlive.firebase.database; FirebaseDatabase(firebase.database()) } + get() = rethrow { FirebaseDatabase(getDatabase()) } actual fun Firebase.database(app: FirebaseApp) = - rethrow { dev.gitlive.firebase.database; FirebaseDatabase(firebase.database(app.js)) } + rethrow { FirebaseDatabase(getDatabase(app = app.js)) } actual fun Firebase.database(url: String) = - rethrow { dev.gitlive.firebase.database; FirebaseDatabase(firebase.app().database(url)) } + rethrow { FirebaseDatabase(getDatabase(url = url)) } actual fun Firebase.database(app: FirebaseApp, url: String) = - rethrow { dev.gitlive.firebase.database; FirebaseDatabase(app.js.database(url)) } + rethrow { FirebaseDatabase(getDatabase(app = app.js, url = url)) } -actual class FirebaseDatabase internal constructor(val js: firebase.database.Database) { - actual fun reference(path: String) = rethrow { DatabaseReference(js.ref(path), js) } - actual fun reference() = rethrow { DatabaseReference(js.ref(), js) } +actual class FirebaseDatabase internal constructor(val js: Database) { + actual fun reference(path: String) = rethrow { DatabaseReference(ref(js, path), js) } + actual fun reference() = rethrow { DatabaseReference(ref(js), js) } actual fun setPersistenceEnabled(enabled: Boolean) {} - actual fun setLoggingEnabled(enabled: Boolean) = rethrow { firebase.database.enableLogging(enabled) } - actual fun useEmulator(host: String, port: Int) = rethrow { js.useEmulator(host, port) } + actual fun setLoggingEnabled(enabled: Boolean) = rethrow { enableLogging(enabled) } + actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) } } actual open class Query internal constructor( - open val js: firebase.database.Query, - val database: firebase.database.Database + open val js: JsQuery, + val database: Database ) { - actual fun orderByKey() = Query(js.orderByKey(), database) - actual fun orderByValue() = Query(js.orderByValue(), database) - actual fun orderByChild(path: String) = Query(js.orderByChild(path), database) - - actual val valueEvents get() = callbackFlow { - val listener = rethrow { - js.on( - "value", - { it, _ -> trySend(DataSnapshot(it, database)) }, - { close(DatabaseException(it)).run { Unit } } - ) + actual fun orderByKey() = Query(query(js, jsOrderByKey()), database) + actual fun orderByValue() = Query(query(js, jsOrderByValue()), database) + actual fun orderByChild(path: String) = Query(query(js, jsOrderByChild(path)), database) + + actual val valueEvents + get() = callbackFlow { + val unsubscribe = rethrow { + onValue( + query = js, + callback = { trySend(DataSnapshot(it, database)) }, + cancelCallback = { close(DatabaseException(it)).run { } } + ) + } + awaitClose { rethrow { unsubscribe() } } } - awaitClose { rethrow { js.off("value", listener) } } - } actual fun childEvents(vararg types: ChildEvent.Type) = callbackFlow { - val listeners = rethrow { + val unsubscribes = rethrow { types.map { type -> - "child_${type.name.lowercase()}".let { eventType -> - eventType to js.on( - eventType, - { snapshot, previousChildName -> - trySend( - ChildEvent( + val callback: ChangeSnapshotCallback = { snapshot, previousChildName -> + trySend( + ChildEvent( DataSnapshot(snapshot, database), - type, - previousChildName - ) - ) - }, - { close(DatabaseException(it)).run { Unit } } + type, + previousChildName + ) ) } + + val cancelCallback: CancelCallback = { + close(DatabaseException(it)).run { } + } + + when (type) { + ChildEvent.Type.ADDED -> onChildAdded(js, callback, cancelCallback) + ChildEvent.Type.CHANGED -> onChildChanged(js, callback, cancelCallback) + ChildEvent.Type.MOVED -> onChildMoved(js, callback, cancelCallback) + ChildEvent.Type.REMOVED -> onChildRemoved(js, callback, cancelCallback) + } } } - awaitClose { rethrow { listeners.forEach { (eventType, listener) -> js.off(eventType, listener) } } } + awaitClose { rethrow { unsubscribes.forEach { it.invoke() } } } } - actual fun startAt(value: String, key: String?) = Query(js.startAt(value, key ?: undefined), database) + actual fun startAt(value: String, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database) - actual fun startAt(value: Double, key: String?) = Query(js.startAt(value, key ?: undefined), database) + actual fun startAt(value: Double, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database) - actual fun startAt(value: Boolean, key: String?) = Query(js.startAt(value, key ?: undefined), database) + actual fun startAt(value: Boolean, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database) - actual fun endAt(value: String, key: String?) = Query(js.endAt(value, key ?: undefined), database) + actual fun endAt(value: String, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database) - actual fun endAt(value: Double, key: String?) = Query(js.endAt(value, key ?: undefined), database) + actual fun endAt(value: Double, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database) - actual fun endAt(value: Boolean, key: String?) = Query(js.endAt(value, key ?: undefined), database) + actual fun endAt(value: Boolean, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database) - actual fun limitToFirst(limit: Int) = Query(js.limitToFirst(limit), database) + actual fun limitToFirst(limit: Int) = Query(query(js, jsLimitToFirst(limit)), database) - actual fun limitToLast(limit: Int) = Query(js.limitToLast(limit), database) + actual fun limitToLast(limit: Int) = Query(query(js, jsLimitToLast(limit)), database) - actual fun equalTo(value: String, key: String?) = Query(js.equalTo(value, key ?: undefined), database) + actual fun equalTo(value: String, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database) - actual fun equalTo(value: Double, key: String?) = Query(js.equalTo(value, key ?: undefined), database) + actual fun equalTo(value: Double, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database) - actual fun equalTo(value: Boolean, key: String?) = Query(js.equalTo(value, key ?: undefined), database) + actual fun equalTo(value: Boolean, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database) override fun toString() = js.toString() } actual class DatabaseReference internal constructor( - override val js: firebase.database.Reference, - database: firebase.database.Database -): Query(js, database) { + override val js: JsDatabaseReference, + database: Database +) : Query(js, database) { actual val key get() = rethrow { js.key } - actual fun push() = rethrow { DatabaseReference(js.push(), database) } - actual fun child(path: String) = rethrow { DatabaseReference(js.child(path), database) } + actual fun push() = rethrow { DatabaseReference(push(js), database) } + actual fun child(path: String) = rethrow { DatabaseReference(child(js, path), database) } - actual fun onDisconnect() = rethrow { OnDisconnect(js.onDisconnect(), database) } + actual fun onDisconnect() = rethrow { OnDisconnect(onDisconnect(js), database) } actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = - rethrow { js.update(encode(update, encodeDefaults)).awaitWhileOnline(database) } + rethrow { update(js, encode(update, encodeDefaults) ?: json()).awaitWhileOnline(database) } - actual suspend fun removeValue() = rethrow { js.remove().awaitWhileOnline(database) } + actual suspend fun removeValue() = rethrow { remove(js).awaitWhileOnline(database) } actual suspend inline fun setValue(value: T?, encodeDefaults: Boolean) = rethrow { - js.set(encode(value, encodeDefaults)).awaitWhileOnline(database) + set(js, encode(value, encodeDefaults)).awaitWhileOnline(database) } actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) = - rethrow { js.set(encode(strategy, value, encodeDefaults)).awaitWhileOnline(database) } + rethrow { set(js, encode(strategy, value, encodeDefaults)).awaitWhileOnline(database) } - actual suspend fun runTransaction(strategy: KSerializer, transactionUpdate: (currentData: T) -> T): DataSnapshot { - val deferred = CompletableDeferred() - js.transaction( - transactionUpdate, - { error, _, snapshot -> - if (error != null) { - deferred.completeExceptionally(error) - } else { - deferred.complete(DataSnapshot(snapshot!!, database)) - } - }, - applyLocally = false - ) - return deferred.await() - } + actual suspend fun runTransaction(strategy: KSerializer, transactionUpdate: (currentData: T) -> T): DataSnapshot = + rethrow { + val result = runTransaction( + js, + transactionUpdate, + ).awaitWhileOnline(database) + DataSnapshot(result.snapshot, database) + } } actual class DataSnapshot internal constructor( - val js: firebase.database.DataSnapshot, - val database: firebase.database.Database + val js: JsDataSnapshot, + val database: Database ) { - actual val value get(): Any? { check(!hasChildren) { "DataSnapshot.value can only be used for primitive values (snapshots without children)" } return js.`val`() @@ -167,8 +180,8 @@ actual class DataSnapshot internal constructor( actual fun child(path: String) = DataSnapshot(js.child(path), database) actual val hasChildren get() = js.hasChildren() actual val children: Iterable = rethrow { - ArrayList(js.numChildren()).also { - js.forEach { snapshot -> it.add(DataSnapshot(snapshot, database)) } + ArrayList(js.size).also { + js.forEach { snapshot -> it.add(DataSnapshot(snapshot, database)); false /* don't cancel enumeration */ } } } actual val ref: DatabaseReference @@ -177,15 +190,15 @@ actual class DataSnapshot internal constructor( } actual class OnDisconnect internal constructor( - val js: firebase.database.OnDisconnect, - val database: firebase.database.Database + val js: JsOnDisconnect, + val database: Database ) { actual suspend fun removeValue() = rethrow { js.remove().awaitWhileOnline(database) } actual suspend fun cancel() = rethrow { js.cancel().awaitWhileOnline(database) } actual suspend fun updateChildren(update: Map, encodeDefaults: Boolean) = - rethrow { js.update(encode(update, encodeDefaults)).awaitWhileOnline(database) } + rethrow { js.update(encode(update, encodeDefaults) ?: json()).awaitWhileOnline(database) } actual suspend inline fun setValue(value: T, encodeDefaults: Boolean) = rethrow { js.set(encode(value, encodeDefaults)).awaitWhileOnline(database) } @@ -205,12 +218,12 @@ inline fun rethrow(function: () -> R): R { return function() } catch (e: Exception) { throw e - } catch(e: dynamic) { + } catch (e: dynamic) { throw DatabaseException(e) } } -suspend fun Promise.awaitWhileOnline(database: firebase.database.Database): T = coroutineScope { +suspend fun Promise.awaitWhileOnline(database: Database): T = coroutineScope { val notConnected = FirebaseDatabase(database) .reference(".info/connected") diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/callbacks.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/callbacks.kt new file mode 100644 index 000000000..86a592432 --- /dev/null +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/callbacks.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase.database.externals + +typealias ChangeSnapshotCallback = (data: DataSnapshot, previousChildName: String?) -> Unit +typealias ValueSnapshotCallback = (data: DataSnapshot) -> Unit +typealias CancelCallback = (error: Throwable) -> Unit diff --git a/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/database.kt b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/database.kt new file mode 100644 index 000000000..52f71422b --- /dev/null +++ b/firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/externals/database.kt @@ -0,0 +1,147 @@ +@file:JsModule("firebase/database") +@file:JsNonModule + +package dev.gitlive.firebase.database.externals + +import dev.gitlive.firebase.* +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Promise + +external fun child(parent: DatabaseReference, path: String): DatabaseReference + +external fun connectDatabaseEmulator( + db: Database, + host: String, + port: Int, + options: Any? = definedExternally +) + +external fun enableLogging(enabled: Boolean?, persistent: Boolean? = definedExternally) + +external fun endAt(value: Any?, key: String? = definedExternally): QueryConstraint + +external fun endBefore(value: Any?, key: String? = definedExternally): QueryConstraint + +external fun equalTo(value: Any?, key: String? = definedExternally): QueryConstraint + +external fun get(query: Query): Promise + +external fun getDatabase( + app: FirebaseApp? = definedExternally, + url: String? = definedExternally +): Database + +external fun increment(delta: Double): Any + +external fun limitToFirst(limit: Int): QueryConstraint + +external fun limitToLast(limit: Int): QueryConstraint + +external fun off(query: Query, eventType: String?, callback: Any?) + +external fun goOffline(db: Database) + +external fun goOnline(db: Database) + +external fun onChildAdded( + query: Query, + callback: ChangeSnapshotCallback, + cancelCallback: CancelCallback? = definedExternally, +): Unsubscribe + +external fun onChildChanged( + query: Query, + callback: ChangeSnapshotCallback, + cancelCallback: CancelCallback? = definedExternally, +): Unsubscribe + +external fun onChildMoved( + query: Query, + callback: ChangeSnapshotCallback, + cancelCallback: CancelCallback? = definedExternally, +): Unsubscribe + +external fun onChildRemoved( + query: Query, + callback: ChangeSnapshotCallback, + cancelCallback: CancelCallback? = definedExternally, +): Unsubscribe + +external fun onValue( + query: Query, + callback: ValueSnapshotCallback, + cancelCallback: CancelCallback? = definedExternally, +): Unsubscribe + +external fun onDisconnect(ref: DatabaseReference): OnDisconnect + +external fun orderByChild(path: String): QueryConstraint + +external fun orderByKey(): QueryConstraint + +external fun orderByValue(): QueryConstraint + +external fun push(parent: DatabaseReference, value: Any? = definedExternally): ThenableReference + +external fun query(query: Query, vararg queryConstraints: QueryConstraint): Query + +external fun ref(db: Database, path: String? = definedExternally): DatabaseReference + +external fun remove(ref: DatabaseReference): Promise + +external fun serverTimestamp(): Any + +external fun set(ref: DatabaseReference, value: Any?): Promise + +external fun startAfter(value: Any?, key: String? = definedExternally): QueryConstraint + +external fun startAt(value: Any?, key: String? = definedExternally): QueryConstraint + +external fun update(ref: DatabaseReference, values: Any): Promise + +external fun runTransaction( + ref: DatabaseReference, + transactionUpdate: (currentData: T) -> T, + options: Any? = definedExternally +): Promise + +external interface Database { + val app: FirebaseApp +} + +external interface Query { + val ref: DatabaseReference +} + +external interface QueryConstraint + +external interface DatabaseReference : Query { + val key: String? + val parent: DatabaseReference? + val root: DatabaseReference +} + +external interface ThenableReference : DatabaseReference + +external interface DataSnapshot { + val key: String? + val size: Int + val ref: DatabaseReference + fun `val`(): Any + fun exists(): Boolean + fun forEach(action: (a: DataSnapshot) -> Boolean): Boolean + fun child(path: String): DataSnapshot + fun hasChildren(): Boolean; +} + +external interface OnDisconnect { + fun cancel(): Promise + fun remove(): Promise + fun set(value: Any?): Promise + fun update(value: Any): Promise +} + +external interface TransactionResult { + val committed: Boolean + val snapshot: DataSnapshot +} diff --git a/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt index 1ba05f836..f40fcf915 100644 --- a/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -1,8 +1,26 @@ package dev.gitlive.firebase.database -actual val emulatorHost: String = "localhost" +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.database.externals.getDatabase +import dev.gitlive.firebase.database.externals.goOffline +import dev.gitlive.firebase.database.externals.goOnline +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import kotlin.time.Duration.Companion.seconds + +actual val emulatorHost: String = "127.0.0.1" // in JS tests connection is refused if we use localhost actual val context: Any = Unit -actual fun runTest(test: suspend () -> Unit) { - runTest { test() } -} \ No newline at end of file +actual fun runTest(test: suspend () -> Unit) = kotlinx.coroutines.test.runTest { + // in JS tests we need to wait for the database to be connected + awaitDatabaseConnection() + test() +} + +private suspend fun awaitDatabaseConnection() = withContext(Dispatchers.Default) { + withTimeout(5.seconds) { + Firebase.database.reference(".info/connected").valueEvents.first { it.value() } + } +} diff --git a/firebase-firestore/karma.config.d/karma.conf.js b/firebase-firestore/karma.config.d/karma.conf.js new file mode 100644 index 000000000..e2301eddd --- /dev/null +++ b/firebase-firestore/karma.config.d/karma.conf.js @@ -0,0 +1,8 @@ +// Some tests are fluky in GitHub Actions, so we increase the timeout. +config.set({ + client: { + mocha: { + timeout: 5000 + } + }, +}); diff --git a/firebase-firestore/package.json b/firebase-firestore/package.json index f386153b0..dd417f8b3 100644 --- a/firebase-firestore/package.json +++ b/firebase-firestore/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-firestore", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-firestore.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-firestore/src/androidAndroidTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidAndroidTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 9c3f485e2..9985c9a98 100644 --- a/firebase-firestore/src/androidAndroidTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidAndroidTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -13,4 +13,4 @@ actual val emulatorHost: String = "10.0.2.2" actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext -actual fun runTest(test: suspend CoroutineScope.() -> Unit) = runBlocking { test() } +actual fun runTest(test: suspend CoroutineScope.() -> Unit) = kotlinx.coroutines.test.runTest { test() } diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt index d310df37b..21ceaf2e4 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/TimestampTests.kt @@ -68,7 +68,7 @@ class TimestampTests { "updatedAt" to timestamp.nativeValue, "deletedAt" to timestamp.nativeValue ) - val decoded: TestData = decode(obj) + val decoded: TestData = decode(TestData.serializer(), obj) assertEquals("uid123", decoded.uid) with(decoded.createdAt) { assertEquals(timestamp, this) @@ -95,7 +95,7 @@ class TimestampTests { "updatedAt" to Timestamp.now().nativeValue, "deletedAt" to null ) - val decoded: TestData = decode(obj) + val decoded: TestData = decode(TestData.serializer(), obj) assertEquals("uid123", decoded.uid) assertNotNull(decoded.updatedAt) assertNull(decoded.deletedAt) @@ -103,10 +103,11 @@ class TimestampTests { @Test fun serializers() = runTest { - assertEquals(BaseTimestampSerializer, (Timestamp(0, 0) as BaseTimestamp).firebaseSerializer()) - assertEquals(BaseTimestampSerializer, (Timestamp.ServerTimestamp as BaseTimestamp).firebaseSerializer()) - assertEquals(TimestampSerializer, Timestamp(0, 0).firebaseSerializer()) - assertEquals(ServerTimestampSerializer, Timestamp.ServerTimestamp.firebaseSerializer()) + //todo dont work in js due to use of reified type in firebaseSerializer - uncomment once switched to IR +// assertEquals(BaseTimestampSerializer, (Timestamp(0, 0) as BaseTimestamp).firebaseSerializer()) +// assertEquals(BaseTimestampSerializer, (Timestamp.ServerTimestamp as BaseTimestamp).firebaseSerializer()) +// assertEquals(TimestampSerializer, Timestamp(0, 0).firebaseSerializer()) +// assertEquals(ServerTimestampSerializer, Timestamp.ServerTimestamp.firebaseSerializer()) } @Test diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index b6cc28fd7..86c180622 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -9,12 +9,15 @@ import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable import kotlin.random.Random import kotlin.test.BeforeTest import kotlin.test.Test @@ -26,15 +29,15 @@ import kotlin.test.assertTrue expect val emulatorHost: String expect val context: Any -expect fun runTest(test: suspend CoroutineScope.() -> Unit) +expect fun runTest(test: suspend CoroutineScope.() -> Unit): TestResult class FirebaseFirestoreTest { @Serializable data class FirestoreTest( - val prop1: String, + val prop1: String, val time: Double = 0.0, - val count: Int = 0, + val count: Int = 0, val list: List = emptyList(), ) @@ -146,7 +149,7 @@ class FirebaseFirestoreTest { doc.set(FirestoreTimeTest.serializer(), FirestoreTimeTest("ServerTimestamp", Timestamp.ServerTimestamp)) - assertNotEquals(Timestamp.ServerTimestamp, doc.get().get("time")) + assertNotEquals(Timestamp.ServerTimestamp, doc.get().get("time", BaseTimestamp.serializer())) assertNotEquals(Timestamp.ServerTimestamp, doc.get().data(FirestoreTimeTest.serializer()).time) } @@ -157,11 +160,9 @@ class FirebaseFirestoreTest { .document("test${Random.nextInt()}") val deferredPendingWritesSnapshot = async { - withTimeout(5000) { - doc.snapshots.filter { it.exists }.first() - } + doc.snapshots.filter { it.exists }.first() } - delay(100) // makes possible to catch pending writes snapshot + nonSkippedDelay(100) // makes possible to catch pending writes snapshot doc.set( FirestoreTimeTest.serializer(), @@ -170,7 +171,7 @@ class FirebaseFirestoreTest { val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNull(pendingWritesSnapshot.get("time", ServerTimestampBehavior.NONE)) + assertNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, ServerTimestampBehavior.NONE)) } @Test @@ -180,17 +181,15 @@ class FirebaseFirestoreTest { .document("test${Random.nextInt()}") val deferredPendingWritesSnapshot = async { - withTimeout(5000) { - doc.snapshots.filter { it.exists }.first() - } + doc.snapshots.filter { it.exists }.first() } - delay(100) // makes possible to catch pending writes snapshot + nonSkippedDelay(100) // makes possible to catch pending writes snapshot doc.set(FirestoreTimeTest.serializer(), FirestoreTimeTest("ServerTimestampBehavior", Timestamp.ServerTimestamp)) val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNotNull(pendingWritesSnapshot.get("time", ServerTimestampBehavior.ESTIMATE)) + assertNotNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, ServerTimestampBehavior.ESTIMATE)) assertNotEquals(Timestamp.ServerTimestamp, pendingWritesSnapshot.data(FirestoreTimeTest.serializer(), ServerTimestampBehavior.ESTIMATE).time) } @@ -201,17 +200,15 @@ class FirebaseFirestoreTest { .document("test${Random.nextInt()}") val deferredPendingWritesSnapshot = async { - withTimeout(5000) { - doc.snapshots.filter { it.exists }.first() - } + doc.snapshots.filter { it.exists }.first() } - delay(100) // makes possible to catch pending writes snapshot + nonSkippedDelay(100) // makes possible to catch pending writes snapshot doc.set(FirestoreTimeTest.serializer(), FirestoreTimeTest("ServerTimestampBehavior", Timestamp.ServerTimestamp)) val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNull(pendingWritesSnapshot.get("time", ServerTimestampBehavior.PREVIOUS)) + assertNull(pendingWritesSnapshot.get("time", BaseTimestamp.serializer().nullable, ServerTimestampBehavior.PREVIOUS)) } @Test @@ -445,11 +442,9 @@ class FirebaseFirestoreTest { .document("test${Random.nextInt()}") val deferredPendingWritesSnapshot = async { - withTimeout(5000) { - doc.snapshots.filter { it.exists }.first() - } + doc.snapshots.filter { it.exists }.first() } - delay(100) // makes possible to catch pending writes snapshot + nonSkippedDelay(100) // makes possible to catch pending writes snapshot doc.set(DoubleTimestamp.serializer(), DoubleTimestamp(DoubleAsTimestampSerializer.serverTimestamp)) @@ -478,9 +473,9 @@ class FirebaseFirestoreTest { val ms = 12345678.0 - doc.set(LegacyDocument(time = ms)) + doc.set(LegacyDocument.serializer(), LegacyDocument(time = ms)) - val fetched: NewDocument = doc.get().data() + val fetched: NewDocument = doc.get().data(NewDocument.serializer()) assertEquals(ms, fetched.time.toMilliseconds()) } @@ -494,27 +489,27 @@ class FirebaseFirestoreTest { val collection = Firebase.firestore .collection("testQueryByTimestamp") - val timestamp = Timestamp.now() + val timestamp = Timestamp.fromMilliseconds(1693262549000.0) val pastTimestamp = Timestamp(timestamp.seconds - 60, 12345000) // note: iOS truncates 3 last digits of nanoseconds due to internal conversions val futureTimestamp = Timestamp(timestamp.seconds + 60, 78910000) - collection.add(DocumentWithTimestamp(pastTimestamp)) - collection.add(DocumentWithTimestamp(futureTimestamp)) + collection.add(DocumentWithTimestamp.serializer(), DocumentWithTimestamp(pastTimestamp)) + collection.add(DocumentWithTimestamp.serializer(), DocumentWithTimestamp(futureTimestamp)) val equalityQueryResult = collection.where( path = FieldPath(DocumentWithTimestamp::time.name), equalTo = pastTimestamp - ).get().documents.map { it.data() } + ).get().documents.map { it.data(DocumentWithTimestamp.serializer()) }.toSet() - assertEquals(listOf(DocumentWithTimestamp(pastTimestamp)), equalityQueryResult) + assertEquals(setOf(DocumentWithTimestamp(pastTimestamp)), equalityQueryResult) val gtQueryResult = collection.where( path = FieldPath(DocumentWithTimestamp::time.name), greaterThan = timestamp - ).get().documents.map { it.data() } + ).get().documents.map { it.data(DocumentWithTimestamp.serializer()) }.toSet() - assertEquals(listOf(DocumentWithTimestamp(futureTimestamp)), gtQueryResult) + assertEquals(setOf(DocumentWithTimestamp(futureTimestamp)), gtQueryResult) } private suspend fun setupFirestoreData() { @@ -528,4 +523,8 @@ class FirebaseFirestoreTest { .document("three") .set(FirestoreTest.serializer(), FirestoreTest("ccc")) } + + private suspend fun nonSkippedDelay(timeout: Long) = withContext(Dispatchers.Default) { + delay(timeout) + } } diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt index 808704cf5..9b14b7af3 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt @@ -4,7 +4,7 @@ import dev.gitlive.firebase.* import kotlinx.serialization.Serializable /** A class representing a platform specific Firebase GeoPoint. */ -actual typealias NativeGeoPoint = firebase.firestore.GeoPoint +actual typealias NativeGeoPoint = dev.gitlive.firebase.firestore.externals.GeoPoint /** A class representing a Firebase GeoPoint. */ @Serializable(with = GeoPointSerializer::class) diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt index b82cf083a..dabec0055 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt @@ -1,6 +1,5 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* import kotlinx.serialization.Serializable /** A base class that could be used to combine [Timestamp] and [Timestamp.ServerTimestamp] in the same field. */ @@ -8,7 +7,7 @@ import kotlinx.serialization.Serializable actual sealed class BaseTimestamp /** A class representing a platform specific Firebase Timestamp. */ -actual typealias NativeTimestamp = firebase.firestore.Timestamp +actual typealias NativeTimestamp = dev.gitlive.firebase.firestore.externals.Timestamp /** A class representing a Firebase Timestamp. */ @Serializable(with = TimestampSerializer::class) @@ -22,7 +21,7 @@ actual class Timestamp internal actual constructor( override fun equals(other: Any?): Boolean = this === other || other is Timestamp && nativeValue.isEqual(other.nativeValue) - override fun hashCode(): Int = nativeValue.hashCode() + override fun hashCode(): Int = nativeValue.toMillis().hashCode() override fun toString(): String = nativeValue.toString() actual companion object { @@ -33,4 +32,3 @@ actual class Timestamp internal actual constructor( @Serializable(with = ServerTimestampSerializer::class) actual object ServerTimestamp: BaseTimestamp() } - diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt new file mode 100644 index 000000000..6fa365353 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/externals/firestore.kt @@ -0,0 +1,285 @@ +@file:JsModule("firebase/firestore") +@file:JsNonModule + +package dev.gitlive.firebase.firestore.externals + +import dev.gitlive.firebase.Unsubscribe +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Json +import kotlin.js.Promise + +external class FieldPath(vararg fieldNames: String) { + companion object { + val documentId: FieldPath + } + fun isEqual(other: FieldPath): Boolean + +} + +external fun refEqual(left: DocumentReference, right: DocumentReference): Boolean + +external fun addDoc(reference: CollectionReference, data: Any): Promise + +external fun arrayRemove(vararg elements: Any): FieldValue + +external fun arrayUnion(vararg elements: Any): FieldValue + +external fun clearIndexedDbPersistence(firestore: Firestore): Promise + +external fun collection(firestore: Firestore, collectionPath: String): CollectionReference + +external fun collection(reference: DocumentReference, collectionPath: String): CollectionReference + +external fun collectionGroup(firestore: Firestore, collectionId: String): Query + +external fun connectFirestoreEmulator( + firestore: Firestore, + host: String, + port: Int, + options: Any? = definedExternally +) + +external fun deleteDoc(reference: DocumentReference): Promise + +external fun deleteField(): FieldValue + +external fun disableNetwork(firestore: Firestore): Promise + +external fun doc(firestore: Firestore, documentPath: String): DocumentReference + +external fun doc(firestore: CollectionReference, documentPath: String? = definedExternally): DocumentReference + +external fun enableIndexedDbPersistence( + firestore: Firestore, + persistenceSettings: Any? = definedExternally +): Promise + +external fun enableNetwork(firestore: Firestore): Promise + +external fun endAt(document: DocumentSnapshot): QueryConstraint + +external fun endAt(vararg fieldValues: Any): QueryConstraint + +external fun endBefore(document: DocumentSnapshot): QueryConstraint + +external fun endBefore(vararg fieldValues: Any): QueryConstraint + +external fun getDoc( + reference: DocumentReference, + options: Any? = definedExternally +): Promise + +external fun getDocs(query: Query): Promise + +external fun getFirestore(app: FirebaseApp? = definedExternally): Firestore + +external fun increment(n: Int): FieldValue + +external fun initializeFirestore(app: FirebaseApp, settings: Any): Firestore + +external fun limit(limit: Number): QueryConstraint + +external fun onSnapshot( + reference: DocumentReference, + next: (snapshot: DocumentSnapshot) -> Unit, + error: (error: Throwable) -> Unit +): Unsubscribe + +external fun onSnapshot( + reference: DocumentReference, + options: Json, + next: (snapshot: DocumentSnapshot) -> Unit, + error: (error: Throwable) -> Unit +): Unsubscribe + +external fun onSnapshot( + reference: Query, + next: (snapshot: QuerySnapshot) -> Unit, + error: (error: Throwable) -> Unit +): Unsubscribe + +external fun onSnapshot( + reference: Query, + options: Json, + next: (snapshot: QuerySnapshot) -> Unit, + error: (error: Throwable) -> Unit +): Unsubscribe + +external fun orderBy(field: String, direction: Any): QueryConstraint + +external fun orderBy(field: FieldPath, direction: Any): QueryConstraint + +external fun query(query: Query, vararg queryConstraints: QueryConstraint): Query + +external fun runTransaction( + firestore: Firestore, + updateFunction: (transaction: Transaction) -> Promise, + options: Any? = definedExternally +): Promise + +external fun serverTimestamp(): FieldValue + +external fun setDoc( + documentReference: DocumentReference, + data: Any, + options: Any? = definedExternally +): Promise + +external fun setLogLevel(logLevel: String) + +external fun startAfter(document: DocumentSnapshot): QueryConstraint + +external fun startAfter(vararg fieldValues: Any): QueryConstraint + +external fun startAt(document: DocumentSnapshot): QueryConstraint + +external fun startAt(vararg fieldValues: Any): QueryConstraint + +external fun updateDoc(reference: DocumentReference, data: Any): Promise + +external fun updateDoc( + reference: DocumentReference, + field: String, + value: Any?, + vararg moreFieldsAndValues: Any? +): Promise + +external fun updateDoc( + reference: DocumentReference, + field: FieldPath, + value: Any?, + vararg moreFieldsAndValues: Any? +): Promise + +external fun where(field: String, opStr: String, value: Any?): QueryConstraint + +external fun where(field: FieldPath, opStr: String, value: Any?): QueryConstraint + +external fun writeBatch(firestore: Firestore): WriteBatch + +external interface Firestore { + val app: FirebaseApp +} + +external class GeoPoint constructor(latitude: Double, longitude: Double) { + val latitude: Double + val longitude: Double + fun isEqual(other: GeoPoint): Boolean +} + +external interface CollectionReference : Query { + val id: String + val path: String + val parent: DocumentReference? +} + +external interface DocumentChange { + val doc: DocumentSnapshot + val newIndex: Int + val oldIndex: Int + val type: String +} + +external class DocumentReference { + val id: String + val path: String + val parent: CollectionReference +} + +external interface DocumentSnapshot { + val id: String + val ref: DocumentReference + val metadata: SnapshotMetadata + fun data(options: Any? = definedExternally): Any? + fun exists(): Boolean + fun get(fieldPath: String, options: Any? = definedExternally): Any? + fun get(fieldPath: FieldPath, options: Any? = definedExternally): Any? +} + +external class FieldValue { + fun isEqual(other: FieldValue): Boolean +} + +external interface Query + +external interface QueryConstraint + +external interface QuerySnapshot { + val docs: Array + val empty: Boolean + val metadata: SnapshotMetadata + fun docChanges(): Array +} + +external interface SnapshotMetadata { + val hasPendingWrites: Boolean + val fromCache: Boolean +} + +external interface Transaction { + fun get(documentReference: DocumentReference): Promise + + fun set( + documentReference: DocumentReference, + data: Any, + options: Any? = definedExternally + ): Transaction + + fun update(documentReference: DocumentReference, data: Any): Transaction + + fun update( + documentReference: DocumentReference, + field: String, + value: Any?, + vararg moreFieldsAndValues: Any? + ): Transaction + + fun update( + documentReference: DocumentReference, + field: FieldPath, + value: Any?, + vararg moreFieldsAndValues: Any? + ): Transaction + + fun delete(documentReference: DocumentReference): Transaction +} + +external interface WriteBatch { + fun commit(): Promise + + fun delete(documentReference: DocumentReference): WriteBatch + + fun set( + documentReference: DocumentReference, + data: Any, + options: Any? = definedExternally + ): WriteBatch + + fun update(documentReference: DocumentReference, data: Any): WriteBatch + + fun update( + documentReference: DocumentReference, + field: String, + value: Any?, + vararg moreFieldsAndValues: Any? + ): WriteBatch + + fun update( + documentReference: DocumentReference, + field: FieldPath, + value: Any?, + vararg moreFieldsAndValues: Any? + ): WriteBatch +} + +external class Timestamp(seconds: Double, nanoseconds: Double) { + companion object { + fun now(): Timestamp + } + + val seconds: Double + val nanoseconds: Double + fun toMillis(): Double + + fun isEqual(other: Timestamp): Boolean +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 1c347e1e8..46833d684 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -4,7 +4,12 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.decode +import dev.gitlive.firebase.encode +import dev.gitlive.firebase.firestore.externals.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.await import kotlinx.coroutines.channels.awaitClose @@ -15,12 +20,32 @@ import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy import kotlin.js.json +import dev.gitlive.firebase.firestore.externals.CollectionReference as JsCollectionReference +import dev.gitlive.firebase.firestore.externals.DocumentChange as JsDocumentChange +import dev.gitlive.firebase.firestore.externals.DocumentReference as JsDocumentReference +import dev.gitlive.firebase.firestore.externals.DocumentSnapshot as JsDocumentSnapshot +import dev.gitlive.firebase.firestore.externals.FieldPath as JsFieldPath +import dev.gitlive.firebase.firestore.externals.Query as JsQuery +import dev.gitlive.firebase.firestore.externals.QuerySnapshot as JsQuerySnapshot +import dev.gitlive.firebase.firestore.externals.SnapshotMetadata as JsSnapshotMetadata +import dev.gitlive.firebase.firestore.externals.Transaction as JsTransaction +import dev.gitlive.firebase.firestore.externals.WriteBatch as JsWriteBatch +import dev.gitlive.firebase.firestore.externals.arrayRemove as jsArrayRemove +import dev.gitlive.firebase.firestore.externals.arrayUnion as jsArrayUnion +import dev.gitlive.firebase.firestore.externals.endAt as jsEndAt +import dev.gitlive.firebase.firestore.externals.endBefore as jsEndBefore +import dev.gitlive.firebase.firestore.externals.increment as jsIncrement +import dev.gitlive.firebase.firestore.externals.limit as jsLimit +import dev.gitlive.firebase.firestore.externals.startAfter as jsStartAfter +import dev.gitlive.firebase.firestore.externals.startAt as jsStartAt +import dev.gitlive.firebase.firestore.externals.updateDoc as jsUpdate +import dev.gitlive.firebase.firestore.externals.where as jsWhere actual val Firebase.firestore get() = - rethrow { dev.gitlive.firebase.firestore; FirebaseFirestore(firebase.firestore()) } + rethrow { FirebaseFirestore(getFirestore()) } actual fun Firebase.firestore(app: FirebaseApp) = - rethrow { dev.gitlive.firebase.firestore; FirebaseFirestore(firebase.app().firestore()) } + rethrow { FirebaseFirestore(getFirestore(app.js)) } /** Helper method to perform an update operation. */ private fun performUpdate( @@ -31,50 +56,54 @@ private fun performUpdate( /** Helper method to perform an update operation. */ private fun performUpdate( fieldsAndValues: Array>, - update: (firebase.firestore.FieldPath, Any?, Array) -> R + update: (dev.gitlive.firebase.firestore.externals.FieldPath, Any?, Array) -> R ) = performUpdate(fieldsAndValues, { it.js }, { encode(it, true) }, update) -actual class FirebaseFirestore(val js: firebase.firestore.Firestore) { +actual class FirebaseFirestore(jsFirestore: Firestore) { - actual fun collection(collectionPath: String) = rethrow { CollectionReference(js.collection(collectionPath)) } + var js: Firestore = jsFirestore + private set - actual fun collectionGroup(collectionId: String) = Query(js.collectionGroup(collectionId)) + actual fun collection(collectionPath: String) = rethrow { CollectionReference(collection(js, collectionPath)) } - actual fun document(documentPath: String) = rethrow { DocumentReference(js.doc(documentPath)) } + actual fun collectionGroup(collectionId: String) = rethrow { Query(collectionGroup(js, collectionId)) } - actual fun batch() = rethrow { WriteBatch(js.batch()) } + actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } + + actual fun batch() = rethrow { WriteBatch(writeBatch(js)) } actual fun setLoggingEnabled(loggingEnabled: Boolean) = - rethrow { firebase.firestore.setLogLevel( if(loggingEnabled) "error" else "silent") } + rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - rethrow { js.runTransaction { GlobalScope.promise { Transaction(it).func() } }.await() } + rethrow { runTransaction(js, { GlobalScope.promise { Transaction(it).func() } } ).await() } actual suspend fun clearPersistence() = - rethrow { js.clearPersistence().await() } + rethrow { clearIndexedDbPersistence(js).await() } - actual fun useEmulator(host: String, port: Int) = rethrow { js.useEmulator(host, port) } + actual fun useEmulator(host: String, port: Int) = rethrow { connectFirestoreEmulator(js, host, port) } actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) { - if(persistenceEnabled == true) js.enablePersistence() + if(persistenceEnabled == true) enableIndexedDbPersistence(js) - js.settings(json().apply { + val settings = json().apply { sslEnabled?.let { set("ssl", it) } host?.let { set("host", it) } cacheSizeBytes?.let { set("cacheSizeBytes", it) } - }) + } + js = initializeFirestore(js.app, settings) } actual suspend fun disableNetwork() { - rethrow { js.disableNetwork().await() } + rethrow { disableNetwork(js).await() } } actual suspend fun enableNetwork() { - rethrow { js.enableNetwork().await() } + rethrow { enableNetwork(js).await() } } } -actual class WriteBatch(val js: firebase.firestore.WriteBatch) { +actual class WriteBatch(val js: JsWriteBatch) { actual inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean) = rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("merge" to merge)) } @@ -128,7 +157,7 @@ actual class WriteBatch(val js: firebase.firestore.WriteBatch) { } -actual class Transaction(val js: firebase.firestore.Transaction) { +actual class Transaction(val js: JsTransaction) { actual fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean) = rethrow { js.set(documentRef.js, encode(data, encodeDefaults)!!, json("merge" to merge)) } @@ -183,7 +212,7 @@ actual class Transaction(val js: firebase.firestore.Transaction) { } /** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = firebase.firestore.DocumentReference +actual typealias NativeDocumentReference = JsDocumentReference @Serializable(with = DocumentReferenceSerializer::class) actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) { @@ -198,52 +227,53 @@ actual class DocumentReference actual constructor(internal actual val nativeValu actual val parent: CollectionReference get() = rethrow { CollectionReference(js.parent) } - actual fun collection(collectionPath: String) = rethrow { CollectionReference(js.collection(collectionPath)) } + actual fun collection(collectionPath: String) = rethrow { CollectionReference(collection(js, collectionPath)) } actual suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(encode(data, encodeDefaults)!!, json("merge" to merge)).await() } + rethrow { setDoc(js, encode(data, encodeDefaults)!!, json("merge" to merge)).await() } actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(encode(data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } + rethrow { setDoc(js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } actual suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(encode(data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } + rethrow { setDoc(js, encode(data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean) = - rethrow { js.set(encode(strategy, data, encodeDefaults)!!, json("merge" to merge)).await() } + rethrow { setDoc(js, encode(strategy, data, encodeDefaults)!!, json("merge" to merge)).await() } actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) = - rethrow { js.set(encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } + rethrow { setDoc(js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFields)).await() } actual suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) = - rethrow { js.set(encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } + rethrow { setDoc(js, encode(strategy, data, encodeDefaults)!!, json("mergeFields" to mergeFieldPaths.map { it.js }.toTypedArray())).await() } actual suspend inline fun update(data: T, encodeDefaults: Boolean) = - rethrow { js.update(encode(data, encodeDefaults)!!).await() } + rethrow { jsUpdate(js, encode(data, encodeDefaults)!!).await() } actual suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { js.update(encode(strategy, data, encodeDefaults)!!).await() } + rethrow { jsUpdate(js, encode(strategy, data, encodeDefaults)!!).await() } actual suspend fun update(vararg fieldsAndValues: Pair) = rethrow { performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - js.update(field, value, *moreFieldsAndValues) + jsUpdate(js, field, value, *moreFieldsAndValues) }?.await() }.run { Unit } actual suspend fun update(vararg fieldsAndValues: Pair) = rethrow { performUpdate(fieldsAndValues) { field, value, moreFieldsAndValues -> - js.update(field, value, *moreFieldsAndValues) + jsUpdate(js, field, value, *moreFieldsAndValues) }?.await() }.run { Unit } - actual suspend fun delete() = rethrow { js.delete().await() } + actual suspend fun delete() = rethrow { deleteDoc(js).await() } - actual suspend fun get() = rethrow { DocumentSnapshot(js.get().await()) } + actual suspend fun get() = rethrow { DocumentSnapshot(getDoc(js).await()) } actual val snapshots: Flow get() = snapshots() - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val unsubscribe = js.onSnapshot( + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val unsubscribe = onSnapshot( + js, json("includeMetadataChanges" to includeMetadataChanges), { trySend(DocumentSnapshot(it)) }, { close(errorToException(it)) } @@ -252,28 +282,28 @@ actual class DocumentReference actual constructor(internal actual val nativeValu } override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && nativeValue.isEqual(other.nativeValue) + this === other || other is DocumentReference && refEqual(nativeValue, other.nativeValue) override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = "DocumentReference(path=$path)" } -actual open class Query(open val js: firebase.firestore.Query) { +actual open class Query(open val js: JsQuery) { - actual suspend fun get() = rethrow { QuerySnapshot(js.get().await()) } + actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) } - actual fun limit(limit: Number) = Query(js.limit(limit.toDouble())) + actual fun limit(limit: Number) = Query(query(js, jsLimit(limit))) - internal actual fun _where(field: String, equalTo: Any?) = rethrow { Query(js.where(field, "==", equalTo)) } - internal actual fun _where(path: FieldPath, equalTo: Any?) = rethrow { Query(js.where(path.js, "==", equalTo)) } + internal actual fun _where(field: String, equalTo: Any?) = rethrow { Query(query(js, jsWhere(field, "==", equalTo))) } + internal actual fun _where(path: FieldPath, equalTo: Any?) = rethrow { Query(query(js, jsWhere(path.js, "==", equalTo))) } - internal actual fun _where(field: String, equalTo: DocumentReference) = rethrow { Query(js.where(field, "==", equalTo.js)) } - internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = rethrow { Query(js.where(path.js, "==", equalTo.js)) } + internal actual fun _where(field: String, equalTo: DocumentReference) = rethrow { Query(query(js, jsWhere(field, "==", equalTo.js))) } + internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = rethrow { Query(query(js, jsWhere(path.js, "==", equalTo.js))) } internal actual fun _where(field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = rethrow { Query( - (lessThan?.let {js.where(field, "<", it) } ?: js).let { js2 -> - (greaterThan?.let { js2.where(field, ">", it) } ?: js2).let { js3 -> - arrayContains?.let { js3.where(field, "array-contains", it) } ?: js3 + (lessThan?.let { query(js, jsWhere(field, "<", it)) } ?: js).let { js2 -> + (greaterThan?.let { query(js2, jsWhere(field, ">", it)) } ?: js2).let { js3 -> + arrayContains?.let { query(js3, jsWhere(field, "array-contains", it)) } ?: js3 } } ) @@ -281,53 +311,54 @@ actual open class Query(open val js: firebase.firestore.Query) { internal actual fun _where(path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = rethrow { Query( - (lessThan?.let {js.where(path.js, "<", it) } ?: js).let { js2 -> - (greaterThan?.let { js2.where(path.js, ">", it) } ?: js2).let { js3 -> - arrayContains?.let { js3.where(path.js, "array-contains", it) } ?: js3 + (lessThan?.let { query(js, jsWhere(path.js, "<", it)) } ?: js).let { js2 -> + (greaterThan?.let { query(js2, jsWhere(path.js, ">", it)) } ?: js2).let { js3 -> + arrayContains?.let { query(js3, jsWhere(path.js, "array-contains", it)) } ?: js3 } } ) } internal actual fun _where(field: String, inArray: List?, arrayContainsAny: List?) = Query( - (inArray?.let { js.where(field, "in", it.toTypedArray()) } ?: js).let { js2 -> - arrayContainsAny?.let { js2.where(field, "array-contains-any", it.toTypedArray()) } ?: js2 + (inArray?.let { query(js, jsWhere(field, "in", it.toTypedArray())) } ?: js).let { js2 -> + arrayContainsAny?.let { query(js2, jsWhere(field, "array-contains-any", it.toTypedArray())) } ?: js2 } ) internal actual fun _where(path: FieldPath, inArray: List?, arrayContainsAny: List?) = Query( - (inArray?.let { js.where(path.js, "in", it.toTypedArray()) } ?: js).let { js2 -> - arrayContainsAny?.let { js2.where(path.js, "array-contains-any", it.toTypedArray()) } ?: js2 + (inArray?.let { query(js, jsWhere(path.js, "in", it.toTypedArray())) } ?: js).let { js2 -> + arrayContainsAny?.let { query(js2, jsWhere(path.js, "array-contains-any", it.toTypedArray())) } ?: js2 } ) internal actual fun _orderBy(field: String, direction: Direction) = rethrow { - Query(js.orderBy(field, direction.jsString)) + Query(query(js, orderBy(field, direction.jsString))) } internal actual fun _orderBy(field: FieldPath, direction: Direction) = rethrow { - Query(js.orderBy(field.js, direction.jsString)) + Query(query(js, orderBy(field.js, direction.jsString))) } - internal actual fun _startAfter(document: DocumentSnapshot) = rethrow { Query(js.startAfter(document.js)) } + internal actual fun _startAfter(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAfter(document.js))) } - internal actual fun _startAfter(vararg fieldValues: Any) = rethrow { Query(js.startAfter(*fieldValues)) } + internal actual fun _startAfter(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAfter(*fieldValues))) } - internal actual fun _startAt(document: DocumentSnapshot) = rethrow { Query(js.startAt(document.js)) } + internal actual fun _startAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAt(document.js))) } - internal actual fun _startAt(vararg fieldValues: Any) = rethrow { Query(js.startAt(*fieldValues)) } + internal actual fun _startAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAt(*fieldValues))) } - internal actual fun _endBefore(document: DocumentSnapshot) = rethrow { Query(js.endBefore(document.js)) } + internal actual fun _endBefore(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndBefore(document.js))) } - internal actual fun _endBefore(vararg fieldValues: Any) = rethrow { Query(js.endBefore(*fieldValues)) } + internal actual fun _endBefore(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndBefore(*fieldValues))) } - internal actual fun _endAt(document: DocumentSnapshot) = rethrow { Query(js.endAt(document.js)) } + internal actual fun _endAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndAt(document.js))) } - internal actual fun _endAt(vararg fieldValues: Any) = rethrow { Query(js.endAt(*fieldValues)) } + internal actual fun _endAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndAt(*fieldValues))) } actual val snapshots get() = callbackFlow { val unsubscribe = rethrow { - js.onSnapshot( + onSnapshot( + js, { trySend(QuerySnapshot(it)) }, { close(errorToException(it)) } ) @@ -337,7 +368,8 @@ actual open class Query(open val js: firebase.firestore.Query) { actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { val unsubscribe = rethrow { - js.onSnapshot( + onSnapshot( + js, json("includeMetadataChanges" to includeMetadataChanges), { trySend(QuerySnapshot(it)) }, { close(errorToException(it)) } @@ -347,24 +379,24 @@ actual open class Query(open val js: firebase.firestore.Query) { } } -actual class CollectionReference(override val js: firebase.firestore.CollectionReference) : Query(js) { +actual class CollectionReference(override val js: JsCollectionReference) : Query(js) { actual val path: String get() = rethrow { js.path } - actual val document get() = rethrow { DocumentReference(js.doc()) } + actual val document get() = rethrow { DocumentReference(doc(js)) } actual val parent get() = rethrow { js.parent?.let{DocumentReference(it)} } - actual fun document(documentPath: String) = rethrow { DocumentReference(js.doc(documentPath)) } + actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } actual suspend inline fun add(data: T, encodeDefaults: Boolean) = - rethrow { DocumentReference(js.add(encode(data, encodeDefaults)!!).await()) } + rethrow { DocumentReference(addDoc(js, encode(data, encodeDefaults)!!).await()) } actual suspend fun add(data: T, strategy: SerializationStrategy, encodeDefaults: Boolean) = - rethrow { DocumentReference(js.add(encode(strategy, data, encodeDefaults)!!).await()) } + rethrow { DocumentReference(addDoc(js, encode(strategy, data, encodeDefaults)!!).await()) } actual suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) = - rethrow { DocumentReference(js.add(encode(strategy, data, encodeDefaults)!!).await()) } + rethrow { DocumentReference(addDoc(js, encode(strategy, data, encodeDefaults)!!).await()) } } actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @@ -372,7 +404,7 @@ actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExc @Suppress("EXTENSION_SHADOWED_BY_MEMBER") actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code -actual class QuerySnapshot(val js: firebase.firestore.QuerySnapshot) { +actual class QuerySnapshot(val js: JsQuerySnapshot) { actual val documents get() = js.docs.map { DocumentSnapshot(it) } actual val documentChanges @@ -380,7 +412,7 @@ actual class QuerySnapshot(val js: firebase.firestore.QuerySnapshot) { actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) } -actual class DocumentChange(val js: firebase.firestore.DocumentChange) { +actual class DocumentChange(val js: JsDocumentChange) { actual val document: DocumentSnapshot get() = DocumentSnapshot(js.doc) actual val newIndex: Int @@ -391,7 +423,7 @@ actual class DocumentChange(val js: firebase.firestore.DocumentChange) { get() = ChangeType.values().first { it.jsString == js.type } } -actual class DocumentSnapshot(val js: firebase.firestore.DocumentSnapshot) { +actual class DocumentSnapshot(val js: JsDocumentSnapshot) { actual val id get() = rethrow { js.id } actual val reference get() = rethrow { DocumentReference(js.ref) } @@ -409,23 +441,23 @@ actual class DocumentSnapshot(val js: firebase.firestore.DocumentSnapshot) { rethrow { decode(strategy, js.get(field, getTimestampsOptions(serverTimestampBehavior))) } actual fun contains(field: String) = rethrow { js.get(field) != undefined } - actual val exists get() = rethrow { js.exists } + actual val exists get() = rethrow { js.exists() } actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) fun getTimestampsOptions(serverTimestampBehavior: ServerTimestampBehavior) = json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) } -actual class SnapshotMetadata(val js: firebase.firestore.SnapshotMetadata) { +actual class SnapshotMetadata(val js: JsSnapshotMetadata) { actual val hasPendingWrites: Boolean get() = js.hasPendingWrites actual val isFromCache: Boolean get() = js.fromCache } -actual class FieldPath private constructor(val js: firebase.firestore.FieldPath) { +actual class FieldPath private constructor(val js: JsFieldPath) { actual constructor(vararg fieldNames: String) : this(dev.gitlive.firebase.firestore.rethrow { - js("Reflect").construct(firebase.firestore.FieldPath, fieldNames).unsafeCast() + js("Reflect").construct(JsFieldPath, fieldNames).unsafeCast() }) - actual val documentId: FieldPath get() = FieldPath(firebase.firestore.FieldPath.documentId) + actual val documentId: FieldPath get() = FieldPath(JsFieldPath.documentId) override fun equals(other: Any?): Boolean = other is FieldPath && js.isEqual(other.js) override fun hashCode(): Int = js.hashCode() @@ -433,7 +465,7 @@ actual class FieldPath private constructor(val js: firebase.firestore.FieldPath) } /** Represents a platform specific Firebase FieldValue. */ -private typealias NativeFieldValue = firebase.firestore.FieldValue +private typealias NativeFieldValue = dev.gitlive.firebase.firestore.externals.FieldValue /** Represents a Firebase FieldValue. */ @Serializable(with = FieldValueSerializer::class) @@ -448,11 +480,11 @@ actual class FieldValue internal actual constructor(internal actual val nativeVa override fun toString(): String = nativeValue.toString() actual companion object { - actual val serverTimestamp: FieldValue get() = rethrow { FieldValue(NativeFieldValue.serverTimestamp()) } - actual val delete: FieldValue get() = rethrow { FieldValue(NativeFieldValue.delete()) } - actual fun increment(value: Int): FieldValue = rethrow { FieldValue(firebase.firestore.FieldValue.increment(value)) } - actual fun arrayUnion(vararg elements: Any): FieldValue = rethrow { FieldValue(NativeFieldValue.arrayUnion(*elements)) } - actual fun arrayRemove(vararg elements: Any): FieldValue = rethrow { FieldValue(NativeFieldValue.arrayRemove(*elements)) } + actual val serverTimestamp: FieldValue get() = rethrow { FieldValue(serverTimestamp()) } + actual val delete: FieldValue get() = rethrow { FieldValue(deleteField()) } + actual fun increment(value: Int): FieldValue = rethrow { FieldValue(jsIncrement(value)) } + actual fun arrayUnion(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayUnion(*elements)) } + actual fun arrayRemove(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayRemove(*elements)) } } } diff --git a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index f4cc24ae9..e5037604f 100644 --- a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -11,6 +11,4 @@ actual val emulatorHost: String = "localhost" actual val context: Any = Unit -actual fun runTest(test: suspend CoroutineScope.() -> Unit) { - runTest { test() } -} +actual fun runTest(test: suspend CoroutineScope.() -> Unit) = runTest { test() } diff --git a/firebase-functions/package.json b/firebase-functions/package.json index 5b0a5383e..e0db81fe6 100644 --- a/firebase-functions/package.json +++ b/firebase-functions/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-functions", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-functions.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt index be109eaaa..f1f3a225a 100644 --- a/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/commonMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -39,4 +39,3 @@ expect fun Firebase.functions(app: FirebaseApp): FirebaseFunctions expect fun Firebase.functions(app: FirebaseApp, region: String): FirebaseFunctions expect class FirebaseFunctionsException: FirebaseException - diff --git a/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/externals/HttpsCallableExt.kt b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/externals/HttpsCallableExt.kt new file mode 100644 index 000000000..ef4d3f6f0 --- /dev/null +++ b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/externals/HttpsCallableExt.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase.functions.externals + +import kotlin.js.Promise + +operator fun HttpsCallable.invoke() = asDynamic()() as Promise +operator fun HttpsCallable.invoke(data: Any?) = asDynamic()(data) as Promise diff --git a/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/externals/functions.kt b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/externals/functions.kt new file mode 100644 index 000000000..7cf5388c6 --- /dev/null +++ b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/externals/functions.kt @@ -0,0 +1,24 @@ +@file:JsModule("firebase/functions") +@file:JsNonModule + +package dev.gitlive.firebase.functions.externals + +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Json + +external fun connectFunctionsEmulator(functions: Functions, host: String, port: Int) + +external fun getFunctions( + app: FirebaseApp? = definedExternally, + regionOrCustomDomain: String? = definedExternally +): Functions + +external fun httpsCallable(functions: Functions, name: String, options: Json?): HttpsCallable + +external interface Functions + +external interface HttpsCallableResult { + val data: Any? +} + +external interface HttpsCallable diff --git a/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt index 575d7c885..419deb7ce 100644 --- a/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/jsMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -5,32 +5,35 @@ package dev.gitlive.firebase.functions import dev.gitlive.firebase.* +import dev.gitlive.firebase.functions.externals.* import kotlinx.coroutines.await import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import org.w3c.dom.url.URL import kotlin.js.json +import dev.gitlive.firebase.functions.externals.HttpsCallableResult as JsHttpsCallableResult actual val Firebase.functions: FirebaseFunctions - get() = rethrow { dev.gitlive.firebase.functions; FirebaseFunctions(firebase.functions()) } + get() = rethrow { FirebaseFunctions(getFunctions()) } actual fun Firebase.functions(region: String) = - rethrow { dev.gitlive.firebase.functions; FirebaseFunctions(firebase.app().functions(region)) } + rethrow { FirebaseFunctions(getFunctions(regionOrCustomDomain = region)) } actual fun Firebase.functions(app: FirebaseApp) = - rethrow { dev.gitlive.firebase.functions; FirebaseFunctions(firebase.functions(app.js)) } + rethrow { FirebaseFunctions(getFunctions(app.js)) } actual fun Firebase.functions(app: FirebaseApp, region: String) = - rethrow { dev.gitlive.firebase.functions; FirebaseFunctions(app.js.functions(region)) } + rethrow { FirebaseFunctions(getFunctions(app.js, region)) } -actual class FirebaseFunctions internal constructor(val js: firebase.functions.Functions) { +actual class FirebaseFunctions internal constructor(val js: Functions) { actual fun httpsCallable(name: String, timeout: Long?) = - rethrow { HttpsCallableReference(js.httpsCallable(name, timeout?.let { json("timeout" to timeout.toDouble()) })) } + rethrow { HttpsCallableReference(httpsCallable(js, name, timeout?.let { json("timeout" to timeout.toDouble()) })) } - actual fun useEmulator(host: String, port: Int) = js.useEmulator(host, port) + actual fun useEmulator(host: String, port: Int) = connectFunctionsEmulator(js, host, port) } @Suppress("UNCHECKED_CAST") -actual class HttpsCallableReference internal constructor(val js: firebase.functions.HttpsCallable) { +actual class HttpsCallableReference internal constructor(val js: HttpsCallable) { actual suspend operator fun invoke() = rethrow { HttpsCallableResult(js().await()) } @@ -42,7 +45,7 @@ actual class HttpsCallableReference internal constructor(val js: firebase.functi rethrow { HttpsCallableResult(js(encode(strategy, data, encodeDefaults)).await()) } } -actual class HttpsCallableResult constructor(val js: firebase.functions.HttpsCallableResult) { +actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { actual inline fun data() = rethrow { decode(value = js.data) } diff --git a/firebase-installations/package.json b/firebase-installations/package.json index 0b53f6fe5..94aaac8c5 100644 --- a/firebase-installations/package.json +++ b/firebase-installations/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-installations", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-installations.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/externals/installations.kt b/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/externals/installations.kt new file mode 100644 index 000000000..c2b236f76 --- /dev/null +++ b/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/externals/installations.kt @@ -0,0 +1,17 @@ +@file:JsModule("firebase/installations") +@file:JsNonModule + +package dev.gitlive.firebase.installations.externals + +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Promise + +external fun delete(installations: Installations): Promise + +external fun getId(installations: Installations): Promise + +external fun getInstallations(app: FirebaseApp? = definedExternally): Installations + +external fun getToken(installations: Installations, forceRefresh: Boolean): Promise + +external interface Installations diff --git a/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt b/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt index 0f33b3ac1..7329c3626 100644 --- a/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt +++ b/firebase-installations/src/jsMain/kotlin/dev/gitlive/firebase/installations/installations.kt @@ -1,28 +1,23 @@ package dev.gitlive.firebase.installations import dev.gitlive.firebase.* +import dev.gitlive.firebase.installations.externals.* import kotlinx.coroutines.await actual val Firebase.installations - get() = rethrow { - dev.gitlive.firebase.installations - FirebaseInstallations(firebase.installations()) - } + get() = rethrow { FirebaseInstallations(getInstallations()) } actual fun Firebase.installations(app: FirebaseApp) = - rethrow { - dev.gitlive.firebase.installations - FirebaseInstallations(firebase.installations(app.js)) - } + rethrow { FirebaseInstallations(getInstallations(app.js)) } -actual class FirebaseInstallations internal constructor(val js: firebase.installations.Installations) { +actual class FirebaseInstallations internal constructor(val js: Installations) { - actual suspend fun delete() = rethrow { js.delete().await() } + actual suspend fun delete() = rethrow { delete(js).await() } - actual suspend fun getId(): String = rethrow { js.getId().await() } + actual suspend fun getId(): String = rethrow { getId(js).await() } actual suspend fun getToken(forceRefresh: Boolean): String = - rethrow { js.getToken(forceRefresh).await() } + rethrow { getToken(js, forceRefresh).await() } } actual open class FirebaseInstallationsException(code: String?, cause: Throwable): FirebaseException(code, cause) diff --git a/firebase-perf/package.json b/firebase-perf/package.json index 2135293d4..ac32fe16c 100644 --- a/firebase-perf/package.json +++ b/firebase-perf/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-perf", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-perf.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt index 5a69e221c..6f4fb98ac 100644 --- a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -9,11 +9,12 @@ import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.TestResult import kotlin.test.* expect val emulatorHost: String expect val context: Any -expect fun runTest(test: suspend CoroutineScope.() -> Unit) +expect fun runTest(test: suspend CoroutineScope.() -> Unit): TestResult class FirebasePerformanceTest { diff --git a/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/externals/performance.kt b/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/externals/performance.kt new file mode 100644 index 000000000..dc6a04d0a --- /dev/null +++ b/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/externals/performance.kt @@ -0,0 +1,27 @@ +@file:JsModule("firebase/performance") +@file:JsNonModule + +package dev.gitlive.firebase.perf.externals + +import dev.gitlive.firebase.externals.FirebaseApp + +external fun getPerformance(app: FirebaseApp? = definedExternally): FirebasePerformance + +external fun trace(performance: FirebasePerformance, name: String): PerformanceTrace + +external interface FirebasePerformance { + var dataCollectionEnabled: Boolean + var instrumentationEnabled: Boolean +} + +external interface PerformanceTrace { + fun getAttribute(attr: String): String? + fun getAttributes(): Map + fun getMetric(metricName: String): Int + fun incrementMetric(metricName: String, num: Int? = definedExternally) + fun putAttribute(attr: String, value: String) + fun putMetric(metricName: String, num: Int) + fun removeAttribute(attr: String) + fun start() + fun stop() +} diff --git a/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index ae3ff9be4..2f89a26c7 100644 --- a/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,10 +1,10 @@ package dev.gitlive.firebase.perf.metrics -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.perf.externals.PerformanceTrace import dev.gitlive.firebase.perf.rethrow -actual class Trace internal constructor(private val js: firebase.performance.PerformanceTrace) { +actual class Trace internal constructor(private val js: PerformanceTrace) { actual fun start() = rethrow { js.start() } actual fun stop() = rethrow { js.stop() } diff --git a/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/performance.kt index e96e36777..ed27b5001 100644 --- a/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/jsMain/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -3,24 +3,24 @@ package dev.gitlive.firebase.perf import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.perf.externals.getPerformance +import dev.gitlive.firebase.perf.externals.trace import dev.gitlive.firebase.perf.metrics.Trace +import dev.gitlive.firebase.perf.externals.FirebasePerformance as JsFirebasePerformance actual val Firebase.performance: FirebasePerformance get() = rethrow { - dev.gitlive.firebase.performance - FirebasePerformance(firebase.performance()) + FirebasePerformance(getPerformance()) } actual fun Firebase.performance(app: FirebaseApp): FirebasePerformance = rethrow { - dev.gitlive.firebase.performance - FirebasePerformance(firebase.performance(app.js)) + FirebasePerformance(getPerformance(app.js)) } -actual class FirebasePerformance internal constructor(val js: firebase.performance) { +actual class FirebasePerformance internal constructor(val js: JsFirebasePerformance) { actual fun newTrace(traceName: String): Trace = rethrow { - Trace(js.trace(traceName)) + Trace(trace(js, traceName)) } actual fun isPerformanceCollectionEnabled(): Boolean = js.dataCollectionEnabled diff --git a/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/performance.kt index 85fcd695b..7f68c87d2 100644 --- a/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -15,9 +15,7 @@ actual val emulatorHost: String = "localhost" actual val context: Any = Unit -actual fun runTest(test: suspend CoroutineScope.() -> Unit) { - kotlinx.coroutines.test.runTest { test() } -} +actual fun runTest(test: suspend CoroutineScope.() -> Unit) = kotlinx.coroutines.test.runTest { test() } class JsPerformanceTest { diff --git a/firebase-storage/package.json b/firebase-storage/package.json index b88318437..6b757c5ec 100644 --- a/firebase-storage/package.json +++ b/firebase-storage/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-storage", - "version": "1.9.0", + "version": "1.10.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-storage.js", "scripts": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", "dependencies": { - "@gitlive/firebase-app": "1.9.0", + "@gitlive/firebase-app": "1.10.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt index af32eea56..868d380a5 100644 --- a/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/androidMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -43,7 +43,10 @@ actual class FirebaseStorage(val android: com.google.firebase.storage.FirebaseSt android.useEmulator(host, port) } - actual val reference = StorageReference(android.reference) + actual val reference get() = StorageReference(android.reference) + + actual fun reference(location: String )= StorageReference(android.getReference(location)) + } actual class StorageReference(val android: com.google.firebase.storage.StorageReference) { @@ -62,6 +65,8 @@ actual class StorageReference(val android: com.google.firebase.storage.StorageRe actual suspend fun listAll(): ListResult = ListResult(android.listAll().await()) + actual suspend fun putFile(file: File) = android.putFile(file.uri).await().run {} + actual fun putFileResumable(file: File): ProgressFlow { val android = android.putFile(file.uri) diff --git a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt index 949d8d425..ada31266b 100644 --- a/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/commonMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -20,6 +20,8 @@ expect class FirebaseStorage { fun useEmulator(host: String, port: Int) val reference: StorageReference + fun reference(location: String): StorageReference + } expect class StorageReference { @@ -38,6 +40,8 @@ expect class StorageReference { suspend fun listAll(): ListResult + suspend fun putFile(file: File) + fun putFileResumable(file: File): ProgressFlow } diff --git a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt index e554a574c..3a988926e 100644 --- a/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/iosMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -50,6 +50,8 @@ actual class FirebaseStorage(val ios: FIRStorage) { } actual val reference get() = StorageReference(ios.reference()) + + actual fun reference(location: String) = StorageReference(ios.referenceWithPath(location)) } actual class StorageReference(val ios: FIRStorageReference) { @@ -74,6 +76,8 @@ actual class StorageReference(val ios: FIRStorageReference) { } } + actual suspend fun putFile(file: File) = ios.awaitResult { putFile(file.url, null, completion = it) }.run {} + actual fun putFileResumable(file: File): ProgressFlow { val ios = ios.putFile(file.url) diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt new file mode 100644 index 000000000..dbd7768d4 --- /dev/null +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/externals/storage.kt @@ -0,0 +1,67 @@ +@file:JsModule("firebase/storage") +@file:JsNonModule + +package dev.gitlive.firebase.storage.externals + +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Promise + +external fun getStorage(app: FirebaseApp? = definedExternally): FirebaseStorage + +external fun ref(storage: FirebaseStorage, url: String? = definedExternally): StorageReference +external fun ref(ref: StorageReference, url: String? = definedExternally): StorageReference + +external fun getDownloadURL(ref: StorageReference): Promise + +external fun uploadBytes(ref: StorageReference, file: dynamic): Promise + +external fun uploadBytesResumable(ref: StorageReference, data: dynamic): UploadTask + +external fun deleteObject(ref: StorageReference): Promise; + +external fun listAll(ref: StorageReference): Promise; + +external fun connectFirestoreEmulator( + storage: FirebaseStorage, + host: String, + port: Double, + options: Any? = definedExternally +) + +external interface FirebaseStorage { + var maxOperationRetryTime: Double + var maxUploadRetryTime: Double +} + +external interface StorageReference { + val bucket: String + val fullPath: String + val name: String + val parent: StorageReference? + val root: StorageReference + val storage: FirebaseStorage +} + +external open class ListResult { + val items: Array + val nextPageToken: String + val prefixes: Array +} + +external interface StorageError + +external interface UploadTaskSnapshot { + val bytesTransferred: Number + val ref: StorageReference + val state: String + val task: UploadTask + val totalBytes: Number +} + +external class UploadTask : Promise { + fun cancel(): Boolean; + fun on(event: String, next: (snapshot: UploadTaskSnapshot) -> Unit, error: (a: StorageError) -> Unit, complete: () -> Unit): () -> Unit + fun pause(): Boolean; + fun resume(): Boolean; + val snapshot: UploadTaskSnapshot +} diff --git a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt index ea7e51bab..9ba0380d5 100644 --- a/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/jsMain/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -7,7 +7,7 @@ package dev.gitlive.firebase.storage import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.firebase +import dev.gitlive.firebase.storage.externals.* import kotlinx.coroutines.await import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.awaitClose @@ -15,33 +15,35 @@ import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emitAll - -actual val Firebase.storage get() = - rethrow { dev.gitlive.firebase.storage; FirebaseStorage(firebase.storage()) } +actual val Firebase.storage + get() = FirebaseStorage(getStorage()) actual fun Firebase.storage(app: FirebaseApp) = - rethrow { dev.gitlive.firebase.storage; FirebaseStorage(firebase.app().storage()) } + FirebaseStorage(getStorage(app.js)) -actual class FirebaseStorage(val js: firebase.storage.Storage) { +actual class FirebaseStorage(val js: dev.gitlive.firebase.storage.externals.FirebaseStorage) { actual val maxOperationRetryTimeMillis = js.maxOperationRetryTime.toLong() actual val maxUploadRetryTimeMillis = js.maxUploadRetryTime.toLong() actual fun setMaxOperationRetryTimeMillis(maxOperationRetryTimeMillis: Long) { - js.setMaxOperationRetryTime(maxOperationRetryTimeMillis.toDouble()) + js.maxOperationRetryTime = maxOperationRetryTimeMillis.toDouble() } actual fun setMaxUploadRetryTimeMillis(maxUploadRetryTimeMillis: Long) { - js.setMaxUploadRetryTime(maxUploadRetryTimeMillis.toDouble()) + js.maxUploadRetryTime = maxUploadRetryTimeMillis.toDouble() } actual fun useEmulator(host: String, port: Int) { - js.useEmulator(host, port) + connectFirestoreEmulator(js, host, port.toDouble()) } - actual val reference: StorageReference get() = StorageReference(js.ref()) + actual val reference: StorageReference get() = StorageReference(ref(js)) + + actual fun reference(location: String) = rethrow { StorageReference(ref(js, location)) } + } -actual class StorageReference(val js: firebase.storage.Reference) { +actual class StorageReference(val js: dev.gitlive.firebase.storage.externals.StorageReference) { actual val path: String get() = js.fullPath actual val name: String get() = js.name actual val bucket: String get() = js.bucket @@ -49,16 +51,18 @@ actual class StorageReference(val js: firebase.storage.Reference) { actual val root: StorageReference get() = StorageReference(js.root) actual val storage: FirebaseStorage get() = FirebaseStorage(js.storage) - actual fun child(path: String): StorageReference = StorageReference(js.child(path)) + actual fun child(path: String): StorageReference = StorageReference(ref(js, path)) + + actual suspend fun delete() = rethrow { deleteObject(js).await() } - actual suspend fun delete() = rethrow { js.delete().await() } + actual suspend fun getDownloadUrl(): String = rethrow { getDownloadURL(js).await().toString() } - actual suspend fun getDownloadUrl(): String = rethrow { js.getDownloadURL().await().toString() } + actual suspend fun listAll(): ListResult = rethrow { ListResult(listAll(js).await()) } - actual suspend fun listAll(): ListResult = rethrow { ListResult(js.listAll().await()) } + actual suspend fun putFile(file: File): Unit = rethrow { uploadBytes(js, file).await() } actual fun putFileResumable(file: File): ProgressFlow = rethrow { - val uploadTask = js.put(file) + val uploadTask = uploadBytesResumable(js, file) val flow = callbackFlow { val unsubscribe = uploadTask.on( @@ -88,7 +92,7 @@ actual class StorageReference(val js: firebase.storage.Reference) { } -actual class ListResult(js: firebase.storage.ListResult) { +actual class ListResult(js: dev.gitlive.firebase.storage.externals.ListResult) { actual val prefixes: List = js.prefixes.map { StorageReference(it) } actual val items: List = js.items.map { StorageReference(it) } actual val pageToken: String? = js.nextPageToken diff --git a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt index 284915533..2d1f07ca4 100644 --- a/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt +++ b/firebase-storage/src/jvmMain/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt @@ -31,6 +31,10 @@ actual class FirebaseStorage { actual val reference: StorageReference get() = TODO("Not yet implemented") + actual fun reference(location: String): StorageReference { + TODO("Not yet implemented") + } + } actual class StorageReference { @@ -66,6 +70,9 @@ actual class StorageReference { TODO("Not yet implemented") } + actual suspend fun putFile(file: File) { + } + } actual class ListResult { diff --git a/gradle.properties b/gradle.properties index 470af0404..6384d22a4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,7 +41,7 @@ firebase-app.skipJsTests=false firebase-auth.skipJsTests=false firebase-common.skipJsTests=false firebase-config.skipJsTests=false -firebase-database.skipJsTests=true +firebase-database.skipJsTests=false firebase-firestore.skipJsTests=false firebase-functions.skipJsTests=false firebase-installations.skipJsTests=false @@ -49,14 +49,14 @@ firebase-perf.skipJsTests=false firebase-storage.skipJsTests=false # Versions: -firebase-app.version=1.9.0 -firebase-auth.version=1.9.0 -firebase-common.version=1.9.0 -firebase-config.version=1.9.0 -firebase-database.version=1.9.0 -firebase-firestore.version=1.9.0 -firebase-functions.version=1.9.0 -firebase-installations.version=1.9.0 -firebase-perf.version=1.9.0 -firebase-crashlytics.version=1.9.0 -firebase-storage.version=1.9.0 +firebase-app.version=1.10.0 +firebase-auth.version=1.10.0 +firebase-common.version=1.10.0 +firebase-config.version=1.10.0 +firebase-database.version=1.10.0 +firebase-firestore.version=1.10.0 +firebase-functions.version=1.10.0 +firebase-installations.version=1.10.0 +firebase-perf.version=1.10.0 +firebase-crashlytics.version=1.10.0 +firebase-storage.version=1.10.0 diff --git a/test/firebase.json b/test/firebase.json index fa2ab410a..5b55e4f2c 100644 --- a/test/firebase.json +++ b/test/firebase.json @@ -2,6 +2,9 @@ "firestore": { "rules": "firestore.rules" }, + "storage": { + "rules": "storage.rules" + }, "database": { "rules": "database.rules.json" },