diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..5aae8fff1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*.{kt,kts}] +ktlint_code_style = intellij_idea +ktlint_ignore_back_ticked_identifier = true + +ktlint_standard = enabled +ktlint_standard_no-wildcard-imports = disabled +ktlint_standard_filename = disabled +ktlint_standard_import-ordering = disabled +ktlint_standard_function-naming = disabled + +ktlint_experimental = disabled + diff --git a/.github/actions/setup_test_action/action.yml b/.github/actions/setup_test_action/action.yml index d6fb24503..8e88ccacb 100644 --- a/.github/actions/setup_test_action/action.yml +++ b/.github/actions/setup_test_action/action.yml @@ -5,7 +5,7 @@ runs: using: "composite" steps: - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' @@ -18,7 +18,7 @@ runs: shell: bash run: npm update - name: Gradle cache - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v4 - name: Grant execute permission for gradlew shell: bash run: chmod +x gradlew @@ -29,4 +29,4 @@ runs: shell: bash run: | firebase emulators:start --config=./test/firebase.json & - wait-on http://127.0.0.1:9099 \ No newline at end of file + wait-on http://127.0.0.1:9099 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..10ef83118 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4f2821da7..273eef84e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,25 +15,29 @@ env: jobs: build: - runs-on: macos-13 + runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' - - uses: gradle/gradle-build-action@v2 + - uses: gradle/actions/setup-gradle@v4 - name: Setup versions run: ./gradlew :updateVersions - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Publish Firebase Analytics + run: ./gradlew :firebase-analytics:publish - name: Publish Firebase App run: ./gradlew :firebase-app:publish - name: Publish Firebase Auth run: ./gradlew :firebase-auth:publish - name: Publish Firebase Common run: ./gradlew :firebase-common:publish + - name: Publish Firebase Common Internal + run: ./gradlew :firebase-common-internal:publish - name: Publish Firebase Config run: ./gradlew :firebase-config:publish - name: Publish Firebase Database @@ -42,6 +46,8 @@ jobs: run: ./gradlew :firebase-firestore:publish - name: Publish Firebase Functions run: ./gradlew :firebase-functions:publish + - name: Publish Firebase Messaging + run: ./gradlew :firebase-messaging:publish - name: Publish Firebase Storage run: ./gradlew :firebase-storage:publish - name: Publish Firebase Installations @@ -49,4 +55,29 @@ jobs: - name: Publish Firebase Performance run: ./gradlew :firebase-perf:publish - name: Publish Firebase Crashlytics - run: ./gradlew :firebase-crashlytics:publish \ No newline at end of file + run: ./gradlew :firebase-crashlytics:publish + documentation: + runs-on: macos-13 + steps: + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - uses: gradle/gradle-build-action@v3 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Generate documentation + run: ./gradlew dokkaHtmlMultiModule + - name: Uploading build folder + uses: actions/upload-artifact@v4 + with: + name: artefact + path: build/dokka/htmlMultiModule + - name: Deploy API documentation to Github Pages + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: build/dokka/htmlMultiModule + target-folder: docs diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b6a614b7c..f9f9f79f3 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,109 +8,171 @@ on: branches: [ master, prerelease ] jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - name: ktLint + run: ./gradlew lintKotlin + - name: run apiCheck + run: ./gradlew apiCheck + jobMatrixSetup: + runs-on: ubuntu-latest + outputs: + emulator_jobs_matrix: ${{ steps.dataStep.outputs.emulator_jobs_matrix }} + ios_test_jobs_matrix: ${{ steps.dataStep.outputs.ios_test_jobs_matrix }} + js_test_jobs_matrix: ${{ steps.dataStep.outputs.js_test_jobs_matrix }} + jvm_test_jobs_matrix: ${{ steps.dataStep.outputs.jvm_test_jobs_matrix }} + steps: + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + cache: gradle + - name: Prepare the matrix JSON + run: ./gradlew ciJobsMatrixSetup + - id: dataStep + run: | + echo " + emulator_jobs_matrix=$(jq -c . < ./build/emulator_jobs_matrix.json) + ios_test_jobs_matrix=$(jq -c . < ./build/ios_test_jobs_matrix.json) + js_test_jobs_matrix=$(jq -c . < ./build/js_test_jobs_matrix.json) + jvm_test_jobs_matrix=$(jq -c . < ./build/jvm_test_jobs_matrix.json) + " >> $GITHUB_OUTPUT build-android: - runs-on: macos-13 + needs: jobMatrixSetup + runs-on: ubuntu-latest strategy: - matrix: - api-level: [ 34 ] + fail-fast: false + matrix: ${{ fromJson(needs.jobMatrixSetup.outputs.emulator_jobs_matrix) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Setup test environment uses: ./.github/actions/setup_test_action - - name: AVD cache - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }}-${{ runner.os }}-${{ runner.arch }} - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86_64 - target: google_apis - avd-name: pixel6_API${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." + - name: Set Artifact Name + run: | + echo "ARCHIVE_KEY=$(echo ${{ matrix.gradle_tasks }} | cut -d: -f2)" >> $GITHUB_ENV + - name: Apply Android licenses + run: ./gradlew ciSdkManagerLicenses - name: Run Android Instrumented Tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86_64 - target: google_apis - avd-name: pixel6_API${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: ./gradlew connectedAndroidTest + run: ./gradlew ${{ matrix.gradle_tasks }} - name: Upload Android test artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: - name: "Android Test Report HTML" + name: Android ${{ env.ARCHIVE_KEY }} Test Report HTML path: "**/build/reports/androidTests/" - name: Upload Firebase Debug Log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: - name: "Firebase Debug Log" + name: Android ${{ env.ARCHIVE_KEY }} Firebase Debug Log path: "**/firebase-debug.log" build-js: + needs: jobMatrixSetup runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.jobMatrixSetup.outputs.js_test_jobs_matrix) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup test environment uses: ./.github/actions/setup_test_action + timeout-minutes: 10 + - name: Set Artifact Name + run: | + echo "ARCHIVE_KEY=$(echo ${{ matrix.gradle_tasks }} | cut -d: -f2)" >> $GITHUB_ENV - name: Run JS Tests - run: ./gradlew cleanTest jsTest + run: ./gradlew ${{ matrix.gradle_tasks }} - name: Upload JS test artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: - name: "JS Test Report HTML" + name: JS ${{ env.ARCHIVE_KEY }} Test Report HTML path: | **/build/reports/tests/jsTest/ **/build/reports/tests/jsBrowserTest/ **/build/reports/tests/jsNodeTest/ - name: Upload Firebase Debug Log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: - name: "Firebase Debug Log" + name: JS ${{ env.ARCHIVE_KEY }} Firebase Debug Log path: "**/firebase-debug.log" build-ios: - runs-on: macos-13 + needs: jobMatrixSetup + runs-on: macos-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.jobMatrixSetup.outputs.ios_test_jobs_matrix) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cocoapods cache - uses: actions/cache@v3 - id: cocoapods-cache + uses: actions/cache@v4 with: path: | ~/.cocoapods ~/Library/Caches/CocoaPods */build/cocoapods */build/classes - key: cocoapods-cache + key: cocoapods-cache-v2 - name: Setup test environment uses: ./.github/actions/setup_test_action + - name: Set Artifact Name + run: | + echo "ARCHIVE_KEY=$(echo ${{ matrix.gradle_tasks }} | cut -d: -f2)" >> $GITHUB_ENV - name: Run iOS Tests - run: ./gradlew cleanTest iosX64Test + run: ./gradlew ${{ matrix.gradle_tasks }} - name: Upload iOS test artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: - name: "iOS Test Report HTML" - path: "**/build/reports/tests/iosX64Test/" + name: iOS ${{ env.ARCHIVE_KEY }} Test Report HTML + path: "**/build/reports/tests/iosSimulatorArm64Test/" - name: Upload Firebase Debug Log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: - name: "Firebase Debug Log" + name: iOS ${{ env.ARCHIVE_KEY }} Firebase Debug Log path: "**/firebase-debug.log" - + build-jvm: + needs: jobMatrixSetup + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.jobMatrixSetup.outputs.jvm_test_jobs_matrix) }} + steps: + - uses: actions/checkout@v4 + - name: Setup test environment + uses: ./.github/actions/setup_test_action + timeout-minutes: 10 + - name: Set Artifact Name + run: | + echo "ARCHIVE_KEY=$(echo ${{ matrix.gradle_tasks }} | cut -d: -f2)" >> $GITHUB_ENV + - name: Run JVM Tests + run: ./gradlew ${{ matrix.gradle_tasks }} + - name: Upload JVM test artifact + uses: actions/upload-artifact@v4 + if: failure() + with: + name: JVM ${{ env.ARCHIVE_KEY }} Test Report HTML + path: | + **/build/reports/tests/jvmTest/ + - name: Upload Firebase Debug Log + uses: actions/upload-artifact@v4 + if: failure() + with: + name: JVM ${{ env.ARCHIVE_KEY }} Firebase Debug Log + path: "**/firebase-debug.log" \ No newline at end of file diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..86097d791 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,23 @@ +name: Push + +on: [ push ] + +jobs: + formatKotlin: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + - name: formatKotlin + run: ./gradlew formatKotlin + - uses: stefanzweifel/git-auto-commit-action@v5 + - name: lintKotlin + run: ./gradlew lintKotlin diff --git a/.gitignore b/.gitignore index adbab484d..5891965c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Project exclude paths -/.gradle/ +/**/.gradle/ /**/build/ /.idea/ local.properties @@ -11,3 +11,5 @@ Firebase*.zip *.log /kotlin-js-store/ /kotlin-js-store/yarn.lock + +.kotlin/ diff --git a/README.md b/README.md index 23b728aa3..f92376210 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Firebase Kotlin SDK GitHub last commit

+

Firebase Kotlin SDK GitHub last commit

Built and maintained with 🧡 by GitLive
Development teams merge faster with GitLive
@@ -14,18 +14,19 @@ Firebase as a backend for ()`. +To support [contextual serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization) or [open polymorphism](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism) the `serializersModule` can be overridden in the `buildSettings` closure: + +```kotlin +@Serializable +abstract class AbstractCity { + abstract val name: String +} + +@Serializable +@SerialName("capital") +data class Capital(override val name: String, val isSeatOfGovernment: Boolean) : AbstractCity() + +val module = SerializersModule { + polymorphic(AbstractCity::class, AbstractCity.serializer()) { + subclass(Capital::class, Capital.serializer()) + } +} + +val city = Capital("London", true) +db.collection("cities").document("UK").set(AbstractCity.serializer(), city) { + encodeDefaults = true + serializersModule = module + +} +val storedCity = db.collection("cities").document("UK").get().data(AbstractCity.serializer()) { + serializersModule = module +} +```

Server Timestamp

@@ -184,21 +215,39 @@ user.updateProfile(profileUpdates) //...becomes... -user.updateProfile(displayName = "state", photoURL = "CA") +user.updateProfile(displayName = "Jane Q. User", photoURL = "https://example.com/jane-q-user/profile.jpg") ``` -

Named arguments

-To improve readability functions such as the Cloud Firestore query operators use named arguments: + +

Infix notation

+ +To improve readability and reduce boilerplate for functions such as the Cloud Firestore query operators are built with infix notation: ```kotlin citiesRef.whereEqualTo("state", "CA") citiesRef.whereArrayContains("regions", "west_coast") +citiesRef.where(Filter.and( + Filter.equalTo("state", "CA"), + Filter.or( + Filter.equalTo("capital", true), + Filter.greaterThanOrEqualTo("population", 1000000) + ) +)) //...becomes... -citiesRef.where("state", equalTo = "CA") -citiesRef.where("regions", arrayContains = "west_coast") +citiesRef.where { "state" equalTo "CA" } +citiesRef.where { "regions" contains "west_coast" } +citiesRef.where { + all( + "state" equalTo "CA", + any( + "capital" equalTo true, + "population" greaterThanOrEqualTo 1000000 + ) + ) +} ```

Operator overloading

@@ -213,13 +262,13 @@ In cases where it makes sense, such as Firebase Functions HTTPS Callable, operat ## Multiplatform -The Firebase Kotlin SDK provides a common API to access Firebase for projects targeting *iOS*, *Android* and *JS* meaning you can use Firebase directly in your common code. Under the hood, the SDK achieves this by binding to the respective official Firebase SDK for each supported platform. +The Firebase Kotlin SDK provides a common API to access Firebase for projects targeting *iOS*, *Android*, *JVM* and *JS* meaning you can use Firebase directly in your common code. Under the hood, the SDK achieves this by binding to the respective official Firebase SDK for each supported platform. -It uses the Firebase Java SDK to support the JVM target. +It uses the Firebase Java SDK to support the JVM target. The library requires [additional initialization](https://github.com/GitLiveApp/firebase-java-sdk?tab=readme-ov-file#initializing-the-sdk) compared to the official Firebase SDKs. ### Accessing the underlying Firebase SDK -In some cases you might want to access the underlying official Firebase SDK in platform specific code, for example when the common API is missing the functionality you need. For this purpose each class in the SDK has `android`, `ios` and `js` properties which holds the equivalent object of the underlying official Firebase SDK. +In some cases you might want to access the underlying official Firebase SDK in platform specific code, for example when the common API is missing the functionality you need. For this purpose each class in the SDK has `android`, `ios` and `js` extension properties that hold the equivalent object of the underlying official Firebase SDK. For *JVM*, as the `firebase-java-sdk` is a direct port of the Firebase Android SDK, is it also accessed via the `android` property. These properties are only accessible from the equivalent target's source set. For example to disable persistence in Cloud Firestore on Android you can write the following in your Android specific code (e.g. `androidMain` or `androidTest`): @@ -229,6 +278,17 @@ These properties are only accessible from the equivalent target's source set. Fo .build() ``` +### Running on iOS + +On iOS the official [Firebase iOS SDK](https://github.com/firebase/firebase-ios-sdk) in not linked as a transtive dependency. Therefore, any project using this SDK needs to link the actual Firestore SDK as well. This can be done through your preferred installation method (Cocoapods/SPM). + +Similarly, tests require linking as well. Make sure to add the required frameworks to the search path of your test targets. This can be done by specifying a `cocoapods` block in your `build.gradle`: +```kotlin +cocoapods { + pod("FirebaseCore") // Repeat for Firebase pods required by your project, e.g FirebaseFirestore for the `firebase-firestore` module. +} +``` + ## Contributing If you'd like to contribute to this project then you can fork this repository. You can build and test the project locally. @@ -236,3 +296,45 @@ You can build and test the project locally. 2. Install cocoapods via `sudo gem install -n /usr/local/bin cocoapods` 3. Install the GitLive plugin into IntelliJ 4. After a gradle sync then run `publishToMavenLocal` + +### Testing +To run the tests you can use the following gradle tasks: + +`./gradlew connectedAndroidTest` (an emulator needs to be running) + +`./gradlew iosSimulatorArm64Test` (On Apple Chip) `./gradlew iosX64Test` (On Intel Chip) + +`./gradlew jsNodeTest` + +`./gradlew jvmTest` + +For some tests you need to have the firebase emulator suite running. After installing them you can run the following command inside the `test` directory: + +`firebase emulators:start` + +### Documentation +For every publicly available class or function there needs to be documentation written in the [KDoc syntax](https://kotlinlang.org/docs/kotlin-doc.html). +We publish a new version of the documentation after every release and can be found [here](https://gitliveapp.github.io/firebase-kotlin-sdk/) + +### Validation +To ensure changes to the public API are well documented, this library validates its binary API. If you make changes to the API, make sure to run + +`./gradlew apiDump` + +### Code style +This library uses the [Intellij Kotlin code style](https://www.jetbrains.com/help/idea/code-style-kotlin.html). Run the linter to make sure you follow these styles. + +`./gradlew formatKotlin` to format to the proper style +`./gradlew lintKotlin` to validate the correct style is used + +### Compatibility with the official [Firebase Android SDK](https://github.com/firebase/firebase-android-sdk) + +When this project began, the official Firebase Android SDK was a pure java library and the separate Kotlin extensions (KTX) module consisted of only a few extensions providing some syntactic sugar, for example `Firebase.firestore` instead of `Firebase.getInstance(),` which we naturally copied for the Firebase Kotlin SDK. + +But with the majority of the Android SDK being designed for Java, the Kotlin SDK was primarily guided by our [Kotlin-first design](https://github.com/GitLiveApp/firebase-kotlin-sdk/?tab=readme-ov-file#kotlin-first-design) principles. + +More recently, with the official SDK for Android providing better support for Kotlin and the inclusion of the new Kotlin-friendly features direct in the main modules, the API differences between the official SDK and this project are likely to start to blur. Therefore, in particular for developers porting android code to multiplatform, one of our goals going forward will be API compatibility with the Android SDK where possible. + +For contributors this means following these points when adding new code to the public API of this project: +- **Match the [Android SDKs API](https://firebase.google.com/docs/reference/kotlin/packages).** When adding new API coverage use the Android SDK as the guide on what the public API should be in regard to naming, parameters etc. The goal here is *near binary compatibility*, meaning code consuming the Android SDK compiles *as is* with the Kotlin SDK after just changing the package imports from `com.google` to `dev.gitlive`. +- **Follow our [Kotlin-first design](https://github.com/GitLiveApp/firebase-kotlin-sdk/?tab=readme-ov-file#kotlin-first-design) principles when needed.** If the API you are adding coverage for is new, and it's Kotlin-first in the Android SDK, then you can simply just match the Android SDKs API as described in the first point, but if it's an older Java-first API then ideally we would include an identical API for API compatibility *plus* a Kotlin-first overload. A good example for this is where the Builder pattern is employed in the Android SDK, here we can follow [this Kotlin-first design principle](https://github.com/GitLiveApp/firebase-kotlin-sdk/?tab=readme-ov-file#default-arguments) and provide both methods, one taking the options created with the builder and an overload with default arguments to avoid the builder boilerplate for developers not porting an existing android code base. diff --git a/build.gradle.kts b/build.gradle.kts index 1a1cfc7fe..ac5150679 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,41 +1,42 @@ +import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent -import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask - -repositories { - google() - mavenCentral() -} +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.DokkaBaseConfiguration +import org.jetbrains.dokka.gradle.AbstractDokkaTask +import org.jetbrains.dokka.gradle.DokkaTaskPartial +import java.net.URL +import java.io.InputStream plugins { - kotlin("multiplatform") apply false - kotlin("native.cocoapods") apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlinx.serialization) apply false + alias(libs.plugins.multiplatform) apply false + alias(libs.plugins.native.cocoapods) apply false + alias(libs.plugins.test.logger.plugin) apply false + alias(libs.plugins.ben.manes.versions) apply false + alias(libs.plugins.kotlinter) apply false + alias(libs.plugins.kotlinx.binarycompatibilityvalidator) + alias(libs.plugins.dokka) id("base") - id("com.github.ben-manes.versions") version "0.42.0" + id("testOptionsConvention") } buildscript { - repositories { - google() - mavenCentral() - gradlePluginPortal() - maven { - url = uri("https://plugins.gradle.org/m2/") - } - } dependencies { - classpath("com.android.tools.build:gradle:${project.extra["gradlePluginVersion"]}") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.extra["kotlinVersion"]}") - classpath("com.adarshr:gradle-test-logger-plugin:3.2.0") + classpath(libs.dokka.base) } } val compileSdkVersion by extra(34) -val minSdkVersion by extra(23) +val targetSdkVersion by extra(34) +val minSdkVersion by extra(21) tasks { register("updateVersions") { dependsOn( + "firebase-analytics:updateVersion", "firebase-analytics:updateDependencyVersion", "firebase-app:updateVersion", "firebase-app:updateDependencyVersion", "firebase-auth:updateVersion", "firebase-auth:updateDependencyVersion", "firebase-common:updateVersion", "firebase-common:updateDependencyVersion", @@ -44,17 +45,69 @@ tasks { "firebase-firestore:updateVersion", "firebase-firestore:updateDependencyVersion", "firebase-functions:updateVersion", "firebase-functions:updateDependencyVersion", "firebase-installations:updateVersion", "firebase-installations:updateDependencyVersion", + "firebase-messaging:updateVersion", "firebase-messaging:updateDependencyVersion", "firebase-perf:updateVersion", "firebase-perf:updateDependencyVersion", "firebase-storage:updateVersion", "firebase-storage:updateDependencyVersion" ) } } +private val dokkaCopyrightMessage = "© 2024 GitLive Ltd." +private val dokkaHomepageUrl = "https://github.com/GitLiveApp/firebase-kotlin-sdk" + +tasks.withType().configureEach { + val version = project.property("firebase-app.version") as String + moduleVersion.set(version) + moduleName.set("Firebase Kotlin SDK") + + pluginConfiguration { + customAssets = listOf(file("documentation/gitlive-logo.png"), file("documentation/homepage.svg")) + customStyleSheets = listOf(file("documentation/logo-styles.css")) + footerMessage = dokkaCopyrightMessage + homepageLink = dokkaHomepageUrl + } +} + subprojects { group = "dev.gitlive" - + + val nonDocumentationList = listOf("test-utils", "firebase-common", "firebase-common-internal") + val skipDocumentation = nonDocumentationList.contains(project.name) + if (!skipDocumentation) { + apply(plugin = "org.jetbrains.dokka") + } + + this.tasks.withType().configureEach { + pluginConfiguration { + footerMessage = dokkaCopyrightMessage + separateInheritedMembers = false + homepageLink = dokkaHomepageUrl + } + dokkaSourceSets { + configureEach { + documentedVisibilities.set(setOf(DokkaConfiguration.Visibility.PUBLIC)) + includes.setFrom("documentation.md") + + sourceLink { + localDirectory.set(projectDir.resolve("src")) + remoteUrl.set(URL("$dokkaHomepageUrl/tree/master/${project.name}/src")) + } + } + if (this.names.contains("jsMain")) { + named("jsMain") { + perPackageOption { + // External files for JS should not be documented since they will not be available + matchingRegex.set(".*.externals.*") + suppress.set(true) + } + } + } + } + } + apply(plugin = "com.adarshr.test-logger") + apply(plugin = "org.jmailen.kotlinter") repositories { mavenLocal() @@ -106,31 +159,27 @@ subprojects { } afterEvaluate { - - val coroutinesVersion: String by project - val firebaseBoMVersion: String by project - dependencies { - "commonMainImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") - "androidMainImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion") - "androidMainImplementation"(platform("com.google.firebase:firebase-bom:$firebaseBoMVersion")) + "commonMainImplementation"(libs.kotlinx.coroutines.core) + "androidMainImplementation"(libs.kotlinx.coroutines.play.services) + "androidMainImplementation"(platform(libs.firebase.bom)) "commonTestImplementation"(kotlin("test-common")) "commonTestImplementation"(kotlin("test-annotations-common")) if (this@afterEvaluate.name != "firebase-crashlytics") { - "jvmMainApi"("dev.gitlive:firebase-java-sdk:0.1.2") - "jvmMainApi"("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion") { + "jvmMainApi"(libs.gitlive.firebase.java.sdk) + "jvmMainApi"(libs.kotlinx.coroutines.play.services) { exclude("com.google.android.gms") } "jsTestImplementation"(kotlin("test-js")) "jvmTestImplementation"(kotlin("test-junit")) - "jvmTestImplementation"("junit:junit:4.13.2") + "jvmTestImplementation"(libs.junit) } "androidInstrumentedTestImplementation"(kotlin("test-junit")) "androidUnitTestImplementation"(kotlin("test-junit")) - "androidInstrumentedTestImplementation"("junit:junit:4.13.2") - "androidInstrumentedTestImplementation"("androidx.test:core:1.5.0") - "androidInstrumentedTestImplementation"("androidx.test.ext:junit:1.1.5") - "androidInstrumentedTestImplementation"("androidx.test:runner:1.5.2") + "androidInstrumentedTestImplementation"(libs.junit) + "androidInstrumentedTestImplementation"(libs.androidx.test.core) + "androidInstrumentedTestImplementation"(libs.androidx.test.junit) + "androidInstrumentedTestImplementation"(libs.androidx.test.runner) } } @@ -222,3 +271,56 @@ tasks.withType>() + gradleTasks.addAll(EmulatorJobsMatrix().getJvmTestTaskList(rootProject = rootProject)) + gradleTasks.addAll(EmulatorJobsMatrix().getJsTestTaskList(rootProject = rootProject)) + gradleTasks.addAll(EmulatorJobsMatrix().getIosTestTaskList(rootProject = rootProject)) + gradleTasks.add(listOf("ciSdkManagerLicenses")) + gradleTasks.addAll(EmulatorJobsMatrix().getEmulatorTaskList(rootProject = rootProject)) + gradleTasks.forEach { + exec { + executable = File( + project.rootDir, + if (Os.isFamily(Os.FAMILY_WINDOWS)) "gradlew.bat" else "gradlew", + ) + .also { it.setExecutable(true) } + .absolutePath + args = it + println("exec: ${this.commandLine.joinToString(separator = " ")}") + }.apply { println("ExecResult: $this") } + } + } +} + +tasks.register("ciJobsMatrixSetup") { + doLast { + EmulatorJobsMatrix().createMatrixJsonFiles(rootProject = rootProject) + } +} + +tasks.register("ciSdkManagerLicenses") { + doLast { + val sdkDirPath = getAndroidSdkPath(rootDir = rootDir) + getSdkmanagerFile(rootDir = rootDir)?.let { sdkmanagerFile -> + val yesInputStream = object : InputStream() { + private val yesString = "y\n" + private var counter = 0 + override fun read(): Int = yesString[counter % 2].also { counter++ }.code + } + exec { + executable = sdkmanagerFile.absolutePath + args = listOf("--list", "--sdk_root=$sdkDirPath") + println("exec: ${this.commandLine.joinToString(separator = " ")}") + }.apply { println("ExecResult: $this") } + exec { + executable = sdkmanagerFile.absolutePath + args = listOf("--licenses", "--sdk_root=$sdkDirPath") + standardInput = yesInputStream + println("exec: ${this.commandLine.joinToString(separator = " ")}") + }.apply { println("ExecResult: $this") } + } + } +} diff --git a/convention-plugin-test-option/build.gradle.kts b/convention-plugin-test-option/build.gradle.kts new file mode 100644 index 000000000..67975cc59 --- /dev/null +++ b/convention-plugin-test-option/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + `kotlin-dsl` +} + +dependencies { + compileOnly(libs.android.gradle.plugin) + compileOnly(libs.gson) +} diff --git a/convention-plugin-test-option/settings.gradle.kts b/convention-plugin-test-option/settings.gradle.kts new file mode 100644 index 000000000..5f0643dfc --- /dev/null +++ b/convention-plugin-test-option/settings.gradle.kts @@ -0,0 +1,11 @@ +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/convention-plugin-test-option/src/main/kotlin/EmulatorJobsMatrix.kt b/convention-plugin-test-option/src/main/kotlin/EmulatorJobsMatrix.kt new file mode 100644 index 000000000..5f216f4c6 --- /dev/null +++ b/convention-plugin-test-option/src/main/kotlin/EmulatorJobsMatrix.kt @@ -0,0 +1,107 @@ +import com.google.gson.GsonBuilder +import org.gradle.api.Project +import java.io.File +import java.util.Properties + +class EmulatorJobsMatrix { + + private val gson by lazy { + GsonBuilder() + .disableHtmlEscaping() + .setPrettyPrinting() + .create() + } + + fun createMatrixJsonFiles(rootProject: Project) { + mapOf( + "emulator_jobs_matrix.json" to getEmulatorTaskList(rootProject = rootProject), + "ios_test_jobs_matrix.json" to getIosTestTaskList(rootProject = rootProject), + "js_test_jobs_matrix.json" to getJsTestTaskList(rootProject = rootProject), + "jvm_test_jobs_matrix.json" to getJvmTestTaskList(rootProject = rootProject) + ) + .mapValues { entry -> entry.value.map { it.joinToString(separator = " ") } } + .forEach { (fileName: String, taskList: List) -> + val matrix = mapOf("gradle_tasks" to taskList) + val jsonText = gson.toJson(matrix) + rootProject.layout.buildDirectory.asFile.get().also { buildDir -> + buildDir.mkdirs() + File(buildDir, fileName).writeText(jsonText) + } + } + } + + fun getIosTestTaskList(rootProject: Project): List> = + rootProject.subprojects.filter { subProject -> + subProject.name == "test-utils" || + (rootProject.property("${subProject.name}.skipIosTests") == "true").not() + }.map { subProject -> + when (val osArch = System.getProperty("os.arch")) { + "x86", "i386", "ia-32", "i686" -> "${subProject.path}:iosX86Test" + "x86_64", "amd64", "x64", "x86-64" -> "${subProject.path}:iosX64Test" + "arm", "arm-v7", "armv7", "arm32", + "arm64", "arm-v8", "aarch64" -> "${subProject.path}:iosSimulatorArm64Test" + + else -> throw Error("Unexpected System.getProperty(\"os.arch\") = $osArch") + } + }.map { listOf("cleanTest", it) } + + fun getJsTestTaskList(rootProject: Project): List> = + rootProject.subprojects.filter { subProject -> + subProject.name == "test-utils" || + (rootProject.property("${subProject.name}.skipJsTests") == "true").not() + }.map { subProject -> + "${subProject.path}:jsTest" + }.map { listOf("cleanTest", it) } + + fun getJvmTestTaskList(rootProject: Project): List> = + rootProject.subprojects.filter { subProject -> + subProject.name == "test-utils" || + (rootProject.property("${subProject.name}.skipJvmTests") == "true").not() + }.map { subProject -> + "${subProject.path}:jvmTest" + }.map { listOf("cleanTest", it) } + + fun getEmulatorTaskList(rootProject: Project): List> = + rootProject.subprojects.filter { subProject -> + File(subProject.projectDir, "src${File.separator}commonTest").exists() || + File( + subProject.projectDir, + "src${File.separator}androidInstrumentedTest" + ).exists() + }.map { subProject -> + "${subProject.path}:gradleManagedDeviceDebugAndroidTest" + }.map { taskName -> + mutableListOf(taskName).also { + it.add("--no-parallel") + it.add("--max-workers=1") + it.add("-Pandroid.testoptions.manageddevices.emulator.gpu=swiftshader_indirect") + it.add("-Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true") + }.also { + if (!true.toString().equals(other = System.getenv("CI"), ignoreCase = true)) { + it.add("--enable-display") + } + } + } +} + +fun getAndroidSdkPath(rootDir: File): String? = + Properties().apply { + val propertiesFile = File(rootDir, "local.properties") + if (propertiesFile.exists()) { + load(propertiesFile.inputStream()) + } + }.getProperty("sdk.dir").let { propertiesSdkDirPath -> + (propertiesSdkDirPath ?: System.getenv("ANDROID_HOME")) + } + +fun getSdkmanagerFile(rootDir: File): File? = + getAndroidSdkPath(rootDir = rootDir)?.let { sdkDirPath -> + println("sdkDirPath: $sdkDirPath") + val files = File(sdkDirPath).walk().filter { file -> + file.path.contains("cmdline-tools") && file.path.endsWith("sdkmanager") + } + files.forEach { println("walk: ${it.absolutePath}") } + val sdkmanagerFile = files.firstOrNull() + println("sdkmanagerFile: $sdkmanagerFile") + sdkmanagerFile + } diff --git a/convention-plugin-test-option/src/main/kotlin/TestOptionsConfig.kt b/convention-plugin-test-option/src/main/kotlin/TestOptionsConfig.kt new file mode 100644 index 000000000..765ef822e --- /dev/null +++ b/convention-plugin-test-option/src/main/kotlin/TestOptionsConfig.kt @@ -0,0 +1,36 @@ +import com.android.build.api.dsl.ManagedVirtualDevice +import com.android.build.api.dsl.TestOptions +import org.gradle.api.Project +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.provideDelegate + +fun TestOptions.configureTestOptions(project: Project) { + val targetSdkVersion: Int by project + targetSdk = targetSdkVersion + unitTests { + isIncludeAndroidResources = true + all { test: org.gradle.api.tasks.testing.Test -> + test.testLogging { + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + events = setOf( + org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED, + org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED, + org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED, + org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_OUT, + org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR, + org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED + ) + } + } + } + animationsDisabled = true + emulatorSnapshots { + enableForTestFailures = false + } + managedDevices.devices.create("gradleManagedDevice") { + device = "Pixel 2" + apiLevel = 33 + systemImageSource = "google-atd" + require64Bit = true + } +} diff --git a/convention-plugin-test-option/src/main/kotlin/testOptionsConvention.gradle.kts b/convention-plugin-test-option/src/main/kotlin/testOptionsConvention.gradle.kts new file mode 100644 index 000000000..4f298e665 --- /dev/null +++ b/convention-plugin-test-option/src/main/kotlin/testOptionsConvention.gradle.kts @@ -0,0 +1,2 @@ +plugins { +} diff --git a/documentation/gitlive-logo.png b/documentation/gitlive-logo.png new file mode 100644 index 000000000..c8ccd128e Binary files /dev/null and b/documentation/gitlive-logo.png differ diff --git a/documentation/homepage.svg b/documentation/homepage.svg new file mode 100644 index 000000000..4bf93759e --- /dev/null +++ b/documentation/homepage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/documentation/logo-styles.css b/documentation/logo-styles.css new file mode 100644 index 000000000..7b9f8fa86 --- /dev/null +++ b/documentation/logo-styles.css @@ -0,0 +1,5 @@ +:root { + --dokka-logo-image-url: url('../images/gitlive-logo.png'); + --dokka-logo-height: 50px; + --dokka-logo-width: 50px; +} \ No newline at end of file diff --git a/firebase-analytics/api/android/firebase-analytics.api b/firebase-analytics/api/android/firebase-analytics.api new file mode 100644 index 000000000..80e01f521 --- /dev/null +++ b/firebase-analytics/api/android/firebase-analytics.api @@ -0,0 +1,211 @@ +public final class dev/gitlive/firebase/analytics/AnalyticEventConstantsKt { + public static final fun getEvent (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsEvents; + public static final fun getParam (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParam; + public static final fun getUserProperty (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsUserProperty; +} + +public final class dev/gitlive/firebase/analytics/AnalyticsKt { + public static final fun logEvent (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public static final fun setConsent (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;Lkotlin/jvm/functions/Function1;)V + public static final fun setSessionTimeoutInterval (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;J)V +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalytics { + public fun (Lcom/google/firebase/analytics/FirebaseAnalytics;)V + public final fun getSessionId (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun logEvent (Ljava/lang/String;Ljava/util/Map;)V + public static synthetic fun logEvent$default (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V + public final fun resetAnalyticsData ()V + public final fun setAnalyticsCollectionEnabled (Z)V + public final fun setConsent (Ljava/util/Map;)V + public final fun setDefaultEventParameters (Ljava/util/Map;)V + public final fun setSessionTimeoutInterval-LRDsOJo (J)V + public final fun setUserId (Ljava/lang/String;)V + public final fun setUserProperty (Ljava/lang/String;Ljava/lang/String;)V +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus : java/lang/Enum { + public static final field DENIED Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public static final field GRANTED Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public static fun values ()[Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType : java/lang/Enum { + public static final field AD_PERSONALIZATION Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static final field AD_STORAGE Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static final field AD_USER_DATA Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static final field ANALYTICS_STORAGE Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static fun values ()[Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder { + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder; + public static synthetic fun copy$default (Ldev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder;Ljava/util/Map;ILjava/lang/Object;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder; + public fun equals (Ljava/lang/Object;)Z + public final fun getAdPersonalization ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getAdStorage ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getAdUserData ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getAnalyticsStorage ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getConsentSettings ()Ljava/util/Map; + public fun hashCode ()I + public final fun setAdPersonalization (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public final fun setAdStorage (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public final fun setAdUserData (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public final fun setAnalyticsStorage (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsEvents { + public static final field ADD_PAYMENT_INFO Ljava/lang/String; + public static final field ADD_SHIPPING_INFO Ljava/lang/String; + public static final field ADD_TO_CART Ljava/lang/String; + public static final field ADD_TO_WISHLIST Ljava/lang/String; + public static final field AD_IMPRESSION Ljava/lang/String; + public static final field APP_OPEN Ljava/lang/String; + public static final field BEGIN_CHECKOUT Ljava/lang/String; + public static final field CAMPAIGN_DETAILS Ljava/lang/String; + public static final field EARN_VIRTUAL_CURRENCY Ljava/lang/String; + public static final field GENERATE_LEAD Ljava/lang/String; + public static final field INSTANCE Ldev/gitlive/firebase/analytics/FirebaseAnalyticsEvents; + public static final field JOIN_GROUP Ljava/lang/String; + public static final field LEVEL_END Ljava/lang/String; + public static final field LEVEL_START Ljava/lang/String; + public static final field LEVEL_UP Ljava/lang/String; + public static final field LOGIN Ljava/lang/String; + public static final field POST_SCORE Ljava/lang/String; + public static final field PURCHASE Ljava/lang/String; + public static final field REFUND Ljava/lang/String; + public static final field REMOVE_FROM_CART Ljava/lang/String; + public static final field SCREEN_VIEW Ljava/lang/String; + public static final field SEARCH Ljava/lang/String; + public static final field SELECT_CONTENT Ljava/lang/String; + public static final field SELECT_ITEM Ljava/lang/String; + public static final field SELECT_PROMOTION Ljava/lang/String; + public static final field SHARE Ljava/lang/String; + public static final field SIGN_UP Ljava/lang/String; + public static final field SPEND_VIRTUAL_CURRENCY Ljava/lang/String; + public static final field TUTORIAL_BEGIN Ljava/lang/String; + public static final field TUTORIAL_COMPLETE Ljava/lang/String; + public static final field UNLOCK_ACHIEVEMENT Ljava/lang/String; + public static final field VIEW_CART Ljava/lang/String; + public static final field VIEW_ITEM Ljava/lang/String; + public static final field VIEW_ITEM_LIST Ljava/lang/String; + public static final field VIEW_PROMOTION Ljava/lang/String; + public static final field VIEW_SEARCH_RESULTS Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsException : java/lang/Exception { + public fun (Ljava/lang/String;)V +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsParam { + public static final field ACHIEVEMENT_ID Ljava/lang/String; + public static final field ACLID Ljava/lang/String; + public static final field AD_FORMAT Ljava/lang/String; + public static final field AD_PLATFORM Ljava/lang/String; + public static final field AD_SOURCE Ljava/lang/String; + public static final field AD_UNIT_NAME Ljava/lang/String; + public static final field AFFILIATION Ljava/lang/String; + public static final field CAMPAIGN Ljava/lang/String; + public static final field CAMPAIGN_ID Ljava/lang/String; + public static final field CHARACTER Ljava/lang/String; + public static final field CONTENT Ljava/lang/String; + public static final field CONTENT_TYPE Ljava/lang/String; + public static final field COUPON Ljava/lang/String; + public static final field CP1 Ljava/lang/String; + public static final field CREATIVE_FORMAT Ljava/lang/String; + public static final field CREATIVE_NAME Ljava/lang/String; + public static final field CREATIVE_SLOT Ljava/lang/String; + public static final field CURRENCY Ljava/lang/String; + public static final field DESTINATION Ljava/lang/String; + public static final field DISCOUNT Ljava/lang/String; + public static final field END_DATE Ljava/lang/String; + public static final field EXTEND_SESSION Ljava/lang/String; + public static final field FLIGHT_NUMBER Ljava/lang/String; + public static final field GROUP_ID Ljava/lang/String; + public static final field INDEX Ljava/lang/String; + public static final field INSTANCE Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParam; + public static final field ITEMS Ljava/lang/String; + public static final field ITEM_BRAND Ljava/lang/String; + public static final field ITEM_CATEGORY Ljava/lang/String; + public static final field ITEM_CATEGORY2 Ljava/lang/String; + public static final field ITEM_CATEGORY3 Ljava/lang/String; + public static final field ITEM_CATEGORY4 Ljava/lang/String; + public static final field ITEM_CATEGORY5 Ljava/lang/String; + public static final field ITEM_ID Ljava/lang/String; + public static final field ITEM_LIST_ID Ljava/lang/String; + public static final field ITEM_LIST_NAME Ljava/lang/String; + public static final field ITEM_NAME Ljava/lang/String; + public static final field ITEM_VARIANT Ljava/lang/String; + public static final field LEVEL Ljava/lang/String; + public static final field LEVEL_NAME Ljava/lang/String; + public static final field LOCATION Ljava/lang/String; + public static final field LOCATION_ID Ljava/lang/String; + public static final field MARKETING_TACTIC Ljava/lang/String; + public static final field MEDIUM Ljava/lang/String; + public static final field METHOD Ljava/lang/String; + public static final field NUMBER_OF_NIGHTS Ljava/lang/String; + public static final field NUMBER_OF_PASSENGERS Ljava/lang/String; + public static final field NUMBER_OF_ROOMS Ljava/lang/String; + public static final field ORIGIN Ljava/lang/String; + public static final field PAYMENT_TYPE Ljava/lang/String; + public static final field PRICE Ljava/lang/String; + public static final field PROMOTION_ID Ljava/lang/String; + public static final field PROMOTION_NAME Ljava/lang/String; + public static final field QUANTITY Ljava/lang/String; + public static final field SCORE Ljava/lang/String; + public static final field SCREEN_CLASS Ljava/lang/String; + public static final field SCREEN_NAME Ljava/lang/String; + public static final field SEARCH_TERM Ljava/lang/String; + public static final field SHIPPING Ljava/lang/String; + public static final field SHIPPING_TIER Ljava/lang/String; + public static final field SOURCE Ljava/lang/String; + public static final field SOURCE_PLATFORM Ljava/lang/String; + public static final field START_DATE Ljava/lang/String; + public static final field SUCCESS Ljava/lang/String; + public static final field TAX Ljava/lang/String; + public static final field TERM Ljava/lang/String; + public static final field TRANSACTION_ID Ljava/lang/String; + public static final field TRAVEL_CLASS Ljava/lang/String; + public static final field VALUE Ljava/lang/String; + public static final field VIRTUAL_CURRENCY_NAME Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsParameters { + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParameters; + public static synthetic fun copy$default (Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParameters;Ljava/util/Map;ILjava/lang/Object;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParameters; + public fun equals (Ljava/lang/Object;)Z + public final fun getParameters ()Ljava/util/Map; + public fun hashCode ()I + public final fun param (Ljava/lang/String;D)V + public final fun param (Ljava/lang/String;I)V + public final fun param (Ljava/lang/String;J)V + public final fun param (Ljava/lang/String;Ljava/lang/String;)V + public final fun param (Ljava/lang/String;Z)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsUserProperty { + public static final field ALLOW_AD_PERSONALIZATION_SIGNALS Ljava/lang/String; + public static final field INSTANCE Ldev/gitlive/firebase/analytics/FirebaseAnalyticsUserProperty; + public static final field SIGN_UP_METHOD Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/analyticsAndroid { + public static final fun analytics (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics; + public static final fun getAnalytics (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics; + public static final fun getAndroid (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;)Lcom/google/firebase/analytics/FirebaseAnalytics; +} + diff --git a/firebase-analytics/api/jvm/firebase-analytics.api b/firebase-analytics/api/jvm/firebase-analytics.api new file mode 100644 index 000000000..e165ffccb --- /dev/null +++ b/firebase-analytics/api/jvm/firebase-analytics.api @@ -0,0 +1,209 @@ +public final class dev/gitlive/firebase/analytics/AnalyticEventConstantsKt { + public static final fun getEvent (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsEvents; + public static final fun getParam (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParam; + public static final fun getUserProperty (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsUserProperty; +} + +public final class dev/gitlive/firebase/analytics/AnalyticsKt { + public static final fun logEvent (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public static final fun setConsent (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;Lkotlin/jvm/functions/Function1;)V + public static final fun setSessionTimeoutInterval (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;J)V +} + +public final class dev/gitlive/firebase/analytics/Analytics_jvmKt { + public static final fun analytics (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics; + public static final fun getAnalytics (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalytics { + public fun ()V + public final fun getSessionId (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun logEvent (Ljava/lang/String;Ljava/util/Map;)V + public static synthetic fun logEvent$default (Ldev/gitlive/firebase/analytics/FirebaseAnalytics;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V + public final fun resetAnalyticsData ()V + public final fun setAnalyticsCollectionEnabled (Z)V + public final fun setConsent (Ljava/util/Map;)V + public final fun setDefaultEventParameters (Ljava/util/Map;)V + public final fun setSessionTimeoutInterval-LRDsOJo (J)V + public final fun setUserId (Ljava/lang/String;)V + public final fun setUserProperty (Ljava/lang/String;Ljava/lang/String;)V +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus : java/lang/Enum { + public static final field DENIED Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public static final field GRANTED Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public static fun values ()[Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType : java/lang/Enum { + public static final field AD_PERSONALIZATION Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static final field AD_STORAGE Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static final field AD_USER_DATA Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static final field ANALYTICS_STORAGE Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; + public static fun values ()[Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentType; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder { + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder; + public static synthetic fun copy$default (Ldev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder;Ljava/util/Map;ILjava/lang/Object;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsConsentBuilder; + public fun equals (Ljava/lang/Object;)Z + public final fun getAdPersonalization ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getAdStorage ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getAdUserData ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getAnalyticsStorage ()Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus; + public final fun getConsentSettings ()Ljava/util/Map; + public fun hashCode ()I + public final fun setAdPersonalization (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public final fun setAdStorage (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public final fun setAdUserData (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public final fun setAnalyticsStorage (Ldev/gitlive/firebase/analytics/FirebaseAnalytics$ConsentStatus;)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsEvents { + public static final field ADD_PAYMENT_INFO Ljava/lang/String; + public static final field ADD_SHIPPING_INFO Ljava/lang/String; + public static final field ADD_TO_CART Ljava/lang/String; + public static final field ADD_TO_WISHLIST Ljava/lang/String; + public static final field AD_IMPRESSION Ljava/lang/String; + public static final field APP_OPEN Ljava/lang/String; + public static final field BEGIN_CHECKOUT Ljava/lang/String; + public static final field CAMPAIGN_DETAILS Ljava/lang/String; + public static final field EARN_VIRTUAL_CURRENCY Ljava/lang/String; + public static final field GENERATE_LEAD Ljava/lang/String; + public static final field INSTANCE Ldev/gitlive/firebase/analytics/FirebaseAnalyticsEvents; + public static final field JOIN_GROUP Ljava/lang/String; + public static final field LEVEL_END Ljava/lang/String; + public static final field LEVEL_START Ljava/lang/String; + public static final field LEVEL_UP Ljava/lang/String; + public static final field LOGIN Ljava/lang/String; + public static final field POST_SCORE Ljava/lang/String; + public static final field PURCHASE Ljava/lang/String; + public static final field REFUND Ljava/lang/String; + public static final field REMOVE_FROM_CART Ljava/lang/String; + public static final field SCREEN_VIEW Ljava/lang/String; + public static final field SEARCH Ljava/lang/String; + public static final field SELECT_CONTENT Ljava/lang/String; + public static final field SELECT_ITEM Ljava/lang/String; + public static final field SELECT_PROMOTION Ljava/lang/String; + public static final field SHARE Ljava/lang/String; + public static final field SIGN_UP Ljava/lang/String; + public static final field SPEND_VIRTUAL_CURRENCY Ljava/lang/String; + public static final field TUTORIAL_BEGIN Ljava/lang/String; + public static final field TUTORIAL_COMPLETE Ljava/lang/String; + public static final field UNLOCK_ACHIEVEMENT Ljava/lang/String; + public static final field VIEW_CART Ljava/lang/String; + public static final field VIEW_ITEM Ljava/lang/String; + public static final field VIEW_ITEM_LIST Ljava/lang/String; + public static final field VIEW_PROMOTION Ljava/lang/String; + public static final field VIEW_SEARCH_RESULTS Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsException : com/google/firebase/FirebaseException { +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsParam { + public static final field ACHIEVEMENT_ID Ljava/lang/String; + public static final field ACLID Ljava/lang/String; + public static final field AD_FORMAT Ljava/lang/String; + public static final field AD_PLATFORM Ljava/lang/String; + public static final field AD_SOURCE Ljava/lang/String; + public static final field AD_UNIT_NAME Ljava/lang/String; + public static final field AFFILIATION Ljava/lang/String; + public static final field CAMPAIGN Ljava/lang/String; + public static final field CAMPAIGN_ID Ljava/lang/String; + public static final field CHARACTER Ljava/lang/String; + public static final field CONTENT Ljava/lang/String; + public static final field CONTENT_TYPE Ljava/lang/String; + public static final field COUPON Ljava/lang/String; + public static final field CP1 Ljava/lang/String; + public static final field CREATIVE_FORMAT Ljava/lang/String; + public static final field CREATIVE_NAME Ljava/lang/String; + public static final field CREATIVE_SLOT Ljava/lang/String; + public static final field CURRENCY Ljava/lang/String; + public static final field DESTINATION Ljava/lang/String; + public static final field DISCOUNT Ljava/lang/String; + public static final field END_DATE Ljava/lang/String; + public static final field EXTEND_SESSION Ljava/lang/String; + public static final field FLIGHT_NUMBER Ljava/lang/String; + public static final field GROUP_ID Ljava/lang/String; + public static final field INDEX Ljava/lang/String; + public static final field INSTANCE Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParam; + public static final field ITEMS Ljava/lang/String; + public static final field ITEM_BRAND Ljava/lang/String; + public static final field ITEM_CATEGORY Ljava/lang/String; + public static final field ITEM_CATEGORY2 Ljava/lang/String; + public static final field ITEM_CATEGORY3 Ljava/lang/String; + public static final field ITEM_CATEGORY4 Ljava/lang/String; + public static final field ITEM_CATEGORY5 Ljava/lang/String; + public static final field ITEM_ID Ljava/lang/String; + public static final field ITEM_LIST_ID Ljava/lang/String; + public static final field ITEM_LIST_NAME Ljava/lang/String; + public static final field ITEM_NAME Ljava/lang/String; + public static final field ITEM_VARIANT Ljava/lang/String; + public static final field LEVEL Ljava/lang/String; + public static final field LEVEL_NAME Ljava/lang/String; + public static final field LOCATION Ljava/lang/String; + public static final field LOCATION_ID Ljava/lang/String; + public static final field MARKETING_TACTIC Ljava/lang/String; + public static final field MEDIUM Ljava/lang/String; + public static final field METHOD Ljava/lang/String; + public static final field NUMBER_OF_NIGHTS Ljava/lang/String; + public static final field NUMBER_OF_PASSENGERS Ljava/lang/String; + public static final field NUMBER_OF_ROOMS Ljava/lang/String; + public static final field ORIGIN Ljava/lang/String; + public static final field PAYMENT_TYPE Ljava/lang/String; + public static final field PRICE Ljava/lang/String; + public static final field PROMOTION_ID Ljava/lang/String; + public static final field PROMOTION_NAME Ljava/lang/String; + public static final field QUANTITY Ljava/lang/String; + public static final field SCORE Ljava/lang/String; + public static final field SCREEN_CLASS Ljava/lang/String; + public static final field SCREEN_NAME Ljava/lang/String; + public static final field SEARCH_TERM Ljava/lang/String; + public static final field SHIPPING Ljava/lang/String; + public static final field SHIPPING_TIER Ljava/lang/String; + public static final field SOURCE Ljava/lang/String; + public static final field SOURCE_PLATFORM Ljava/lang/String; + public static final field START_DATE Ljava/lang/String; + public static final field SUCCESS Ljava/lang/String; + public static final field TAX Ljava/lang/String; + public static final field TERM Ljava/lang/String; + public static final field TRANSACTION_ID Ljava/lang/String; + public static final field TRAVEL_CLASS Ljava/lang/String; + public static final field VALUE Ljava/lang/String; + public static final field VIRTUAL_CURRENCY_NAME Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsParameters { + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParameters; + public static synthetic fun copy$default (Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParameters;Ljava/util/Map;ILjava/lang/Object;)Ldev/gitlive/firebase/analytics/FirebaseAnalyticsParameters; + public fun equals (Ljava/lang/Object;)Z + public final fun getParameters ()Ljava/util/Map; + public fun hashCode ()I + public final fun param (Ljava/lang/String;D)V + public final fun param (Ljava/lang/String;I)V + public final fun param (Ljava/lang/String;J)V + public final fun param (Ljava/lang/String;Ljava/lang/String;)V + public final fun param (Ljava/lang/String;Z)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/analytics/FirebaseAnalyticsUserProperty { + public static final field ALLOW_AD_PERSONALIZATION_SIGNALS Ljava/lang/String; + public static final field INSTANCE Ldev/gitlive/firebase/analytics/FirebaseAnalyticsUserProperty; + public static final field SIGN_UP_METHOD Ljava/lang/String; +} + diff --git a/firebase-analytics/build.gradle.kts b/firebase-analytics/build.gradle.kts new file mode 100644 index 000000000..d5ec0a598 --- /dev/null +++ b/firebase-analytics/build.gradle.kts @@ -0,0 +1,169 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree + +/* + * Copyright (c) 2023 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +version = project.property("firebase-analytics.version") as String + +plugins { + id("com.android.library") + kotlin("native.cocoapods") + kotlin("multiplatform") + id("testOptionsConvention") +} + +android { + val minSdkVersion: Int by project + val compileSdkVersion: Int by project + + compileSdk = compileSdkVersion + namespace = "dev.gitlive.firebase.analytics" + + defaultConfig { + minSdk = minSdkVersion + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + testOptions.configureTestOptions(project) + packaging { + resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") + resources.pickFirsts.add("META-INF/AL2.0") + resources.pickFirsts.add("META-INF/LGPL2.1") + } + lint { + abortOnError = false + } +} + +val supportIosTarget = project.property("skipIosTarget") != "true" + +kotlin { + explicitApi() + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + targets.configureEach { + compilations.configureEach { + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } + } + } + + @Suppress("OPT_IN_USAGE") + androidTarget { + instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + publishAllLibraryVariants() + } + + jvm() + + if (supportIosTarget) { + iosArm64() + iosX64() + iosSimulatorArm64() + cocoapods { + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() + framework { + baseName = "FirebaseAnalytics" + } + noPodspec() + pod("FirebaseAnalytics") { + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") + } + } + } + + js(IR) { + useCommonJs() + nodejs { + testTask { + useKarma { + useChromeHeadless() + } + } + } + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + } + + sourceSets { + all { + languageSettings.apply { + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() + progressiveMode = true + if (name.lowercase().contains("ios")) { + optIn("kotlinx.cinterop.ExperimentalForeignApi") + } + } + } + + getByName("commonMain") { + dependencies { + api(project(":firebase-app")) + implementation(project(":firebase-common")) + } + } + + getByName("commonTest") { + dependencies { + implementation(project(":test-utils")) + } + } + + getByName("androidMain") { + dependencies { + api(libs.google.firebase.analytics) + } + } + } +} + +if (project.property("firebase-analytics.skipIosTests") == "true") { + tasks.forEach { + if (it.name.contains("ios", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-analytics.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-analytics.skipJsTests") == "true") { + tasks.forEach { + if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) +} diff --git a/firebase-analytics/documentation.md b/firebase-analytics/documentation.md new file mode 100644 index 000000000..c26c410d6 --- /dev/null +++ b/firebase-analytics/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-analytics +This module is a direct forward of the Firebase Analytics library. It provides the main functionality, like logging events. \ No newline at end of file diff --git a/firebase-analytics/firebase_analytics.podspec b/firebase-analytics/firebase_analytics.podspec new file mode 100644 index 000000000..74c7cb847 --- /dev/null +++ b/firebase-analytics/firebase_analytics.podspec @@ -0,0 +1,39 @@ +Pod::Spec.new do |spec| + spec.name = 'firebase_analytics' + spec.version = '1.8.1' + spec.homepage = '' + spec.source = { :http=> ''} + spec.authors = '' + spec.license = '' + spec.summary = '' + spec.vendored_frameworks = 'build/cocoapods/framework/firebase_analytics.framework' + spec.libraries = 'c++' + + + + spec.pod_target_xcconfig = { + 'KOTLIN_PROJECT_PATH' => ':firebase-analytics', + 'PRODUCT_MODULE_NAME' => 'firebase_analytics', + } + + spec.script_phases = [ + { + :name => 'Build firebase_analytics', + :execution_position => :before_compile, + :shell_path => '/bin/sh', + :script => <<-SCRIPT + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" + exit 0 + fi + set -ev + REPO_ROOT="$PODS_TARGET_SRCROOT" + "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ + -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ + -Pkotlin.native.cocoapods.archs="$ARCHS" \ + -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" + SCRIPT + } + ] + +end diff --git a/firebase-analytics/package.json b/firebase-analytics/package.json new file mode 100644 index 000000000..da4f8da37 --- /dev/null +++ b/firebase-analytics/package.json @@ -0,0 +1,31 @@ +{ + "name": "@gitlive/firebase-analytics", + "version": "2.0.0", + "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", + "main": "firebase-analytics.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/GitLiveApp/firebase-kotlin-sdk.git" + }, + "keywords": [ + "kotlin", + "multiplatform", + "kotlin-js", + "firebase" + ], + "author": "dev.gitlive", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/GitLiveApp/firebase-kotlin-sdk/issues" + }, + "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", + "dependencies": { + "@gitlive/firebase-app": "2.0.0", + "firebase": "9.19.1", + "kotlin": "1.6.10", + "kotlinx-coroutines-core": "1.6.1-native-mt" + } +} diff --git a/firebase-analytics/src/androidInstrumentedTest/AndroidManifest.xml b/firebase-analytics/src/androidInstrumentedTest/AndroidManifest.xml new file mode 100644 index 000000000..3d8df4081 --- /dev/null +++ b/firebase-analytics/src/androidInstrumentedTest/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/firebase-analytics/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..2f6d67096 --- /dev/null +++ b/firebase-analytics/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:JvmName("tests") + +package dev.gitlive.firebase.analytics + +import androidx.test.platform.app.InstrumentationRegistry + +actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-analytics/src/androidMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/androidMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..b41f10c69 --- /dev/null +++ b/firebase-analytics/src/androidMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,100 @@ +@file:JvmName("analyticsAndroid") + +package dev.gitlive.firebase.analytics + +import android.os.Bundle +import com.google.firebase.analytics.analytics +import com.google.firebase.analytics.setConsent +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import kotlinx.coroutines.tasks.await +import kotlin.time.Duration + +public actual val Firebase.analytics: FirebaseAnalytics + get() = FirebaseAnalytics(com.google.firebase.Firebase.analytics) + +public actual fun Firebase.analytics(app: FirebaseApp): FirebaseAnalytics = + FirebaseAnalytics(com.google.firebase.Firebase.analytics) + +public val FirebaseAnalytics.android: com.google.firebase.analytics.FirebaseAnalytics get() = android + +public actual class FirebaseAnalytics(internal val android: com.google.firebase.analytics.FirebaseAnalytics) { + public actual fun logEvent(name: String, parameters: Map?) { + android.logEvent(name, parameters?.toBundle()) + } + public actual fun setUserProperty(name: String, value: String) { + android.setUserProperty(name, value) + } + public actual fun setUserId(id: String?) { + android.setUserId(id) + } + public actual fun resetAnalyticsData() { + android.resetAnalyticsData() + } + public actual fun setDefaultEventParameters(parameters: Map) { + android.setDefaultEventParameters(parameters.toBundle()) + } + + public actual fun setAnalyticsCollectionEnabled(enabled: Boolean) { + android.setAnalyticsCollectionEnabled(enabled) + } + + public actual fun setSessionTimeoutInterval(sessionTimeoutInterval: Duration) { + android.setSessionTimeoutDuration(sessionTimeoutInterval.inWholeMilliseconds) + } + + public actual suspend fun getSessionId(): Long? = android.sessionId.await() + + public actual fun setConsent(consentSettings: Map) { + consentSettings.entries.associate { + it.key to when (it.value) { + ConsentStatus.GRANTED -> com.google.firebase.analytics.FirebaseAnalytics.ConsentStatus.GRANTED + ConsentStatus.DENIED -> com.google.firebase.analytics.FirebaseAnalytics.ConsentStatus.DENIED + } + }.let { androidConsentSettings -> + android.setConsent { + androidConsentSettings.entries.forEach { + when (it.key) { + ConsentType.AD_PERSONALIZATION -> + this.adPersonalization = it.value + + ConsentType.AD_STORAGE -> + this.adStorage = it.value + + ConsentType.AD_USER_DATA -> + this.adUserData = it.value + + ConsentType.ANALYTICS_STORAGE -> + this.analyticsStorage = it.value + } + } + } + } + } + + public actual enum class ConsentType { + AD_PERSONALIZATION, + AD_STORAGE, + AD_USER_DATA, + ANALYTICS_STORAGE, + } + + public actual enum class ConsentStatus { + GRANTED, + DENIED, + } +} + +public actual class FirebaseAnalyticsException(message: String) : Exception(message) + +private fun Map.toBundle() = Bundle().apply { + forEach { (key, value) -> + when (value::class) { + String::class -> putString(key, value as String) + Int::class -> putInt(key, value as Int) + Long::class -> putLong(key, value as Long) + Double::class -> putDouble(key, value as Double) + Boolean::class -> putBoolean(key, value as Boolean) + } + } +} diff --git a/firebase-analytics/src/androidUnitTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/androidUnitTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..53eee5cd5 --- /dev/null +++ b/firebase-analytics/src/androidUnitTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.analytics + +import org.junit.Ignore + +actual val context: Any = "" + +actual typealias IgnoreForAndroidUnitTest = Ignore diff --git a/firebase-analytics/src/commonMain/kotlin/dev/gitlive/firebase/analytics/AnalyticEventConstants.kt b/firebase-analytics/src/commonMain/kotlin/dev/gitlive/firebase/analytics/AnalyticEventConstants.kt new file mode 100644 index 000000000..240c8d4f1 --- /dev/null +++ b/firebase-analytics/src/commonMain/kotlin/dev/gitlive/firebase/analytics/AnalyticEventConstants.kt @@ -0,0 +1,127 @@ +@file:Suppress("UnusedReceiverParameter") + +package dev.gitlive.firebase.analytics + +public val FirebaseAnalytics.Event: FirebaseAnalyticsEvents + get() = FirebaseAnalyticsEvents + +public object FirebaseAnalyticsEvents { + public const val ADD_PAYMENT_INFO: String = "add_payment_info" + public const val ADD_SHIPPING_INFO: String = "add_shipping_info" + public const val ADD_TO_CART: String = "add_to_cart" + public const val ADD_TO_WISHLIST: String = "add_to_wishlist" + public const val AD_IMPRESSION: String = "ad_impression" + public const val APP_OPEN: String = "app_open" + public const val BEGIN_CHECKOUT: String = "begin_checkout" + public const val CAMPAIGN_DETAILS: String = "campaign_details" + public const val EARN_VIRTUAL_CURRENCY: String = "earn_virtual_currency" + public const val GENERATE_LEAD: String = "generate_lead" + public const val JOIN_GROUP: String = "join_group" + public const val LEVEL_END: String = "level_end" + public const val LEVEL_START: String = "level_start" + public const val LEVEL_UP: String = "level_up" + public const val LOGIN: String = "login" + public const val POST_SCORE: String = "post_score" + public const val PURCHASE: String = "purchase" + public const val REFUND: String = "refund" + public const val REMOVE_FROM_CART: String = "remove_from_cart" + public const val SCREEN_VIEW: String = "screen_view" + public const val SEARCH: String = "search" + public const val SELECT_CONTENT: String = "select_content" + public const val SELECT_ITEM: String = "select_item" + public const val SELECT_PROMOTION: String = "select_promotion" + public const val SHARE: String = "share" + public const val SIGN_UP: String = "sign_up" + public const val SPEND_VIRTUAL_CURRENCY: String = "spend_virtual_currency" + public const val TUTORIAL_BEGIN: String = "tutorial_begin" + public const val TUTORIAL_COMPLETE: String = "tutorial_complete" + public const val UNLOCK_ACHIEVEMENT: String = "unlock_achievement" + public const val VIEW_CART: String = "view_cart" + public const val VIEW_ITEM: String = "view_item" + public const val VIEW_ITEM_LIST: String = "view_item_list" + public const val VIEW_PROMOTION: String = "view_promotion" + public const val VIEW_SEARCH_RESULTS: String = "view_search_results" +} + +public val FirebaseAnalytics.Param: FirebaseAnalyticsParam + get() = FirebaseAnalyticsParam + +public object FirebaseAnalyticsParam { + public const val ACHIEVEMENT_ID: String = "achievement_id" + public const val ACLID: String = "aclid" + public const val AD_FORMAT: String = "ad_format" + public const val AD_PLATFORM: String = "ad_platform" + public const val AD_SOURCE: String = "ad_source" + public const val AD_UNIT_NAME: String = "ad_unit_name" + public const val AFFILIATION: String = "affiliation" + public const val CAMPAIGN: String = "campaign" + public const val CAMPAIGN_ID: String = "campaign_id" + public const val CHARACTER: String = "character" + public const val CONTENT: String = "content" + public const val CONTENT_TYPE: String = "content_type" + public const val COUPON: String = "coupon" + public const val CP1: String = "cp1" + public const val CREATIVE_FORMAT: String = "creative_format" + public const val CREATIVE_NAME: String = "creative_name" + public const val CREATIVE_SLOT: String = "creative_slot" + public const val CURRENCY: String = "currency" + public const val DESTINATION: String = "destination" + public const val DISCOUNT: String = "discount" + public const val END_DATE: String = "end_date" + public const val EXTEND_SESSION: String = "extend_session" + public const val FLIGHT_NUMBER: String = "flight_number" + public const val GROUP_ID: String = "group_id" + public const val INDEX: String = "index" + public const val ITEMS: String = "items" + public const val ITEM_BRAND: String = "item_brand" + public const val ITEM_CATEGORY: String = "item_category" + public const val ITEM_CATEGORY2: String = "item_category2" + public const val ITEM_CATEGORY3: String = "item_category3" + public const val ITEM_CATEGORY4: String = "item_category4" + public const val ITEM_CATEGORY5: String = "item_category5" + public const val ITEM_ID: String = "item_id" + public const val ITEM_LIST_ID: String = "item_list_id" + public const val ITEM_LIST_NAME: String = "item_list_name" + public const val ITEM_NAME: String = "item_name" + public const val ITEM_VARIANT: String = "item_variant" + public const val LEVEL: String = "level" + public const val LEVEL_NAME: String = "level_name" + public const val LOCATION: String = "location" + public const val LOCATION_ID: String = "location_id" + public const val MARKETING_TACTIC: String = "marketing_tactic" + public const val MEDIUM: String = "medium" + public const val METHOD: String = "method" + public const val NUMBER_OF_NIGHTS: String = "number_of_nights" + public const val NUMBER_OF_PASSENGERS: String = "number_of_passengers" + public const val NUMBER_OF_ROOMS: String = "number_of_rooms" + public const val ORIGIN: String = "origin" + public const val PAYMENT_TYPE: String = "payment_type" + public const val PRICE: String = "price" + public const val PROMOTION_ID: String = "promotion_id" + public const val PROMOTION_NAME: String = "promotion_name" + public const val QUANTITY: String = "quantity" + public const val SCORE: String = "score" + public const val SCREEN_CLASS: String = "screen_class" + public const val SCREEN_NAME: String = "screen_name" + public const val SEARCH_TERM: String = "search_term" + public const val SHIPPING: String = "shipping" + public const val SHIPPING_TIER: String = "shipping_tier" + public const val SOURCE: String = "source" + public const val SOURCE_PLATFORM: String = "source_platform" + public const val START_DATE: String = "start_date" + public const val SUCCESS: String = "success" + public const val TAX: String = "tax" + public const val TERM: String = "term" + public const val TRANSACTION_ID: String = "transaction_id" + public const val TRAVEL_CLASS: String = "travel_class" + public const val VALUE: String = "value" + public const val VIRTUAL_CURRENCY_NAME: String = "virtual_currency_name" +} + +public val FirebaseAnalytics.UserProperty: FirebaseAnalyticsUserProperty + get() = FirebaseAnalyticsUserProperty + +public object FirebaseAnalyticsUserProperty { + public const val ALLOW_AD_PERSONALIZATION_SIGNALS: String = "allow_personalized_ads" + public const val SIGN_UP_METHOD: String = "sign_up_method" +} diff --git a/firebase-analytics/src/commonMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/commonMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..a4fa35880 --- /dev/null +++ b/firebase-analytics/src/commonMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,114 @@ +package dev.gitlive.firebase.analytics + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +public expect val Firebase.analytics: FirebaseAnalytics + +/** Returns the [FirebaseStorage] instance of a given [FirebaseApp]. */ +public expect fun Firebase.analytics(app: FirebaseApp): FirebaseAnalytics + +public expect class FirebaseAnalytics { + public fun logEvent(name: String, parameters: Map? = null) + public fun setUserProperty(name: String, value: String) + public fun setUserId(id: String?) + public fun setAnalyticsCollectionEnabled(enabled: Boolean) + public fun setSessionTimeoutInterval(sessionTimeoutInterval: Duration) + public suspend fun getSessionId(): Long? + public fun resetAnalyticsData() + public fun setDefaultEventParameters(parameters: Map) + public fun setConsent(consentSettings: Map) + + public enum class ConsentType { + AD_PERSONALIZATION, + AD_STORAGE, + AD_USER_DATA, + ANALYTICS_STORAGE, + } + + public enum class ConsentStatus { + GRANTED, + DENIED, + } +} + +@Deprecated("Use Kotlin Duration", replaceWith = ReplaceWith("setSessionTimeoutInterval(sessionTimeoutInterval.milliseconds)")) +public fun FirebaseAnalytics.setSessionTimeoutInterval(sessionTimeoutInterval: Long) { + setSessionTimeoutInterval(sessionTimeoutInterval.milliseconds) +} + +public fun FirebaseAnalytics.setConsent(builder: FirebaseAnalyticsConsentBuilder.() -> Unit) { + val consentBuilder = FirebaseAnalyticsConsentBuilder() + consentBuilder.builder() + setConsent(consentBuilder.consentSettings) +} + +public fun FirebaseAnalytics.logEvent(name: String, builder: FirebaseAnalyticsParameters.() -> Unit) { + val params = FirebaseAnalyticsParameters() + params.builder() + logEvent(name, params.parameters) +} + +public expect class FirebaseAnalyticsException + +public data class FirebaseAnalyticsParameters( + val parameters: MutableMap = mutableMapOf(), +) { + public fun param(key: String, value: String) { + parameters[key] = value + } + + public fun param(key: String, value: Double) { + parameters[key] = value + } + + public fun param(key: String, value: Long) { + parameters[key] = value + } + + public fun param(key: String, value: Int) { + parameters[key] = value + } + + public fun param(key: String, value: Boolean) { + parameters[key] = value + } +} + +public data class FirebaseAnalyticsConsentBuilder( + val consentSettings: MutableMap = mutableMapOf(), +) { + var adPersonalization: FirebaseAnalytics.ConsentStatus? + get() = consentSettings[FirebaseAnalytics.ConsentType.AD_PERSONALIZATION] + set(value) { + value?.let { + consentSettings[FirebaseAnalytics.ConsentType.AD_PERSONALIZATION] = it + } + } + + var adStorage: FirebaseAnalytics.ConsentStatus? + get() = consentSettings[FirebaseAnalytics.ConsentType.AD_STORAGE] + set(value) { + value?.let { + consentSettings[FirebaseAnalytics.ConsentType.AD_STORAGE] = it + } + } + + var adUserData: FirebaseAnalytics.ConsentStatus? + get() = consentSettings[FirebaseAnalytics.ConsentType.AD_USER_DATA] + set(value) { + value?.let { + consentSettings[FirebaseAnalytics.ConsentType.AD_USER_DATA] = it + } + } + + var analyticsStorage: FirebaseAnalytics.ConsentStatus? + get() = consentSettings[FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE] + set(value) { + value?.let { + consentSettings[FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE] = it + } + } +} diff --git a/firebase-analytics/src/commonTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/commonTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..9f1f5c529 --- /dev/null +++ b/firebase-analytics/src/commonTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase.analytics + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertNotNull + +expect val context: Any +expect annotation class IgnoreForAndroidUnitTest() + +@IgnoreForAndroidUnitTest +class FirebaseAnalyticsTest { + + lateinit var analytics: FirebaseAnalytics + + @BeforeTest + fun initializeFirebase() { + val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize( + context, + FirebaseOptions( + applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", + apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", + databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", + storageBucket = "fir-kotlin-sdk.appspot.com", + projectId = "fir-kotlin-sdk", + gcmSenderId = "846484016111", + ), + ) + + analytics = Firebase.analytics(app) + } + + @AfterTest + fun deinitializeFirebase() = runBlockingTest { + Firebase.apps(context).forEach { + it.delete() + } + } + + @Test + fun testAnalyticsShouldNotCrash() { + assertNotNull(analytics) + + // This should not crash, otherwise the test will fail + analytics.logEvent("test") { + param("key", "value") + } + } +} diff --git a/firebase-analytics/src/iosMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/iosMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..70133e694 --- /dev/null +++ b/firebase-analytics/src/iosMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,92 @@ +package dev.gitlive.firebase.analytics + +import cocoapods.FirebaseAnalytics.FIRAnalytics +import cocoapods.FirebaseAnalytics.setConsent +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import kotlinx.coroutines.CompletableDeferred +import platform.Foundation.NSError +import kotlin.time.Duration +import kotlin.time.DurationUnit + +public actual val Firebase.analytics: FirebaseAnalytics + get() = FirebaseAnalytics(FIRAnalytics) + +public actual fun Firebase.analytics(app: FirebaseApp): FirebaseAnalytics = FirebaseAnalytics(FIRAnalytics) + +public val FirebaseAnalytics.ios: FIRAnalytics.Companion get() = ios + +public actual class FirebaseAnalytics(internal val ios: FIRAnalytics.Companion) { + public actual fun logEvent(name: String, parameters: Map?) { + val mappedParameters: Map? = parameters?.map { it.key to it.value }?.toMap() + ios.logEventWithName(name, mappedParameters) + } + public actual fun setUserProperty(name: String, value: String) { + ios.setUserPropertyString(value, name) + } + public actual fun setUserId(id: String?) { + ios.setUserID(id) + } + public actual fun resetAnalyticsData() { + ios.resetAnalyticsData() + } + + public actual fun setAnalyticsCollectionEnabled(enabled: Boolean) { + ios.setAnalyticsCollectionEnabled(enabled) + } + + public actual fun setSessionTimeoutInterval(sessionTimeoutInterval: Duration) { + ios.setSessionTimeoutInterval(sessionTimeoutInterval.toDouble(DurationUnit.SECONDS)) + } + + public actual suspend fun getSessionId(): Long? = ios.awaitResult { sessionIDWithCompletion(it) } + + public actual fun setDefaultEventParameters(parameters: Map) { + val mappedParameters: Map = parameters.map { it.key to it.value }.toMap() + ios.setDefaultEventParameters(mappedParameters) + } + + public actual fun setConsent(consentSettings: Map) { + val mappedConsentSettings: Map = consentSettings.map { it.key.name to it.value.name }.toMap() + ios.setConsent(mappedConsentSettings) + } + + public actual enum class ConsentType { + AD_PERSONALIZATION, + AD_STORAGE, + AD_USER_DATA, + ANALYTICS_STORAGE, + } + + public actual enum class ConsentStatus { + GRANTED, + DENIED, + } +} + +public actual class FirebaseAnalyticsException(message: String) : FirebaseException(message) + +internal suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { + val job = CompletableDeferred() + function { error -> + if (error == null) { + job.complete(Unit) + } else { + job.completeExceptionally(FirebaseAnalyticsException(error.toString())) + } + } + job.await() +} + +internal suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { + val job = CompletableDeferred() + function { result, error -> + if (error == null) { + job.complete(result) + } else { + job.completeExceptionally(FirebaseAnalyticsException(error.toString())) + } + } + return job.await() as R +} diff --git a/firebase-analytics/src/iosTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/iosTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..8fbb53329 --- /dev/null +++ b/firebase-analytics/src/iosTest/kotlin/dev/gitlive/firebase/analytics/analytics.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.analytics + +actual val context: Any = Unit + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-analytics/src/jsMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/jsMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..a27b8e367 --- /dev/null +++ b/firebase-analytics/src/jsMain/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,101 @@ +package dev.gitlive.firebase.analytics + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.analytics.externals.getAnalytics +import dev.gitlive.firebase.js +import kotlinx.coroutines.await +import kotlin.time.Duration + +public actual val Firebase.analytics: FirebaseAnalytics + get() = FirebaseAnalytics(getAnalytics()) + +public actual fun Firebase.analytics(app: FirebaseApp): FirebaseAnalytics = + FirebaseAnalytics(getAnalytics(app.js)) + +public val FirebaseAnalytics.js: dev.gitlive.firebase.analytics.externals.FirebaseAnalytics get() = js + +public actual class FirebaseAnalytics(internal val js: dev.gitlive.firebase.analytics.externals.FirebaseAnalytics) { + public actual fun logEvent( + name: String, + parameters: Map?, + ) { + dev.gitlive.firebase.analytics.externals.logEvent(js, name, parameters) + } + + public actual fun setUserProperty(name: String, value: String) { + dev.gitlive.firebase.analytics.externals.setUserProperty(js, name, value) + } + + public actual fun setUserId(id: String?) { + dev.gitlive.firebase.analytics.externals.setUserId(js, id) + } + + public actual fun setAnalyticsCollectionEnabled(enabled: Boolean) { + dev.gitlive.firebase.analytics.externals.setAnalyticsCollectionEnabled(js, enabled) + } + + public actual fun setSessionTimeoutInterval(sessionTimeoutInterval: Duration) { + dev.gitlive.firebase.analytics.externals.setSessionTimeoutInterval(js, sessionTimeoutInterval.inWholeMilliseconds) + } + + public actual suspend fun getSessionId(): Long? = rethrow { dev.gitlive.firebase.analytics.externals.getSessionId(js).await() } + + public actual fun resetAnalyticsData() { + dev.gitlive.firebase.analytics.externals.resetAnalyticsData(js) + } + + public actual fun setDefaultEventParameters(parameters: Map) { + dev.gitlive.firebase.analytics.externals.setDefaultEventParameters(js, parameters) + } + + public actual fun setConsent(consentSettings: Map) { + val consent = dev.gitlive.firebase.analytics.externals.ConsentSettings() + consentSettings.forEach { + when (it.key) { + ConsentType.AD_PERSONALIZATION -> consent.ad_personalization = it.value.name + ConsentType.AD_STORAGE -> consent.ad_storage = it.value.name + ConsentType.AD_USER_DATA -> consent.ad_user_data = it.value.name + ConsentType.ANALYTICS_STORAGE -> consent.analytics_storage = it.value.name + } + } + dev.gitlive.firebase.analytics.externals.setConsent(js, consent) + } + + public actual enum class ConsentType { + AD_PERSONALIZATION, + AD_STORAGE, + AD_USER_DATA, + ANALYTICS_STORAGE, + } + + public actual enum class ConsentStatus { + GRANTED, + DENIED, + } +} + +public actual open class FirebaseAnalyticsException(code: String, cause: Throwable) : FirebaseException(code, cause) + +internal inline fun rethrow(function: () -> R): R { + try { + return function() + } catch (e: Exception) { + throw e + } catch (e: dynamic) { + throw errorToException(e) + } +} + +internal fun errorToException(error: dynamic) = (error?.code ?: error?.message ?: "") + .toString() + .lowercase() + .let { code -> + when { + else -> { + println("Unknown error code in ${JSON.stringify(error)}") + FirebaseAnalyticsException(code, error.unsafeCast()) + } + } + } diff --git a/firebase-analytics/src/jsMain/kotlin/dev/gitlive/firebase/analytics/externals/analytics.kt b/firebase-analytics/src/jsMain/kotlin/dev/gitlive/firebase/analytics/externals/analytics.kt new file mode 100644 index 000000000..dc29bf175 --- /dev/null +++ b/firebase-analytics/src/jsMain/kotlin/dev/gitlive/firebase/analytics/externals/analytics.kt @@ -0,0 +1,32 @@ +@file:Suppress("ktlint:standard:property-naming", "PropertyName") +@file:JsModule("firebase/analytics") +@file:JsNonModule + +package dev.gitlive.firebase.analytics.externals + +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Promise + +public external fun getAnalytics(app: FirebaseApp? = definedExternally): FirebaseAnalytics + +public external fun logEvent(app: FirebaseAnalytics, name: String, parameters: Map?) +public external fun setUserProperty(app: FirebaseAnalytics, name: String, value: String) +public external fun setUserId(app: FirebaseAnalytics, id: String?) +public external fun resetAnalyticsData(app: FirebaseAnalytics) +public external fun setDefaultEventParameters(app: FirebaseAnalytics, parameters: Map) +public external fun setAnalyticsCollectionEnabled(app: FirebaseAnalytics, enabled: Boolean) +public external fun setSessionTimeoutInterval(app: FirebaseAnalytics, sessionTimeoutInterval: Long) +public external fun getSessionId(app: FirebaseAnalytics): Promise +public external fun setConsent(app: FirebaseAnalytics, consentSettings: ConsentSettings) + +public external interface FirebaseAnalytics + +public external class ConsentSettings { + public var ad_personalization: String? + public var ad_storage: String? + public var ad_user_data: String? + public var analytics_storage: String? + public var functionality_storage: String? + public var personalization_storage: String? + public var security_storage: String? +} diff --git a/firebase-analytics/src/jsTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/jsTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..e9532b753 --- /dev/null +++ b/firebase-analytics/src/jsTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,6 @@ +package dev.gitlive.firebase.analytics + +actual val context: Any = Unit + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-analytics/src/jvmMain/kotlin/dev/gitlive/firebase/analytics/analytics.jvm.kt b/firebase-analytics/src/jvmMain/kotlin/dev/gitlive/firebase/analytics/analytics.jvm.kt new file mode 100644 index 000000000..1c0b62d05 --- /dev/null +++ b/firebase-analytics/src/jvmMain/kotlin/dev/gitlive/firebase/analytics/analytics.jvm.kt @@ -0,0 +1,40 @@ +package dev.gitlive.firebase.analytics + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import kotlin.time.Duration + +public actual val Firebase.analytics: FirebaseAnalytics + get() = TODO("Not yet implemented") + +public actual fun Firebase.analytics(app: FirebaseApp): FirebaseAnalytics { + TODO("Not yet implemented") +} + +public actual class FirebaseAnalytics { + public actual fun setUserProperty(name: String, value: String) {} + public actual fun setUserId(id: String?) {} + public actual fun resetAnalyticsData() {} + public actual fun setAnalyticsCollectionEnabled(enabled: Boolean) {} + public actual fun setSessionTimeoutInterval(sessionTimeoutInterval: Duration) {} + public actual suspend fun getSessionId(): Long? = TODO("Not yet implemented") + public actual fun setDefaultEventParameters(parameters: Map) {} + public actual fun logEvent(name: String, parameters: Map?) {} + + public actual fun setConsent(consentSettings: Map) {} + + public actual enum class ConsentType { + AD_PERSONALIZATION, + AD_STORAGE, + AD_USER_DATA, + ANALYTICS_STORAGE, + } + + public actual enum class ConsentStatus { + GRANTED, + DENIED, + } +} + +public actual class FirebaseAnalyticsException internal constructor(message: String) : FirebaseException(message) diff --git a/firebase-analytics/src/jvmTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt b/firebase-analytics/src/jvmTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt new file mode 100644 index 000000000..ed8644cac --- /dev/null +++ b/firebase-analytics/src/jvmTest/kotlin/dev/gitlive/firebase/analytics/analytics.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:JvmName("tests") + +package dev.gitlive.firebase.analytics + +import dev.gitlive.firebase.testContext + +actual val context: Any = testContext + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-app/api/android/firebase-app.api b/firebase-app/api/android/firebase-app.api new file mode 100644 index 000000000..e0042d959 --- /dev/null +++ b/firebase-app/api/android/firebase-app.api @@ -0,0 +1,57 @@ +public final class dev/gitlive/firebase/CommonKt { + public static final fun getOptions (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/FirebaseOptions; +} + +public final class dev/gitlive/firebase/Firebase { + public static final field INSTANCE Ldev/gitlive/firebase/Firebase; +} + +public final class dev/gitlive/firebase/FirebaseApp { + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getOptions ()Ldev/gitlive/firebase/FirebaseOptions; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/FirebaseKt { + public static final fun app (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun apps (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;)Ljava/util/List; + public static synthetic fun apps$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;ILjava/lang/Object;)Ljava/util/List; + public static final fun getAndroid (Ldev/gitlive/firebase/FirebaseApp;)Lcom/google/firebase/FirebaseApp; + public static final fun getApp (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun initialize (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun initialize (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun initialize (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;Ljava/lang/String;)Ldev/gitlive/firebase/FirebaseApp; + public static synthetic fun initialize$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; + public static synthetic fun initialize$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; + public static synthetic fun initialize$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; +} + +public final class dev/gitlive/firebase/FirebaseOptions { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/FirebaseOptions; + public static synthetic fun copy$default (Ldev/gitlive/firebase/FirebaseOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseOptions; + public fun equals (Ljava/lang/Object;)Z + public final fun getApiKey ()Ljava/lang/String; + public final fun getApplicationId ()Ljava/lang/String; + public final fun getAuthDomain ()Ljava/lang/String; + public final fun getDatabaseUrl ()Ljava/lang/String; + public final fun getGaTrackingId ()Ljava/lang/String; + public final fun getGcmSenderId ()Ljava/lang/String; + public final fun getProjectId ()Ljava/lang/String; + public final fun getStorageBucket ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/firebase-app/api/jvm/firebase-app.api b/firebase-app/api/jvm/firebase-app.api new file mode 100644 index 000000000..e0042d959 --- /dev/null +++ b/firebase-app/api/jvm/firebase-app.api @@ -0,0 +1,57 @@ +public final class dev/gitlive/firebase/CommonKt { + public static final fun getOptions (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/FirebaseOptions; +} + +public final class dev/gitlive/firebase/Firebase { + public static final field INSTANCE Ldev/gitlive/firebase/Firebase; +} + +public final class dev/gitlive/firebase/FirebaseApp { + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getOptions ()Ldev/gitlive/firebase/FirebaseOptions; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/FirebaseKt { + public static final fun app (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun apps (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;)Ljava/util/List; + public static synthetic fun apps$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;ILjava/lang/Object;)Ljava/util/List; + public static final fun getAndroid (Ldev/gitlive/firebase/FirebaseApp;)Lcom/google/firebase/FirebaseApp; + public static final fun getApp (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun initialize (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun initialize (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;)Ldev/gitlive/firebase/FirebaseApp; + public static final fun initialize (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;Ljava/lang/String;)Ldev/gitlive/firebase/FirebaseApp; + public static synthetic fun initialize$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; + public static synthetic fun initialize$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; + public static synthetic fun initialize$default (Ldev/gitlive/firebase/Firebase;Ljava/lang/Object;Ldev/gitlive/firebase/FirebaseOptions;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseApp; +} + +public final class dev/gitlive/firebase/FirebaseOptions { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/FirebaseOptions; + public static synthetic fun copy$default (Ldev/gitlive/firebase/FirebaseOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/FirebaseOptions; + public fun equals (Ljava/lang/Object;)Z + public final fun getApiKey ()Ljava/lang/String; + public final fun getApplicationId ()Ljava/lang/String; + public final fun getAuthDomain ()Ljava/lang/String; + public final fun getDatabaseUrl ()Ljava/lang/String; + public final fun getGaTrackingId ()Ljava/lang/String; + public final fun getGcmSenderId ()Ljava/lang/String; + public final fun getProjectId ()Ljava/lang/String; + public final fun getStorageBucket ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/firebase-app/build.gradle.kts b/firebase-app/build.gradle.kts index 635e9cd76..b98d62ede 100644 --- a/firebase-app/build.gradle.kts +++ b/firebase-app/build.gradle.kts @@ -1,4 +1,6 @@ -import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -11,6 +13,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -26,15 +29,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -48,10 +47,23 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + freeCompilerArgs.add("-Xconsistent-data-class-copy-visibility") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -60,20 +72,9 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() if (supportIosTarget) { iosArm64() @@ -81,13 +82,14 @@ kotlin { iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseApp" } noPodspec() pod("FirebaseCore") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") } } } @@ -95,32 +97,26 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true if (name.lowercase().contains("ios")) { optIn("kotlinx.cinterop.ExperimentalForeignApi") @@ -142,7 +138,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-common-ktx") + api(libs.google.firebase.common.ktx) } } @@ -158,6 +154,12 @@ if (project.property("firebase-app.skipIosTests") == "true") { } } +if (project.property("firebase-app.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-app.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-app/documentation.md b/firebase-app/documentation.md new file mode 100644 index 000000000..c2bf0c03e --- /dev/null +++ b/firebase-app/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-app +This module is a direct forward of the Firebase App library. It provides the default FirebaseApp instances. \ No newline at end of file diff --git a/firebase-app/package.json b/firebase-app/package.json index 7725a328d..9fceec0af 100644 --- a/firebase-app/package.json +++ b/firebase-app/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-app", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-common": "2.0.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-app/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firebase.kt index bdc3f7e6c..6a91ae72f 100644 --- a/firebase-app/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firebase.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase import androidx.test.platform.app.InstrumentationRegistry diff --git a/firebase-app/src/androidMain/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/androidMain/kotlin/dev/gitlive/firebase/firebase.kt index 37c834ac3..2efc65e03 100644 --- a/firebase-app/src/androidMain/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/androidMain/kotlin/dev/gitlive/firebase/firebase.kt @@ -6,41 +6,43 @@ package dev.gitlive.firebase import android.content.Context -actual typealias FirebaseException = com.google.firebase.FirebaseException +public actual typealias FirebaseException = com.google.firebase.FirebaseException -actual typealias FirebaseNetworkException = com.google.firebase.FirebaseNetworkException +public actual typealias FirebaseNetworkException = com.google.firebase.FirebaseNetworkException -actual typealias FirebaseTooManyRequestsException = com.google.firebase.FirebaseTooManyRequestsException +public actual typealias FirebaseTooManyRequestsException = com.google.firebase.FirebaseTooManyRequestsException -actual typealias FirebaseApiNotAvailableException = com.google.firebase.FirebaseApiNotAvailableException +public actual typealias FirebaseApiNotAvailableException = com.google.firebase.FirebaseApiNotAvailableException -actual val Firebase.app: FirebaseApp +public val FirebaseApp.android: com.google.firebase.FirebaseApp get() = android + +public actual val Firebase.app: FirebaseApp get() = FirebaseApp(com.google.firebase.FirebaseApp.getInstance()) -actual fun Firebase.app(name: String): FirebaseApp = +public actual fun Firebase.app(name: String): FirebaseApp = FirebaseApp(com.google.firebase.FirebaseApp.getInstance(name)) -actual fun Firebase.initialize(context: Any?): FirebaseApp? = +public actual fun Firebase.initialize(context: Any?): FirebaseApp? = com.google.firebase.FirebaseApp.initializeApp(context as Context)?.let { FirebaseApp(it) } -actual fun Firebase.initialize(context: Any?, options: FirebaseOptions, name: String): FirebaseApp = +public actual fun Firebase.initialize(context: Any?, options: FirebaseOptions, name: String): FirebaseApp = FirebaseApp(com.google.firebase.FirebaseApp.initializeApp(context as Context, options.toAndroid(), name)) -actual fun Firebase.initialize(context: Any?, options: FirebaseOptions) = +public actual fun Firebase.initialize(context: Any?, options: FirebaseOptions): FirebaseApp = FirebaseApp(com.google.firebase.FirebaseApp.initializeApp(context as Context, options.toAndroid())) -actual data class FirebaseApp internal constructor(val android: com.google.firebase.FirebaseApp) { +public actual data class FirebaseApp internal constructor(internal val android: com.google.firebase.FirebaseApp) { actual val name: String get() = android.name actual val options: FirebaseOptions get() = android.options.run { FirebaseOptions(applicationId, apiKey, databaseUrl, gaTrackingId, storageBucket, projectId, gcmSenderId) } - actual suspend fun delete() { + public actual suspend fun delete() { android.delete() } } -actual fun Firebase.apps(context: Any?) = com.google.firebase.FirebaseApp.getApps(context as Context) +public actual fun Firebase.apps(context: Any?): List = com.google.firebase.FirebaseApp.getApps(context as Context) .map { FirebaseApp(it) } private fun FirebaseOptions.toAndroid() = com.google.firebase.FirebaseOptions.Builder() diff --git a/firebase-app/src/androidUnitTest/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/androidUnitTest/kotlin/dev/gitlive/firebase/firebase.kt index 9810ef6a8..1ba1b6865 100644 --- a/firebase-app/src/androidUnitTest/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/androidUnitTest/kotlin/dev/gitlive/firebase/firebase.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase import org.junit.Ignore diff --git a/firebase-app/src/commonMain/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/commonMain/kotlin/dev/gitlive/firebase/firebase.kt index a02e952c9..fe03f6c3e 100644 --- a/firebase-app/src/commonMain/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/commonMain/kotlin/dev/gitlive/firebase/firebase.kt @@ -4,6 +4,7 @@ @file:JvmMultifileClass @file:JvmName("CommonKt") + package dev.gitlive.firebase import kotlin.jvm.JvmMultifileClass @@ -12,54 +13,115 @@ import kotlin.jvm.JvmName /** * Single access point to all firebase sdks from Kotlin. * - *

Acts as a target for extension methods provided by sdks. + * Acts as a target for extension methods provided by sdks. */ -object Firebase +public object Firebase -expect class FirebaseApp { - val name: String - val options: FirebaseOptions - suspend fun delete() +/** + * The entry point of Firebase SDKs. It holds common configuration and state for Firebase APIs. Most + * applications don't need to directly interact with FirebaseApp. + * + * For a vast majority of apps, FirebaseInitProvider will handle the initialization of + * Firebase for the default project that it's configured to work with, via the data contained in the + * app's `google-services.json` file. This `ContentProvider` + * is merged into the app's manifest by default when building with Gradle, + * and it runs automatically at app launch. No additional lines of code are needed in this + * case. + * + * Any `FirebaseApp` initialization must occur only in the main process of the app. + * Use of Firebase in processes other than the main process is not supported and will likely cause + * problems related to resource contention. + */ +public expect class FirebaseApp { + /** Returns the unique name of this app. */ + public val name: String + + /** Returns the specified [FirebaseOptions]. */ + public val options: FirebaseOptions + + /** + * Deletes the [FirebaseApp] and all its data. All calls to this [FirebaseApp] + * instance will throw once it has been called. + * + * A no-op if delete was called before. + */ + public suspend fun delete() } /** Returns the default firebase app instance. */ -expect val Firebase.app: FirebaseApp +public expect val Firebase.app: FirebaseApp /** Returns a named firebase app instance. */ -expect fun Firebase.app(name: String): FirebaseApp +public expect fun Firebase.app(name: String): FirebaseApp /** Returns all firebase app instances. */ -expect fun Firebase.apps(context: Any? = null): List +public expect fun Firebase.apps(context: Any? = null): List /** Initializes and returns a FirebaseApp. */ -expect fun Firebase.initialize(context: Any? = null): FirebaseApp? +public expect fun Firebase.initialize(context: Any? = null): FirebaseApp? /** Initializes and returns a FirebaseApp. */ -expect fun Firebase.initialize(context: Any? = null, options: FirebaseOptions): FirebaseApp +public expect fun Firebase.initialize(context: Any? = null, options: FirebaseOptions): FirebaseApp /** Initializes and returns a FirebaseApp. */ -expect fun Firebase.initialize(context: Any? = null, options: FirebaseOptions, name: String): FirebaseApp +public expect fun Firebase.initialize(context: Any? = null, options: FirebaseOptions, name: String): FirebaseApp /** Returns options of default FirebaseApp */ -val Firebase.options: FirebaseOptions +@Suppress("UnusedReceiverParameter") +public val Firebase.options: FirebaseOptions get() = Firebase.app.options -data class FirebaseOptions( +/** Configurable Firebase options. */ +public data class FirebaseOptions( + /** The Google App ID that is used to uniquely identify an instance of an app. */ val applicationId: String, + + /** + * API key used for authenticating requests from your app, e.g. + * AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk, used to identify your app to Google servers. + */ val apiKey: String, + + /** The database root URL, e.g. http://abc-xyz-123.firebaseio.com. */ val databaseUrl: String? = null, + + /** + * The tracking ID for Google Analytics, e.g. UA-12345678-1, used to configure Google Analytics. + */ val gaTrackingId: String? = null, + + /** The Google Cloud Storage bucket name, e.g. abc-xyz-123.storage.firebase.com. */ val storageBucket: String? = null, + + /** The Google Cloud project ID, e.g. my-project-1234 */ val projectId: String? = null, + + /** + * The Project Number from the Google Developer's console, for example 012345678901, used to + * configure Google Cloud Messaging. + */ val gcmSenderId: String? = null, - val authDomain: String? = null -) -expect open class FirebaseException : Exception + /** The auth domain. */ + val authDomain: String? = null, +) -expect class FirebaseNetworkException : FirebaseException +/** + * Exception that gets thrown when an operation on Firebase fails. + */ +public expect open class FirebaseException : Exception -expect open class FirebaseTooManyRequestsException : FirebaseException +/** + * Exception that gets thrown when an operation on Firebase fails. + */ +public expect class FirebaseNetworkException : FirebaseException -expect open class FirebaseApiNotAvailableException : FirebaseException +/** + * Exception that gets thrown when an operation on Firebase fails. + */ +public expect open class FirebaseTooManyRequestsException : FirebaseException +/** + * Exception that gets thrown when an operation on Firebase fails. + */ +public expect open class FirebaseApiNotAvailableException : FirebaseException diff --git a/firebase-app/src/commonTest/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/commonTest/kotlin/dev/gitlive/firebase/firebase.kt index 5cf41e354..5b59bbe2e 100644 --- a/firebase-app/src/commonTest/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/commonTest/kotlin/dev/gitlive/firebase/firebase.kt @@ -8,8 +8,7 @@ expect annotation class IgnoreForAndroidUnitTest() @IgnoreForAndroidUnitTest class FirebaseAppTest { - - @IgnoreForAndroidUnitTest + @Test fun testInitialize() = runTest { Firebase.initialize( @@ -20,8 +19,8 @@ class FirebaseAppTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) assertEquals(1, Firebase.apps(context).size) @@ -30,5 +29,4 @@ class FirebaseAppTest { it.delete() } } - -} \ No newline at end of file +} diff --git a/firebase-app/src/iosMain/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/iosMain/kotlin/dev/gitlive/firebase/firebase.kt index a68e315e2..fc20a3b7a 100644 --- a/firebase-app/src/iosMain/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/iosMain/kotlin/dev/gitlive/firebase/firebase.kt @@ -8,48 +8,50 @@ import cocoapods.FirebaseCore.FIRApp import cocoapods.FirebaseCore.FIROptions import kotlinx.coroutines.CompletableDeferred -actual open class FirebaseException(message: String) : Exception(message) -actual open class FirebaseNetworkException(message: String) : FirebaseException(message) -actual open class FirebaseTooManyRequestsException(message: String) : FirebaseException(message) -actual open class FirebaseApiNotAvailableException(message: String) : FirebaseException(message) +public actual open class FirebaseException(message: String) : Exception(message) +public actual open class FirebaseNetworkException(message: String) : FirebaseException(message) +public actual open class FirebaseTooManyRequestsException(message: String) : FirebaseException(message) +public actual open class FirebaseApiNotAvailableException(message: String) : FirebaseException(message) -actual val Firebase.app: FirebaseApp +public val FirebaseApp.ios: FIRApp get() = ios + +public actual val Firebase.app: FirebaseApp get() = FirebaseApp(FIRApp.defaultApp()!!) -actual fun Firebase.app(name: String): FirebaseApp = +public actual fun Firebase.app(name: String): FirebaseApp = FirebaseApp(FIRApp.appNamed(name)!!) -actual fun Firebase.initialize(context: Any?): FirebaseApp? = +public actual fun Firebase.initialize(context: Any?): FirebaseApp? = FIRApp.configure().let { app } -actual fun Firebase.initialize(context: Any?, options: FirebaseOptions, name: String): FirebaseApp = +public actual fun Firebase.initialize(context: Any?, options: FirebaseOptions, name: String): FirebaseApp = FIRApp.configureWithName(name, options.toIos()).let { app(name) } -actual fun Firebase.initialize(context: Any?, options: FirebaseOptions) = +public actual fun Firebase.initialize(context: Any?, options: FirebaseOptions): FirebaseApp = FIRApp.configureWithOptions(options.toIos()).let { app } -actual data class FirebaseApp internal constructor(val ios: FIRApp) { +public actual data class FirebaseApp internal constructor(internal val ios: FIRApp) { actual val name: String get() = ios.name actual val options: FirebaseOptions get() = ios.options.run { FirebaseOptions(bundleID, APIKey!!, databaseURL!!, trackingID, storageBucket, projectID, GCMSenderID) } - actual suspend fun delete() { + public actual suspend fun delete() { val deleted = CompletableDeferred() ios.deleteApp { deleted.complete(Unit) } deleted.await() } } -actual fun Firebase.apps(context: Any?) = FIRApp.allApps() +public actual fun Firebase.apps(context: Any?): List = FIRApp.allApps() .orEmpty() .values .map { FirebaseApp(it as FIRApp) } private fun FirebaseOptions.toIos() = FIROptions(this@toIos.applicationId, this@toIos.gcmSenderId ?: "").apply { - APIKey = this@toIos.apiKey - databaseURL = this@toIos.databaseUrl - trackingID = this@toIos.gaTrackingId - storageBucket = this@toIos.storageBucket - projectID = this@toIos.projectId - } + APIKey = this@toIos.apiKey + databaseURL = this@toIos.databaseUrl + trackingID = this@toIos.gaTrackingId + storageBucket = this@toIos.storageBucket + projectID = this@toIos.projectId +} 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 index ab2066cf9..2563e0311 100644 --- a/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/externals/app.kt +++ b/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/externals/app.kt @@ -5,28 +5,28 @@ package dev.gitlive.firebase.externals import kotlin.js.Promise -external fun initializeApp(options: Any, name: String = definedExternally): FirebaseApp +public external fun initializeApp(options: Any, name: String = definedExternally): FirebaseApp -external fun getApp(name: String = definedExternally): FirebaseApp +public external fun getApp(name: String = definedExternally): FirebaseApp -external fun getApps(): Array +public external fun getApps(): Array -external fun deleteApp(app: FirebaseApp): Promise +public external fun deleteApp(app: FirebaseApp): Promise -external interface FirebaseApp { - val automaticDataCollectionEnabled: Boolean - val name: String - val options: FirebaseOptions +public external interface FirebaseApp { + public val automaticDataCollectionEnabled: Boolean + public val name: String + public 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? +public external interface FirebaseOptions { + public val apiKey: String + public val appId: String + public val authDomain: String? + public val databaseURL: String? + public val measurementId: String? + public val messagingSenderId: String? + public val gaTrackingId: String? + public val projectId: String? + public 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 ed7619ab9..d51f28633 100644 --- a/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/firebase.kt @@ -11,35 +11,37 @@ import dev.gitlive.firebase.externals.initializeApp import kotlin.js.json import dev.gitlive.firebase.externals.FirebaseApp as JsFirebaseApp -actual val Firebase.app: FirebaseApp +public actual val Firebase.app: FirebaseApp get() = FirebaseApp(getApp()) -actual fun Firebase.app(name: String): FirebaseApp = +public actual fun Firebase.app(name: String): FirebaseApp = FirebaseApp(getApp(name)) -actual fun Firebase.initialize(context: Any?): FirebaseApp? = +public 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 = +public actual fun Firebase.initialize(context: Any?, options: FirebaseOptions, name: String): FirebaseApp = FirebaseApp(initializeApp(options.toJson(), name)) -actual fun Firebase.initialize(context: Any?, options: FirebaseOptions) = +public actual fun Firebase.initialize(context: Any?, options: FirebaseOptions): FirebaseApp = FirebaseApp(initializeApp(options.toJson())) -actual class FirebaseApp internal constructor(val js: JsFirebaseApp) { - actual val name: String +public val FirebaseApp.js: JsFirebaseApp get() = js + +public actual class FirebaseApp internal constructor(internal val js: JsFirebaseApp) { + public actual val name: String get() = js.name - actual val options: FirebaseOptions + public actual val options: FirebaseOptions get() = js.options.run { FirebaseOptions(appId, apiKey, databaseURL, gaTrackingId, storageBucket, projectId, messagingSenderId, authDomain) } - actual suspend fun delete() { + public actual suspend fun delete() { deleteApp(js) } } -actual fun Firebase.apps(context: Any?) = getApps().map { FirebaseApp(it) } +public actual fun Firebase.apps(context: Any?): List = getApps().map { FirebaseApp(it) } private fun FirebaseOptions.toJson() = json( "apiKey" to apiKey, @@ -49,10 +51,10 @@ private fun FirebaseOptions.toJson() = json( "projectId" to (projectId ?: undefined), "gaTrackingId" to (gaTrackingId ?: undefined), "messagingSenderId" to (gcmSenderId ?: undefined), - "authDomain" to (authDomain ?: undefined) + "authDomain" to (authDomain ?: undefined), ) -actual open class FirebaseException(code: String?, cause: Throwable) : Exception("$code: ${cause.message}", cause) -actual open class FirebaseNetworkException(code: String?, cause: Throwable) : FirebaseException(code, cause) -actual open class FirebaseTooManyRequestsException(code: String?, cause: Throwable) : FirebaseException(code, cause) -actual open class FirebaseApiNotAvailableException(code: String?, cause: Throwable) : FirebaseException(code, cause) +public actual open class FirebaseException(code: String?, cause: Throwable) : Exception("$code: ${cause.message}", cause) +public actual open class FirebaseNetworkException(code: String?, cause: Throwable) : FirebaseException(code, cause) +public actual open class FirebaseTooManyRequestsException(code: String?, cause: Throwable) : FirebaseException(code, cause) +public actual open class FirebaseApiNotAvailableException(code: String?, cause: Throwable) : FirebaseException(code, cause) diff --git a/firebase-app/src/jvmTest/kotlin/dev/gitlive/firebase/firebase.kt b/firebase-app/src/jvmTest/kotlin/dev/gitlive/firebase/firebase.kt index abcab363e..a5dd86da4 100644 --- a/firebase-app/src/jvmTest/kotlin/dev/gitlive/firebase/firebase.kt +++ b/firebase-app/src/jvmTest/kotlin/dev/gitlive/firebase/firebase.kt @@ -3,12 +3,10 @@ */ @file:JvmName("tests") -package dev.gitlive.firebase -import android.content.Context -import com.google.firebase.FirebasePlatform +package dev.gitlive.firebase -actual val context: Any get() = Context().also { FirebasePlatform.initializeFirebasePlatform(MockFirebasePlatform()) } +actual val context: Any = testContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-auth/api/android/firebase-auth.api b/firebase-auth/api/android/firebase-auth.api new file mode 100644 index 000000000..a5ac17791 --- /dev/null +++ b/firebase-auth/api/android/firebase-auth.api @@ -0,0 +1,286 @@ +public abstract class dev/gitlive/firebase/auth/ActionCodeResult { +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$PasswordReset : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$RecoverEmail : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; + public final fun getPreviousEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$RevertSecondFactorAddition : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; + public final fun getMultiFactorInfo ()Ldev/gitlive/firebase/auth/MultiFactorInfo; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$SignInWithEmailLink : dev/gitlive/firebase/auth/ActionCodeResult { + public static final field INSTANCE Ldev/gitlive/firebase/auth/ActionCodeResult$SignInWithEmailLink; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$VerifyBeforeChangeEmail : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; + public final fun getPreviousEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$VerifyEmail : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeSettings { + public fun (Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ldev/gitlive/firebase/auth/AndroidPackageName; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Z + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;)Ldev/gitlive/firebase/auth/ActionCodeSettings; + public static synthetic fun copy$default (Ldev/gitlive/firebase/auth/ActionCodeSettings;Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/auth/ActionCodeSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getAndroidPackageName ()Ldev/gitlive/firebase/auth/AndroidPackageName; + public final fun getCanHandleCodeInApp ()Z + public final fun getDynamicLinkDomain ()Ljava/lang/String; + public final fun getIOSBundleId ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/AndroidPackageName { + public fun (Ljava/lang/String;ZLjava/lang/String;)V + public synthetic fun (Ljava/lang/String;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Z + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;ZLjava/lang/String;)Ldev/gitlive/firebase/auth/AndroidPackageName; + public static synthetic fun copy$default (Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/auth/AndroidPackageName; + public fun equals (Ljava/lang/Object;)Z + public final fun getInstallIfNotAvailable ()Z + public final fun getMinimumVersion ()Ljava/lang/String; + public final fun getPackageName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class dev/gitlive/firebase/auth/AuthCredential { + public fun (Lcom/google/firebase/auth/AuthCredential;)V + public fun getAndroid ()Lcom/google/firebase/auth/AuthCredential; + public final fun getProviderId ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/AuthResult { + public final fun getUser ()Ldev/gitlive/firebase/auth/FirebaseUser; +} + +public final class dev/gitlive/firebase/auth/AuthTokenResult { + public fun (Lcom/google/firebase/auth/GetTokenResult;)V + public final fun getClaims ()Ljava/util/Map; + public final fun getSignInProvider ()Ljava/lang/String; + public final fun getToken ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/CredentialsKt { + public static final fun getAndroid (Ldev/gitlive/firebase/auth/OAuthProvider;)Lcom/google/firebase/auth/OAuthProvider; +} + +public final class dev/gitlive/firebase/auth/EmailAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/EmailAuthProvider; + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; + public final fun credentialWithLink (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/FacebookAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/FacebookAuthProvider; + public final fun credential (Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/FirebaseAuth { + public final fun applyActionCode (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun checkActionCode (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun confirmPasswordReset (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun createUserWithEmailAndPassword (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fetchSignInMethodsForEmail (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getAuthStateChanged ()Lkotlinx/coroutines/flow/Flow; + public final fun getCurrentUser ()Ldev/gitlive/firebase/auth/FirebaseUser; + public final fun getIdTokenChanged ()Lkotlinx/coroutines/flow/Flow; + public final fun getLanguageCode ()Ljava/lang/String; + public final fun isSignInWithEmailLink (Ljava/lang/String;)Z + public final fun sendPasswordResetEmail (Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendPasswordResetEmail$default (Ldev/gitlive/firebase/auth/FirebaseAuth;Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun sendSignInLinkToEmail (Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setLanguageCode (Ljava/lang/String;)V + public final fun signInAnonymously (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithCredential (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithCustomToken (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithEmailAndPassword (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithEmailLink (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signOut (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateCurrentUser (Ldev/gitlive/firebase/auth/FirebaseUser;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun useEmulator (Ljava/lang/String;I)V + public final fun verifyPasswordResetCode (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/FirebaseUser { + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getDisplayName ()Ljava/lang/String; + public final fun getEmail ()Ljava/lang/String; + public final fun getIdToken (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getIdTokenResult (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getMetaData ()Ldev/gitlive/firebase/auth/UserMetaData; + public final fun getMultiFactor ()Ldev/gitlive/firebase/auth/MultiFactor; + public final fun getPhoneNumber ()Ljava/lang/String; + public final fun getPhotoURL ()Ljava/lang/String; + public final fun getProviderData ()Ljava/util/List; + public final fun getProviderId ()Ljava/lang/String; + public final fun getUid ()Ljava/lang/String; + public final fun isAnonymous ()Z + public final fun isEmailVerified ()Z + public final fun linkWithCredential (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun reauthenticate (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun reauthenticateAndRetrieveData (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun reload (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun sendEmailVerification (Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendEmailVerification$default (Ldev/gitlive/firebase/auth/FirebaseUser;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun unlink (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateEmail (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updatePassword (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updatePhoneNumber (Ldev/gitlive/firebase/auth/PhoneAuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateProfile (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateProfile$default (Ldev/gitlive/firebase/auth/FirebaseUser;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun verifyBeforeUpdateEmail (Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun verifyBeforeUpdateEmail$default (Ldev/gitlive/firebase/auth/FirebaseUser;Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/GithubAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/GithubAuthProvider; + public final fun credential (Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/GoogleAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/GoogleAuthProvider; + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/MultiFactor { + public fun (Lcom/google/firebase/auth/MultiFactor;)V + public final fun enroll (Ldev/gitlive/firebase/auth/MultiFactorAssertion;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getEnrolledFactors ()Ljava/util/List; + public final fun getSession (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unenroll (Ldev/gitlive/firebase/auth/MultiFactorInfo;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unenroll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/MultiFactorAssertion { + public fun (Lcom/google/firebase/auth/MultiFactorAssertion;)V + public final fun getFactorId ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/MultiFactorInfo { + public fun (Lcom/google/firebase/auth/MultiFactorInfo;)V + public final fun getDisplayName ()Ljava/lang/String; + public final fun getEnrollmentTime ()D + public final fun getFactorId ()Ljava/lang/String; + public final fun getUid ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/MultiFactorResolver { + public fun (Lcom/google/firebase/auth/MultiFactorResolver;)V + public final fun getAuth ()Ldev/gitlive/firebase/auth/FirebaseAuth; + public final fun getHints ()Ljava/util/List; + public final fun getSession ()Ldev/gitlive/firebase/auth/MultiFactorSession; + public final fun resolveSignIn (Ldev/gitlive/firebase/auth/MultiFactorAssertion;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/MultiFactorSession { + public fun (Lcom/google/firebase/auth/MultiFactorSession;)V +} + +public final class dev/gitlive/firebase/auth/MultifactorKt { + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactor;)Lcom/google/firebase/auth/MultiFactor; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorAssertion;)Lcom/google/firebase/auth/MultiFactorAssertion; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorInfo;)Lcom/google/firebase/auth/MultiFactorInfo; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorResolver;)Lcom/google/firebase/auth/MultiFactorResolver; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorSession;)Lcom/google/firebase/auth/MultiFactorSession; +} + +public final class dev/gitlive/firebase/auth/OAuthCredential : dev/gitlive/firebase/auth/AuthCredential { + public fun (Lcom/google/firebase/auth/OAuthCredential;)V + public synthetic fun getAndroid ()Lcom/google/firebase/auth/AuthCredential; + public fun getAndroid ()Lcom/google/firebase/auth/OAuthCredential; +} + +public final class dev/gitlive/firebase/auth/OAuthProvider { + public static final field Companion Ldev/gitlive/firebase/auth/OAuthProvider$Companion; + public fun (Lcom/google/firebase/auth/OAuthProvider;)V + public fun (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ldev/gitlive/firebase/auth/FirebaseAuth;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ldev/gitlive/firebase/auth/FirebaseAuth;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/gitlive/firebase/auth/OAuthProvider$Companion { + public final fun credential (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/OAuthCredential; + public static synthetic fun credential$default (Ldev/gitlive/firebase/auth/OAuthProvider$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/auth/OAuthCredential; +} + +public final class dev/gitlive/firebase/auth/PhoneAuthCredential : dev/gitlive/firebase/auth/AuthCredential { + public fun (Lcom/google/firebase/auth/PhoneAuthCredential;)V + public synthetic fun getAndroid ()Lcom/google/firebase/auth/AuthCredential; + public fun getAndroid ()Lcom/google/firebase/auth/PhoneAuthCredential; +} + +public final class dev/gitlive/firebase/auth/PhoneAuthProvider { + public fun (Ldev/gitlive/firebase/auth/FirebaseAuth;)V + public synthetic fun (Ldev/gitlive/firebase/auth/FirebaseAuth;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function0;)V + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/PhoneAuthCredential; + public final fun getCreateOptionsBuilder ()Lkotlin/jvm/functions/Function0; + public final fun verifyPhoneNumber (Ljava/lang/String;Ldev/gitlive/firebase/auth/PhoneVerificationProvider;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class dev/gitlive/firebase/auth/PhoneVerificationProvider { + public abstract fun codeSent (Lkotlin/jvm/functions/Function1;)V + public abstract fun getActivity ()Landroid/app/Activity; + public abstract fun getTimeout ()J + public abstract fun getUnit ()Ljava/util/concurrent/TimeUnit; + public abstract fun getVerificationCode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/TwitterAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/TwitterAuthProvider; + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/UserInfo { + public fun (Lcom/google/firebase/auth/UserInfo;)V + public final fun getDisplayName ()Ljava/lang/String; + public final fun getEmail ()Ljava/lang/String; + public final fun getPhoneNumber ()Ljava/lang/String; + public final fun getPhotoURL ()Ljava/lang/String; + public final fun getProviderId ()Ljava/lang/String; + public final fun getUid ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/UserKt { + public static final fun getAndroid (Ldev/gitlive/firebase/auth/FirebaseUser;)Lcom/google/firebase/auth/FirebaseUser; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/UserInfo;)Lcom/google/firebase/auth/UserInfo; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/UserMetaData;)Lcom/google/firebase/auth/FirebaseUserMetadata; +} + +public final class dev/gitlive/firebase/auth/UserMetaData { + public fun (Lcom/google/firebase/auth/FirebaseUserMetadata;)V + public final fun getCreationTime ()Ljava/lang/Double; + public final fun getLastSignInTime ()Ljava/lang/Double; +} + +public final class dev/gitlive/firebase/auth/android { + public static final fun auth (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/auth/FirebaseAuth; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/AuthResult;)Lcom/google/firebase/auth/AuthResult; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/AuthTokenResult;)Lcom/google/firebase/auth/GetTokenResult; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/FirebaseAuth;)Lcom/google/firebase/auth/FirebaseAuth; + public static final fun getAuth (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/auth/FirebaseAuth; +} + diff --git a/firebase-auth/api/jvm/firebase-auth.api b/firebase-auth/api/jvm/firebase-auth.api new file mode 100644 index 000000000..170ac9afa --- /dev/null +++ b/firebase-auth/api/jvm/firebase-auth.api @@ -0,0 +1,285 @@ +public abstract class dev/gitlive/firebase/auth/ActionCodeResult { +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$PasswordReset : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$RecoverEmail : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; + public final fun getPreviousEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$RevertSecondFactorAddition : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; + public final fun getMultiFactorInfo ()Ldev/gitlive/firebase/auth/MultiFactorInfo; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$SignInWithEmailLink : dev/gitlive/firebase/auth/ActionCodeResult { + public static final field INSTANCE Ldev/gitlive/firebase/auth/ActionCodeResult$SignInWithEmailLink; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$VerifyBeforeChangeEmail : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; + public final fun getPreviousEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeResult$VerifyEmail : dev/gitlive/firebase/auth/ActionCodeResult { + public final fun getEmail ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/ActionCodeSettings { + public fun (Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ldev/gitlive/firebase/auth/AndroidPackageName; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Z + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;)Ldev/gitlive/firebase/auth/ActionCodeSettings; + public static synthetic fun copy$default (Ldev/gitlive/firebase/auth/ActionCodeSettings;Ljava/lang/String;Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/auth/ActionCodeSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getAndroidPackageName ()Ldev/gitlive/firebase/auth/AndroidPackageName; + public final fun getCanHandleCodeInApp ()Z + public final fun getDynamicLinkDomain ()Ljava/lang/String; + public final fun getIOSBundleId ()Ljava/lang/String; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/AndroidPackageName { + public fun (Ljava/lang/String;ZLjava/lang/String;)V + public synthetic fun (Ljava/lang/String;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Z + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;ZLjava/lang/String;)Ldev/gitlive/firebase/auth/AndroidPackageName; + public static synthetic fun copy$default (Ldev/gitlive/firebase/auth/AndroidPackageName;Ljava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/auth/AndroidPackageName; + public fun equals (Ljava/lang/Object;)Z + public final fun getInstallIfNotAvailable ()Z + public final fun getMinimumVersion ()Ljava/lang/String; + public final fun getPackageName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public class dev/gitlive/firebase/auth/AuthCredential { + public fun (Lcom/google/firebase/auth/AuthCredential;)V + public fun getAndroid ()Lcom/google/firebase/auth/AuthCredential; + public final fun getProviderId ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/AuthResult { + public final fun getUser ()Ldev/gitlive/firebase/auth/FirebaseUser; +} + +public final class dev/gitlive/firebase/auth/AuthTokenResult { + public fun (Lcom/google/firebase/auth/GetTokenResult;)V + public final fun getClaims ()Ljava/util/Map; + public final fun getSignInProvider ()Ljava/lang/String; + public final fun getToken ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/CredentialsKt { + public static final fun getAndroid (Lcom/google/firebase/auth/OAuthProvider;)Lcom/google/firebase/auth/OAuthProvider; + public static final fun getAndroid (Lcom/google/firebase/auth/PhoneAuthProvider;)Lcom/google/firebase/auth/PhoneAuthProvider; +} + +public final class dev/gitlive/firebase/auth/EmailAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/EmailAuthProvider; + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; + public final fun credentialWithLink (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/FacebookAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/FacebookAuthProvider; + public final fun credential (Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/FirebaseAuth { + public final fun applyActionCode (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun checkActionCode (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun confirmPasswordReset (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun createUserWithEmailAndPassword (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fetchSignInMethodsForEmail (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getAuthStateChanged ()Lkotlinx/coroutines/flow/Flow; + public final fun getCurrentUser ()Ldev/gitlive/firebase/auth/FirebaseUser; + public final fun getIdTokenChanged ()Lkotlinx/coroutines/flow/Flow; + public final fun getLanguageCode ()Ljava/lang/String; + public final fun isSignInWithEmailLink (Ljava/lang/String;)Z + public final fun sendPasswordResetEmail (Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendPasswordResetEmail$default (Ldev/gitlive/firebase/auth/FirebaseAuth;Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun sendSignInLinkToEmail (Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setLanguageCode (Ljava/lang/String;)V + public final fun signInAnonymously (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithCredential (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithCustomToken (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithEmailAndPassword (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signInWithEmailLink (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun signOut (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateCurrentUser (Ldev/gitlive/firebase/auth/FirebaseUser;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun useEmulator (Ljava/lang/String;I)V + public final fun verifyPasswordResetCode (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/FirebaseUser { + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getDisplayName ()Ljava/lang/String; + public final fun getEmail ()Ljava/lang/String; + public final fun getIdToken (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getIdTokenResult (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getMetaData ()Ldev/gitlive/firebase/auth/UserMetaData; + public final fun getMultiFactor ()Ldev/gitlive/firebase/auth/MultiFactor; + public final fun getPhoneNumber ()Ljava/lang/String; + public final fun getPhotoURL ()Ljava/lang/String; + public final fun getProviderData ()Ljava/util/List; + public final fun getProviderId ()Ljava/lang/String; + public final fun getUid ()Ljava/lang/String; + public final fun isAnonymous ()Z + public final fun isEmailVerified ()Z + public final fun linkWithCredential (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun reauthenticate (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun reauthenticateAndRetrieveData (Ldev/gitlive/firebase/auth/AuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun reload (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun sendEmailVerification (Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendEmailVerification$default (Ldev/gitlive/firebase/auth/FirebaseUser;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun unlink (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateEmail (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updatePassword (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updatePhoneNumber (Ldev/gitlive/firebase/auth/PhoneAuthCredential;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateProfile (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateProfile$default (Ldev/gitlive/firebase/auth/FirebaseUser;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun verifyBeforeUpdateEmail (Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun verifyBeforeUpdateEmail$default (Ldev/gitlive/firebase/auth/FirebaseUser;Ljava/lang/String;Ldev/gitlive/firebase/auth/ActionCodeSettings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/GithubAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/GithubAuthProvider; + public final fun credential (Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/GoogleAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/GoogleAuthProvider; + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/MultiFactor { + public fun (Lcom/google/firebase/auth/MultiFactor;)V + public final fun enroll (Ldev/gitlive/firebase/auth/MultiFactorAssertion;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getEnrolledFactors ()Ljava/util/List; + public final fun getSession (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unenroll (Ldev/gitlive/firebase/auth/MultiFactorInfo;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unenroll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/MultiFactorAssertion { + public fun (Lcom/google/firebase/auth/MultiFactorAssertion;)V + public final fun getFactorId ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/MultiFactorInfo { + public fun (Lcom/google/firebase/auth/MultiFactorInfo;)V + public final fun getDisplayName ()Ljava/lang/String; + public final fun getEnrollmentTime ()D + public final fun getFactorId ()Ljava/lang/String; + public final fun getUid ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/MultiFactorResolver { + public fun (Lcom/google/firebase/auth/MultiFactorResolver;)V + public final fun getAuth ()Ldev/gitlive/firebase/auth/FirebaseAuth; + public final fun getHints ()Ljava/util/List; + public final fun getSession ()Ldev/gitlive/firebase/auth/MultiFactorSession; + public final fun resolveSignIn (Ldev/gitlive/firebase/auth/MultiFactorAssertion;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/MultiFactorSession { + public fun (Lcom/google/firebase/auth/MultiFactorSession;)V +} + +public final class dev/gitlive/firebase/auth/MultifactorKt { + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactor;)Lcom/google/firebase/auth/MultiFactor; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorAssertion;)Lcom/google/firebase/auth/MultiFactorAssertion; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorInfo;)Lcom/google/firebase/auth/MultiFactorInfo; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorResolver;)Lcom/google/firebase/auth/MultiFactorResolver; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/MultiFactorSession;)Lcom/google/firebase/auth/MultiFactorSession; +} + +public final class dev/gitlive/firebase/auth/OAuthCredential : dev/gitlive/firebase/auth/AuthCredential { + public fun (Lcom/google/firebase/auth/OAuthCredential;)V + public synthetic fun getAndroid ()Lcom/google/firebase/auth/AuthCredential; + public fun getAndroid ()Lcom/google/firebase/auth/OAuthCredential; +} + +public final class dev/gitlive/firebase/auth/OAuthProvider { + public static final field Companion Ldev/gitlive/firebase/auth/OAuthProvider$Companion; + public fun (Lcom/google/firebase/auth/OAuthProvider;)V + public fun (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ldev/gitlive/firebase/auth/FirebaseAuth;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ldev/gitlive/firebase/auth/FirebaseAuth;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/gitlive/firebase/auth/OAuthProvider$Companion { + public final fun credential (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/OAuthCredential; + public static synthetic fun credential$default (Ldev/gitlive/firebase/auth/OAuthProvider$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/auth/OAuthCredential; +} + +public final class dev/gitlive/firebase/auth/PhoneAuthCredential : dev/gitlive/firebase/auth/AuthCredential { + public fun (Lcom/google/firebase/auth/PhoneAuthCredential;)V + public synthetic fun getAndroid ()Lcom/google/firebase/auth/AuthCredential; + public fun getAndroid ()Lcom/google/firebase/auth/PhoneAuthCredential; +} + +public final class dev/gitlive/firebase/auth/PhoneAuthProvider { + public fun (Lcom/google/firebase/auth/PhoneAuthProvider;)V + public fun (Ldev/gitlive/firebase/auth/FirebaseAuth;)V + public synthetic fun (Ldev/gitlive/firebase/auth/FirebaseAuth;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/PhoneAuthCredential; + public final fun verifyPhoneNumber (Ljava/lang/String;Ldev/gitlive/firebase/auth/PhoneVerificationProvider;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class dev/gitlive/firebase/auth/PhoneVerificationProvider { + public abstract fun codeSent (Lkotlin/jvm/functions/Function1;)V + public abstract fun getActivity ()Landroid/app/Activity; + public abstract fun getTimeout ()J + public abstract fun getUnit ()Ljava/util/concurrent/TimeUnit; + public abstract fun getVerificationCode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/auth/TwitterAuthProvider { + public static final field INSTANCE Ldev/gitlive/firebase/auth/TwitterAuthProvider; + public final fun credential (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/auth/AuthCredential; +} + +public final class dev/gitlive/firebase/auth/UserInfo { + public fun (Lcom/google/firebase/auth/UserInfo;)V + public final fun getDisplayName ()Ljava/lang/String; + public final fun getEmail ()Ljava/lang/String; + public final fun getPhoneNumber ()Ljava/lang/String; + public final fun getPhotoURL ()Ljava/lang/String; + public final fun getProviderId ()Ljava/lang/String; + public final fun getUid ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/auth/UserKt { + public static final fun getAndroid (Ldev/gitlive/firebase/auth/FirebaseUser;)Lcom/google/firebase/auth/FirebaseUser; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/UserInfo;)Lcom/google/firebase/auth/UserInfo; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/UserMetaData;)Lcom/google/firebase/auth/FirebaseUserMetadata; +} + +public final class dev/gitlive/firebase/auth/UserMetaData { + public fun (Lcom/google/firebase/auth/FirebaseUserMetadata;)V + public final fun getCreationTime ()Ljava/lang/Double; + public final fun getLastSignInTime ()Ljava/lang/Double; +} + +public final class dev/gitlive/firebase/auth/android { + public static final fun auth (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/auth/FirebaseAuth; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/AuthResult;)Lcom/google/firebase/auth/AuthResult; + public static final fun getAndroid (Ldev/gitlive/firebase/auth/AuthTokenResult;)Lcom/google/firebase/auth/GetTokenResult; + public static final fun getAuth (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/auth/FirebaseAuth; +} + diff --git a/firebase-auth/build.gradle.kts b/firebase-auth/build.gradle.kts index 03cb349a3..e1447399b 100644 --- a/firebase-auth/build.gradle.kts +++ b/firebase-auth/build.gradle.kts @@ -1,4 +1,9 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTests +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest /* * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. @@ -10,31 +15,26 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") - //id("com.quittle.android-emulator") version "0.2.0" + id("testOptionsConvention") } android { - val minSdkVersion: Int by project val compileSdkVersion: Int by project compileSdk = compileSdkVersion namespace = "dev.gitlive.firebase.auth" defaultConfig { - minSdk = minSdkVersion + minSdk = 23 // Auth has a MinSDK of 23. See https://github.com/firebase/firebase-android-sdk/issues/5927#issuecomment-2093466572 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -45,26 +45,25 @@ android { } } -// Optional configuration -//androidEmulator { -// emulator { -// name("givlive_emulator") -// sdkVersion(28) -// abi("x86_64") -// includeGoogleApis(true) // Defaults to false -// -// } -// headless(false) -// logEmulatorOutput(false) -//} - val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -73,25 +72,23 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } + jvm() + if (supportIosTarget) { iosArm64() - iosX64() - iosSimulatorArm64() + iosX64().enableKeychainForTests() + iosSimulatorArm64().enableKeychainForTests() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseAuth" } noPodspec() pod("FirebaseAuth") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") } } } @@ -99,29 +96,17 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) - } - } - - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" } } } @@ -129,10 +114,8 @@ kotlin { sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") if (name.lowercase().contains("ios")) { @@ -157,7 +140,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-auth-ktx") + api(libs.google.firebase.auth.ktx) } } } @@ -169,12 +152,41 @@ if (project.property("firebase-auth.skipIosTests") == "true") { } } +if (project.property("firebase-auth.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-auth.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } } } +if (supportIosTarget) { + tasks.create("launchIosSimulator") { + commandLine("open", "-a", "Simulator") + } + + tasks.withType().configureEach { + dependsOn("launchIosSimulator") + standalone.set(false) + device.set("booted") + } +} + +fun KotlinNativeTargetWithSimulatorTests.enableKeychainForTests() { + testRuns.configureEach { + executionSource.binary.linkerOpts( + "-sectcreate", + "__TEXT", + "__entitlements", + file("$projectDir/src/commonTest/resources/entitlements.plist").absolutePath + ) + } +} + signing { val signingKey: String? by project val signingPassword: String? by project diff --git a/firebase-auth/documentation.md b/firebase-auth/documentation.md new file mode 100644 index 000000000..2ff7437e5 --- /dev/null +++ b/firebase-auth/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-auth +This module is a direct forward of the Firebase Authentication library. It provides the main functionality, like authenticating with Google or Apple. \ No newline at end of file diff --git a/firebase-auth/package.json b/firebase-auth/package.json index 8f9f047e8..23da53f91 100644 --- a/firebase-auth/package.json +++ b/firebase-auth/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-auth", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-auth/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/auth/auth.kt index 0d4d18e96..0b86ac10a 100644 --- a/firebase-auth/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.auth import androidx.test.platform.app.InstrumentationRegistry diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt index acdbc36fd..fec2e73a0 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -3,6 +3,7 @@ */ @file:JvmName("android") + package dev.gitlive.firebase.auth import com.google.firebase.auth.ActionCodeEmailInfo @@ -11,85 +12,90 @@ import com.google.firebase.auth.ActionCodeResult.* import com.google.firebase.auth.FirebaseAuth.AuthStateListener import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android as publicAndroid import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.tasks.await -actual val Firebase.auth +public val FirebaseAuth.android: com.google.firebase.auth.FirebaseAuth get() = com.google.firebase.auth.FirebaseAuth.getInstance() + +public actual val Firebase.auth: FirebaseAuth get() = FirebaseAuth(com.google.firebase.auth.FirebaseAuth.getInstance()) -actual fun Firebase.auth(app: FirebaseApp) = - FirebaseAuth(com.google.firebase.auth.FirebaseAuth.getInstance(app.android)) +public actual fun Firebase.auth(app: FirebaseApp): FirebaseAuth = + FirebaseAuth(com.google.firebase.auth.FirebaseAuth.getInstance(app.publicAndroid)) -actual data class FirebaseAuth internal constructor(val android: com.google.firebase.auth.FirebaseAuth) { - actual val currentUser: FirebaseUser? +public actual class FirebaseAuth internal constructor(internal val android: com.google.firebase.auth.FirebaseAuth) { + public actual val currentUser: FirebaseUser? get() = android.currentUser?.let { FirebaseUser(it) } - actual val authStateChanged: Flow get() = callbackFlow { - val listener = object : AuthStateListener { - override fun onAuthStateChanged(auth: com.google.firebase.auth.FirebaseAuth) { - trySend(auth.currentUser?.let { FirebaseUser(it) }) - } - } + public actual val authStateChanged: Flow get() = callbackFlow { + val listener = AuthStateListener { auth -> trySend(auth.currentUser?.let { FirebaseUser(it) }) } android.addAuthStateListener(listener) awaitClose { android.removeAuthStateListener(listener) } } - actual val idTokenChanged get(): Flow = callbackFlow { - val listener = object : com.google.firebase.auth.FirebaseAuth.IdTokenListener { - override fun onIdTokenChanged(auth: com.google.firebase.auth.FirebaseAuth) { - trySend(auth.currentUser?.let { FirebaseUser(it) }) - } - } + public actual val idTokenChanged: Flow get() = callbackFlow { + val listener = com.google.firebase.auth.FirebaseAuth.IdTokenListener { auth -> trySend(auth.currentUser?.let { FirebaseUser(it) }) } android.addIdTokenListener(listener) awaitClose { android.removeIdTokenListener(listener) } } - actual var languageCode: String + public actual var languageCode: String get() = android.languageCode ?: "" - set(value) { android.setLanguageCode(value) } - + set(value) { + android.setLanguageCode(value) + } - actual suspend fun applyActionCode(code: String) = android.applyActionCode(code).await().run { Unit } - actual suspend fun confirmPasswordReset(code: String, newPassword: String) = android.confirmPasswordReset(code, newPassword).await().run { Unit } + public actual suspend fun applyActionCode(code: String) { + android.applyActionCode(code).await() + } + public actual suspend fun confirmPasswordReset(code: String, newPassword: String) { + android.confirmPasswordReset(code, newPassword).await() + } - actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = + public actual suspend fun createUserWithEmailAndPassword(email: String, password: String): AuthResult = AuthResult(android.createUserWithEmailAndPassword(email, password).await()) - actual suspend fun fetchSignInMethodsForEmail(email: String): List = android.fetchSignInMethodsForEmail(email).await().signInMethods.orEmpty() + @Suppress("DEPRECATION") + public actual suspend fun fetchSignInMethodsForEmail(email: String): List = android.fetchSignInMethodsForEmail(email).await().signInMethods.orEmpty() - actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { + public actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { android.sendPasswordResetEmail(email, actionCodeSettings?.toAndroid()).await() } - actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = android.sendSignInLinkToEmail(email, actionCodeSettings.toAndroid()).await().run { Unit } + public actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) { + android.sendSignInLinkToEmail(email, actionCodeSettings.toAndroid()).await() + } - actual fun isSignInWithEmailLink(link: String) = android.isSignInWithEmailLink(link) + public actual fun isSignInWithEmailLink(link: String): Boolean = android.isSignInWithEmailLink(link) - actual suspend fun signInWithEmailAndPassword(email: String, password: String) = + public actual suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult = AuthResult(android.signInWithEmailAndPassword(email, password).await()) - actual suspend fun signInWithCustomToken(token: String) = + public actual suspend fun signInWithCustomToken(token: String): AuthResult = AuthResult(android.signInWithCustomToken(token).await()) - actual suspend fun signInAnonymously() = AuthResult(android.signInAnonymously().await()) + public actual suspend fun signInAnonymously(): AuthResult = AuthResult(android.signInAnonymously().await()) - actual suspend fun signInWithCredential(authCredential: AuthCredential) = + public actual suspend fun signInWithCredential(authCredential: AuthCredential): AuthResult = AuthResult(android.signInWithCredential(authCredential.android).await()) - actual suspend fun signInWithEmailLink(email: String, link: String) = + public actual suspend fun signInWithEmailLink(email: String, link: String): AuthResult = AuthResult(android.signInWithEmailLink(email, link).await()) - actual suspend fun signOut() = android.signOut() + public actual suspend fun signOut(): Unit = android.signOut() - actual suspend fun updateCurrentUser(user: FirebaseUser) = android.updateCurrentUser(user.android).await().run { Unit } - actual suspend fun verifyPasswordResetCode(code: String): String = android.verifyPasswordResetCode(code).await() + public actual suspend fun updateCurrentUser(user: FirebaseUser) { + android.updateCurrentUser(user.android).await() + } + public actual suspend fun verifyPasswordResetCode(code: String): String = android.verifyPasswordResetCode(code).await() - actual suspend fun checkActionCode(code: String): T { + public actual suspend fun checkActionCode(code: String): T { val result = android.checkActionCode(code).await() @Suppress("UNCHECKED_CAST") - return when(result.operation) { + return when (result.operation) { SIGN_IN_WITH_EMAIL_LINK -> ActionCodeResult.SignInWithEmailLink VERIFY_EMAIL -> ActionCodeResult.VerifyEmail(result.info!!.email) PASSWORD_RESET -> ActionCodeResult.PasswordReset(result.info!!.email) @@ -107,26 +113,31 @@ actual data class FirebaseAuth internal constructor(val android: com.google.fire } as T } - actual fun useEmulator(host: String, port: Int) = android.useEmulator(host, port) + public actual fun useEmulator(host: String, port: Int): Unit = android.useEmulator(host, port) } -actual class AuthResult internal constructor(val android: com.google.firebase.auth.AuthResult) { - actual val user: FirebaseUser? +public val AuthResult.android: com.google.firebase.auth.AuthResult get() = android + +public actual class AuthResult internal constructor(internal val android: com.google.firebase.auth.AuthResult) { + public actual val user: FirebaseUser? get() = android.user?.let { FirebaseUser(it) } } -actual class AuthTokenResult(val android: com.google.firebase.auth.GetTokenResult) { +public val AuthTokenResult.android: com.google.firebase.auth.GetTokenResult get() = android + +public actual class AuthTokenResult(internal val android: com.google.firebase.auth.GetTokenResult) { // actual val authTimestamp: Long // get() = android.authTimestamp - actual val claims: Map + public actual val claims: Map get() = android.claims + // actual val expirationTimestamp: Long // get() = android.expirationTimestamp // actual val issuedAtTimestamp: Long // get() = android.issuedAtTimestamp - actual val signInProvider: String? + public actual val signInProvider: String? get() = android.signInProvider - actual val token: String? + public actual val token: String? get() = android.token } @@ -138,13 +149,13 @@ internal fun ActionCodeSettings.toAndroid() = com.google.firebase.auth.ActionCod .also { iOSBundleId?.run { it.setIOSBundleId(this) } } .build() -actual typealias FirebaseAuthException = com.google.firebase.auth.FirebaseAuthException -actual typealias FirebaseAuthActionCodeException = com.google.firebase.auth.FirebaseAuthActionCodeException -actual typealias FirebaseAuthEmailException = com.google.firebase.auth.FirebaseAuthEmailException -actual typealias FirebaseAuthInvalidCredentialsException = com.google.firebase.auth.FirebaseAuthInvalidCredentialsException -actual typealias FirebaseAuthWeakPasswordException = com.google.firebase.auth.FirebaseAuthWeakPasswordException -actual typealias FirebaseAuthInvalidUserException = com.google.firebase.auth.FirebaseAuthInvalidUserException -actual typealias FirebaseAuthMultiFactorException = com.google.firebase.auth.FirebaseAuthMultiFactorException -actual typealias FirebaseAuthRecentLoginRequiredException = com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException -actual typealias FirebaseAuthUserCollisionException = com.google.firebase.auth.FirebaseAuthUserCollisionException -actual typealias FirebaseAuthWebException = com.google.firebase.auth.FirebaseAuthWebException +public actual typealias FirebaseAuthException = com.google.firebase.auth.FirebaseAuthException +public actual typealias FirebaseAuthActionCodeException = com.google.firebase.auth.FirebaseAuthActionCodeException +public actual typealias FirebaseAuthEmailException = com.google.firebase.auth.FirebaseAuthEmailException +public actual typealias FirebaseAuthInvalidCredentialsException = com.google.firebase.auth.FirebaseAuthInvalidCredentialsException +public actual typealias FirebaseAuthWeakPasswordException = com.google.firebase.auth.FirebaseAuthWeakPasswordException +public actual typealias FirebaseAuthInvalidUserException = com.google.firebase.auth.FirebaseAuthInvalidUserException +public actual typealias FirebaseAuthMultiFactorException = com.google.firebase.auth.FirebaseAuthMultiFactorException +public actual typealias FirebaseAuthRecentLoginRequiredException = com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException +public actual typealias FirebaseAuthUserCollisionException = com.google.firebase.auth.FirebaseAuthUserCollisionException +public actual typealias FirebaseAuthWebException = com.google.firebase.auth.FirebaseAuthWebException diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index f1681a17e..914e4e4f3 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -6,46 +6,45 @@ package dev.gitlive.firebase.auth import android.app.Activity import com.google.firebase.FirebaseException -import com.google.firebase.auth.OAuthProvider +import com.google.firebase.auth.OAuthProvider as AndroidOAuthProvider import com.google.firebase.auth.PhoneAuthOptions import com.google.firebase.auth.PhoneAuthProvider import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -import kotlinx.coroutines.tasks.await import java.util.concurrent.TimeUnit -actual open class AuthCredential(open val android: com.google.firebase.auth.AuthCredential) { - actual val providerId: String +public actual open class AuthCredential(public open val android: com.google.firebase.auth.AuthCredential) { + public actual val providerId: String get() = android.provider } -actual class PhoneAuthCredential(override val android: com.google.firebase.auth.PhoneAuthCredential) : AuthCredential(android) +public actual class PhoneAuthCredential(override val android: com.google.firebase.auth.PhoneAuthCredential) : AuthCredential(android) -actual class OAuthCredential(override val android: com.google.firebase.auth.OAuthCredential) : AuthCredential(android) +public actual class OAuthCredential(override val android: com.google.firebase.auth.OAuthCredential) : AuthCredential(android) -actual object EmailAuthProvider { - actual fun credential( +public actual object EmailAuthProvider { + public actual fun credential( email: String, - password: String + password: String, ): AuthCredential = AuthCredential(com.google.firebase.auth.EmailAuthProvider.getCredential(email, password)) - actual fun credentialWithLink( + public actual fun credentialWithLink( email: String, - emailLink: String + emailLink: String, ): AuthCredential = AuthCredential(com.google.firebase.auth.EmailAuthProvider.getCredentialWithLink(email, emailLink)) } -actual object FacebookAuthProvider { - actual fun credential(accessToken: String): AuthCredential = AuthCredential(com.google.firebase.auth.FacebookAuthProvider.getCredential(accessToken)) +public actual object FacebookAuthProvider { + public actual fun credential(accessToken: String): AuthCredential = AuthCredential(com.google.firebase.auth.FacebookAuthProvider.getCredential(accessToken)) } -actual object GithubAuthProvider { - actual fun credential(token: String): AuthCredential = AuthCredential(com.google.firebase.auth.GithubAuthProvider.getCredential(token)) +public actual object GithubAuthProvider { + public actual fun credential(token: String): AuthCredential = AuthCredential(com.google.firebase.auth.GithubAuthProvider.getCredential(token)) } -actual object GoogleAuthProvider { - actual fun credential(idToken: String?, accessToken: String?): AuthCredential { +public actual object GoogleAuthProvider { + public actual fun credential(idToken: String?, accessToken: String?): AuthCredential { require(idToken != null || accessToken != null) { "Both parameters are optional but at least one must be present." } @@ -53,39 +52,41 @@ actual object GoogleAuthProvider { } } -actual class OAuthProvider(val android: com.google.firebase.auth.OAuthProvider) { +public val OAuthProvider.android: AndroidOAuthProvider get() = android - actual constructor( +public actual class OAuthProvider(internal val android: AndroidOAuthProvider) { + + public actual constructor( provider: String, scopes: List, customParameters: Map, - auth: FirebaseAuth + auth: FirebaseAuth, ) : this( - com.google.firebase.auth.OAuthProvider + AndroidOAuthProvider .newBuilder(provider, auth.android) .setScopes(scopes) .addCustomParameters(customParameters) - .build() + .build(), ) - actual companion object { - actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { - val builder = OAuthProvider.newCredentialBuilder(providerId) + public actual companion object { + public actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { + val builder = AndroidOAuthProvider.newCredentialBuilder(providerId) accessToken?.let { builder.setAccessToken(it) } idToken?.let { builder.setIdToken(it) } - rawNonce?.let { builder.setIdTokenWithRawNonce(idToken!!, it) } + rawNonce?.let { builder.setIdTokenWithRawNonce(idToken!!, it) } return OAuthCredential(builder.build() as com.google.firebase.auth.OAuthCredential) } } } -actual class PhoneAuthProvider(val createOptionsBuilder: () -> PhoneAuthOptions.Builder) { +public actual class PhoneAuthProvider(public val createOptionsBuilder: () -> PhoneAuthOptions.Builder) { - actual constructor(auth: FirebaseAuth) : this({ PhoneAuthOptions.newBuilder(auth.android) }) + public actual constructor(auth: FirebaseAuth) : this({ PhoneAuthOptions.newBuilder(auth.android) }) - actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(com.google.firebase.auth.PhoneAuthProvider.getCredential(verificationId, smsCode)) + public actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(PhoneAuthProvider.getCredential(verificationId, smsCode)) - actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = coroutineScope { + public actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = coroutineScope { val response = CompletableDeferred>() val callback = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @@ -121,7 +122,6 @@ actual class PhoneAuthProvider(val createOptionsBuilder: () -> PhoneAuthOptions. override fun onVerificationFailed(error: FirebaseException) { response.complete(Result.failure(error)) } - } val options = createOptionsBuilder() @@ -136,14 +136,14 @@ actual class PhoneAuthProvider(val createOptionsBuilder: () -> PhoneAuthOptions. } } -actual interface PhoneVerificationProvider { - val activity: Activity - val timeout: Long - val unit: TimeUnit - fun codeSent(triggerResend: (Unit) -> Unit) - suspend fun getVerificationCode(): String +public actual interface PhoneVerificationProvider { + public val activity: Activity + public val timeout: Long + public val unit: TimeUnit + public fun codeSent(triggerResend: (Unit) -> Unit) + public suspend fun getVerificationCode(): String } -actual object TwitterAuthProvider { - actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(com.google.firebase.auth.TwitterAuthProvider.getCredential(token, secret)) +public actual object TwitterAuthProvider { + public actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(com.google.firebase.auth.TwitterAuthProvider.getCredential(token, secret)) } diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt index 7b68960c2..8a32d501f 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt @@ -6,37 +6,53 @@ package dev.gitlive.firebase.auth import kotlinx.coroutines.tasks.await -actual class MultiFactor(val android: com.google.firebase.auth.MultiFactor) { - actual val enrolledFactors: List +public val MultiFactor.android: com.google.firebase.auth.MultiFactor get() = android + +public actual class MultiFactor(internal val android: com.google.firebase.auth.MultiFactor) { + public actual val enrolledFactors: List get() = android.enrolledFactors.map { MultiFactorInfo(it) } - actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) = android.enroll(multiFactorAssertion.android, displayName).await().run { Unit } - actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(android.session.await()) - actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) = android.unenroll(multiFactorInfo.android).await().run { Unit } - actual suspend fun unenroll(factorUid: String) = android.unenroll(factorUid).await().run { Unit } + public actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) { + android.enroll(multiFactorAssertion.android, displayName).await() + } + public actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(android.session.await()) + public actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) { + android.unenroll(multiFactorInfo.android).await() + } + public actual suspend fun unenroll(factorUid: String) { + android.unenroll(factorUid).await() + } } -actual class MultiFactorInfo(val android: com.google.firebase.auth.MultiFactorInfo) { - actual val displayName: String? +public val MultiFactorInfo.android: com.google.firebase.auth.MultiFactorInfo get() = android + +public actual class MultiFactorInfo(internal val android: com.google.firebase.auth.MultiFactorInfo) { + public actual val displayName: String? get() = android.displayName - actual val enrollmentTime: Double + public actual val enrollmentTime: Double get() = android.enrollmentTimestamp.toDouble() - actual val factorId: String + public actual val factorId: String get() = android.factorId - actual val uid: String + public actual val uid: String get() = android.uid } -actual class MultiFactorAssertion(val android: com.google.firebase.auth.MultiFactorAssertion) { - actual val factorId: String +public val MultiFactorAssertion.android: com.google.firebase.auth.MultiFactorAssertion get() = android + +public actual class MultiFactorAssertion(internal val android: com.google.firebase.auth.MultiFactorAssertion) { + public actual val factorId: String get() = android.factorId } -actual class MultiFactorSession(val android: com.google.firebase.auth.MultiFactorSession) +public val MultiFactorSession.android: com.google.firebase.auth.MultiFactorSession get() = android -actual class MultiFactorResolver(val android: com.google.firebase.auth.MultiFactorResolver) { - actual val auth: FirebaseAuth = FirebaseAuth(android.firebaseAuth) - actual val hints: List = android.hints.map { MultiFactorInfo(it) } - actual val session: MultiFactorSession = MultiFactorSession(android.session) +public actual class MultiFactorSession(internal val android: com.google.firebase.auth.MultiFactorSession) - actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(android.resolveSignIn(assertion.android).await()) -} \ No newline at end of file +public val MultiFactorResolver.android: com.google.firebase.auth.MultiFactorResolver get() = android + +public actual class MultiFactorResolver(internal val android: com.google.firebase.auth.MultiFactorResolver) { + public actual val auth: FirebaseAuth = FirebaseAuth(android.firebaseAuth) + public actual val hints: List = android.hints.map { MultiFactorInfo(it) } + public actual val session: MultiFactorSession = MultiFactorSession(android.session) + + public actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(android.resolveSignIn(assertion.android).await()) +} diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt index 5ff3a286d..8fa60207a 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/user.kt @@ -8,73 +8,94 @@ import android.net.Uri import com.google.firebase.auth.UserProfileChangeRequest import kotlinx.coroutines.tasks.await -actual class FirebaseUser internal constructor(val android: com.google.firebase.auth.FirebaseUser) : FirebaseUserProfile { - actual val uid: String +public val FirebaseUser.android: com.google.firebase.auth.FirebaseUser get() = android + +public actual class FirebaseUser internal constructor(internal val android: com.google.firebase.auth.FirebaseUser) { + public actual val uid: String get() = android.uid - override val displayName: String? + public actual val displayName: String? get() = android.displayName - actual val email: String? + public actual val email: String? get() = android.email - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = android.phoneNumber - override val photoURL: String? + public actual val photoURL: String? get() = android.photoUrl?.toString() - actual val isAnonymous: Boolean + public actual val isAnonymous: Boolean get() = android.isAnonymous - actual val isEmailVerified: Boolean + public actual val isEmailVerified: Boolean get() = android.isEmailVerified - actual val metaData: UserMetaData? - get() = android.metadata?.let{ UserMetaData(it) } - actual val multiFactor: MultiFactor + public actual val metaData: UserMetaData? + get() = android.metadata?.let { UserMetaData(it) } + public actual val multiFactor: MultiFactor get() = MultiFactor(android.multiFactor) - actual val providerData: List + public actual val providerData: List get() = android.providerData.map { UserInfo(it) } - actual val providerId: String + public actual val providerId: String get() = android.providerId - actual suspend fun delete() = android.delete().await().run { Unit } - actual suspend fun reload() = android.reload().await().run { Unit } - actual suspend fun getIdToken(forceRefresh: Boolean): String? = android.getIdToken(forceRefresh).await().token - actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = android.getIdToken(forceRefresh).await().run { AuthTokenResult(this) } - actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = AuthResult(android.linkWithCredential(credential.android).await()) - actual suspend fun reauthenticate(credential: AuthCredential) = android.reauthenticate(credential.android).await().run { Unit } - actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = AuthResult(android.reauthenticateAndRetrieveData(credential.android).await()) - actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) { + public actual suspend fun delete() { + android.delete().await() + } + public actual suspend fun reload() { + android.reload().await() + } + public actual suspend fun getIdToken(forceRefresh: Boolean): String? = android.getIdToken(forceRefresh).await().token + public actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = android.getIdToken(forceRefresh).await().run { AuthTokenResult(this) } + public actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = AuthResult(android.linkWithCredential(credential.android).await()) + public actual suspend fun reauthenticate(credential: AuthCredential) { + android.reauthenticate(credential.android).await() + } + public actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = AuthResult(android.reauthenticateAndRetrieveData(credential.android).await()) + public actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) { val request = actionCodeSettings?.let { android.sendEmailVerification(it.toAndroid()) } ?: android.sendEmailVerification() request.await() } - actual suspend fun unlink(provider: String): FirebaseUser? = android.unlink(provider).await().user?.let { FirebaseUser(it) } - actual suspend fun updateEmail(email: String) = android.updateEmail(email).await().run { Unit } - actual suspend fun updatePassword(password: String) = android.updatePassword(password).await().run { Unit } - actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential) = android.updatePhoneNumber(credential.android).await().run { Unit } - override suspend fun updateProfile(displayName: String?, photoUrl: String?) { - val request = UserProfileChangeRequest.Builder().apply { - this.displayName = displayName - photoUri = photoUrl?.let { Uri.parse(it) } - }.build() + public actual suspend fun unlink(provider: String): FirebaseUser? = android.unlink(provider).await().user?.let { FirebaseUser(it) } + + @Suppress("DEPRECATION") + public actual suspend fun updateEmail(email: String) { + android.updateEmail(email).await() + } + public actual suspend fun updatePassword(password: String) { + android.updatePassword(password).await() + } + public actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential) { + android.updatePhoneNumber(credential.android).await() + } + public actual suspend fun updateProfile(displayName: String?, photoUrl: String?) { + val request = UserProfileChangeRequest.Builder() + .apply { setDisplayName(displayName) } + .apply { photoUri = photoUrl?.let { Uri.parse(it) } } + .build() android.updateProfile(request).await() } - actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = - android.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.toAndroid()).await().run { Unit } + public actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) { + android.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.toAndroid()).await() + } } -actual class UserInfo(val android: com.google.firebase.auth.UserInfo) { - actual val displayName: String? +public val UserInfo.android: com.google.firebase.auth.UserInfo get() = android + +public actual class UserInfo(internal val android: com.google.firebase.auth.UserInfo) { + public actual val displayName: String? get() = android.displayName - actual val email: String? + public actual val email: String? get() = android.email - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = android.phoneNumber - actual val photoURL: String? + public actual val photoURL: String? get() = android.photoUrl?.toString() - actual val providerId: String + public actual val providerId: String get() = android.providerId - actual val uid: String + public actual val uid: String get() = android.uid } -actual class UserMetaData(val android: com.google.firebase.auth.FirebaseUserMetadata) { - actual val creationTime: Double? +public val UserMetaData.android: com.google.firebase.auth.FirebaseUserMetadata get() = android + +public actual class UserMetaData(internal val android: com.google.firebase.auth.FirebaseUserMetadata) { + public actual val creationTime: Double? get() = android.creationTimestamp.toDouble() - actual val lastSignInTime: Double? + public actual val lastSignInTime: Double? get() = android.lastSignInTimestamp.toDouble() } diff --git a/firebase-auth/src/androidUnitTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/androidUnitTest/kotlin/dev/gitlive/firebase/auth/auth.kt index 216b48f4c..3b9348d5a 100644 --- a/firebase-auth/src/androidUnitTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/androidUnitTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.auth import org.junit.Ignore diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 8f6eb0c01..f66ce3c03 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -2,8 +2,6 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER") - package dev.gitlive.firebase.auth import dev.gitlive.firebase.Firebase @@ -11,77 +9,80 @@ import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException import kotlinx.coroutines.flow.Flow -expect val Firebase.auth: FirebaseAuth +public expect val Firebase.auth: FirebaseAuth + +public expect fun Firebase.auth(app: FirebaseApp): FirebaseAuth -expect fun Firebase.auth(app: FirebaseApp): FirebaseAuth +public expect class FirebaseAuth { + public val currentUser: FirebaseUser? + public val authStateChanged: Flow + public val idTokenChanged: Flow + public var languageCode: String + public suspend fun applyActionCode(code: String) + public suspend fun checkActionCode(code: String): T + public suspend fun confirmPasswordReset(code: String, newPassword: String) + public suspend fun createUserWithEmailAndPassword(email: String, password: String): AuthResult -expect class FirebaseAuth { - val currentUser: FirebaseUser? - val authStateChanged: Flow - val idTokenChanged: Flow - var languageCode: String - suspend fun applyActionCode(code: String) - suspend fun checkActionCode(code: String): T - suspend fun confirmPasswordReset(code: String, newPassword: String) - suspend fun createUserWithEmailAndPassword(email: String, password: String): AuthResult - suspend fun fetchSignInMethodsForEmail(email: String): List - suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings? = null) - suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) - fun isSignInWithEmailLink(link: String): Boolean - suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult - suspend fun signInWithCustomToken(token: String): AuthResult - suspend fun signInAnonymously(): AuthResult - suspend fun signInWithCredential(authCredential: AuthCredential): AuthResult - suspend fun signInWithEmailLink(email: String, link: String): AuthResult - suspend fun signOut() - suspend fun updateCurrentUser(user: FirebaseUser) - suspend fun verifyPasswordResetCode(code: String): String - fun useEmulator(host: String, port: Int) + @Deprecated("Migrating off of this method is recommended as a security best-practice. Learn more in the Identity Platform documentation for [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection).") + public suspend fun fetchSignInMethodsForEmail(email: String): List + public suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings? = null) + public suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) + public fun isSignInWithEmailLink(link: String): Boolean + public suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult + public suspend fun signInWithCustomToken(token: String): AuthResult + public suspend fun signInAnonymously(): AuthResult + public suspend fun signInWithCredential(authCredential: AuthCredential): AuthResult + public suspend fun signInWithEmailLink(email: String, link: String): AuthResult + public suspend fun signOut() + public suspend fun updateCurrentUser(user: FirebaseUser) + public suspend fun verifyPasswordResetCode(code: String): String + public fun useEmulator(host: String, port: Int) } -expect class AuthResult { - val user: FirebaseUser? +public expect class AuthResult { + public val user: FirebaseUser? } -expect class AuthTokenResult { +public expect class AuthTokenResult { // val authTimestamp: Long - val claims: Map + public val claims: Map + // val expirationTimestamp: Long // val issuedAtTimestamp: Long - val signInProvider: String? - val token: String? + public val signInProvider: String? + public val token: String? } -sealed class ActionCodeResult { - object SignInWithEmailLink : ActionCodeResult() - class PasswordReset internal constructor(val email: String) : ActionCodeResult() - class VerifyEmail internal constructor(val email: String) : ActionCodeResult() - class RecoverEmail internal constructor(val email: String, val previousEmail: String) : ActionCodeResult() - class VerifyBeforeChangeEmail internal constructor(val email: String, val previousEmail: String) : ActionCodeResult() - class RevertSecondFactorAddition internal constructor(val email: String, val multiFactorInfo: MultiFactorInfo?) : ActionCodeResult() +public sealed class ActionCodeResult { + public data object SignInWithEmailLink : ActionCodeResult() + public class PasswordReset internal constructor(public val email: String) : ActionCodeResult() + public class VerifyEmail internal constructor(public val email: String) : ActionCodeResult() + public class RecoverEmail internal constructor(public val email: String, public val previousEmail: String) : ActionCodeResult() + public class VerifyBeforeChangeEmail internal constructor(public val email: String, public val previousEmail: String) : ActionCodeResult() + public class RevertSecondFactorAddition internal constructor(public val email: String, public val multiFactorInfo: MultiFactorInfo?) : ActionCodeResult() } -data class ActionCodeSettings( +public data class ActionCodeSettings( val url: String, val androidPackageName: AndroidPackageName? = null, val dynamicLinkDomain: String? = null, val canHandleCodeInApp: Boolean = false, - val iOSBundleId: String? = null + val iOSBundleId: String? = null, ) -data class AndroidPackageName( +public data class AndroidPackageName( val packageName: String, val installIfNotAvailable: Boolean = true, - val minimumVersion: String? = null + val minimumVersion: String? = null, ) -expect open class FirebaseAuthException : FirebaseException -expect class FirebaseAuthActionCodeException : FirebaseAuthException -expect class FirebaseAuthEmailException : FirebaseAuthException -expect open class FirebaseAuthInvalidCredentialsException : FirebaseAuthException -expect class FirebaseAuthWeakPasswordException: FirebaseAuthInvalidCredentialsException -expect class FirebaseAuthInvalidUserException : FirebaseAuthException -expect class FirebaseAuthMultiFactorException: FirebaseAuthException -expect class FirebaseAuthRecentLoginRequiredException : FirebaseAuthException -expect class FirebaseAuthUserCollisionException : FirebaseAuthException -expect class FirebaseAuthWebException : FirebaseAuthException +public expect open class FirebaseAuthException : FirebaseException +public expect class FirebaseAuthActionCodeException : FirebaseAuthException +public expect class FirebaseAuthEmailException : FirebaseAuthException +public expect open class FirebaseAuthInvalidCredentialsException : FirebaseAuthException +public expect class FirebaseAuthWeakPasswordException : FirebaseAuthInvalidCredentialsException +public expect class FirebaseAuthInvalidUserException : FirebaseAuthException +public expect class FirebaseAuthMultiFactorException : FirebaseAuthException +public expect class FirebaseAuthRecentLoginRequiredException : FirebaseAuthException +public expect class FirebaseAuthUserCollisionException : FirebaseAuthException +public expect class FirebaseAuthWebException : FirebaseAuthException diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 28ed0b801..c7ea6e450 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -6,48 +6,48 @@ package dev.gitlive.firebase.auth import dev.gitlive.firebase.Firebase -expect open class AuthCredential { - val providerId: String +public expect open class AuthCredential { + public val providerId: String } -expect class PhoneAuthCredential : AuthCredential +public expect class PhoneAuthCredential : AuthCredential -expect class OAuthCredential : AuthCredential +public expect class OAuthCredential : AuthCredential -expect object EmailAuthProvider { - fun credential(email: String, password: String): AuthCredential - fun credentialWithLink(email: String, emailLink: String): AuthCredential +public expect object EmailAuthProvider { + public fun credential(email: String, password: String): AuthCredential + public fun credentialWithLink(email: String, emailLink: String): AuthCredential } -expect object FacebookAuthProvider { - fun credential(accessToken: String): AuthCredential +public expect object FacebookAuthProvider { + public fun credential(accessToken: String): AuthCredential } -expect object GithubAuthProvider { - fun credential(token: String): AuthCredential +public expect object GithubAuthProvider { + public fun credential(token: String): AuthCredential } -expect object GoogleAuthProvider { - fun credential(idToken: String?, accessToken: String?): AuthCredential +public expect object GoogleAuthProvider { + public fun credential(idToken: String?, accessToken: String?): AuthCredential } -expect class OAuthProvider constructor( +public expect class OAuthProvider( provider: String, scopes: List = emptyList(), customParameters: Map = emptyMap(), - auth: FirebaseAuth = Firebase.auth + auth: FirebaseAuth = Firebase.auth, ) { - companion object { - fun credential(providerId: String, accessToken: String? = null, idToken: String? = null, rawNonce: String? = null): OAuthCredential + public companion object { + public fun credential(providerId: String, accessToken: String? = null, idToken: String? = null, rawNonce: String? = null): OAuthCredential } } -expect class PhoneAuthProvider constructor(auth: FirebaseAuth = Firebase.auth) { - fun credential(verificationId: String, smsCode: String): PhoneAuthCredential - suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential +public expect class PhoneAuthProvider(auth: FirebaseAuth = Firebase.auth) { + public fun credential(verificationId: String, smsCode: String): PhoneAuthCredential + public suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential } -expect interface PhoneVerificationProvider +public expect interface PhoneVerificationProvider -expect object TwitterAuthProvider { - fun credential(token: String, secret: String): AuthCredential +public expect object TwitterAuthProvider { + public fun credential(token: String, secret: String): AuthCredential } diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt index 04d3b11aa..4b52aebe2 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt @@ -4,31 +4,31 @@ package dev.gitlive.firebase.auth -expect class MultiFactor { - val enrolledFactors: List - suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) - suspend fun getSession(): MultiFactorSession - suspend fun unenroll(multiFactorInfo: MultiFactorInfo) - suspend fun unenroll(factorUid: String) +public expect class MultiFactor { + public val enrolledFactors: List + public suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) + public suspend fun getSession(): MultiFactorSession + public suspend fun unenroll(multiFactorInfo: MultiFactorInfo) + public suspend fun unenroll(factorUid: String) } -expect class MultiFactorInfo { - val displayName: String? - val enrollmentTime: Double - val factorId: String - val uid: String +public expect class MultiFactorInfo { + public val displayName: String? + public val enrollmentTime: Double + public val factorId: String + public val uid: String } -expect class MultiFactorAssertion { - val factorId: String +public expect class MultiFactorAssertion { + public val factorId: String } -expect class MultiFactorSession +public expect class MultiFactorSession -expect class MultiFactorResolver { - val auth: FirebaseAuth - val hints: List - val session: MultiFactorSession +public expect class MultiFactorResolver { + public val auth: FirebaseAuth + public val hints: List + public val session: MultiFactorSession - suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult -} \ No newline at end of file + public suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult +} diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/user.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/user.kt index bdbddc6af..61422f4f3 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/user.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/user.kt @@ -4,48 +4,46 @@ package dev.gitlive.firebase.auth -// Javascript IR compilation has issues with referencing to actual values as default parameters, so we should use an interface instead -interface FirebaseUserProfile { - val displayName: String? - val photoURL: String? - suspend fun updateProfile(displayName: String? = this.displayName, photoUrl: String? = this.photoURL) -} +public expect class FirebaseUser { + public val uid: String + public val displayName: String? + public val email: String? + public val phoneNumber: String? + public val photoURL: String? + public val isAnonymous: Boolean + public val isEmailVerified: Boolean + public val metaData: UserMetaData? + public val multiFactor: MultiFactor + public val providerData: List + public val providerId: String + public suspend fun delete() + public suspend fun reload() + public suspend fun getIdToken(forceRefresh: Boolean): String? + public suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult + public suspend fun linkWithCredential(credential: AuthCredential): AuthResult + public suspend fun reauthenticate(credential: AuthCredential) + public suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult + public suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings? = null) + public suspend fun unlink(provider: String): FirebaseUser? -expect class FirebaseUser : FirebaseUserProfile { - val uid: String - val email: String? - val phoneNumber: String? - val isAnonymous: Boolean - val isEmailVerified: Boolean - val metaData: UserMetaData? - val multiFactor: MultiFactor - val providerData: List - val providerId: String - suspend fun delete() - suspend fun reload() - suspend fun getIdToken(forceRefresh: Boolean): String? - suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult - suspend fun linkWithCredential(credential: AuthCredential): AuthResult - suspend fun reauthenticate(credential: AuthCredential) - suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult - suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings? = null) - suspend fun unlink(provider: String): FirebaseUser? - suspend fun updateEmail(email: String) - suspend fun updatePassword(password: String) - suspend fun updatePhoneNumber(credential: PhoneAuthCredential) - suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings? = null) + @Deprecated("Use verifyBeforeUpdateEmail instead", replaceWith = ReplaceWith("verifyBeforeUpdateEmail(email)")) + public suspend fun updateEmail(email: String) + public suspend fun updatePassword(password: String) + public suspend fun updatePhoneNumber(credential: PhoneAuthCredential) + public suspend fun updateProfile(displayName: String? = this.displayName, photoUrl: String? = this.photoURL) + public suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings? = null) } -expect class UserInfo { - val displayName: String? - val email: String? - val phoneNumber: String? - val photoURL: String? - val providerId: String - val uid: String +public expect class UserInfo { + public val displayName: String? + public val email: String? + public val phoneNumber: String? + public val photoURL: String? + public val providerId: String + public val uid: String } -expect class UserMetaData { - val creationTime: Double? - val lastSignInTime: Double? +public expect class UserMetaData { + public val creationTime: Double? + public val lastSignInTime: Double? } 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 ceaf34929..47db21755 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 @@ -4,7 +4,12 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest import kotlin.random.Random import kotlin.test.* @@ -27,8 +32,8 @@ class FirebaseAuthTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) auth = Firebase.auth(app).apply { @@ -66,6 +71,7 @@ class FirebaseAuthTest { } @Test + @Ignore fun testFetchSignInMethods() = runTest { val email = "test+${Random.nextInt(100000)}@test.com" var signInMethodResult = auth.fetchSignInMethodsForEmail(email) diff --git a/firebase-auth/src/commonTest/resources/entitlements.plist b/firebase-auth/src/commonTest/resources/entitlements.plist new file mode 100644 index 000000000..12a365278 --- /dev/null +++ b/firebase-auth/src/commonTest/resources/entitlements.plist @@ -0,0 +1,12 @@ + + + + + application-identifier + {PERSONAL_ID}.bundle.id + keychain-access-groups + + {PERSONAL_ID}.bundle.id + + + \ No newline at end of file diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 04a743c86..b659b0c68 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -10,83 +10,87 @@ import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.FirebaseNetworkException import dev.gitlive.firebase.auth.ActionCodeResult.* +import dev.gitlive.firebase.ios import kotlinx.cinterop.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.Flow import platform.Foundation.NSError import platform.Foundation.NSURL +public val FirebaseAuth.ios: FIRAuth get() = FIRAuth.auth() -actual val Firebase.auth +public actual val Firebase.auth: FirebaseAuth get() = FirebaseAuth(FIRAuth.auth()) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.auth(app: FirebaseApp): FirebaseAuth = FirebaseAuth( - FIRAuth.authWithApp(app.ios as objcnames.classes.FIRApp) +public actual fun Firebase.auth(app: FirebaseApp): FirebaseAuth = FirebaseAuth( + FIRAuth.authWithApp(app.ios as objcnames.classes.FIRApp), ) -actual data class FirebaseAuth internal constructor(val ios: FIRAuth) { +public actual class FirebaseAuth internal constructor(internal val ios: FIRAuth) { - actual val currentUser: FirebaseUser? + public actual val currentUser: FirebaseUser? get() = ios.currentUser?.let { FirebaseUser(it) } - actual val authStateChanged get() = callbackFlow { + public actual val authStateChanged: Flow get() = callbackFlow { val handle = ios.addAuthStateDidChangeListener { _, user -> trySend(user?.let { FirebaseUser(it) }) } awaitClose { ios.removeAuthStateDidChangeListener(handle) } } - actual val idTokenChanged get() = callbackFlow { + public actual val idTokenChanged: Flow get() = callbackFlow { val handle = ios.addIDTokenDidChangeListener { _, user -> trySend(user?.let { FirebaseUser(it) }) } awaitClose { ios.removeIDTokenDidChangeListener(handle) } } - actual var languageCode: String + public actual var languageCode: String get() = ios.languageCode ?: "" - set(value) { ios.setLanguageCode(value) } + set(value) { + ios.setLanguageCode(value) + } - actual suspend fun applyActionCode(code: String) = ios.await { applyActionCode(code, it) }.run { Unit } - actual suspend fun confirmPasswordReset(code: String, newPassword: String) = ios.await { confirmPasswordResetWithCode(code, newPassword, it) }.run { Unit } + public actual suspend fun applyActionCode(code: String): Unit = ios.await { applyActionCode(code, it) } + public actual suspend fun confirmPasswordReset(code: String, newPassword: String): Unit = ios.await { confirmPasswordResetWithCode(code, newPassword, it) } - actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = + public actual suspend fun createUserWithEmailAndPassword(email: String, password: String): AuthResult = AuthResult(ios.awaitResult { createUserWithEmail(email = email, password = password, completion = it) }) @Suppress("UNCHECKED_CAST") - actual suspend fun fetchSignInMethodsForEmail(email: String) = + public actual suspend fun fetchSignInMethodsForEmail(email: String): List = ios.awaitResult?> { fetchSignInMethodsForEmail(email, it) }.orEmpty() as List - actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { + public actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { ios.await { actionCodeSettings?.let { actionSettings -> sendPasswordResetWithEmail(email, actionSettings.toIos(), it) } ?: sendPasswordResetWithEmail(email = email, completion = it) } } - actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = ios.await { sendSignInLinkToEmail(email, actionCodeSettings.toIos(), it) }.run { Unit } + public actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings): Unit = ios.await { sendSignInLinkToEmail(email, actionCodeSettings.toIos(), it) } - actual fun isSignInWithEmailLink(link: String) = ios.isSignInWithEmailLink(link) + public actual fun isSignInWithEmailLink(link: String): Boolean = ios.isSignInWithEmailLink(link) - actual suspend fun signInWithEmailAndPassword(email: String, password: String) = + public actual suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult = AuthResult(ios.awaitResult { signInWithEmail(email = email, password = password, completion = it) }) - actual suspend fun signInWithCustomToken(token: String) = + public actual suspend fun signInWithCustomToken(token: String): AuthResult = AuthResult(ios.awaitResult { signInWithCustomToken(token, it) }) - actual suspend fun signInAnonymously() = + public actual suspend fun signInAnonymously(): AuthResult = AuthResult(ios.awaitResult { signInAnonymouslyWithCompletion(it) }) - actual suspend fun signInWithCredential(authCredential: AuthCredential) = + public actual suspend fun signInWithCredential(authCredential: AuthCredential): AuthResult = AuthResult(ios.awaitResult { signInWithCredential(authCredential.ios, it) }) - actual suspend fun signInWithEmailLink(email: String, link: String) = + public actual suspend fun signInWithEmailLink(email: String, link: String): AuthResult = AuthResult(ios.awaitResult { signInWithEmail(email = email, link = link, completion = it) }) - actual suspend fun signOut() = ios.throwError { signOut(it) }.run { Unit } + public actual suspend fun signOut(): Unit = ios.throwError { signOut(it) } - actual suspend fun updateCurrentUser(user: FirebaseUser) = ios.await { updateCurrentUser(user.ios, it) }.run { Unit } - actual suspend fun verifyPasswordResetCode(code: String): String = ios.awaitResult { verifyPasswordResetCode(code, it) } + public actual suspend fun updateCurrentUser(user: FirebaseUser): Unit = ios.await { updateCurrentUser(user.ios, it) } + public actual suspend fun verifyPasswordResetCode(code: String): String = ios.awaitResult { verifyPasswordResetCode(code, it) } - actual suspend fun checkActionCode(code: String): T { + public actual suspend fun checkActionCode(code: String): T { val result: FIRActionCodeInfo = ios.awaitResult { checkActionCode(code, it) } @Suppress("UNCHECKED_CAST") - return when(result.operation) { + return when (result.operation) { FIRActionCodeOperationEmailLink -> SignInWithEmailLink FIRActionCodeOperationVerifyEmail -> VerifyEmail(result.email!!) FIRActionCodeOperationPasswordReset -> PasswordReset(result.email!!) @@ -98,47 +102,50 @@ actual data class FirebaseAuth internal constructor(val ios: FIRAuth) { } as T } - actual fun useEmulator(host: String, port: Int) = ios.useEmulatorWithHost(host, port.toLong()) + public actual fun useEmulator(host: String, port: Int): Unit = ios.useEmulatorWithHost(host, port.toLong()) } +public val AuthResult.ios: FIRAuthDataResult get() = ios -actual class AuthResult internal constructor(val ios: FIRAuthDataResult) { - actual val user: FirebaseUser? +public actual class AuthResult internal constructor(internal val ios: FIRAuthDataResult) { + public actual val user: FirebaseUser? get() = FirebaseUser(ios.user) } -actual class AuthTokenResult(val ios: FIRAuthTokenResult) { +public val AuthTokenResult.ios: FIRAuthTokenResult get() = ios +public actual class AuthTokenResult(internal val ios: FIRAuthTokenResult) { // actual val authTimestamp: Long // get() = ios.authDate - actual val claims: Map + public actual val claims: Map get() = ios.claims.map { it.key.toString() to it.value as Any }.toMap() + // actual val expirationTimestamp: Long // get() = ios.expirationDate // actual val issuedAtTimestamp: Long // get() = ios.issuedAtDate - actual val signInProvider: String? + public actual val signInProvider: String? get() = ios.signInProvider - actual val token: String? + public actual val token: String? get() = ios.token } internal fun ActionCodeSettings.toIos() = FIRActionCodeSettings().also { - it.URL = NSURL.URLWithString(url) + it.URL = NSURL.URLWithString(url) androidPackageName?.run { it.setAndroidPackageName(packageName, installIfNotAvailable, minimumVersion) } it.dynamicLinkDomain = dynamicLinkDomain it.handleCodeInApp = canHandleCodeInApp iOSBundleId?.run { it.setIOSBundleID(this) } } -actual open class FirebaseAuthException(message: String): FirebaseException(message) -actual open class FirebaseAuthActionCodeException(message: String): FirebaseAuthException(message) -actual open class FirebaseAuthEmailException(message: String): FirebaseAuthException(message) -actual open class FirebaseAuthInvalidCredentialsException(message: String): FirebaseAuthException(message) -actual open class FirebaseAuthWeakPasswordException(message: String): FirebaseAuthInvalidCredentialsException(message) -actual open class FirebaseAuthInvalidUserException(message: String): FirebaseAuthException(message) -actual open class FirebaseAuthMultiFactorException(message: String): FirebaseAuthException(message) -actual open class FirebaseAuthRecentLoginRequiredException(message: String): FirebaseAuthException(message) -actual open class FirebaseAuthUserCollisionException(message: String): FirebaseAuthException(message) -actual open class FirebaseAuthWebException(message: String): FirebaseAuthException(message) +public actual open class FirebaseAuthException(message: String) : FirebaseException(message) +public actual open class FirebaseAuthActionCodeException(message: String) : FirebaseAuthException(message) +public actual open class FirebaseAuthEmailException(message: String) : FirebaseAuthException(message) +public actual open class FirebaseAuthInvalidCredentialsException(message: String) : FirebaseAuthException(message) +public actual open class FirebaseAuthWeakPasswordException(message: String) : FirebaseAuthInvalidCredentialsException(message) +public actual open class FirebaseAuthInvalidUserException(message: String) : FirebaseAuthException(message) +public actual open class FirebaseAuthMultiFactorException(message: String) : FirebaseAuthException(message) +public actual open class FirebaseAuthRecentLoginRequiredException(message: String) : FirebaseAuthException(message) +public actual open class FirebaseAuthUserCollisionException(message: String) : FirebaseAuthException(message) +public actual open class FirebaseAuthWebException(message: String) : FirebaseAuthException(message) internal fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { memScoped { @@ -154,34 +161,33 @@ internal fun T.throwError(block: T.(errorPointer: CPointer T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { val job = CompletableDeferred() - val callback = { result: R?, error: NSError? -> - if(error == null) { + function { result, error -> + if (error == null) { job.complete(result) } else { job.completeExceptionally(error.toException()) } } - function(callback) return job.await() as R } internal suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { val job = CompletableDeferred() - val callback = { error: NSError? -> - if(error == null) { + function { error -> + if (error == null) { job.complete(Unit) } else { job.completeExceptionally(error.toException()) } } - function(callback) job.await() } -private fun NSError.toException() = when(domain) { - FIRAuthErrorDomain -> when(code) { +private fun NSError.toException() = when (domain) { + FIRAuthErrorDomain -> when (code) { FIRAuthErrorCodeInvalidActionCode, - FIRAuthErrorCodeExpiredActionCode -> FirebaseAuthActionCodeException(toString()) + FIRAuthErrorCodeExpiredActionCode, + -> FirebaseAuthActionCodeException(toString()) FIRAuthErrorCodeInvalidEmail -> FirebaseAuthEmailException(toString()) @@ -193,7 +199,8 @@ private fun NSError.toException() = when(domain) { FIRAuthErrorCodeMissingVerificationID, FIRAuthErrorCodeMissingVerificationCode, FIRAuthErrorCodeUserTokenExpired, - FIRAuthErrorCodeInvalidCredential -> FirebaseAuthInvalidCredentialsException(toString()) + FIRAuthErrorCodeInvalidCredential, + -> FirebaseAuthInvalidCredentialsException(toString()) FIRAuthErrorCodeWeakPassword -> FirebaseAuthWeakPasswordException(toString()) @@ -204,15 +211,18 @@ private fun NSError.toException() = when(domain) { FIRAuthErrorCodeSecondFactorAlreadyEnrolled, FIRAuthErrorCodeSecondFactorRequired, FIRAuthErrorCodeMaximumSecondFactorCountExceeded, - FIRAuthErrorCodeMultiFactorInfoNotFound -> FirebaseAuthMultiFactorException(toString()) + FIRAuthErrorCodeMultiFactorInfoNotFound, + -> FirebaseAuthMultiFactorException(toString()) FIRAuthErrorCodeEmailAlreadyInUse, FIRAuthErrorCodeAccountExistsWithDifferentCredential, - FIRAuthErrorCodeCredentialAlreadyInUse -> FirebaseAuthUserCollisionException(toString()) + FIRAuthErrorCodeCredentialAlreadyInUse, + -> FirebaseAuthUserCollisionException(toString()) FIRAuthErrorCodeWebContextAlreadyPresented, FIRAuthErrorCodeWebContextCancelled, - FIRAuthErrorCodeWebInternalError -> FirebaseAuthWebException(toString()) + FIRAuthErrorCodeWebInternalError, + -> FirebaseAuthWebException(toString()) FIRAuthErrorCodeNetworkError -> FirebaseNetworkException(toString()) diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 365fee8ce..08387a0e1 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -6,59 +6,61 @@ package dev.gitlive.firebase.auth import cocoapods.FirebaseAuth.* -actual open class AuthCredential(open val ios: FIRAuthCredential) { - actual val providerId: String +public actual open class AuthCredential(public open val ios: FIRAuthCredential) { + public actual val providerId: String get() = ios.provider } -actual class PhoneAuthCredential(override val ios: FIRPhoneAuthCredential) : AuthCredential(ios) -actual class OAuthCredential(override val ios: FIROAuthCredential) : AuthCredential(ios) +public actual class PhoneAuthCredential(override val ios: FIRPhoneAuthCredential) : AuthCredential(ios) +public actual class OAuthCredential(override val ios: FIROAuthCredential) : AuthCredential(ios) -actual object EmailAuthProvider { - actual fun credential( +public actual object EmailAuthProvider { + public actual fun credential( email: String, - password: String + password: String, ): AuthCredential = AuthCredential(FIREmailAuthProvider.credentialWithEmail(email = email, password = password)) - actual fun credentialWithLink( + public actual fun credentialWithLink( email: String, - emailLink: String + emailLink: String, ): AuthCredential = AuthCredential(FIREmailAuthProvider.credentialWithEmail(email = email, link = emailLink)) } -actual object FacebookAuthProvider { - actual fun credential(accessToken: String): AuthCredential = AuthCredential(FIRFacebookAuthProvider.credentialWithAccessToken(accessToken)) +public actual object FacebookAuthProvider { + public actual fun credential(accessToken: String): AuthCredential = AuthCredential(FIRFacebookAuthProvider.credentialWithAccessToken(accessToken)) } -actual object GithubAuthProvider { - actual fun credential(token: String): AuthCredential = AuthCredential(FIRGitHubAuthProvider.credentialWithToken(token)) +public actual object GithubAuthProvider { + public actual fun credential(token: String): AuthCredential = AuthCredential(FIRGitHubAuthProvider.credentialWithToken(token)) } -actual object GoogleAuthProvider { - actual fun credential(idToken: String?, accessToken: String?): AuthCredential { +public actual object GoogleAuthProvider { + public actual fun credential(idToken: String?, accessToken: String?): AuthCredential { requireNotNull(idToken) { "idToken must not be null" } requireNotNull(accessToken) { "accessToken must not be null" } return AuthCredential(FIRGoogleAuthProvider.credentialWithIDToken(idToken, accessToken)) } } -actual class OAuthProvider(val ios: FIROAuthProvider) { +public val OAuthProvider.ios: FIROAuthProvider get() = ios - actual constructor( +public actual class OAuthProvider(internal val ios: FIROAuthProvider) { + + public actual constructor( provider: String, scopes: List, customParameters: Map, - auth: FirebaseAuth + auth: FirebaseAuth, ) : this(FIROAuthProvider.providerWithProviderID(provider, auth.ios)) { ios.setScopes(scopes) @Suppress("UNCHECKED_CAST") ios.setCustomParameters(customParameters as Map) } - actual companion object { - actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { + public actual companion object { + public actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { val credential = when { idToken == null -> FIROAuthProvider.credentialWithProviderID(providerID = providerId, accessToken = accessToken!!) accessToken == null -> FIROAuthProvider.credentialWithProviderID(providerID = providerId, IDToken = idToken, rawNonce = rawNonce!!) @@ -70,24 +72,26 @@ actual class OAuthProvider(val ios: FIROAuthProvider) { } } -actual class PhoneAuthProvider(val ios: FIRPhoneAuthProvider) { +public val PhoneAuthProvider.ios: FIRPhoneAuthProvider get() = ios + +public actual class PhoneAuthProvider(internal val ios: FIRPhoneAuthProvider) { - actual constructor(auth: FirebaseAuth) : this(FIRPhoneAuthProvider.providerWithAuth(auth.ios)) + public actual constructor(auth: FirebaseAuth) : this(FIRPhoneAuthProvider.providerWithAuth(auth.ios)) - actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(ios.credentialWithVerificationID(verificationId, smsCode)) + public actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(ios.credentialWithVerificationID(verificationId, smsCode)) - actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential { + public actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential { val verificationId: String = ios.awaitResult { ios.verifyPhoneNumber(phoneNumber, verificationProvider.delegate, it) } val verificationCode = verificationProvider.getVerificationCode() return credential(verificationId, verificationCode) } } -actual interface PhoneVerificationProvider { - val delegate: FIRAuthUIDelegateProtocol - suspend fun getVerificationCode(): String +public actual interface PhoneVerificationProvider { + public val delegate: FIRAuthUIDelegateProtocol? + public suspend fun getVerificationCode(): String } -actual object TwitterAuthProvider { - actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(FIRTwitterAuthProvider.credentialWithToken(token, secret)) +public actual object TwitterAuthProvider { + public actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(FIRTwitterAuthProvider.credentialWithToken(token, secret)) } diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt index 2822e7406..f389b6abb 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt @@ -6,37 +6,47 @@ package dev.gitlive.firebase.auth import cocoapods.FirebaseAuth.* -actual class MultiFactor(val ios: FIRMultiFactor) { - actual val enrolledFactors: List - get() = ios.enrolledFactors.mapNotNull { info -> (info as? FIRMultiFactorInfo)?.let{ MultiFactorInfo(it) } } - actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) = ios.await { enrollWithAssertion(multiFactorAssertion.ios, displayName, it) }.run { Unit } - actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(ios.awaitResult { getSessionWithCompletion(completion = it) }) - actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) = ios.await { unenrollWithInfo(multiFactorInfo.ios, it) }.run { Unit } - actual suspend fun unenroll(factorUid: String) = ios.await { unenrollWithFactorUID(factorUid, it) }.run { Unit } +public val MultiFactor.ios: FIRMultiFactor get() = ios + +public actual class MultiFactor(internal val ios: FIRMultiFactor) { + public actual val enrolledFactors: List + get() = ios.enrolledFactors.mapNotNull { info -> (info as? FIRMultiFactorInfo)?.let { MultiFactorInfo(it) } } + public actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?): Unit = ios.await { enrollWithAssertion(multiFactorAssertion.ios, displayName, it) } + public actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(ios.awaitResult { getSessionWithCompletion(completion = it) }) + public actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo): Unit = ios.await { unenrollWithInfo(multiFactorInfo.ios, it) } + public actual suspend fun unenroll(factorUid: String): Unit = ios.await { unenrollWithFactorUID(factorUid, it) } } -actual class MultiFactorInfo(val ios: FIRMultiFactorInfo) { - actual val displayName: String? +public val MultiFactorInfo.ios: FIRMultiFactorInfo get() = ios + +public actual class MultiFactorInfo(internal val ios: FIRMultiFactorInfo) { + public actual val displayName: String? get() = ios.displayName - actual val enrollmentTime: Double - get() = ios.enrollmentDate.timeIntervalSinceReferenceDate.toDouble() - actual val factorId: String + public actual val enrollmentTime: Double + get() = ios.enrollmentDate.timeIntervalSinceReferenceDate + public actual val factorId: String get() = ios.factorID - actual val uid: String + public actual val uid: String get() = ios.UID } -actual class MultiFactorAssertion(val ios: FIRMultiFactorAssertion) { - actual val factorId: String +public val MultiFactorAssertion.ios: FIRMultiFactorAssertion get() = ios + +public actual class MultiFactorAssertion(internal val ios: FIRMultiFactorAssertion) { + public actual val factorId: String get() = ios.factorID } -actual class MultiFactorSession(val ios: FIRMultiFactorSession) +public val MultiFactorSession.ios: FIRMultiFactorSession get() = ios + +public actual class MultiFactorSession(internal val ios: FIRMultiFactorSession) -actual class MultiFactorResolver(val ios: FIRMultiFactorResolver) { - actual val auth: FirebaseAuth = FirebaseAuth(ios.auth) - actual val hints: List = ios.hints.mapNotNull { hint -> (hint as? FIRMultiFactorInfo)?.let { MultiFactorInfo(it) } } - actual val session: MultiFactorSession = MultiFactorSession(ios.session) +public val MultiFactorResolver.ios: FIRMultiFactorResolver get() = ios - actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(ios.awaitResult { resolveSignInWithAssertion(assertion.ios, it) }) -} \ No newline at end of file +public actual class MultiFactorResolver(internal val ios: FIRMultiFactorResolver) { + public actual val auth: FirebaseAuth = FirebaseAuth(ios.auth) + public actual val hints: List = ios.hints.mapNotNull { hint -> (hint as? FIRMultiFactorInfo)?.let { MultiFactorInfo(it) } } + public actual val session: MultiFactorSession = MultiFactorSession(ios.session) + + public actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(ios.awaitResult { resolveSignInWithAssertion(assertion.ios, it) }) +} diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt index f4f0b6c70..f2f6c67cc 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/user.kt @@ -10,93 +10,99 @@ import cocoapods.FirebaseAuth.FIRUserInfoProtocol import cocoapods.FirebaseAuth.FIRUserMetadata import platform.Foundation.NSURL -actual class FirebaseUser internal constructor(val ios: FIRUser) : FirebaseUserProfile { - actual val uid: String +public val FirebaseUser.ios: FIRUser get() = ios + +public actual class FirebaseUser internal constructor(internal val ios: FIRUser) { + public actual val uid: String get() = ios.uid - override val displayName: String? + public actual val displayName: String? get() = ios.displayName - actual val email: String? + public actual val email: String? get() = ios.email - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = ios.phoneNumber - override val photoURL: String? + public actual val photoURL: String? get() = ios.photoURL?.absoluteString - actual val isAnonymous: Boolean + public actual val isAnonymous: Boolean get() = ios.anonymous - actual val isEmailVerified: Boolean + public actual val isEmailVerified: Boolean get() = ios.emailVerified - actual val metaData: UserMetaData? + public actual val metaData: UserMetaData? get() = UserMetaData(ios.metadata) - actual val multiFactor: MultiFactor + public actual val multiFactor: MultiFactor get() = MultiFactor(ios.multiFactor) - actual val providerData: List + public actual val providerData: List get() = ios.providerData.mapNotNull { provider -> (provider as? FIRUserInfoProtocol)?.let { UserInfo(it) } } - actual val providerId: String + public actual val providerId: String get() = ios.providerID - actual suspend fun delete() = ios.await { deleteWithCompletion(it) }.run { Unit } + public actual suspend fun delete(): Unit = ios.await { deleteWithCompletion(it) } - actual suspend fun reload() = ios.await { reloadWithCompletion(it) }.run { Unit } + public actual suspend fun reload(): Unit = ios.await { reloadWithCompletion(it) } - actual suspend fun getIdToken(forceRefresh: Boolean): String? = + public actual suspend fun getIdToken(forceRefresh: Boolean): String? = ios.awaitResult { getIDTokenForcingRefresh(forceRefresh, it) } - actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = + public actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = AuthTokenResult(ios.awaitResult { getIDTokenResultForcingRefresh(forceRefresh, it) }) - actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = + public actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = AuthResult(ios.awaitResult { linkWithCredential(credential.ios, it) }) - actual suspend fun reauthenticate(credential: AuthCredential) = - ios.awaitResult { reauthenticateWithCredential(credential.ios, it) }.run { Unit } + public actual suspend fun reauthenticate(credential: AuthCredential) { + ios.awaitResult { reauthenticateWithCredential(credential.ios, it) } + } - actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = + public actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = AuthResult(ios.awaitResult { reauthenticateWithCredential(credential.ios, it) }) - actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) = ios.await { + public actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?): Unit = ios.await { actionCodeSettings?.let { settings -> sendEmailVerificationWithActionCodeSettings(settings.toIos(), it) } ?: sendEmailVerificationWithCompletion(it) } - actual suspend fun unlink(provider: String): FirebaseUser? { + public actual suspend fun unlink(provider: String): FirebaseUser? { val user: FIRUser? = ios.awaitResult { unlinkFromProvider(provider, it) } return user?.let { FirebaseUser(it) } } - actual suspend fun updateEmail(email: String) = ios.await { updateEmail(email, it) }.run { Unit } - actual suspend fun updatePassword(password: String) = ios.await { updatePassword(password, it) }.run { Unit } - actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential) = ios.await { updatePhoneNumberCredential(credential.ios, it) }.run { Unit } - override suspend fun updateProfile(displayName: String?, photoUrl: String?) { - val request = ios.profileChangeRequest().apply { - this.displayName = displayName - this.photoURL = photoUrl?.let { NSURL.URLWithString(it) } - } + public actual suspend fun updateEmail(email: String): Unit = ios.await { updateEmail(email, it) } + public actual suspend fun updatePassword(password: String): Unit = ios.await { updatePassword(password, it) } + public actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential): Unit = ios.await { updatePhoneNumberCredential(credential.ios, it) } + public actual suspend fun updateProfile(displayName: String?, photoUrl: String?) { + val request = ios.profileChangeRequest() + .apply { setDisplayName(displayName) } + .apply { setPhotoURL(photoUrl?.let { NSURL.URLWithString(it) }) } ios.await { request.commitChangesWithCompletion(it) } } - actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = ios.await { + public actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?): Unit = ios.await { actionCodeSettings?.let { actionSettings -> sendEmailVerificationBeforeUpdatingEmail(newEmail, actionSettings.toIos(), it) } ?: sendEmailVerificationBeforeUpdatingEmail(newEmail, it) - }.run { Unit } + } } -actual class UserInfo(val ios: FIRUserInfoProtocol) { - actual val displayName: String? +public val UserInfo.ios: FIRUserInfoProtocol get() = ios + +public actual class UserInfo(internal val ios: FIRUserInfoProtocol) { + public actual val displayName: String? get() = ios.displayName - actual val email: String? + public actual val email: String? get() = ios.email - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = ios.phoneNumber - actual val photoURL: String? + public actual val photoURL: String? get() = ios.photoURL?.absoluteString - actual val providerId: String + public actual val providerId: String get() = ios.providerID - actual val uid: String + public actual val uid: String get() = ios.uid } -actual class UserMetaData(val ios: FIRUserMetadata) { - actual val creationTime: Double? - get() = ios.creationDate?.timeIntervalSinceReferenceDate?.toDouble() - actual val lastSignInTime: Double? - get() = ios.lastSignInDate?.timeIntervalSinceReferenceDate?.toDouble() +public val UserMetaData.ios: FIRUserMetadata get() = ios + +public actual class UserMetaData(internal val ios: FIRUserMetadata) { + public actual val creationTime: Double? + get() = ios.creationDate?.timeIntervalSinceReferenceDate + public actual val lastSignInTime: Double? + get() = ios.lastSignInDate?.timeIntervalSinceReferenceDate } 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 9bfc2e61b..a222d4d6e 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 @@ -4,125 +4,157 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.auth.externals.* +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.FirebaseNetworkException +import dev.gitlive.firebase.auth.externals.Auth +import dev.gitlive.firebase.auth.externals.getAuth +import dev.gitlive.firebase.auth.externals.applyActionCode +import dev.gitlive.firebase.auth.externals.confirmPasswordReset +import dev.gitlive.firebase.auth.externals.createUserWithEmailAndPassword +import dev.gitlive.firebase.auth.externals.sendPasswordResetEmail +import dev.gitlive.firebase.auth.externals.fetchSignInMethodsForEmail +import dev.gitlive.firebase.auth.externals.sendSignInLinkToEmail +import dev.gitlive.firebase.auth.externals.isSignInWithEmailLink +import dev.gitlive.firebase.auth.externals.signInWithEmailAndPassword +import dev.gitlive.firebase.auth.externals.signInWithCustomToken +import dev.gitlive.firebase.auth.externals.signInAnonymously +import dev.gitlive.firebase.auth.externals.signInWithCredential +import dev.gitlive.firebase.auth.externals.signInWithEmailLink +import dev.gitlive.firebase.auth.externals.signOut +import dev.gitlive.firebase.auth.externals.updateCurrentUser +import dev.gitlive.firebase.auth.externals.verifyPasswordResetCode +import dev.gitlive.firebase.auth.externals.checkActionCode +import dev.gitlive.firebase.auth.externals.connectAuthEmulator +import dev.gitlive.firebase.auth.externals.IdTokenResult +import dev.gitlive.firebase.js import kotlinx.coroutines.await import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlin.js.json import dev.gitlive.firebase.auth.externals.AuthResult as JsAuthResult -actual val Firebase.auth +public actual val Firebase.auth: FirebaseAuth get() = rethrow { FirebaseAuth(getAuth()) } -actual fun Firebase.auth(app: FirebaseApp) = +public actual fun Firebase.auth(app: FirebaseApp): FirebaseAuth = rethrow { FirebaseAuth(getAuth(app.js)) } -actual class FirebaseAuth internal constructor(val js: Auth) { +public val FirebaseAuth.js: Auth get() = js - actual val currentUser: FirebaseUser? +public actual class FirebaseAuth internal constructor(internal val js: Auth) { + + public actual val currentUser: FirebaseUser? get() = rethrow { js.currentUser?.let { FirebaseUser(it) } } - actual val authStateChanged get() = callbackFlow { + public actual val authStateChanged: Flow get() = callbackFlow { val unsubscribe = js.onAuthStateChanged { trySend(it?.let { FirebaseUser(it) }) } awaitClose { unsubscribe() } } - actual val idTokenChanged get() = callbackFlow { + public actual val idTokenChanged: Flow get() = callbackFlow { val unsubscribe = js.onIdTokenChanged { trySend(it?.let { FirebaseUser(it) }) } awaitClose { unsubscribe() } } - actual var languageCode: String + public actual var languageCode: String get() = js.languageCode ?: "" - set(value) { js.languageCode = value } + set(value) { + js.languageCode = value + } - actual suspend fun applyActionCode(code: String) = rethrow { applyActionCode(js, code).await() } - actual suspend fun confirmPasswordReset(code: String, newPassword: String) = rethrow { confirmPasswordReset(js, code, newPassword).await() } + public actual suspend fun applyActionCode(code: String): Unit = rethrow { applyActionCode(js, code).await() } + public actual suspend fun confirmPasswordReset(code: String, newPassword: String): Unit = rethrow { confirmPasswordReset(js, code, newPassword).await() } - actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = + public actual suspend fun createUserWithEmailAndPassword(email: String, password: String): AuthResult = rethrow { AuthResult(createUserWithEmailAndPassword(js, email, password).await()) } - actual suspend fun fetchSignInMethodsForEmail(email: String): List = rethrow { fetchSignInMethodsForEmail(js, email).await().asList() } + public actual suspend fun fetchSignInMethodsForEmail(email: String): List = rethrow { fetchSignInMethodsForEmail(js, email).await().asList() } - actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) = + public actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?): Unit = rethrow { sendPasswordResetEmail(js, email, actionCodeSettings?.toJson()).await() } - actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = + public actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings): Unit = rethrow { sendSignInLinkToEmail(js, email, actionCodeSettings.toJson()).await() } - actual fun isSignInWithEmailLink(link: String) = rethrow { isSignInWithEmailLink(js, link) } + public actual fun isSignInWithEmailLink(link: String): Boolean = rethrow { isSignInWithEmailLink(js, link) } - actual suspend fun signInWithEmailAndPassword(email: String, password: String) = + public actual suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult = rethrow { AuthResult(signInWithEmailAndPassword(js, email, password).await()) } - actual suspend fun signInWithCustomToken(token: String) = + public actual suspend fun signInWithCustomToken(token: String): AuthResult = rethrow { AuthResult(signInWithCustomToken(js, token).await()) } - actual suspend fun signInAnonymously() = + public actual suspend fun signInAnonymously(): AuthResult = rethrow { AuthResult(signInAnonymously(js).await()) } - actual suspend fun signInWithCredential(authCredential: AuthCredential) = + public actual suspend fun signInWithCredential(authCredential: AuthCredential): AuthResult = rethrow { AuthResult(signInWithCredential(js, authCredential.js).await()) } - actual suspend fun signInWithEmailLink(email: String, link: String) = + public actual suspend fun signInWithEmailLink(email: String, link: String): AuthResult = rethrow { AuthResult(signInWithEmailLink(js, email, link).await()) } - actual suspend fun signOut() = rethrow { signOut(js).await() } + public actual suspend fun signOut(): Unit = rethrow { signOut(js).await() } - actual suspend fun updateCurrentUser(user: FirebaseUser) = + public actual suspend fun updateCurrentUser(user: FirebaseUser): Unit = rethrow { updateCurrentUser(js, user.js).await() } - actual suspend fun verifyPasswordResetCode(code: String): String = + public actual suspend fun verifyPasswordResetCode(code: String): String = rethrow { verifyPasswordResetCode(js, code).await() } - actual suspend fun checkActionCode(code: String): T = rethrow { + public actual suspend fun checkActionCode(code: String): T = rethrow { val result = checkActionCode(js, code).await() @Suppress("UNCHECKED_CAST") - return when(result.operation) { + return when (result.operation) { "EMAIL_SIGNIN" -> ActionCodeResult.SignInWithEmailLink "VERIFY_EMAIL" -> ActionCodeResult.VerifyEmail(result.data.email!!) "PASSWORD_RESET" -> ActionCodeResult.PasswordReset(result.data.email!!) "RECOVER_EMAIL" -> ActionCodeResult.RecoverEmail(result.data.email!!, result.data.previousEmail!!) "VERIFY_AND_CHANGE_EMAIL" -> ActionCodeResult.VerifyBeforeChangeEmail( result.data.email!!, - result.data.previousEmail!! + result.data.previousEmail!!, ) "REVERT_SECOND_FACTOR_ADDITION" -> ActionCodeResult.RevertSecondFactorAddition( result.data.email!!, - result.data.multiFactorInfo?.let { MultiFactorInfo(it) } + result.data.multiFactorInfo?.let { MultiFactorInfo(it) }, ) else -> throw UnsupportedOperationException(result.operation) } as T } - actual fun useEmulator(host: String, port: Int) = rethrow { connectAuthEmulator(js, "http://$host:$port") } + public actual fun useEmulator(host: String, port: Int): Unit = rethrow { connectAuthEmulator(js, "http://$host:$port") } } -actual class AuthResult internal constructor(val js: JsAuthResult) { - actual val user: FirebaseUser? +public val AuthResult.js: JsAuthResult get() = js + +public actual class AuthResult internal constructor(internal val js: JsAuthResult) { + public actual val user: FirebaseUser? get() = rethrow { js.user?.let { FirebaseUser(it) } } } -actual class AuthTokenResult(val js: IdTokenResult) { +public val AuthTokenResult.js: IdTokenResult get() = js + +public actual class AuthTokenResult(internal val js: IdTokenResult) { // actual val authTimestamp: Long // get() = js.authTime - actual val claims: Map - get() = (js("Object").keys(js.claims) as Array).mapNotNull { - key -> js.claims[key]?.let { key to it } + public actual val claims: Map + get() = (js("Object").keys(js.claims) as Array).mapNotNull { key -> + js.claims[key]?.let { key to it } }.toMap() + // actual val expirationTimestamp: Long // get() = android.expirationTime // actual val issuedAtTimestamp: Long // get() = js.issuedAtTime - actual val signInProvider: String? + public actual val signInProvider: String? get() = js.signInProvider - actual val token: String? + public actual val token: String? get() = js.token } @@ -131,20 +163,19 @@ internal fun ActionCodeSettings.toJson() = json( "android" to (androidPackageName?.run { json("installApp" to installIfNotAvailable, "minimumVersion" to minimumVersion, "packageName" to packageName) } ?: undefined), "dynamicLinkDomain" to (dynamicLinkDomain ?: undefined), "handleCodeInApp" to canHandleCodeInApp, - "ios" to (iOSBundleId?.run { json("bundleId" to iOSBundleId) } ?: undefined) + "ios" to (iOSBundleId?.run { json("bundleId" to iOSBundleId) } ?: undefined), ) -actual open class FirebaseAuthException(code: String?, cause: Throwable): FirebaseException(code, cause) -actual open class FirebaseAuthActionCodeException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) -actual open class FirebaseAuthEmailException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) -actual open class FirebaseAuthInvalidCredentialsException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) -actual open class FirebaseAuthWeakPasswordException(code: String?, cause: Throwable): FirebaseAuthInvalidCredentialsException(code, cause) -actual open class FirebaseAuthInvalidUserException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) -actual open class FirebaseAuthMultiFactorException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) -actual open class FirebaseAuthRecentLoginRequiredException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) -actual open class FirebaseAuthUserCollisionException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) -actual open class FirebaseAuthWebException(code: String?, cause: Throwable): FirebaseAuthException(code, cause) - +public actual open class FirebaseAuthException(code: String?, cause: Throwable) : FirebaseException(code, cause) +public actual open class FirebaseAuthActionCodeException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) +public actual open class FirebaseAuthEmailException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) +public actual open class FirebaseAuthInvalidCredentialsException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) +public actual open class FirebaseAuthWeakPasswordException(code: String?, cause: Throwable) : FirebaseAuthInvalidCredentialsException(code, cause) +public actual open class FirebaseAuthInvalidUserException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) +public actual open class FirebaseAuthMultiFactorException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) +public actual open class FirebaseAuthRecentLoginRequiredException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) +public actual open class FirebaseAuthUserCollisionException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) +public actual open class FirebaseAuthWebException(code: String?, cause: Throwable) : FirebaseAuthException(code, cause) internal inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.auth.rethrow { function() } @@ -153,30 +184,32 @@ private inline fun rethrow(function: () -> R): R { return function() } catch (e: Exception) { throw e - } catch(e: dynamic) { + } catch (e: dynamic) { throw errorToException(e) } } -private fun errorToException(cause: dynamic) = when(val code = cause.code?.toString()?.lowercase()) { - "auth/invalid-user-token" -> FirebaseAuthInvalidUserException(code, cause) - "auth/requires-recent-login" -> FirebaseAuthRecentLoginRequiredException(code, cause) - "auth/user-disabled" -> FirebaseAuthInvalidUserException(code, cause) - "auth/user-token-expired" -> FirebaseAuthInvalidUserException(code, cause) - "auth/web-storage-unsupported" -> FirebaseAuthWebException(code, cause) - "auth/network-request-failed" -> FirebaseNetworkException(code, cause) - "auth/timeout" -> FirebaseNetworkException(code, cause) - "auth/weak-password" -> FirebaseAuthWeakPasswordException(code, cause) +private fun errorToException(cause: dynamic) = when (val code = cause.code?.toString()?.lowercase()) { + "auth/invalid-user-token" -> FirebaseAuthInvalidUserException(code, cause.unsafeCast()) + "auth/requires-recent-login" -> FirebaseAuthRecentLoginRequiredException(code, cause.unsafeCast()) + "auth/user-disabled" -> FirebaseAuthInvalidUserException(code, cause.unsafeCast()) + "auth/user-token-expired" -> FirebaseAuthInvalidUserException(code, cause.unsafeCast()) + "auth/web-storage-unsupported" -> FirebaseAuthWebException(code, cause.unsafeCast()) + "auth/network-request-failed" -> FirebaseNetworkException(code, cause.unsafeCast()) + "auth/timeout" -> FirebaseNetworkException(code, cause.unsafeCast()) + "auth/weak-password" -> FirebaseAuthWeakPasswordException(code, cause.unsafeCast()) "auth/invalid-credential", "auth/invalid-verification-code", "auth/missing-verification-code", "auth/invalid-verification-id", - "auth/missing-verification-id" -> FirebaseAuthInvalidCredentialsException(code, cause) + "auth/missing-verification-id", + -> FirebaseAuthInvalidCredentialsException(code, cause.unsafeCast()) "auth/maximum-second-factor-count-exceeded", - "auth/second-factor-already-in-use" -> FirebaseAuthMultiFactorException(code, cause) - "auth/credential-already-in-use" -> FirebaseAuthUserCollisionException(code, cause) - "auth/email-already-in-use" -> FirebaseAuthUserCollisionException(code, cause) - "auth/invalid-email" -> FirebaseAuthEmailException(code, cause) + "auth/second-factor-already-in-use", + -> FirebaseAuthMultiFactorException(code, cause.unsafeCast()) + "auth/credential-already-in-use" -> FirebaseAuthUserCollisionException(code, cause.unsafeCast()) + "auth/email-already-in-use" -> FirebaseAuthUserCollisionException(code, cause.unsafeCast()) + "auth/invalid-email" -> FirebaseAuthEmailException(code, cause.unsafeCast()) // "auth/app-deleted" -> // "auth/app-not-authorized" -> // "auth/argument-error" -> @@ -186,6 +219,6 @@ private fun errorToException(cause: dynamic) = when(val code = cause.code?.toStr // "auth/unauthorized-domain" -> else -> { println("Unknown error code in ${JSON.stringify(cause)}") - FirebaseAuthException(code, cause) + FirebaseAuthException(code, cause.unsafeCast()) } } 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 eb6fb9efe..112099734 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 @@ -5,43 +5,45 @@ 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.PhoneAuthProvider as JsPhoneAuthProvider import dev.gitlive.firebase.auth.externals.TwitterAuthProvider import kotlinx.coroutines.await import kotlin.js.json import dev.gitlive.firebase.auth.externals.AuthCredential as JsAuthCredential import dev.gitlive.firebase.auth.externals.OAuthProvider as JsOAuthProvider -actual open class AuthCredential(val js: JsAuthCredential) { - actual val providerId: String +public val AuthCredential.js: JsAuthCredential get() = js + +public actual open class AuthCredential(internal val js: JsAuthCredential) { + public actual val providerId: String get() = js.providerId } -actual class PhoneAuthCredential(js: JsAuthCredential) : AuthCredential(js) -actual class OAuthCredential(js: JsAuthCredential) : AuthCredential(js) +public actual class PhoneAuthCredential(js: JsAuthCredential) : AuthCredential(js) +public actual class OAuthCredential(js: JsAuthCredential) : AuthCredential(js) -actual object EmailAuthProvider { - actual fun credential(email: String, password: String): AuthCredential = +public actual object EmailAuthProvider { + public actual fun credential(email: String, password: String): AuthCredential = AuthCredential(EmailAuthProvider.credential(email, password)) - actual fun credentialWithLink( + public actual fun credentialWithLink( email: String, - emailLink: String + emailLink: String, ): AuthCredential = AuthCredential(EmailAuthProvider.credentialWithLink(email, emailLink)) } -actual object FacebookAuthProvider { - actual fun credential(accessToken: String): AuthCredential = +public actual object FacebookAuthProvider { + public actual fun credential(accessToken: String): AuthCredential = AuthCredential(FacebookAuthProvider.credential(accessToken)) } -actual object GithubAuthProvider { - actual fun credential(token: String): AuthCredential = +public actual object GithubAuthProvider { + public actual fun credential(token: String): AuthCredential = AuthCredential(GithubAuthProvider.credential(token)) } -actual object GoogleAuthProvider { - actual fun credential(idToken: String?, accessToken: String?): AuthCredential { +public actual object GoogleAuthProvider { + public actual fun credential(idToken: String?, accessToken: String?): AuthCredential { require(idToken != null || accessToken != null) { "Both parameters are optional but at least one must be present." } @@ -49,52 +51,56 @@ actual object GoogleAuthProvider { } } -actual class OAuthProvider(val js: JsOAuthProvider) { +public val OAuthProvider.js: JsOAuthProvider get() = js + +public actual class OAuthProvider(internal val js: JsOAuthProvider) { - actual constructor( + public actual constructor( provider: String, scopes: List, customParameters: Map, - auth: FirebaseAuth + auth: FirebaseAuth, ) : this(JsOAuthProvider(provider)) { rethrow { scopes.forEach { js.addScope(it) } js.setCustomParameters(customParameters) } } - actual companion object { - actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential = rethrow { + public actual companion object { + public actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential = rethrow { JsOAuthProvider(providerId) .credential( json( "accessToken" to (accessToken ?: undefined), "idToken" to (idToken ?: undefined), - "rawNonce" to (rawNonce ?: undefined) + "rawNonce" to (rawNonce ?: undefined), ), - accessToken ?: undefined + accessToken ?: undefined, ) .let { OAuthCredential(it) } } } } -actual class PhoneAuthProvider(val js: PhoneAuthProvider) { +public val PhoneAuthProvider.js: JsPhoneAuthProvider get() = js + +public actual class PhoneAuthProvider(internal val js: JsPhoneAuthProvider) { - actual constructor(auth: FirebaseAuth) : this(PhoneAuthProvider(auth.js)) + public actual constructor(auth: FirebaseAuth) : this(JsPhoneAuthProvider(auth.js)) - actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(PhoneAuthProvider.credential(verificationId, smsCode)) - actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = rethrow { + public actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(JsPhoneAuthProvider.credential(verificationId, smsCode)) + public actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = rethrow { val verificationId = js.verifyPhoneNumber(phoneNumber, verificationProvider.verifier).await() val verificationCode = verificationProvider.getVerificationCode(verificationId) credential(verificationId, verificationCode) } } -actual interface PhoneVerificationProvider { - val verifier: ApplicationVerifier - suspend fun getVerificationCode(verificationId: String): String +public actual interface PhoneVerificationProvider { + public val verifier: ApplicationVerifier + public suspend fun getVerificationCode(verificationId: String): String } -actual object TwitterAuthProvider { - actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(TwitterAuthProvider.credential(token, secret)) +public actual object TwitterAuthProvider { + public 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 index ca32fe3c5..e535e6c22 100644 --- 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 @@ -8,292 +8,292 @@ import dev.gitlive.firebase.externals.FirebaseApp import kotlin.js.Json import kotlin.js.Promise -external fun applyActionCode(auth: Auth, code: String): Promise +public external fun applyActionCode(auth: Auth, code: String): Promise -external fun checkActionCode(auth: Auth, code: String): Promise +public external fun checkActionCode(auth: Auth, code: String): Promise -external fun confirmPasswordReset(auth: Auth, code: String, newPassword: String): Promise +public external fun confirmPasswordReset(auth: Auth, code: String, newPassword: String): Promise -external fun connectAuthEmulator(auth: Auth, url: String, options: Any? = definedExternally) +public external fun connectAuthEmulator(auth: Auth, url: String, options: Any? = definedExternally) -external fun createUserWithEmailAndPassword( +public external fun createUserWithEmailAndPassword( auth: Auth, email: String, - password: String + password: String, ): Promise -external fun deleteUser(user: User): Promise +public external fun deleteUser(user: User): Promise -external fun fetchSignInMethodsForEmail(auth: Auth, email: String): Promise> +public external fun fetchSignInMethodsForEmail(auth: Auth, email: String): Promise> -external fun getAuth(app: FirebaseApp? = definedExternally): Auth +public external fun getAuth(app: FirebaseApp? = definedExternally): Auth -external fun initializeAuth(app: FirebaseApp? = definedExternally, deps: dynamic = definedExternally): Auth +public external fun initializeAuth(app: FirebaseApp? = definedExternally, deps: dynamic = definedExternally): Auth -external fun getIdToken(user: User, forceRefresh: Boolean?): Promise +public external fun getIdToken(user: User, forceRefresh: Boolean?): Promise -external fun getIdTokenResult(user: User, forceRefresh: Boolean?): Promise +public external fun getIdTokenResult(user: User, forceRefresh: Boolean?): Promise -external fun isSignInWithEmailLink(auth: Auth, link: String): Boolean +public external fun isSignInWithEmailLink(auth: Auth, link: String): Boolean -external fun linkWithCredential(user: User, credential: AuthCredential): Promise +public external fun linkWithCredential(user: User, credential: AuthCredential): Promise -external fun multiFactor(user: User): MultiFactorUser +public external fun multiFactor(user: User): MultiFactorUser -external fun onAuthStateChanged(auth: Auth, nextOrObserver: (User?) -> Unit): Unsubscribe +public external fun onAuthStateChanged(auth: Auth, nextOrObserver: (User?) -> Unit): Unsubscribe -external fun onIdTokenChanged(auth: Auth, nextOrObserver: (User?) -> Unit): Unsubscribe +public external fun onIdTokenChanged(auth: Auth, nextOrObserver: (User?) -> Unit): Unsubscribe -external fun sendEmailVerification(user: User, actionCodeSettings: Any?): Promise +public external fun sendEmailVerification(user: User, actionCodeSettings: Any?): Promise -external fun reauthenticateWithCredential( +public external fun reauthenticateWithCredential( user: User, - credential: AuthCredential + credential: AuthCredential, ): Promise -external fun reload(user: User): Promise +public external fun reload(user: User): Promise -external fun sendPasswordResetEmail( +public external fun sendPasswordResetEmail( auth: Auth, email: String, - actionCodeSettings: Any? + actionCodeSettings: Any?, ): Promise -external fun sendSignInLinkToEmail( +public external fun sendSignInLinkToEmail( auth: Auth, email: String, - actionCodeSettings: Any? + actionCodeSettings: Any?, ): Promise -external fun signInAnonymously(auth: Auth): Promise +public external fun signInAnonymously(auth: Auth): Promise -external fun signInWithCredential(auth: Auth, authCredential: AuthCredential): Promise +public external fun signInWithCredential(auth: Auth, authCredential: AuthCredential): Promise -external fun signInWithCustomToken(auth: Auth, token: String): Promise +public external fun signInWithCustomToken(auth: Auth, token: String): Promise -external fun signInWithEmailAndPassword( +public external fun signInWithEmailAndPassword( auth: Auth, email: String, - password: String + password: String, ): Promise -external fun signInWithEmailLink(auth: Auth, email: String, link: String): Promise +public external fun signInWithEmailLink(auth: Auth, email: String, link: String): Promise -external fun signInWithPopup(auth: Auth, provider: AuthProvider): Promise +public external fun signInWithPopup(auth: Auth, provider: AuthProvider): Promise -external fun signInWithRedirect(auth: Auth, provider: AuthProvider): Promise +public external fun signInWithRedirect(auth: Auth, provider: AuthProvider): Promise -external fun getRedirectResult(auth: Auth): Promise +public external fun getRedirectResult(auth: Auth): Promise -external fun signOut(auth: Auth): Promise +public external fun signOut(auth: Auth): Promise -external fun unlink(user: User, providerId: String): Promise +public external fun unlink(user: User, providerId: String): Promise -external fun updateCurrentUser(auth: Auth, user: User?): Promise +public external fun updateCurrentUser(auth: Auth, user: User?): Promise -external fun updateEmail(user: User, newEmail: String): Promise +public external fun updateEmail(user: User, newEmail: String): Promise -external fun updatePassword(user: User, newPassword: String): Promise +public external fun updatePassword(user: User, newPassword: String): Promise -external fun updatePhoneNumber(user: User, phoneCredential: AuthCredential): Promise +public external fun updatePhoneNumber(user: User, phoneCredential: AuthCredential): Promise -external fun updateProfile(user: User, profile: ProfileUpdateRequest): Promise +public external fun updateProfile(user: User, profile: Json): Promise -external fun verifyBeforeUpdateEmail( +public external fun verifyBeforeUpdateEmail( user: User, newEmail: String, - actionCodeSettings: Any? + actionCodeSettings: Any?, ): Promise -external fun verifyPasswordResetCode(auth: Auth, code: String): Promise +public external fun verifyPasswordResetCode(auth: Auth, code: String): Promise -external interface Auth { - val currentUser: User? - var languageCode: String? +public external interface Auth { + public val currentUser: User? + public var languageCode: String? - fun onAuthStateChanged(nextOrObserver: (User?) -> Unit): Unsubscribe - fun onIdTokenChanged(nextOrObserver: (User?) -> Unit): Unsubscribe - fun signOut(): Promise - fun updateCurrentUser(user: User?): Promise + public fun onAuthStateChanged(nextOrObserver: (User?) -> Unit): Unsubscribe + public fun onIdTokenChanged(nextOrObserver: (User?) -> Unit): Unsubscribe + public fun signOut(): Promise + public 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 +public external interface UserInfo { + public val displayName: String? + public val email: String? + public val phoneNumber: String? + public val photoURL: String? + public val providerId: String + public 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 +public external interface User : UserInfo { + public val emailVerified: Boolean + public val isAnonymous: Boolean + public val metadata: UserMetadata + public val providerData: Array + public val refreshToken: String + public val tenantId: String? + + public fun delete(): Promise + public fun getIdToken(forceRefresh: Boolean?): Promise + public fun getIdTokenResult(forceRefresh: Boolean?): Promise + public fun reload(): Promise } -external interface UserMetadata { - val creationTime: String? - val lastSignInTime: String? +public external interface UserMetadata { + public val creationTime: String? + public 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 +public external interface IdTokenResult { + public val authTime: String + public val claims: Json + public val expirationTime: String + public val issuedAtTime: String + public val signInProvider: String? + public val signInSecondFactor: String? + public val token: String } -external interface ActionCodeInfo { - val operation: String - val data: ActionCodeData +public external interface ActionCodeInfo { + public val operation: String + public val data: ActionCodeData } -external interface ActionCodeData { - val email: String? - val multiFactorInfo: MultiFactorInfo? - val previousEmail: String? +public external interface ActionCodeData { + public val email: String? + public val multiFactorInfo: MultiFactorInfo? + public val previousEmail: String? } -external interface AuthResult { - val credential: AuthCredential? - val operationType: String? - val user: User? +public external interface AuthResult { + public val credential: AuthCredential? + public val operationType: String? + public val user: User? } -external interface AuthCredential { - val providerId: String - val signInMethod: String +public external interface AuthCredential { + public val providerId: String + public val signInMethod: String } -external interface OAuthCredential : AuthCredential { - val accessToken: String? - val idToken: String? - val secret: String? +public external interface OAuthCredential : AuthCredential { + public val accessToken: String? + public val idToken: String? + public val secret: String? } -external interface UserCredential { - val operationType: String - val providerId: String? - val user: User +public external interface UserCredential { + public val operationType: String + public val providerId: String? + public val user: User } -external interface ProfileUpdateRequest { - val displayName: String? - val photoURL: String? +public external interface ProfileUpdateRequest { + public val displayName: String? + public val photoURL: String? } -external interface MultiFactorUser { - val enrolledFactors: Array +public external interface MultiFactorUser { + public val enrolledFactors: Array - fun enroll(assertion: MultiFactorAssertion, displayName: String?): Promise - fun getSession(): Promise - fun unenroll(option: MultiFactorInfo): Promise - fun unenroll(option: String): Promise + public fun enroll(assertion: MultiFactorAssertion, displayName: String?): Promise + public fun getSession(): Promise + public fun unenroll(option: MultiFactorInfo): Promise + public fun unenroll(option: String): Promise } -external interface MultiFactorInfo { - val displayName: String? - val enrollmentTime: String - val factorId: String - val uid: String +public external interface MultiFactorInfo { + public val displayName: String? + public val enrollmentTime: String + public val factorId: String + public val uid: String } -external interface MultiFactorAssertion { - val factorId: String +public external interface MultiFactorAssertion { + public val factorId: String } -external interface MultiFactorSession +public external interface MultiFactorSession -external interface MultiFactorResolver { - val auth: Auth - val hints: Array - val session: MultiFactorSession +public external interface MultiFactorResolver { + public val auth: Auth + public val hints: Array + public val session: MultiFactorSession - fun resolveSignIn(assertion: MultiFactorAssertion): Promise + public fun resolveSignIn(assertion: MultiFactorAssertion): Promise } -external interface AuthProvider +public external interface AuthProvider -external interface AuthError +public external interface AuthError -external object EmailAuthProvider : AuthProvider { - fun credential(email: String, password: String): AuthCredential - fun credentialWithLink(email: String, emailLink: String): AuthCredential +public external object EmailAuthProvider : AuthProvider { + public fun credential(email: String, password: String): AuthCredential + public fun credentialWithLink(email: String, emailLink: String): AuthCredential } -external object FacebookAuthProvider : AuthProvider { - fun credential(token: String): AuthCredential +public external object FacebookAuthProvider : AuthProvider { + public fun credential(token: String): AuthCredential } -external object GithubAuthProvider : AuthProvider { - fun credential(token: String): AuthCredential +public external object GithubAuthProvider : AuthProvider { + public 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? +public external class GoogleAuthProvider : AuthProvider { + public fun addScope(scope: String) + public companion object { + public fun credential(idToken: String?, accessToken: String?): AuthCredential + public fun credentialFromResult(userCredential: UserCredential): OAuthCredential? + public fun credentialFromError(error: AuthError): OAuthCredential? } } -external class OAuthProvider(providerId: String) : AuthProvider { - val providerId: String - fun credential(optionsOrIdToken: Any?, accessToken: String?): AuthCredential +public external class OAuthProvider(providerId: String) : AuthProvider { + public val providerId: String + public fun credential(optionsOrIdToken: Any?, accessToken: String?): AuthCredential - fun addScope(scope: String) - fun setCustomParameters(customOAuthParameters: Map) + public fun addScope(scope: String) + public fun setCustomParameters(customOAuthParameters: Map) } -external interface OAuthCredentialOptions { - val accessToken: String? - val idToken: String? - val rawNonce: String? +public external interface OAuthCredentialOptions { + public val accessToken: String? + public val idToken: String? + public val rawNonce: String? } -external class PhoneAuthProvider(auth: Auth?) : AuthProvider { - companion object { - fun credential( +public external class PhoneAuthProvider(auth: Auth?) : AuthProvider { + public companion object { + public fun credential( verificationId: String, - verificationCode: String + verificationCode: String, ): AuthCredential } - fun verifyPhoneNumber( + public fun verifyPhoneNumber( phoneInfoOptions: String, - applicationVerifier: ApplicationVerifier + applicationVerifier: ApplicationVerifier, ): Promise } -external interface ApplicationVerifier { - val type: String - fun verify(): Promise +public external interface ApplicationVerifier { + public val type: String + public fun verify(): Promise } -external object TwitterAuthProvider : AuthProvider { - fun credential(token: String, secret: String): AuthCredential +public external object TwitterAuthProvider : AuthProvider { + public fun credential(token: String, secret: String): AuthCredential } -external interface Persistence { - val type: String +public external interface Persistence { + public val type: String } -external val browserLocalPersistence: Persistence -external val browserSessionPersistence: Persistence -external val indexedDBLocalPersistence: Persistence -external val inMemoryPersistence: Persistence +public external val browserLocalPersistence: Persistence +public external val browserSessionPersistence: Persistence +public external val indexedDBLocalPersistence: Persistence +public external val inMemoryPersistence: Persistence -external fun setPersistence(auth: Auth, persistence: Persistence): Promise; +public external fun setPersistence(auth: Auth, persistence: Persistence): Promise 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 d4ad636a7..36daf9f4f 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 @@ -8,41 +8,51 @@ 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: MultiFactorUser) { - actual val enrolledFactors: List +public val MultiFactor.js get() = js + +public actual class MultiFactor(internal val js: MultiFactorUser) { + public actual val enrolledFactors: List get() = rethrow { js.enrolledFactors.map { MultiFactorInfo(it) } } - actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) = + public actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?): Unit = rethrow { js.enroll(multiFactorAssertion.js, displayName).await() } - actual suspend fun getSession(): MultiFactorSession = + public actual suspend fun getSession(): MultiFactorSession = rethrow { MultiFactorSession(js.getSession().await()) } - actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) = + public actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo): Unit = rethrow { js.unenroll(multiFactorInfo.js).await() } - actual suspend fun unenroll(factorUid: String) = + public actual suspend fun unenroll(factorUid: String): Unit = rethrow { js.unenroll(factorUid).await() } } -actual class MultiFactorInfo(val js: JsMultiFactorInfo) { - actual val displayName: String? +public val MultiFactorInfo.js get() = js + +public actual class MultiFactorInfo(internal val js: JsMultiFactorInfo) { + public actual val displayName: String? get() = rethrow { js.displayName } - actual val enrollmentTime: Double + public actual val enrollmentTime: Double get() = rethrow { (Date(js.enrollmentTime).getTime() / 1000.0) } - actual val factorId: String + public actual val factorId: String get() = rethrow { js.factorId } - actual val uid: String + public actual val uid: String get() = rethrow { js.uid } } -actual class MultiFactorAssertion(val js: JsMultiFactorAssertion) { - actual val factorId: String +public val MultiFactorAssertion.js get() = js + +public actual class MultiFactorAssertion(internal val js: JsMultiFactorAssertion) { + public actual val factorId: String get() = rethrow { js.factorId } } -actual class MultiFactorSession(val js: JsMultiFactorSession) +public val MultiFactorSession.js get() = js + +public actual class MultiFactorSession(internal val js: JsMultiFactorSession) + +public val MultiFactorResolver.js get() = js -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) } +public actual class MultiFactorResolver(internal val js: JsMultiFactorResolver) { + public actual val auth: FirebaseAuth = rethrow { FirebaseAuth(js.auth) } + public actual val hints: List = rethrow { js.hints.map { MultiFactorInfo(it) } } + public actual val session: MultiFactorSession = rethrow { MultiFactorSession(js.session) } - actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = rethrow { AuthResult(js.resolveSignIn(assertion.js).await()) } + public actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = rethrow { AuthResult(js.resolveSignIn(assertion.js).await()) } } 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 26cd78a13..179cff18f 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 @@ -6,73 +6,79 @@ import kotlin.js.Date import dev.gitlive.firebase.auth.externals.UserInfo as JsUserInfo import kotlin.js.json -actual class FirebaseUser internal constructor(val js: User) : FirebaseUserProfile { - actual val uid: String +public val FirebaseUser.js get() = js + +public actual class FirebaseUser internal constructor(internal val js: User) { + public actual val uid: String get() = rethrow { js.uid } - override val displayName: String? + public actual val displayName: String? get() = rethrow { js.displayName } - actual val email: String? + public actual val email: String? get() = rethrow { js.email } - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = rethrow { js.phoneNumber } - override val photoURL: String? + public actual val photoURL: String? get() = rethrow { js.photoURL } - actual val isAnonymous: Boolean + public actual val isAnonymous: Boolean get() = rethrow { js.isAnonymous } - actual val isEmailVerified: Boolean + public actual val isEmailVerified: Boolean get() = rethrow { js.emailVerified } - actual val metaData: UserMetaData? + public actual val metaData: UserMetaData? get() = rethrow { UserMetaData(js.metadata) } - actual val multiFactor: MultiFactor + public actual val multiFactor: MultiFactor get() = rethrow { MultiFactor(multiFactor(js)) } - actual val providerData: List + public actual val providerData: List get() = rethrow { js.providerData.map { UserInfo(it) } } - actual val providerId: String + public actual val providerId: String get() = rethrow { js.providerId } - actual suspend fun delete(): Unit = rethrow { js.delete().await() } - actual suspend fun reload(): Unit = 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(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 { + public actual suspend fun delete(): Unit = rethrow { js.delete().await() } + public actual suspend fun reload(): Unit = rethrow { js.reload().await() } + public actual suspend fun getIdToken(forceRefresh: Boolean): String? = rethrow { js.getIdToken(forceRefresh).await() } + public actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = rethrow { AuthTokenResult(getIdTokenResult(js, forceRefresh).await()) } + public actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = rethrow { AuthResult(linkWithCredential(js, credential.js).await()) } + public actual suspend fun reauthenticate(credential: AuthCredential): Unit = rethrow { reauthenticateWithCredential(js, credential.js).await() Unit } - actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = rethrow { AuthResult(reauthenticateWithCredential(js, credential.js).await()) } + public actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = rethrow { AuthResult(reauthenticateWithCredential(js, credential.js).await()) } - 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() } - override suspend fun updateProfile(displayName: String?, photoUrl: String?): Unit = rethrow { - val request = object : ProfileUpdateRequest { - override val displayName: String? = displayName - override val photoURL: String? = photoUrl - } - updateProfile(js, request).await() + public actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?): Unit = rethrow { sendEmailVerification(js, actionCodeSettings?.toJson()).await() } + public actual suspend fun unlink(provider: String): FirebaseUser? = rethrow { FirebaseUser(unlink(js, provider).await()) } + public actual suspend fun updateEmail(email: String): Unit = rethrow { updateEmail(js, email).await() } + public actual suspend fun updatePassword(password: String): Unit = rethrow { updatePassword(js, password).await() } + public actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential): Unit = rethrow { updatePhoneNumber(js, credential.js).await() } + public actual suspend fun updateProfile(displayName: String?, photoUrl: String?): Unit = rethrow { + val request = listOf( + "displayName" to displayName, + "photoURL" to photoUrl, + ) + updateProfile(js, json(*request.toTypedArray())).await() } - actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = rethrow { verifyBeforeUpdateEmail(js, newEmail, actionCodeSettings?.toJson()).await() } + public actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?): Unit = rethrow { verifyBeforeUpdateEmail(js, newEmail, actionCodeSettings?.toJson()).await() } } -actual class UserInfo(val js: JsUserInfo) { - actual val displayName: String? +public val UserInfo.js get() = js + +public actual class UserInfo(internal val js: JsUserInfo) { + public actual val displayName: String? get() = rethrow { js.displayName } - actual val email: String? + public actual val email: String? get() = rethrow { js.email } - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = rethrow { js.phoneNumber } - actual val photoURL: String? + public actual val photoURL: String? get() = rethrow { js.photoURL } - actual val providerId: String + public actual val providerId: String get() = rethrow { js.providerId } - actual val uid: String + public actual val uid: String get() = rethrow { js.uid } } -actual class UserMetaData(val js: UserMetadata) { - actual val creationTime: Double? - get() = rethrow {js.creationTime?.let { (Date(it).getTime() / 1000.0) } } - actual val lastSignInTime: Double? - get() = rethrow {js.lastSignInTime?.let { (Date(it).getTime() / 1000.0) } } +public val UserMetaData.js get() = js + +public actual class UserMetaData(internal val js: UserMetadata) { + public actual val creationTime: Double? + get() = rethrow { js.creationTime?.let { (Date(it).getTime() / 1000.0) } } + public actual val lastSignInTime: Double? + get() = rethrow { js.lastSignInTime?.let { (Date(it).getTime() / 1000.0) } } } 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 832712901..84c4fcca7 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 @@ -4,7 +4,6 @@ package dev.gitlive.firebase.auth - actual val emulatorHost: String = "localhost" actual val context: Any = Unit diff --git a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 839a4b2a4..3eda71f6b 100644 --- a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -3,6 +3,7 @@ */ @file:JvmName("android") + package dev.gitlive.firebase.auth import com.google.firebase.auth.ActionCodeEmailInfo @@ -11,22 +12,23 @@ import com.google.firebase.auth.ActionCodeResult.* import com.google.firebase.auth.FirebaseAuth.AuthStateListener import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android as publicAndroid import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.tasks.await -actual val Firebase.auth +public actual val Firebase.auth get() = FirebaseAuth(com.google.firebase.auth.FirebaseAuth.getInstance()) -actual fun Firebase.auth(app: FirebaseApp) = - FirebaseAuth(com.google.firebase.auth.FirebaseAuth.getInstance(app.android)) +public actual fun Firebase.auth(app: FirebaseApp) = + FirebaseAuth(com.google.firebase.auth.FirebaseAuth.getInstance(app.publicAndroid)) -actual class FirebaseAuth internal constructor(val android: com.google.firebase.auth.FirebaseAuth) { - actual val currentUser: FirebaseUser? +public actual class FirebaseAuth internal constructor(internal val android: com.google.firebase.auth.FirebaseAuth) { + public actual val currentUser: FirebaseUser? get() = android.currentUser?.let { FirebaseUser(it) } - actual val authStateChanged: Flow get() = callbackFlow { + public actual val authStateChanged: Flow get() = callbackFlow { val listener = object : AuthStateListener { override fun onAuthStateChanged(auth: com.google.firebase.auth.FirebaseAuth) { trySend(auth.currentUser?.let { FirebaseUser(it) }) @@ -36,7 +38,7 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase. awaitClose { android.removeAuthStateListener(listener) } } - actual val idTokenChanged get(): Flow = callbackFlow { + public actual val idTokenChanged: Flow get() = callbackFlow { val listener = object : com.google.firebase.auth.FirebaseAuth.IdTokenListener { override fun onIdTokenChanged(auth: com.google.firebase.auth.FirebaseAuth) { trySend(auth.currentUser?.let { FirebaseUser(it) }) @@ -46,50 +48,61 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase. awaitClose { android.removeIdTokenListener(listener) } } - actual var languageCode: String + public actual var languageCode: String get() = android.languageCode.orEmpty() - set(value) { android.setLanguageCode(value) } - + set(value) { + android.setLanguageCode(value) + } - actual suspend fun applyActionCode(code: String) = android.applyActionCode(code).await().run { Unit } - actual suspend fun confirmPasswordReset(code: String, newPassword: String) = android.confirmPasswordReset(code, newPassword).await().run { Unit } + public actual suspend fun applyActionCode(code: String) { + android.applyActionCode(code).await() + } + public actual suspend fun confirmPasswordReset(code: String, newPassword: String) { + android.confirmPasswordReset(code, newPassword).await() + } - actual suspend fun createUserWithEmailAndPassword(email: String, password: String) = + public actual suspend fun createUserWithEmailAndPassword(email: String, password: String): AuthResult = AuthResult(android.createUserWithEmailAndPassword(email, password).await()) - actual suspend fun fetchSignInMethodsForEmail(email: String): List = android.fetchSignInMethodsForEmail(email).await().signInMethods.orEmpty() + public actual suspend fun fetchSignInMethodsForEmail(email: String): List = android.fetchSignInMethodsForEmail(email).await().signInMethods.orEmpty() - actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { + public actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) { android.sendPasswordResetEmail(email, actionCodeSettings?.toAndroid()).await() } - actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = android.sendSignInLinkToEmail(email, actionCodeSettings.toAndroid()).await().run { Unit } + public actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) { + android.sendSignInLinkToEmail(email, actionCodeSettings.toAndroid()).await() + } - actual fun isSignInWithEmailLink(link: String) = android.isSignInWithEmailLink(link) + public actual fun isSignInWithEmailLink(link: String): Boolean = android.isSignInWithEmailLink(link) - actual suspend fun signInWithEmailAndPassword(email: String, password: String) = + public actual suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult = AuthResult(android.signInWithEmailAndPassword(email, password).await()) - actual suspend fun signInWithCustomToken(token: String) = + public actual suspend fun signInWithCustomToken(token: String): AuthResult = AuthResult(android.signInWithCustomToken(token).await()) - actual suspend fun signInAnonymously() = AuthResult(android.signInAnonymously().await()) + public actual suspend fun signInAnonymously(): AuthResult = AuthResult(android.signInAnonymously().await()) - actual suspend fun signInWithCredential(authCredential: AuthCredential) = + public actual suspend fun signInWithCredential(authCredential: AuthCredential): AuthResult = AuthResult(android.signInWithCredential(authCredential.android).await()) - actual suspend fun signInWithEmailLink(email: String, link: String) = + public actual suspend fun signInWithEmailLink(email: String, link: String): AuthResult = AuthResult(android.signInWithEmailLink(email, link).await()) - actual suspend fun signOut() = android.signOut() + public actual suspend fun signOut() { + android.signOut() + } - actual suspend fun updateCurrentUser(user: FirebaseUser) = android.updateCurrentUser(user.android).await().run { Unit } - actual suspend fun verifyPasswordResetCode(code: String): String = android.verifyPasswordResetCode(code).await() + public actual suspend fun updateCurrentUser(user: FirebaseUser) { + android.updateCurrentUser(user.android).await() + } + public actual suspend fun verifyPasswordResetCode(code: String): String = android.verifyPasswordResetCode(code).await() - actual suspend fun checkActionCode(code: String): T { + public actual suspend fun checkActionCode(code: String): T { val result = android.checkActionCode(code).await() @Suppress("UNCHECKED_CAST") - return when(result.operation) { + return when (result.operation) { SIGN_IN_WITH_EMAIL_LINK -> ActionCodeResult.SignInWithEmailLink VERIFY_EMAIL -> ActionCodeResult.VerifyEmail(result.info!!.email) PASSWORD_RESET -> ActionCodeResult.PasswordReset(result.info!!.email) @@ -107,26 +120,33 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase. } as T } - actual fun useEmulator(host: String, port: Int) = android.useEmulator(host, port) + public actual fun useEmulator(host: String, port: Int) { + android.useEmulator(host, port) + } } -actual class AuthResult internal constructor(val android: com.google.firebase.auth.AuthResult) { - actual val user: FirebaseUser? +public val AuthResult.android: com.google.firebase.auth.AuthResult get() = android + +public actual class AuthResult internal constructor(internal val android: com.google.firebase.auth.AuthResult) { + public actual val user: FirebaseUser? get() = android.user?.let { FirebaseUser(it) } } -actual class AuthTokenResult(val android: com.google.firebase.auth.GetTokenResult) { +public val AuthTokenResult.android: com.google.firebase.auth.GetTokenResult get() = android + +public actual class AuthTokenResult(internal val android: com.google.firebase.auth.GetTokenResult) { // actual val authTimestamp: Long // get() = android.authTimestamp - actual val claims: Map + public actual val claims: Map get() = android.claims + // actual val expirationTimestamp: Long // get() = android.expirationTimestamp // actual val issuedAtTimestamp: Long // get() = android.issuedAtTimestamp - actual val signInProvider: String? + public actual val signInProvider: String? get() = android.signInProvider - actual val token: String? + public actual val token: String? get() = android.token } @@ -138,13 +158,13 @@ internal fun ActionCodeSettings.toAndroid() = com.google.firebase.auth.ActionCod .also { iOSBundleId?.run { it.setIOSBundleId(this) } } .build() -actual typealias FirebaseAuthException = com.google.firebase.auth.FirebaseAuthException -actual typealias FirebaseAuthActionCodeException = com.google.firebase.auth.FirebaseAuthActionCodeException -actual typealias FirebaseAuthEmailException = com.google.firebase.auth.FirebaseAuthEmailException -actual typealias FirebaseAuthInvalidCredentialsException = com.google.firebase.auth.FirebaseAuthInvalidCredentialsException -actual typealias FirebaseAuthWeakPasswordException = com.google.firebase.auth.FirebaseAuthWeakPasswordException -actual typealias FirebaseAuthInvalidUserException = com.google.firebase.auth.FirebaseAuthInvalidUserException -actual typealias FirebaseAuthMultiFactorException = com.google.firebase.auth.FirebaseAuthMultiFactorException -actual typealias FirebaseAuthRecentLoginRequiredException = com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException -actual typealias FirebaseAuthUserCollisionException = com.google.firebase.auth.FirebaseAuthUserCollisionException -actual typealias FirebaseAuthWebException = com.google.firebase.auth.FirebaseAuthWebException +public actual typealias FirebaseAuthException = com.google.firebase.auth.FirebaseAuthException +public actual typealias FirebaseAuthActionCodeException = com.google.firebase.auth.FirebaseAuthActionCodeException +public actual typealias FirebaseAuthEmailException = com.google.firebase.auth.FirebaseAuthEmailException +public actual typealias FirebaseAuthInvalidCredentialsException = com.google.firebase.auth.FirebaseAuthInvalidCredentialsException +public actual typealias FirebaseAuthWeakPasswordException = com.google.firebase.auth.FirebaseAuthWeakPasswordException +public actual typealias FirebaseAuthInvalidUserException = com.google.firebase.auth.FirebaseAuthInvalidUserException +public actual typealias FirebaseAuthMultiFactorException = com.google.firebase.auth.FirebaseAuthMultiFactorException +public actual typealias FirebaseAuthRecentLoginRequiredException = com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException +public actual typealias FirebaseAuthUserCollisionException = com.google.firebase.auth.FirebaseAuthUserCollisionException +public actual typealias FirebaseAuthWebException = com.google.firebase.auth.FirebaseAuthWebException diff --git a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index c44acfadd..055f5dc9f 100644 --- a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -14,37 +14,37 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await import java.util.concurrent.TimeUnit -actual open class AuthCredential(open val android: com.google.firebase.auth.AuthCredential) { - actual val providerId: String +public actual open class AuthCredential(public open val android: com.google.firebase.auth.AuthCredential) { + public actual val providerId: String get() = android.provider } -actual class PhoneAuthCredential(override val android: com.google.firebase.auth.PhoneAuthCredential) : AuthCredential(android) +public actual class PhoneAuthCredential(override val android: com.google.firebase.auth.PhoneAuthCredential) : AuthCredential(android) -actual class OAuthCredential(override val android: com.google.firebase.auth.OAuthCredential) : AuthCredential(android) +public actual class OAuthCredential(override val android: com.google.firebase.auth.OAuthCredential) : AuthCredential(android) -actual object EmailAuthProvider { - actual fun credential( +public actual object EmailAuthProvider { + public actual fun credential( email: String, - password: String + password: String, ): AuthCredential = AuthCredential(com.google.firebase.auth.EmailAuthProvider.getCredential(email, password)) - actual fun credentialWithLink( + public actual fun credentialWithLink( email: String, - emailLink: String + emailLink: String, ): AuthCredential = AuthCredential(com.google.firebase.auth.EmailAuthProvider.getCredentialWithLink(email, emailLink)) } -actual object FacebookAuthProvider { - actual fun credential(accessToken: String): AuthCredential = AuthCredential(com.google.firebase.auth.FacebookAuthProvider.getCredential(accessToken)) +public actual object FacebookAuthProvider { + public actual fun credential(accessToken: String): AuthCredential = AuthCredential(com.google.firebase.auth.FacebookAuthProvider.getCredential(accessToken)) } -actual object GithubAuthProvider { - actual fun credential(token: String): AuthCredential = AuthCredential(com.google.firebase.auth.GithubAuthProvider.getCredential(token)) +public actual object GithubAuthProvider { + public actual fun credential(token: String): AuthCredential = AuthCredential(com.google.firebase.auth.GithubAuthProvider.getCredential(token)) } -actual object GoogleAuthProvider { - actual fun credential(idToken: String?, accessToken: String?): AuthCredential { +public actual object GoogleAuthProvider { + public actual fun credential(idToken: String?, accessToken: String?): AuthCredential { require(idToken != null || accessToken != null) { "Both parameters are optional but at least one must be present." } @@ -52,39 +52,43 @@ actual object GoogleAuthProvider { } } -actual class OAuthProvider(val android: com.google.firebase.auth.OAuthProvider) { +public val OAuthProvider.android: com.google.firebase.auth.OAuthProvider get() = android - actual constructor( +public actual class OAuthProvider(internal val android: com.google.firebase.auth.OAuthProvider) { + + public actual constructor( provider: String, scopes: List, customParameters: Map, - auth: FirebaseAuth + auth: FirebaseAuth, ) : this( com.google.firebase.auth.OAuthProvider .newBuilder(provider, auth.android) .setScopes(scopes) .addCustomParameters(customParameters) - .build() + .build(), ) - actual companion object { - actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { + public actual companion object { + public actual fun credential(providerId: String, accessToken: String?, idToken: String?, rawNonce: String?): OAuthCredential { val builder = OAuthProvider.newCredentialBuilder(providerId) accessToken?.let { builder.setAccessToken(it) } idToken?.let { builder.setIdToken(it) } - rawNonce?.let { builder.setIdTokenWithRawNonce(idToken!!, it) } + rawNonce?.let { builder.setIdTokenWithRawNonce(idToken!!, it) } return OAuthCredential(builder.build() as com.google.firebase.auth.OAuthCredential) } } } -actual class PhoneAuthProvider(val android: com.google.firebase.auth.PhoneAuthProvider) { +public val PhoneAuthProvider.android: com.google.firebase.auth.PhoneAuthProvider get() = android + +public actual class PhoneAuthProvider(internal val android: com.google.firebase.auth.PhoneAuthProvider) { - actual constructor(auth: FirebaseAuth) : this(com.google.firebase.auth.PhoneAuthProvider.getInstance(auth.android)) + public actual constructor(auth: FirebaseAuth) : this(com.google.firebase.auth.PhoneAuthProvider.getInstance(auth.android)) - actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(com.google.firebase.auth.PhoneAuthProvider.getCredential(verificationId, smsCode)) + public actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(com.google.firebase.auth.PhoneAuthProvider.getCredential(verificationId, smsCode)) - actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = coroutineScope { + public actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = coroutineScope { val response = CompletableDeferred>() val callback = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @@ -113,7 +117,6 @@ actual class PhoneAuthProvider(val android: com.google.firebase.auth.PhoneAuthPr override fun onVerificationFailed(error: FirebaseException) { response.complete(Result.failure(error)) } - } android.verifyPhoneNumber(phoneNumber, verificationProvider.timeout, verificationProvider.unit, verificationProvider.activity, callback) @@ -121,14 +124,14 @@ actual class PhoneAuthProvider(val android: com.google.firebase.auth.PhoneAuthPr } } -actual interface PhoneVerificationProvider { - val activity: Activity - val timeout: Long - val unit: TimeUnit - fun codeSent(triggerResend: (Unit) -> Unit) - suspend fun getVerificationCode(): String +public actual interface PhoneVerificationProvider { + public val activity: Activity + public val timeout: Long + public val unit: TimeUnit + public fun codeSent(triggerResend: (Unit) -> Unit) + public suspend fun getVerificationCode(): String } -actual object TwitterAuthProvider { - actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(com.google.firebase.auth.TwitterAuthProvider.getCredential(token, secret)) +public actual object TwitterAuthProvider { + public actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(com.google.firebase.auth.TwitterAuthProvider.getCredential(token, secret)) } diff --git a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt index 84b216a4d..8a32d501f 100644 --- a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt +++ b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/multifactor.kt @@ -6,37 +6,53 @@ package dev.gitlive.firebase.auth import kotlinx.coroutines.tasks.await -actual class MultiFactor(val android: com.google.firebase.auth.MultiFactor) { - actual val enrolledFactors: List +public val MultiFactor.android: com.google.firebase.auth.MultiFactor get() = android + +public actual class MultiFactor(internal val android: com.google.firebase.auth.MultiFactor) { + public actual val enrolledFactors: List get() = android.enrolledFactors.map { MultiFactorInfo(it) } - actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) = android.enroll(multiFactorAssertion.android, displayName).await().run { Unit } - actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(android.session.await()) - actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) = android.unenroll(multiFactorInfo.android).await().run { Unit } - actual suspend fun unenroll(factorUid: String) = android.unenroll(factorUid).await().run { Unit } + public actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) { + android.enroll(multiFactorAssertion.android, displayName).await() + } + public actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(android.session.await()) + public actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) { + android.unenroll(multiFactorInfo.android).await() + } + public actual suspend fun unenroll(factorUid: String) { + android.unenroll(factorUid).await() + } } -actual class MultiFactorInfo(val android: com.google.firebase.auth.MultiFactorInfo) { - actual val displayName: String? +public val MultiFactorInfo.android: com.google.firebase.auth.MultiFactorInfo get() = android + +public actual class MultiFactorInfo(internal val android: com.google.firebase.auth.MultiFactorInfo) { + public actual val displayName: String? get() = android.displayName - actual val enrollmentTime: Double + public actual val enrollmentTime: Double get() = android.enrollmentTimestamp.toDouble() - actual val factorId: String + public actual val factorId: String get() = android.factorId - actual val uid: String + public actual val uid: String get() = android.uid } -actual class MultiFactorAssertion(val android: com.google.firebase.auth.MultiFactorAssertion) { - actual val factorId: String +public val MultiFactorAssertion.android: com.google.firebase.auth.MultiFactorAssertion get() = android + +public actual class MultiFactorAssertion(internal val android: com.google.firebase.auth.MultiFactorAssertion) { + public actual val factorId: String get() = android.factorId } -actual class MultiFactorSession(val android: com.google.firebase.auth.MultiFactorSession) +public val MultiFactorSession.android: com.google.firebase.auth.MultiFactorSession get() = android + +public actual class MultiFactorSession(internal val android: com.google.firebase.auth.MultiFactorSession) + +public val MultiFactorResolver.android: com.google.firebase.auth.MultiFactorResolver get() = android -actual class MultiFactorResolver(val android: com.google.firebase.auth.MultiFactorResolver) { - actual val auth: FirebaseAuth = FirebaseAuth(android.firebaseAuth) - actual val hints: List = android.hints.map { MultiFactorInfo(it) } - actual val session: MultiFactorSession = MultiFactorSession(android.session) +public actual class MultiFactorResolver(internal val android: com.google.firebase.auth.MultiFactorResolver) { + public actual val auth: FirebaseAuth = FirebaseAuth(android.firebaseAuth) + public actual val hints: List = android.hints.map { MultiFactorInfo(it) } + public actual val session: MultiFactorSession = MultiFactorSession(android.session) - actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(android.resolveSignIn(assertion.android).await()) + public actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(android.resolveSignIn(assertion.android).await()) } diff --git a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/user.kt b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/user.kt index c7177283e..480bbb67c 100644 --- a/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/user.kt +++ b/firebase-auth/src/jvmMain/kotlin/dev/gitlive/firebase/auth/user.kt @@ -8,73 +8,92 @@ import android.net.Uri import com.google.firebase.auth.UserProfileChangeRequest import kotlinx.coroutines.tasks.await -actual class FirebaseUser internal constructor(val android: com.google.firebase.auth.FirebaseUser) : FirebaseUserProfile { - actual val uid: String +public val FirebaseUser.android: com.google.firebase.auth.FirebaseUser get() = android + +public actual class FirebaseUser internal constructor(internal val android: com.google.firebase.auth.FirebaseUser) { + public actual val uid: String get() = android.uid - override val displayName: String? + public actual val displayName: String? get() = android.displayName - actual val email: String? + public actual val email: String? get() = android.email - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = android.phoneNumber - override val photoURL: String? + public actual val photoURL: String? get() = android.photoUrl?.toString() - actual val isAnonymous: Boolean + public actual val isAnonymous: Boolean get() = android.isAnonymous - actual val isEmailVerified: Boolean + public actual val isEmailVerified: Boolean get() = android.isEmailVerified - actual val metaData: UserMetaData? - get() = android.metadata?.let{ UserMetaData(it) } - actual val multiFactor: MultiFactor + public actual val metaData: UserMetaData? + get() = android.metadata?.let { UserMetaData(it) } + public actual val multiFactor: MultiFactor get() = MultiFactor(android.multiFactor) - actual val providerData: List + public actual val providerData: List get() = android.providerData.map { UserInfo(it) } - actual val providerId: String + public actual val providerId: String get() = android.providerId - actual suspend fun delete() = android.delete().await().run { Unit } - actual suspend fun reload() = android.reload().await().run { Unit } - actual suspend fun getIdToken(forceRefresh: Boolean): String? = android.getIdToken(forceRefresh).await().token - actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = android.getIdToken(forceRefresh).await().run { AuthTokenResult(this) } - actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = AuthResult(android.linkWithCredential(credential.android).await()) - actual suspend fun reauthenticate(credential: AuthCredential) = android.reauthenticate(credential.android).await().run { Unit } - actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = AuthResult(android.reauthenticateAndRetrieveData(credential.android).await()) - actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) { + public actual suspend fun delete() { + android.delete().await() + } + public actual suspend fun reload() { + android.reload().await() + } + public actual suspend fun getIdToken(forceRefresh: Boolean): String? = android.getIdToken(forceRefresh).await().token + public actual suspend fun getIdTokenResult(forceRefresh: Boolean): AuthTokenResult = android.getIdToken(forceRefresh).await().run { AuthTokenResult(this) } + public actual suspend fun linkWithCredential(credential: AuthCredential): AuthResult = AuthResult(android.linkWithCredential(credential.android).await()) + public actual suspend fun reauthenticate(credential: AuthCredential) { + android.reauthenticate(credential.android).await() + } + public actual suspend fun reauthenticateAndRetrieveData(credential: AuthCredential): AuthResult = AuthResult(android.reauthenticateAndRetrieveData(credential.android).await()) + public actual suspend fun sendEmailVerification(actionCodeSettings: ActionCodeSettings?) { val request = actionCodeSettings?.let { android.sendEmailVerification(it.toAndroid()) } ?: android.sendEmailVerification() request.await() } - actual suspend fun unlink(provider: String): FirebaseUser? = android.unlink(provider).await().user?.let { FirebaseUser(it) } - actual suspend fun updateEmail(email: String) = android.updateEmail(email).await().run { Unit } - actual suspend fun updatePassword(password: String) = android.updatePassword(password).await().run { Unit } - actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential) = android.updatePhoneNumber(credential.android).await().run { Unit } - override suspend fun updateProfile(displayName: String?, photoUrl: String?) { + public actual suspend fun unlink(provider: String): FirebaseUser? = android.unlink(provider).await().user?.let { FirebaseUser(it) } + public actual suspend fun updateEmail(email: String) { + android.updateEmail(email).await() + } + public actual suspend fun updatePassword(password: String) { + android.updatePassword(password).await() + } + public actual suspend fun updatePhoneNumber(credential: PhoneAuthCredential) { + android.updatePhoneNumber(credential.android).await() + } + public actual suspend fun updateProfile(displayName: String?, photoUrl: String?) { val request = UserProfileChangeRequest.Builder() - .apply { if(displayName != null) setDisplayName(displayName) } - .apply { if(photoUrl != null) setPhotoUri(Uri.parse(photoUrl)) } + .apply { setDisplayName(displayName) } + .apply { setPhotoUri(photoUrl?.let { Uri.parse(it) }) } .build() android.updateProfile(request).await() } - actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) = - android.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.toAndroid()).await().run { Unit } + public actual suspend fun verifyBeforeUpdateEmail(newEmail: String, actionCodeSettings: ActionCodeSettings?) { + android.verifyBeforeUpdateEmail(newEmail, actionCodeSettings?.toAndroid()).await() + } } -actual class UserInfo(val android: com.google.firebase.auth.UserInfo) { - actual val displayName: String? +public val UserInfo.android: com.google.firebase.auth.UserInfo get() = android + +public actual class UserInfo(internal val android: com.google.firebase.auth.UserInfo) { + public actual val displayName: String? get() = android.displayName - actual val email: String? + public actual val email: String? get() = android.email - actual val phoneNumber: String? + public actual val phoneNumber: String? get() = android.phoneNumber - actual val photoURL: String? + public actual val photoURL: String? get() = android.photoUrl?.toString() - actual val providerId: String + public actual val providerId: String get() = android.providerId - actual val uid: String + public actual val uid: String get() = android.uid } -actual class UserMetaData(val android: com.google.firebase.auth.FirebaseUserMetadata) { - actual val creationTime: Double? +public val UserMetaData.android: com.google.firebase.auth.FirebaseUserMetadata get() = android + +public actual class UserMetaData(internal val android: com.google.firebase.auth.FirebaseUserMetadata) { + public actual val creationTime: Double? get() = android.creationTimestamp.toDouble() - actual val lastSignInTime: Double? + public actual val lastSignInTime: Double? get() = android.lastSignInTimestamp.toDouble() } diff --git a/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt index 20bb3e15f..f0d16373b 100644 --- a/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/jvmTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -3,15 +3,14 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.auth -import android.content.Context -import com.google.firebase.FirebasePlatform -import dev.gitlive.firebase.MockFirebasePlatform +import dev.gitlive.firebase.testContext actual val emulatorHost: String = "10.0.2.2" -actual val context: Any get() = Context().also { FirebasePlatform.initializeFirebasePlatform(MockFirebasePlatform()) } +actual val context: Any = testContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-common-internal/api/android/firebase-common-internal.api b/firebase-common-internal/api/android/firebase-common-internal.api new file mode 100644 index 000000000..49e83fd95 --- /dev/null +++ b/firebase-common-internal/api/android/firebase-common-internal.api @@ -0,0 +1,169 @@ +public final class dev/gitlive/firebase/internal/AndroidEncodedObject { + public static final fun asNativeMap (Ljava/lang/Object;)Ljava/util/Map; + public static final fun getAndroid (Ldev/gitlive/firebase/internal/EncodedObject;)Ljava/util/Map; +} + +public final class dev/gitlive/firebase/internal/DecodeSettingsImpl : dev/gitlive/firebase/DecodeSettings { + public fun ()V + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; +} + +public final class dev/gitlive/firebase/internal/DecodeSettingsImpl$Builder : dev/gitlive/firebase/DecodeSettings$Builder { + public fun ()V + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public final class dev/gitlive/firebase/internal/DecodersKt { + public static final fun decode (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; + public static final fun decode (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;Ldev/gitlive/firebase/DecodeSettings;)Ljava/lang/Object; + public static final fun decode (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/internal/EncodeDecodeSettingsBuilderImpl : dev/gitlive/firebase/EncodeDecodeSettingsBuilder { + public fun ()V + public fun getEncodeDefaults ()Z + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun setEncodeDefaults (Z)V + public fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public final class dev/gitlive/firebase/internal/EncodeDecodeSettingsKt { + public static final fun buildDecodeSettings (Ldev/gitlive/firebase/DecodeSettings$Builder;)Ldev/gitlive/firebase/DecodeSettings; + public static final fun buildEncodeSettings (Ldev/gitlive/firebase/EncodeSettings$Builder;)Ldev/gitlive/firebase/EncodeSettings; +} + +public final class dev/gitlive/firebase/internal/EncodeSettingsImpl : dev/gitlive/firebase/EncodeSettings { + public final fun component1 ()Z + public final fun component2 ()Lkotlinx/serialization/modules/SerializersModule; + public fun equals (Ljava/lang/Object;)Z + public fun getEncodeDefaults ()Z + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/internal/EncodeSettingsImpl$Builder : dev/gitlive/firebase/EncodeSettings$Builder { + public fun ()V + public fun getEncodeDefaults ()Z + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun setEncodeDefaults (Z)V + public fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public abstract interface class dev/gitlive/firebase/internal/EncodedObject { +} + +public final class dev/gitlive/firebase/internal/EncodedObjectImpl : dev/gitlive/firebase/internal/EncodedObject { + public static final synthetic fun box-impl (Ljava/util/Map;)Ldev/gitlive/firebase/internal/EncodedObjectImpl; + public static fun constructor-impl (Ljava/util/Map;)Ljava/util/Map; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/util/Map;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/util/Map;Ljava/util/Map;)Z + public final fun getRaw ()Ljava/util/Map; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/util/Map;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/util/Map;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/util/Map; +} + +public final class dev/gitlive/firebase/internal/EncodedObjectKt { + public static final fun asEncodedObject (Ljava/util/Map;)Ldev/gitlive/firebase/internal/EncodedObject; +} + +public final class dev/gitlive/firebase/internal/EncodersKt { + public static final fun encode (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Ldev/gitlive/firebase/EncodeSettings;)Ljava/lang/Object; + public static final fun encode (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun encode (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z)Ljava/lang/Object; + public static final fun encodeAsObject (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/internal/EncodedObject; + public static synthetic fun encodeAsObject$default (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/internal/EncodedObject; +} + +public final class dev/gitlive/firebase/internal/FirebaseDecoderImpl : dev/gitlive/firebase/FirebaseDecoder { + public fun (Ljava/lang/Object;)V + public fun (Ljava/lang/Object;Ldev/gitlive/firebase/DecodeSettings;)V + public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeDecoder; + public fun decodeBoolean ()Z + public fun decodeByte ()B + public fun decodeChar ()C + public fun decodeDouble ()D + public fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I + public fun decodeFloat ()F + public fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder; + public fun decodeInt ()I + public fun decodeLong ()J + public fun decodeNotNullMark ()Z + public fun decodeNull ()Ljava/lang/Void; + public fun decodeNullableSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public fun decodeSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public fun decodeShort ()S + public fun decodeString ()Ljava/lang/String; + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public final fun getValue ()Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/internal/FirebaseEncoderImpl : dev/gitlive/firebase/FirebaseEncoder { + public fun (Ldev/gitlive/firebase/EncodeSettings;)V + public fun (Z)V + public fun beginCollection (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; + public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeEncoder; + public fun encodeBoolean (Z)V + public fun encodeByte (B)V + public fun encodeChar (C)V + public fun encodeDouble (D)V + public fun encodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V + public fun encodeFloat (F)V + public fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder; + public fun encodeInt (I)V + public fun encodeLong (J)V + public fun encodeNotNullMark ()V + public fun encodeNull ()V + public fun encodeNullableSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeShort (S)V + public fun encodeString (Ljava/lang/String;)V + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public final fun getValue ()Ljava/lang/Object; + public final fun setValue (Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/internal/FirebaseListSerializer : kotlinx/serialization/KSerializer { + public field list Ljava/util/List; + public fun ()V + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/List; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun getList ()Ljava/util/List; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Iterable;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun setList (Ljava/util/List;)V +} + +public final class dev/gitlive/firebase/internal/FirebaseMapSerializer : kotlinx/serialization/KSerializer { + public field keys Ljava/util/List; + public field map Ljava/util/Map; + public fun ()V + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/Map; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun getKeys ()Ljava/util/List; + public final fun getMap ()Ljava/util/Map; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/Map;)V + public final fun setKeys (Ljava/util/List;)V + public final fun setMap (Ljava/util/Map;)V +} + +public final class dev/gitlive/firebase/internal/ReencodeTransformationKt { + public static final fun reencodeTransformation (Lkotlinx/serialization/KSerializer;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun reencodeTransformation$default (Lkotlinx/serialization/KSerializer;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/internal/SpecialValueSerializer : kotlinx/serialization/KSerializer { + public fun (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + diff --git a/firebase-common-internal/api/jvm/firebase-common-internal.api b/firebase-common-internal/api/jvm/firebase-common-internal.api new file mode 100644 index 000000000..49e83fd95 --- /dev/null +++ b/firebase-common-internal/api/jvm/firebase-common-internal.api @@ -0,0 +1,169 @@ +public final class dev/gitlive/firebase/internal/AndroidEncodedObject { + public static final fun asNativeMap (Ljava/lang/Object;)Ljava/util/Map; + public static final fun getAndroid (Ldev/gitlive/firebase/internal/EncodedObject;)Ljava/util/Map; +} + +public final class dev/gitlive/firebase/internal/DecodeSettingsImpl : dev/gitlive/firebase/DecodeSettings { + public fun ()V + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; +} + +public final class dev/gitlive/firebase/internal/DecodeSettingsImpl$Builder : dev/gitlive/firebase/DecodeSettings$Builder { + public fun ()V + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public final class dev/gitlive/firebase/internal/DecodersKt { + public static final fun decode (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; + public static final fun decode (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;Ldev/gitlive/firebase/DecodeSettings;)Ljava/lang/Object; + public static final fun decode (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/internal/EncodeDecodeSettingsBuilderImpl : dev/gitlive/firebase/EncodeDecodeSettingsBuilder { + public fun ()V + public fun getEncodeDefaults ()Z + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun setEncodeDefaults (Z)V + public fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public final class dev/gitlive/firebase/internal/EncodeDecodeSettingsKt { + public static final fun buildDecodeSettings (Ldev/gitlive/firebase/DecodeSettings$Builder;)Ldev/gitlive/firebase/DecodeSettings; + public static final fun buildEncodeSettings (Ldev/gitlive/firebase/EncodeSettings$Builder;)Ldev/gitlive/firebase/EncodeSettings; +} + +public final class dev/gitlive/firebase/internal/EncodeSettingsImpl : dev/gitlive/firebase/EncodeSettings { + public final fun component1 ()Z + public final fun component2 ()Lkotlinx/serialization/modules/SerializersModule; + public fun equals (Ljava/lang/Object;)Z + public fun getEncodeDefaults ()Z + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/internal/EncodeSettingsImpl$Builder : dev/gitlive/firebase/EncodeSettings$Builder { + public fun ()V + public fun getEncodeDefaults ()Z + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public fun setEncodeDefaults (Z)V + public fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public abstract interface class dev/gitlive/firebase/internal/EncodedObject { +} + +public final class dev/gitlive/firebase/internal/EncodedObjectImpl : dev/gitlive/firebase/internal/EncodedObject { + public static final synthetic fun box-impl (Ljava/util/Map;)Ldev/gitlive/firebase/internal/EncodedObjectImpl; + public static fun constructor-impl (Ljava/util/Map;)Ljava/util/Map; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/util/Map;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/util/Map;Ljava/util/Map;)Z + public final fun getRaw ()Ljava/util/Map; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/util/Map;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/util/Map;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/util/Map; +} + +public final class dev/gitlive/firebase/internal/EncodedObjectKt { + public static final fun asEncodedObject (Ljava/util/Map;)Ldev/gitlive/firebase/internal/EncodedObject; +} + +public final class dev/gitlive/firebase/internal/EncodersKt { + public static final fun encode (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Ldev/gitlive/firebase/EncodeSettings;)Ljava/lang/Object; + public static final fun encode (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun encode (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z)Ljava/lang/Object; + public static final fun encodeAsObject (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/internal/EncodedObject; + public static synthetic fun encodeAsObject$default (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/internal/EncodedObject; +} + +public final class dev/gitlive/firebase/internal/FirebaseDecoderImpl : dev/gitlive/firebase/FirebaseDecoder { + public fun (Ljava/lang/Object;)V + public fun (Ljava/lang/Object;Ldev/gitlive/firebase/DecodeSettings;)V + public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeDecoder; + public fun decodeBoolean ()Z + public fun decodeByte ()B + public fun decodeChar ()C + public fun decodeDouble ()D + public fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I + public fun decodeFloat ()F + public fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder; + public fun decodeInt ()I + public fun decodeLong ()J + public fun decodeNotNullMark ()Z + public fun decodeNull ()Ljava/lang/Void; + public fun decodeNullableSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public fun decodeSerializableValue (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public fun decodeShort ()S + public fun decodeString ()Ljava/lang/String; + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public final fun getValue ()Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/internal/FirebaseEncoderImpl : dev/gitlive/firebase/FirebaseEncoder { + public fun (Ldev/gitlive/firebase/EncodeSettings;)V + public fun (Z)V + public fun beginCollection (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; + public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeEncoder; + public fun encodeBoolean (Z)V + public fun encodeByte (B)V + public fun encodeChar (C)V + public fun encodeDouble (D)V + public fun encodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V + public fun encodeFloat (F)V + public fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder; + public fun encodeInt (I)V + public fun encodeLong (J)V + public fun encodeNotNullMark ()V + public fun encodeNull ()V + public fun encodeNullableSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public fun encodeShort (S)V + public fun encodeString (Ljava/lang/String;)V + public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public final fun getValue ()Ljava/lang/Object; + public final fun setValue (Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/internal/FirebaseListSerializer : kotlinx/serialization/KSerializer { + public field list Ljava/util/List; + public fun ()V + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/List; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun getList ()Ljava/util/List; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Iterable;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun setList (Ljava/util/List;)V +} + +public final class dev/gitlive/firebase/internal/FirebaseMapSerializer : kotlinx/serialization/KSerializer { + public field keys Ljava/util/List; + public field map Ljava/util/Map; + public fun ()V + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/Map; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun getKeys ()Ljava/util/List; + public final fun getMap ()Ljava/util/Map; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/util/Map;)V + public final fun setKeys (Ljava/util/List;)V + public final fun setMap (Ljava/util/Map;)V +} + +public final class dev/gitlive/firebase/internal/ReencodeTransformationKt { + public static final fun reencodeTransformation (Lkotlinx/serialization/KSerializer;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun reencodeTransformation$default (Lkotlinx/serialization/KSerializer;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/internal/SpecialValueSerializer : kotlinx/serialization/KSerializer { + public fun (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + diff --git a/firebase-common-internal/build.gradle.kts b/firebase-common-internal/build.gradle.kts new file mode 100644 index 000000000..9f2b857f1 --- /dev/null +++ b/firebase-common-internal/build.gradle.kts @@ -0,0 +1,177 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree + +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +version = project.property("firebase-common-internal.version") as String + +plugins { + id("com.android.library") + kotlin("multiplatform") + kotlin("plugin.serialization") + id("testOptionsConvention") +} + +android { + val minSdkVersion: Int by project + val compileSdkVersion: Int by project + + compileSdk = compileSdkVersion + namespace = "dev.gitlive.firebase.common.internal" + defaultConfig { + minSdk = minSdkVersion + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + testOptions.configureTestOptions(project) + + packaging { + resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") + resources.pickFirsts.add("META-INF/AL2.0") + resources.pickFirsts.add("META-INF/LGPL2.1") + } + lint { + abortOnError = false + } +} + +kotlin { + explicitApi() + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + freeCompilerArgs.add("-Xconsistent-data-class-copy-visibility") + } + targets.configureEach { + compilations.configureEach { + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } + } + } + + @Suppress("OPT_IN_USAGE") + androidTarget { + instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + publishAllLibraryVariants() + } + + jvm() + + val supportIosTarget = project.property("skipIosTarget") != "true" + + if (supportIosTarget) { + iosArm64() + iosX64() + iosSimulatorArm64() + } + + js(IR) { + useCommonJs() + nodejs { + testTask { + useKarma { + useChromeHeadless() + } + } + } + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + } + + sourceSets { + all { + languageSettings.apply { + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() + progressiveMode = true + optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") + optIn("kotlinx.serialization.ExperimentalSerializationApi") + optIn("kotlinx.serialization.InternalSerializationApi") + } + } + + getByName("commonMain") { + dependencies { + implementation(project(":firebase-common")) + api(libs.kotlinx.serialization.core) + } + } + + getByName("commonTest") { + dependencies { + implementation(project(":test-utils")) + } + } + + getByName("androidMain") { + dependencies { + api(libs.google.firebase.common.ktx) + } + } + + getByName("jsMain") { + dependencies { + api(npm("firebase", "10.12.2")) + } + } + + getByName("jvmMain") { + kotlin.srcDir("src/androidMain/kotlin") + } + + getByName("jvmTest") { + dependencies { + implementation(kotlin("test-junit")) + } + kotlin.srcDir("src/androidAndroidTest/kotlin") + } + } +} + +if (project.property("firebase-common.skipIosTests") == "true") { + tasks.forEach { + if (it.name.contains("ios", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-common.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-common.skipJsTests") == "true") { + tasks.forEach { + if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) +} + diff --git a/firebase-common-internal/package.json b/firebase-common-internal/package.json new file mode 100644 index 000000000..c1a555b9e --- /dev/null +++ b/firebase-common-internal/package.json @@ -0,0 +1,32 @@ +{ + "name": "@gitlive/firebase-common-internal", + "version": "2.0.0", + "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", + "main": "firebase-common-internal.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/GitLiveApp/firebase-kotlin-sdk.git" + }, + "keywords": [ + "kotlin", + "multiplatform", + "kotlin-js", + "firebase" + ], + "author": "dev.gitlive", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/GitLiveApp/firebase-kotlin-multiplatform-sdk/issues" + }, + "homepage": "https://github.com/GitLiveApp/firebase-kotlin-multiplatform-sdk", + "dependencies": { + "@gitlive/firebase-common": "2.0.0", + "firebase": "9.19.1", + "kotlin": "1.8.20", + "kotlinx-coroutines-core": "1.6.4", + "kotlinx-serialization-kotlinx-serialization-runtime": "1.3.2" + } +} diff --git a/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..ddae0e54d --- /dev/null +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,8 @@ +@file:JvmName("AndroidEncodedObject") + +package dev.gitlive.firebase.internal + +public val EncodedObject.android: Map get() = getRaw() + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt similarity index 55% rename from firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt rename to firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt index f87a85093..f3cf29346 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt @@ -2,14 +2,14 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.encoding.CompositeDecoder -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) { +internal actual fun FirebaseDecoderImpl.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) { StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false) StructureKind.LIST -> (value as? List<*>).orEmpty().let { FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] } @@ -18,34 +18,27 @@ actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymo StructureKind.MAP -> (value as? Map<*, *>).orEmpty().entries.toList().let { FirebaseCompositeDecoder( it.size, - settings + settings, ) { _, index -> it[index / 2].run { if (index % 2 == 0) key else value } } } - - is PolymorphicKind -> when (settings.polymorphicStructure) { - EncodeDecodeSettings.PolymorphicStructure.MAP -> decodeAsMap(polymorphicIsNested) - EncodeDecodeSettings.PolymorphicStructure.LIST -> decodeAsList() - } + is PolymorphicKind -> decodeAsMap(polymorphicIsNested) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } -actual fun getPolymorphicType(value: Any?, discriminator: String): String = - (value as? Map<*,*>).orEmpty()[discriminator] as String +internal actual fun getPolymorphicType(value: Any?, discriminator: String): String = + (value as? Map<*, *>).orEmpty()[discriminator] as String -private fun FirebaseDecoder.decodeAsList(): CompositeDecoder = (value as? List<*>).orEmpty().let { - FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] } -} -private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> +private fun FirebaseDecoderImpl.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index -> if (isNestedPolymorphic) { - if (index == 0) - map[desc.getElementName(index)] - else { + if (desc.getElementName(index) == "value") { map + } else { + map[desc.getElementName(index)] } } else { map[desc.getElementName(index)] } } -} \ No newline at end of file +} diff --git a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt similarity index 55% rename from firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt rename to firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index 24f97d4a6..eb7be3ba2 100644 --- a/firebase-common/src/androidMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common-internal/src/androidMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -2,33 +2,25 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { +internal actual fun FirebaseEncoderImpl.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when (descriptor.kind) { StructureKind.LIST -> mutableListOf() .also { value = it } .let { FirebaseCompositeEncoder(settings) { _, index, value -> it.add(index, value) } } StructureKind.MAP -> mutableListOf() .let { FirebaseCompositeEncoder(settings, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } - StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor) - is PolymorphicKind -> { - when (settings.polymorphicStructure) { - EncodeDecodeSettings.PolymorphicStructure.MAP -> encodeAsMap(descriptor) - EncodeDecodeSettings.PolymorphicStructure.LIST -> encodeAsList() - } - } + StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor) + is PolymorphicKind -> encodeAsMap(descriptor) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } -private fun FirebaseEncoder.encodeAsList(): FirebaseCompositeEncoder = mutableListOf() - .also { value = it } - .let { FirebaseCompositeEncoder(settings) { _, index, value -> it.add(index, value) } } -private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf() +private fun FirebaseEncoderImpl.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf() .also { value = it } .let { FirebaseCompositeEncoder( @@ -36,6 +28,6 @@ private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseC setPolymorphicType = { discriminator, type -> it[discriminator] = type }, - set = { _, index, value -> it[descriptor.getElementName(index)] = value } + set = { _, index, value -> it[descriptor.getElementName(index)] = value }, ) - } \ No newline at end of file + } diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodeDecodeSettings.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodeDecodeSettings.kt new file mode 100644 index 000000000..b65fb949f --- /dev/null +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodeDecodeSettings.kt @@ -0,0 +1,44 @@ +package dev.gitlive.firebase.internal + +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.EncodeSettings +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule + +@PublishedApi +internal data class EncodeSettingsImpl internal constructor( + override val encodeDefaults: Boolean, + override val serializersModule: SerializersModule, +) : EncodeSettings { + + @PublishedApi + internal class Builder : EncodeSettings.Builder { + override var encodeDefaults: Boolean = true + override var serializersModule: SerializersModule = EmptySerializersModule() + } +} + +@PublishedApi +internal class DecodeSettingsImpl internal constructor( + override val serializersModule: SerializersModule = EmptySerializersModule(), +) : DecodeSettings { + + @PublishedApi + internal class Builder : DecodeSettings.Builder { + override var serializersModule: SerializersModule = EmptySerializersModule() + } +} + +@PublishedApi +internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder { + + override var encodeDefaults: Boolean = true + override var serializersModule: SerializersModule = EmptySerializersModule() +} + +@PublishedApi +internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettingsImpl(encodeDefaults, serializersModule) + +@PublishedApi +internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettingsImpl(serializersModule) diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..7f2bd45aa --- /dev/null +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,29 @@ +package dev.gitlive.firebase.internal + +import kotlin.jvm.JvmInline + +/** + * Platform specific object for storing encoded data that can be used for methods that explicitly require an object. + * This is essentially a [Map] of [String] and [Any]? (as represented by [raw]) but since [encode] gives a platform specific value, this method wraps that. + */ +public sealed interface EncodedObject + +internal fun EncodedObject.getRaw(): Map = when (this) { + is EncodedObjectImpl -> raw +} + +@JvmInline +@PublishedApi +internal value class EncodedObjectImpl(val raw: Map) : EncodedObject + +@PublishedApi +internal expect fun Any.asNativeMap(): Map<*, *>? + +@PublishedApi +internal fun Map<*, *>.asEncodedObject(): EncodedObject = map { (key, value) -> + if (key is String) { + key to value + } else { + throw IllegalArgumentException("Expected a String key but received $key") + } +}.toMap().let(::EncodedObjectImpl) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/Polymorphic.kt similarity index 69% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/Polymorphic.kt index f308d1787..2297be79b 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/Polymorphic.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/Polymorphic.kt @@ -1,5 +1,6 @@ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.FirebaseClassDiscriminator import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor @@ -11,22 +12,20 @@ import kotlinx.serialization.internal.AbstractPolymorphicSerializer * See https://github.com/Kotlin/kotlinx.serialization/blob/master/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt */ @Suppress("UNCHECKED_CAST") -internal fun FirebaseEncoder.encodePolymorphically( +internal fun FirebaseEncoderImpl.encodePolymorphically( serializer: SerializationStrategy, value: T, - settings: EncodeSettings, - ifPolymorphic: (String) -> Unit + ifPolymorphic: (String) -> Unit, ) { - // If serializer is not an AbstractPolymorphicSerializer or if we are encoding this as a list, we can just use the regular serializer + // If serializer is not an AbstractPolymorphicSerializer we can just use the regular serializer // This will result in calling structureEncoder for complicated structures // For PolymorphicKind this will first encode the polymorphic discriminator as a String and the remaining StructureKind.Class as a map of key-value pairs // This will result in a list structured like: (type, { classKey = classValue }) - if (serializer !is AbstractPolymorphicSerializer<*> || settings.polymorphicStructure == EncodeDecodeSettings.PolymorphicStructure.LIST) { + if (serializer !is AbstractPolymorphicSerializer<*>) { serializer.serialize(this, value) return } - // When doing Polymorphic Serialization with EncodeDecodeSettings.PolymorphicStructure.MAP we will use the polymorphic serializer of the class. val casted = serializer as AbstractPolymorphicSerializer val baseClassDiscriminator = serializer.descriptor.classDiscriminator() val actualSerializer = casted.findPolymorphicSerializer(this, value as Any) @@ -35,13 +34,12 @@ internal fun FirebaseEncoder.encodePolymorphically( } @Suppress("UNCHECKED_CAST") -internal fun FirebaseDecoder.decodeSerializableValuePolymorphic( +internal fun FirebaseDecoderImpl.decodeSerializableValuePolymorphic( value: Any?, - decodeSettings: DecodeSettings, deserializer: DeserializationStrategy, ): T { - // If deserializer is not an AbstractPolymorphicSerializer or if we are decoding this from a list, we can just use the regular serializer - if (deserializer !is AbstractPolymorphicSerializer<*> || decodeSettings.polymorphicStructure == EncodeDecodeSettings.PolymorphicStructure.LIST) { + // If deserializer is not an AbstractPolymorphicSerializer we can just use the regular serializer + if (deserializer !is AbstractPolymorphicSerializer<*>) { return deserializer.deserialize(this) } val casted = deserializer as AbstractPolymorphicSerializer @@ -49,7 +47,7 @@ internal fun FirebaseDecoder.decodeSerializableValuePolymorphic( val type = getPolymorphicType(value, discriminator) val actualDeserializer = casted.findPolymorphicSerializerOrNull( structureDecoder(deserializer.descriptor, false), - type + type, ) as DeserializationStrategy return actualDeserializer.deserialize(this) } @@ -63,4 +61,3 @@ internal fun SerialDescriptor.classDiscriminator(): String { } return "type" } - diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/decoders.kt similarity index 54% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/decoders.kt index 1acb9f7d8..27952ac7d 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/decoders.kt @@ -2,8 +2,10 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.FirebaseDecoder import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException @@ -14,131 +16,132 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -inline fun decode(value: Any?): T = decode(value, DecodeSettings()) -inline fun decode(value: Any?, settings: DecodeSettings): T { +public inline fun decode(value: Any?): T = decode(value) {} +public inline fun decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(value, DecodeSettingsImpl.Builder().apply(buildSettings).buildDecodeSettings()) + +@PublishedApi +internal inline fun decode(value: Any?, decodeSettings: DecodeSettings): T { val strategy = serializer() - return decode(strategy as DeserializationStrategy, value, settings) + return decode(strategy as DeserializationStrategy, value, decodeSettings) } -fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value, DecodeSettings()) -fun decode(strategy: DeserializationStrategy, value: Any?, settings: DecodeSettings): T { +public fun decode(strategy: DeserializationStrategy, value: Any?): T = decode(strategy, value) {} +public inline fun decode(strategy: DeserializationStrategy, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(strategy, value, DecodeSettingsImpl.Builder().apply(buildSettings).buildDecodeSettings()) + +@PublishedApi +internal fun decode(strategy: DeserializationStrategy, value: Any?, decodeSettings: DecodeSettings): T { require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" } - return FirebaseDecoder(value, settings).decodeSerializableValue(strategy) + return FirebaseDecoderImpl(value, decodeSettings).decodeSerializableValue(strategy) } -expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder -expect fun getPolymorphicType(value: Any?, discriminator: String): String +internal expect fun FirebaseDecoderImpl.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder +internal expect fun getPolymorphicType(value: Any?, discriminator: String): String -class FirebaseDecoder(val value: Any?, internal val settings: DecodeSettings) : Decoder { +@PublishedApi +internal class FirebaseDecoderImpl(val value: Any?, internal val settings: DecodeSettings) : FirebaseDecoder { - constructor(value: Any?) : this(value, DecodeSettings()) + public constructor(value: Any?) : this(value, DecodeSettingsImpl()) override val serializersModule: SerializersModule = settings.serializersModule - override fun beginStructure(descriptor: SerialDescriptor) = structureDecoder(descriptor, true) + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = structureDecoder(descriptor, true) - override fun decodeString() = decodeString(value) + override fun decodeString(): String = decodeString(value) - override fun decodeDouble() = decodeDouble(value) + override fun decodeDouble(): Double = decodeDouble(value) - override fun decodeLong() = decodeLong(value) + override fun decodeLong(): Long = decodeLong(value) - override fun decodeByte() = decodeByte(value) + override fun decodeByte(): Byte = decodeByte(value) - override fun decodeFloat() = decodeFloat(value) + override fun decodeFloat(): Float = decodeFloat(value) - override fun decodeInt() = decodeInt(value) + override fun decodeInt(): Int = decodeInt(value) - override fun decodeShort() = decodeShort(value) + override fun decodeShort(): Short = decodeShort(value) - override fun decodeBoolean() = decodeBoolean(value) + override fun decodeBoolean(): Boolean = decodeBoolean(value) - override fun decodeChar() = decodeChar(value) + override fun decodeChar(): Char = decodeChar(value) - override fun decodeEnum(enumDescriptor: SerialDescriptor) = decodeEnum(value, enumDescriptor) + override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeEnum(value, enumDescriptor) - override fun decodeNotNullMark() = decodeNotNullMark(value) + override fun decodeNotNullMark(): Boolean = decodeNotNullMark(value) - override fun decodeNull() = decodeNull(value) + override fun decodeNull(): Nothing? = decodeNull(value) - override fun decodeInline(descriptor: SerialDescriptor) = FirebaseDecoder(value, settings) + override fun decodeInline(descriptor: SerialDescriptor): Decoder = FirebaseDecoderImpl(value, settings) - override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { - return decodeSerializableValuePolymorphic(value, settings, deserializer) - } + override fun decodeSerializableValue(deserializer: DeserializationStrategy): T = decodeSerializableValuePolymorphic(value, deserializer) } -class FirebaseClassDecoder( +internal class FirebaseClassDecoder( size: Int, settings: DecodeSettings, private val containsKey: (name: String) -> Boolean, - get: (descriptor: SerialDescriptor, index: Int) -> Any? + get: (descriptor: SerialDescriptor, index: Int) -> Any?, ) : FirebaseCompositeDecoder(size, settings, get) { private var index: Int = 0 - override fun decodeSequentially() = false - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - return (index until descriptor.elementsCount) - .firstOrNull { - !descriptor.isElementOptional(it) || containsKey( - descriptor.getElementName( - it - ) - ) - } - ?.also { index = it + 1 } - ?: DECODE_DONE - } + override fun decodeSequentially(): Boolean = false + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int = (index until descriptor.elementsCount) + .firstOrNull { + !descriptor.isElementOptional(it) || containsKey(descriptor.getElementName(it)) + } + ?.also { index = it + 1 } + ?: DECODE_DONE } -open class FirebaseCompositeDecoder( +internal open class FirebaseCompositeDecoder( private val size: Int, internal val settings: DecodeSettings, private val get: (descriptor: SerialDescriptor, index: Int) -> Any?, -): CompositeDecoder { +) : CompositeDecoder { override val serializersModule: SerializersModule = settings.serializersModule - override fun decodeSequentially() = true + override fun decodeSequentially(): Boolean = true override fun decodeElementIndex(descriptor: SerialDescriptor): Int = throw NotImplementedError() - override fun decodeCollectionSize(descriptor: SerialDescriptor) = size + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = size override fun decodeSerializableElement( descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy, - previousValue: T? - ) = decodeElement(descriptor, index) { - deserializer.deserialize(FirebaseDecoder(it, settings)) + previousValue: T?, + ): T = decodeElement(descriptor, index) { + deserializer.deserialize(FirebaseDecoderImpl(it, settings)) } - override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean = decodeElement(descriptor, index, ::decodeBoolean) - override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte = decodeElement(descriptor, index, ::decodeByte) - override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char = decodeElement(descriptor, index, ::decodeChar) - override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double = decodeElement(descriptor, index, ::decodeDouble) - override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float = decodeElement(descriptor, index, ::decodeFloat) - override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int = decodeElement(descriptor, index, ::decodeInt) - override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long = decodeElement(descriptor, index, ::decodeLong) override fun decodeNullableSerializableElement( descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy, - previousValue: T? + previousValue: T?, ): T? { val isNullabilitySupported = deserializer.descriptor.isNullable return if (isNullabilitySupported || decodeElement(descriptor, index, ::decodeNotNullMark)) { @@ -148,10 +151,10 @@ open class FirebaseCompositeDecoder( } } - override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short = decodeElement(descriptor, index, ::decodeShort) - override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = + override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String = decodeElement(descriptor, index, ::decodeString) override fun endStructure(descriptor: SerialDescriptor) {} @@ -159,78 +162,82 @@ open class FirebaseCompositeDecoder( @ExperimentalSerializationApi override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder = decodeElement(descriptor, index) { - FirebaseDecoder(it, settings) + FirebaseDecoderImpl(it, settings) } - private fun decodeElement(descriptor: SerialDescriptor, index: Int, decoder: (Any?) -> T): T { - return try { - decoder(get(descriptor, index)) - } catch (e: Exception) { - throw SerializationException( - message = "Exception during decoding ${descriptor.serialName} ${descriptor.getElementName(index)}", - cause = e - ) - } + private fun decodeElement(descriptor: SerialDescriptor, index: Int, decoder: (Any?) -> T): T = try { + decoder(get(descriptor, index)) + } catch (e: Exception) { + throw SerializationException( + message = "Exception during decoding ${descriptor.serialName} ${descriptor.getElementName(index)}", + cause = e, + ) } } private fun decodeString(value: Any?) = value.toString() -private fun decodeDouble(value: Any?) = when(value) { +private fun decodeDouble(value: Any?) = when (value) { is Number -> value.toDouble() is String -> value.toDouble() else -> throw SerializationException("Expected $value to be double") } -private fun decodeLong(value: Any?) = when(value) { +private fun decodeLong(value: Any?) = when (value) { is Number -> value.toLong() is String -> value.toLong() else -> throw SerializationException("Expected $value to be long") } -private fun decodeByte(value: Any?) = when(value) { +private fun decodeByte(value: Any?) = when (value) { is Number -> value.toByte() is String -> value.toByte() else -> throw SerializationException("Expected $value to be byte") } -private fun decodeFloat(value: Any?) = when(value) { +private fun decodeFloat(value: Any?) = when (value) { is Number -> value.toFloat() is String -> value.toFloat() else -> throw SerializationException("Expected $value to be float") } -private fun decodeInt(value: Any?) = when(value) { +private fun decodeInt(value: Any?) = when (value) { is Number -> value.toInt() is String -> value.toInt() else -> throw SerializationException("Expected $value to be int") } -private fun decodeShort(value: Any?) = when(value) { +private fun decodeShort(value: Any?) = when (value) { is Number -> value.toShort() is String -> value.toShort() else -> throw SerializationException("Expected $value to be short") } -private fun decodeBoolean(value: Any?) = value as Boolean +private fun decodeBoolean(value: Any?) = when (value) { + is Boolean -> value + is Number -> value.toInt() != 0 + is String -> value.toBoolean() + else -> throw SerializationException("Expected $value to be boolean") +} -private fun decodeChar(value: Any?) = when(value) { - is Number -> value.toChar() +private fun decodeChar(value: Any?) = when (value) { + is Number -> value.toInt().toChar() is String -> value[0] else -> throw SerializationException("Expected $value to be char") } -private fun decodeEnum(value: Any?, enumDescriptor: SerialDescriptor) = when(value) { +private fun decodeEnum(value: Any?, enumDescriptor: SerialDescriptor) = when (value) { is Number -> value.toInt() is String -> enumDescriptor.getElementIndexOrThrow(value) else -> throw SerializationException("Expected $value to be enum") } -//Made internal after 1.0 stabilization +// Made internal after 1.0 stabilization internal fun SerialDescriptor.getElementIndexOrThrow(name: String): Int { val index = getElementIndex(name) - if (index == CompositeDecoder.UNKNOWN_NAME) + if (index == CompositeDecoder.UNKNOWN_NAME) { throw SerializationException("$serialName does not contain element with name '$name'") + } return index } diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt new file mode 100644 index 000000000..474aeea57 --- /dev/null +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/encoders.kt @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase.internal + +import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.FirebaseEncoder +import dev.gitlive.firebase.ValueWithSerializer +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.CompositeEncoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.SerializersModule + +@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(strategy, value) { encodeDefaults = shouldEncodeElementDefault }")) +public fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value) { + this.encodeDefaults = shouldEncodeElementDefault +} + +public inline fun encode(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = + encode(strategy, value, EncodeSettingsImpl.Builder().apply(buildSettings).buildEncodeSettings()) + +@PublishedApi +internal fun encode(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings): Any? = + FirebaseEncoderImpl(encodeSettings).apply { encodeSerializableValue(strategy, value) }.value + +@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.encodeDefaults = shouldEncodeElementDefault }")) +public inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value) { + this.encodeDefaults = shouldEncodeElementDefault +} + +public inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Any? = + encode(value, EncodeSettingsImpl.Builder().apply(buildSettings).buildEncodeSettings()) + +/** + * Encodes data as an [EncodedObject]. + * This is not recommended for manual use, but may be done by the library internally. + * @throws IllegalArgumentException if [value] is not valid as an [EncodedObject] (e.g. not encodable in the form Map + */ +public inline fun encodeAsObject(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { + if (value is Map<*, *> && value.keys.any { it !is String }) { + throw IllegalArgumentException("$value is a Map containing non-String keys. Must be of the form Map") + } + val encoded = encode(strategy, value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") +} + +/** + * Encodes data as an [EncodedObject]. + * This is not recommended for manual use, but may be done by the library internally. + * @throws IllegalArgumentException if [value] is not valid as an [EncodedObject] (e.g. not encodable in the form Map + */ +public inline fun encodeAsObject(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): EncodedObject { + if (value is Map<*, *> && value.keys.any { it !is String }) { + throw IllegalArgumentException("$value is a Map containing non-String keys. Must be of the form Map") + } + val encoded = encode(value, buildSettings) ?: throw IllegalArgumentException("$value was encoded as null. Must be of the form Map") + return encoded.asNativeMap()?.asEncodedObject() ?: throw IllegalArgumentException("$value was encoded as ${encoded::class}. Must be of the form Map") +} + +@PublishedApi +internal inline fun encode(value: T, encodeSettings: EncodeSettings): Any? = value?.let { + FirebaseEncoderImpl(encodeSettings).apply { + if (it is ValueWithSerializer<*> && it.value is T) { + @Suppress("UNCHECKED_CAST") + (it as ValueWithSerializer).let { + encodeSerializableValue(it.serializer, it.value) + } + } else { + encodeSerializableValue(it.firebaseSerializer(), it) + } + }.value +} + +internal expect fun FirebaseEncoderImpl.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder + +@PublishedApi +internal class FirebaseEncoderImpl( + internal val settings: EncodeSettings, +) : FirebaseEncoder { + + public constructor(shouldEncodeElementDefault: Boolean) : this( + EncodeSettingsImpl.Builder().apply { this.encodeDefaults = shouldEncodeElementDefault }.buildEncodeSettings(), + ) + + public var value: Any? = null + + override val serializersModule: SerializersModule = settings.serializersModule + + private var polymorphicDiscriminator: String? = null + + override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { + val encoder = structureEncoder(descriptor) + if (polymorphicDiscriminator != null) { + encoder.encodePolymorphicClassDiscriminator(polymorphicDiscriminator!!, descriptor.serialName) + polymorphicDiscriminator = null + } + return encoder + } + + override fun encodeBoolean(value: Boolean) { + this.value = value + } + + override fun encodeByte(value: Byte) { + this.value = value + } + + override fun encodeChar(value: Char) { + this.value = value + } + + override fun encodeDouble(value: Double) { + this.value = value + } + + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + this.value = enumDescriptor.getElementName(index) + } + + override fun encodeFloat(value: Float) { + this.value = value + } + + override fun encodeInt(value: Int) { + this.value = value + } + + override fun encodeLong(value: Long) { + this.value = value + } + + override fun encodeNotNullMark() { + // no-op + } + + override fun encodeNull() { + this.value = null + } + + override fun encodeShort(value: Short) { + this.value = value + } + + override fun encodeString(value: String) { + this.value = value + } + + override fun encodeInline(descriptor: SerialDescriptor): Encoder = this + + override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { + encodePolymorphically(serializer, value) { + polymorphicDiscriminator = it + } + } +} + +internal open class FirebaseCompositeEncoder( + private val settings: EncodeSettings, + private val end: () -> Unit = {}, + private val setPolymorphicType: (String, String) -> Unit = { _, _ -> }, + private val set: (descriptor: SerialDescriptor, index: Int, value: Any?) -> Unit, +) : CompositeEncoder { + +// private fun SerializationStrategy.toFirebase(): SerializationStrategy = when(descriptor.kind) { +// StructureKind.MAP -> FirebaseMapSerializer(descriptor.getElementDescriptor(1)) as SerializationStrategy +// StructureKind.LIST -> FirebaseListSerializer(descriptor.getElementDescriptor(0)) as SerializationStrategy +// else -> this +// } + + override val serializersModule: SerializersModule = settings.serializersModule + + override fun endStructure(descriptor: SerialDescriptor): Unit = end() + + override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = settings.encodeDefaults + + override fun encodeNullableSerializableElement( + descriptor: SerialDescriptor, + index: Int, + serializer: SerializationStrategy, + value: T?, + ): Unit = set( + descriptor, + index, + value?.let { + FirebaseEncoderImpl(settings).apply { + encodeSerializableValue(serializer, value) + }.value + }, + ) + + override fun encodeSerializableElement( + descriptor: SerialDescriptor, + index: Int, + serializer: SerializationStrategy, + value: T, + ): Unit = set( + descriptor, + index, + FirebaseEncoderImpl(settings).apply { + encodeSerializableValue(serializer, value) + }.value, + ) + + public fun encodeObject(descriptor: SerialDescriptor, index: Int, value: T): Unit = set(descriptor, index, value) + + override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean): Unit = set(descriptor, index, value) + + override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte): Unit = set(descriptor, index, value) + + override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char): Unit = set(descriptor, index, value) + + override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double): Unit = set(descriptor, index, value) + + override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float): Unit = set(descriptor, index, value) + + override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int): Unit = set(descriptor, index, value) + + override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long): Unit = set(descriptor, index, value) + + override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short): Unit = set(descriptor, index, value) + + override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String): Unit = set(descriptor, index, value) + + @ExperimentalSerializationApi + override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder = + FirebaseEncoderImpl(settings) + + public fun encodePolymorphicClassDiscriminator(discriminator: String, type: String) { + setPolymorphicType(discriminator, type) + } +} diff --git a/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/reencodeTransformation.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/reencodeTransformation.kt new file mode 100644 index 000000000..288e28c1e --- /dev/null +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/reencodeTransformation.kt @@ -0,0 +1,23 @@ +package dev.gitlive.firebase.internal + +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import kotlinx.serialization.KSerializer + +public inline fun reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit = {}, transform: (T) -> T): Any? { + val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder) + val oldValue: T = decode(value, encodeDecodeSettingsBuilder.buildDecodeSettings()) + return encode( + transform(oldValue), + encodeDecodeSettingsBuilder.buildEncodeSettings(), + ) +} + +public inline fun reencodeTransformation(strategy: KSerializer, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit = {}, transform: (T) -> T): Any? { + val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder) + val oldValue: T = decode(strategy, value, encodeDecodeSettingsBuilder.buildDecodeSettings()) + return encode( + strategy, + transform(oldValue), + encodeDecodeSettingsBuilder.buildEncodeSettings(), + ) +} diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/serializers.kt similarity index 68% rename from firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt rename to firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/serializers.kt index c5683d146..cb1b1fb00 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/serializers.kt +++ b/firebase-common-internal/src/commonMain/kotlin/dev/gitlive/firebase/internal/serializers.kt @@ -2,17 +2,23 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal -import kotlinx.serialization.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer @Suppress("UNCHECKED_CAST") -inline fun T.firebaseSerializer() = runCatching { serializer() } +public inline fun T.firebaseSerializer(): SerializationStrategy = runCatching { serializer() } .getOrElse { - when(this) { + when (this) { is Map<*, *> -> FirebaseMapSerializer() is List<*> -> FirebaseListSerializer() is Set<*> -> FirebaseListSerializer() @@ -20,12 +26,12 @@ inline fun T.firebaseSerializer() = runCatching { serializer } as SerializationStrategy } -class FirebaseMapSerializer : KSerializer> { +public class FirebaseMapSerializer : KSerializer> { - lateinit var keys: List - lateinit var map: Map + public lateinit var keys: List + public lateinit var map: Map - override val descriptor = object : SerialDescriptor { + override val descriptor: SerialDescriptor = object : SerialDescriptor { override val kind = StructureKind.MAP override val serialName = "kotlin.Map" override val elementsCount get() = map.size @@ -48,7 +54,10 @@ class FirebaseMapSerializer : KSerializer> { collectionEncoder.encodeSerializableElement(it.descriptor, index * 2, it, key) } collectionEncoder.encodeNullableSerializableElement( - serializer.descriptor, index * 2 + 1, serializer, listValue + serializer.descriptor, + index * 2 + 1, + serializer, + listValue, ) } collectionEncoder.endStructure(descriptor) @@ -57,7 +66,7 @@ class FirebaseMapSerializer : KSerializer> { override fun deserialize(decoder: Decoder): Map { val collectionDecoder = decoder.beginStructure(descriptor) as FirebaseCompositeDecoder val map = mutableMapOf() - for(index in 0 until collectionDecoder.decodeCollectionSize(descriptor) * 2 step 2) { + for (index in 0 until collectionDecoder.decodeCollectionSize(descriptor) * 2 step 2) { // map[collectionDecoder.decodeNullableSerializableElement(index) as String] = // collectionDecoder.decodeNullableSerializableElement(index + 1) } @@ -65,11 +74,11 @@ class FirebaseMapSerializer : KSerializer> { } } -class FirebaseListSerializer : KSerializer> { +public class FirebaseListSerializer : KSerializer> { - lateinit var list: List + public lateinit var list: List - override val descriptor = object : SerialDescriptor { + override val descriptor: SerialDescriptor = object : SerialDescriptor { override val kind = StructureKind.LIST override val serialName = "kotlin.List" override val elementsCount get() = list.size @@ -87,7 +96,10 @@ class FirebaseListSerializer : KSerializer> { list.forEachIndexed { index, listValue -> val serializer = (listValue?.firebaseSerializer() ?: Unit.serializer()) as KSerializer collectionEncoder.encodeNullableSerializableElement( - serializer.descriptor, index, serializer, listValue + serializer.descriptor, + index, + serializer, + listValue, ) } collectionEncoder.endStructure(descriptor) @@ -108,26 +120,24 @@ class FirebaseListSerializer : KSerializer> { * A special case of serializer for values natively supported by Firebase and * don't require an additional encoding/decoding. */ -class SpecialValueSerializer( +public class SpecialValueSerializer( serialName: String, private val toNativeValue: (T) -> Any?, - private val fromNativeValue: (Any?) -> T + private val fromNativeValue: (Any?) -> T, ) : KSerializer { - override val descriptor = buildClassSerialDescriptor(serialName) { } + override val descriptor: SerialDescriptor = buildClassSerialDescriptor(serialName) { } override fun serialize(encoder: Encoder, value: T) { - if (encoder is FirebaseEncoder) { + if (encoder is FirebaseEncoderImpl) { encoder.value = toNativeValue(value) } else { throw SerializationException("This serializer must be used with FirebaseEncoder") } } - override fun deserialize(decoder: Decoder): T { - return if (decoder is FirebaseDecoder) { - fromNativeValue(decoder.value) - } else { - throw SerializationException("This serializer must be used with FirebaseDecoder") - } + override fun deserialize(decoder: Decoder): T = if (decoder is FirebaseDecoderImpl) { + fromNativeValue(decoder.value) + } else { + throw SerializationException("This serializer must be used with FirebaseDecoder") } } diff --git a/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt b/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt new file mode 100644 index 000000000..145d57b74 --- /dev/null +++ b/firebase-common-internal/src/commonTest/kotlin/dev/gitlive/firebase/internal/EncodersTest.kt @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.gitlive.firebase.internal + +import dev.gitlive.firebase.nativeAssertEquals +import dev.gitlive.firebase.nativeListOf +import dev.gitlive.firebase.nativeMapOf +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlin.jvm.JvmInline +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull + +@Suppress("unused") +@Serializable +object TestObject { + val map = mapOf("key" to "value", "key2" to 12, "key3" to null) + const val BOOL = false + val nullableBool: Boolean? = null +} + +@Serializable +@JvmInline +value class ValueClass(val int: Int) + +@Serializable +data class TestData( + val map: Map, + val otherMap: Map, + val bool: Boolean = false, + val nullableBool: Boolean? = null, + val valueClass: ValueClass, +) + +@Serializable +sealed class SealedClass { + @Serializable + @SerialName("test") + data class Test(val value: String) : SealedClass() +} + +@Serializable +data class GenericClass( + val inner: T, +) + +@Serializable +abstract class AbstractClass { + abstract val abstractValue: String +} + +@Serializable +@SerialName("implemented") +data class ImplementedClass(override val abstractValue: String, val otherValue: Boolean) : AbstractClass() + +@Serializable +data class NestedClass( + val testData: TestData, + val sealed: SealedClass, + val abstract: AbstractClass, + val testDataList: List, + val sealedList: List, + val abstractList: List, + val testDataMap: Map, + val sealedMap: Map, + val abstractMap: Map, +) + +class EncodersTest { + + @Test + fun encodeDecodePrimaryTypes() { + assertEncode(true) + assertEncode(42) + assertEncode(8.toShort()) + assertEncode(Int.MAX_VALUE.toLong() + 3) + assertEncode(0x03F) + assertEncode(3.33) + assertEncode(6.65f) + assertEncode("Test") + } + + @Test + fun encodeDecodeList() { + val list = listOf("One", "Two", "Three") + val encoded = encode>(list) { encodeDefaults = true } + + nativeAssertEquals(nativeListOf("One", "Two", "Three"), encoded) + + val decoded = decode(ListSerializer(String.serializer()), encoded) + assertEquals(listOf("One", "Two", "Three"), decoded) + } + + @Test + fun encodeDecodeNullableList() { + val list = listOf("One", "Two", null) + val encoded = encode>(list) { encodeDefaults = true } + + nativeAssertEquals(nativeListOf("One", "Two", null), encoded) + + val decoded = decode(ListSerializer(String.serializer().nullable), encoded) + assertEquals(listOf("One", "Two", null), decoded) + } + + @Test + fun encodeDecodeMap() { + val map = mapOf("key" to "value", "key2" to "value2", "key3" to "value3") + val encoded = encode>(map) { encodeDefaults = true } + + nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3"), encoded) + + val decoded = decode(MapSerializer(String.serializer(), String.serializer()), encoded) + assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to "value3"), decoded) + } + + @Test + fun encodeDecodeNullableMap() { + val map = mapOf("key" to "value", "key2" to "value2", "key3" to null) + val encoded = encode>(map) { encodeDefaults = true } + + nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to null), encoded) + + val decoded = decode(MapSerializer(String.serializer(), String.serializer().nullable), encoded) + assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to null), decoded) + } + + @Test + fun encodeDecodeObject() { + val encoded = encode(TestObject.serializer(), TestObject) { encodeDefaults = false } + nativeAssertEquals(nativeMapOf(), encoded) + + val decoded = decode(TestObject.serializer(), encoded) + assertEquals(TestObject, decoded) + } + + @Test + fun encodeDecodeValueClass() { + val testValueClass = ValueClass(42) + val encoded = encode(ValueClass.serializer(), testValueClass) { encodeDefaults = false } + + nativeAssertEquals(42, encoded) + + val decoded = decode(ValueClass.serializer(), encoded) + assertEquals(testValueClass, decoded) + } + + @Test + fun encodeDecodeClass() { + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) + val encoded = encode(TestData.serializer(), testDataClass) { encodeDefaults = false } + + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encoded) + + val decoded = decode(TestData.serializer(), encoded) + assertEquals(testDataClass, decoded) + } + + @Test + fun encodeDecodeClassNullableValue() { + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true, ValueClass(42)) + val encoded = encode(TestData.serializer(), testDataClass) { encodeDefaults = true } + + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true, "valueClass" to 42), encoded) + + val decoded = decode(TestData.serializer(), encoded) + assertEquals(testDataClass, decoded) + } + + @Test + fun encodeDecodeGenericClass() { + val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, valueClass = ValueClass(42)) + val genericClass = GenericClass(innerClass) + val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass) { encodeDefaults = true } + + nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42)), encoded) + + val decoded = decode(GenericClass.serializer(TestData.serializer()), encoded) + assertEquals(genericClass, decoded) + } + + @Test + fun encodeDecodeSealedClass() { + val sealedClass = SealedClass.Test("value") + val encoded = encode(SealedClass.serializer(), sealedClass) { encodeDefaults = true } + + nativeAssertEquals(nativeMapOf("type" to "test", "value" to "value"), encoded) + + val decoded = decode(SealedClass.serializer(), encoded) + assertEquals(sealedClass, decoded) + } + + @Test + fun encodeDecodePolymorphicClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } + } + val abstractClass: AbstractClass = ImplementedClass("value", true) + val encoded = + encode(AbstractClass.serializer(), abstractClass) { + encodeDefaults = true + serializersModule = module + } + + nativeAssertEquals(nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true), encoded) + + val decoded = decode(AbstractClass.serializer(), encoded) { + serializersModule = module + } + assertEquals(abstractClass, decoded) + } + + @Test + fun encodeDecodeNestedClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } + } + + val testData = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) + val sealedClass: SealedClass = SealedClass.Test("value") + val abstractClass: AbstractClass = ImplementedClass("value", true) + val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(testData), listOf(sealedClass), listOf(abstractClass), mapOf(testData to testData), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val encoded = encode(NestedClass.serializer(), nestedClass) { + encodeDefaults = true + serializersModule = module + } + + val testDataEncoded = nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42) + val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") + val abstractEncoded = nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true) + nativeAssertEquals( + nativeMapOf( + "testData" to testDataEncoded, + "sealed" to sealedEncoded, + "abstract" to abstractEncoded, + "testDataList" to nativeListOf(testDataEncoded), + "sealedList" to nativeListOf(sealedEncoded), + "abstractList" to nativeListOf(abstractEncoded), + "testDataMap" to nativeMapOf(testDataEncoded to testDataEncoded), + "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), + "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded), + ), + encoded, + ) + + val decoded = decode(NestedClass.serializer(), encoded) { + serializersModule = module + } + assertEquals(nestedClass, decoded) + } + + @Test + fun reencodeTransformationList() { + val reencoded = reencodeTransformation>(nativeListOf("One", "Two", "Three")) { + assertEquals(listOf("One", "Two", "Three"), it) + it.map { value -> "new$value" } + } + nativeAssertEquals(nativeListOf("newOne", "newTwo", "newThree"), reencoded) + } + + @Test + fun reencodeTransformationMap() { + val reencoded = reencodeTransformation>(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3")) { + assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to "value3"), it) + it.mapValues { (_, value) -> "new-$value" } + } + + nativeAssertEquals(nativeMapOf("key" to "new-value", "key2" to "new-value2", "key3" to "new-value3"), reencoded) + } + + @Test + fun reencodeTransformationObject() { + val reencoded = reencodeTransformation(nativeMapOf(), { encodeDefaults = false }) { + assertEquals(TestObject, it) + it + } + nativeAssertEquals(nativeMapOf(), reencoded) + } + + @Test + fun reencodeTransformationValueClass() { + val reencoded = reencodeTransformation( + 42, + { encodeDefaults = false }, + ) { + assertEquals(ValueClass(42), it) + ValueClass(23) + } + + nativeAssertEquals(23, reencoded) + } + + @Test + fun reencodeTransformationClass() { + val reencoded = reencodeTransformation( + nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true, "valueClass" to 42), + { encodeDefaults = false }, + ) { + assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true, ValueClass(42)), it) + it.copy(map = mapOf("newKey" to "newValue"), nullableBool = null) + } + + nativeAssertEquals(nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), reencoded) + } + + @Test + fun reencodeTransformationNullableValue() { + val reencoded = reencodeTransformation( + nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true, "valueClass" to 42), + { encodeDefaults = false }, + ) { + assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true, valueClass = ValueClass(42)), it) + null + } + + nativeAssertEquals(null, reencoded) + } + + @Test + fun reencodeTransformationGenericClass() { + val reencoded = reencodeTransformation( + GenericClass.serializer(TestData.serializer()), + nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to false, "valueClass" to 42)), + { encodeDefaults = false }, + ) { + assertEquals( + GenericClass(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = false, valueClass = ValueClass(42))), + it, + ) + GenericClass(it.inner.copy(map = mapOf("newKey" to "newValue"), nullableBool = null)) + } + + nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42)), reencoded) + } + + @Test + fun reencodeTransformationSealedClass() { + val reencoded = reencodeTransformation(SealedClass.serializer(), nativeMapOf("type" to "test", "value" to "value")) { + assertEquals(SealedClass.Test("value"), it) + SealedClass.Test("newTest") + } + + nativeAssertEquals(nativeMapOf("type" to "test", "value" to "newTest"), reencoded) + } + + @Test + fun reencodeTransformationPolymorphicClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } + } + + val reencoded = reencodeTransformation( + AbstractClass.serializer(), + nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true), + builder = { + serializersModule = module + }, + ) { + assertEquals(ImplementedClass("value", true), it) + ImplementedClass("new-${it.abstractValue}", false) + } + + nativeAssertEquals(nativeMapOf("type" to "implemented", "abstractValue" to "new-value", "otherValue" to false), reencoded) + } + + @Test + fun reencodeTransformationNestedClass() { + val module = SerializersModule { + polymorphic(AbstractClass::class, AbstractClass.serializer()) { + subclass(ImplementedClass::class, ImplementedClass.serializer()) + } + } + + val testData = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) + val sealedClass: SealedClass = SealedClass.Test("value") + val abstractClass: AbstractClass = ImplementedClass("value", true) + val nestedClass = NestedClass(testData, sealedClass, abstractClass, listOf(testData), listOf(sealedClass), listOf(abstractClass), mapOf(testData to testData), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) + val encoded = encode(NestedClass.serializer(), nestedClass) { + encodeDefaults = true + serializersModule = module + } + + val reencoded = reencodeTransformation(NestedClass.serializer(), encoded, builder = { + encodeDefaults = true + serializersModule = module + }) { + assertEquals(nestedClass, it) + it.copy(sealed = SealedClass.Test("newValue")) + } + + val testDataEncoded = nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null, "valueClass" to 42) + val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") + val abstractEncoded = nativeMapOf("type" to "implemented", "abstractValue" to "value", "otherValue" to true) + nativeAssertEquals( + nativeMapOf( + "testData" to testDataEncoded, + "sealed" to nativeMapOf("type" to "test", "value" to "newValue"), + "abstract" to abstractEncoded, + "testDataList" to nativeListOf(testDataEncoded), + "sealedList" to nativeListOf(sealedEncoded), + "abstractList" to nativeListOf(abstractEncoded), + "testDataMap" to nativeMapOf(testDataEncoded to testDataEncoded), + "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), + "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded), + ), + reencoded, + ) + } + + @Test + fun encodeAsObject() { + val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, null, ValueClass(42)) + val encodedObject = encodeAsObject( + TestData.serializer(), + testDataClass, + ) { encodeDefaults = false } + + nativeAssertEquals(mapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "valueClass" to 42), encodedObject.getRaw()) + + val testMap = mapOf("one" to 1, "two" to null, "three" to false) + assertEquals(testMap, encodeAsObject(testMap).getRaw()) + + assertEquals(emptyMap(), encodeAsObject(TestObject).getRaw()) + + assertFailsWith { + encodeAsObject( + true, + ) + } + assertFailsWith { encodeAsObject(42) } + assertFailsWith { encodeAsObject(8.toShort()) } + assertFailsWith { encodeAsObject(Int.MAX_VALUE.toLong() + 3) } + assertFailsWith { + encodeAsObject( + 0x03F, + ) + } + assertFailsWith { + encodeAsObject( + 3.33, + ) + } + assertFailsWith { + encodeAsObject( + 6.65f, + ) + } + assertFailsWith { encodeAsObject("Test") } + assertFailsWith { + encodeAsObject( + ValueClass(2), + ) + } + assertFailsWith { + encodeAsObject( + mapOf(1 to "one"), + ) + } + assertFailsWith { + encodeAsObject( + listOf("one"), + ) + } + } + + private inline fun assertEncode(value: T) { + val encoded = encode(value) + assertEquals(value, encoded) + assertEquals(value, decode(encoded)) + + val nullableEncoded = encode(null) + assertNull(nullableEncoded) + assertNull(decode(nullableEncoded)) + } +} diff --git a/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..b7221bf7a --- /dev/null +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,6 @@ +package dev.gitlive.firebase.internal + +public val EncodedObject.ios: Map get() = getRaw().mapKeys { (key, _) -> key } + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? = this as? Map<*, *> diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt similarity index 54% rename from firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt rename to firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt index f1bd5cb77..561d4c762 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt @@ -2,39 +2,36 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal +import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind -import kotlinx.serialization.encoding.CompositeDecoder -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when(descriptor.kind) { +internal actual fun FirebaseDecoderImpl.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) { StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false) StructureKind.LIST -> decodeAsList() StructureKind.MAP -> (value as? Map<*, *>).orEmpty().entries.toList().let { - FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index/2].run { if(index % 2 == 0) key else value } } - } - is PolymorphicKind -> when (settings.polymorphicStructure) { - EncodeDecodeSettings.PolymorphicStructure.MAP -> decodeAsMap(polymorphicIsNested) - EncodeDecodeSettings.PolymorphicStructure.LIST -> decodeAsList() + FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index / 2].run { if (index % 2 == 0) key else value } } } + is PolymorphicKind -> decodeAsMap(polymorphicIsNested) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } -actual fun getPolymorphicType(value: Any?, discriminator: String): String = - (value as? Map<*,*>).orEmpty()[discriminator] as String +internal actual fun getPolymorphicType(value: Any?, discriminator: String): String = + (value as? Map<*, *>).orEmpty()[discriminator] as String -private fun FirebaseDecoder.decodeAsList(): CompositeDecoder = (value as? List<*>).orEmpty().let { +private fun FirebaseDecoderImpl.decodeAsList(): CompositeDecoder = (value as? List<*>).orEmpty().let { FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] } } -private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> +private fun FirebaseDecoderImpl.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as? Map<*, *>).orEmpty().let { map -> FirebaseClassDecoder(map.size, settings, { map.containsKey(it) }) { desc, index -> if (isNestedPolymorphic) { - if (index == 0) - map[desc.getElementName(index)] - else { + if (desc.getElementName(index) == "value") { map + } else { + map[desc.getElementName(index)] } } else { map[desc.getElementName(index)] diff --git a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt similarity index 60% rename from firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt rename to firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index d5f7a4a90..bab44db4e 100644 --- a/firebase-common/src/iosMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common-internal/src/iosMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -2,29 +2,26 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.collections.set -actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { +internal actual fun FirebaseEncoderImpl.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when (descriptor.kind) { StructureKind.LIST -> encodeAsList() StructureKind.MAP -> mutableListOf() .let { FirebaseCompositeEncoder(settings, { value = it.chunked(2).associate { (k, v) -> k to v } }) { _, _, value -> it.add(value) } } - StructureKind.CLASS, StructureKind.OBJECT-> encodeAsMap(descriptor) - is PolymorphicKind -> when (settings.polymorphicStructure) { - EncodeDecodeSettings.PolymorphicStructure.MAP -> encodeAsMap(descriptor) - EncodeDecodeSettings.PolymorphicStructure.LIST -> encodeAsList() - } + StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor) + is PolymorphicKind -> encodeAsMap(descriptor) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } -private fun FirebaseEncoder.encodeAsList(): FirebaseCompositeEncoder = mutableListOf() +private fun FirebaseEncoderImpl.encodeAsList(): FirebaseCompositeEncoder = mutableListOf() .also { value = it } .let { FirebaseCompositeEncoder(settings) { _, index, value -> it.add(index, value) } } -private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf() +private fun FirebaseEncoderImpl.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = mutableMapOf() .also { value = it } .let { FirebaseCompositeEncoder( @@ -32,6 +29,6 @@ private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseC setPolymorphicType = { discriminator, type -> it[discriminator] = type }, - set = { _, index, value -> it[descriptor.getElementName(index)] = value } + set = { _, index, value -> it[descriptor.getElementName(index)] = value }, ) } diff --git a/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt new file mode 100644 index 000000000..ad90414d6 --- /dev/null +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/EncodedObject.kt @@ -0,0 +1,33 @@ +package dev.gitlive.firebase.internal + +import kotlin.js.Json +import kotlin.js.json + +public val EncodedObject.js: Json get() = json(*getRaw().entries.map { (key, value) -> key to value }.toTypedArray()) + +@PublishedApi +internal actual fun Any.asNativeMap(): Map<*, *>? { + val json = when (this) { + is Number -> null + is Boolean -> null + is String -> null + is Map<*, *> -> { + if (keys.all { it is String }) { + json(*mapKeys { (key, _) -> key as String }.toList().toTypedArray()) + } else { + null + } + } + is Collection<*> -> null + is Array<*> -> null + else -> { + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + this as Json + } + } ?: return null + val mutableMap = mutableMapOf() + for (key in js("Object").keys(json)) { + mutableMap[key.unsafeCast()] = json[key.unsafeCast()] + } + return mutableMap.toMap() +} diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt similarity index 55% rename from firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt rename to firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt index 0d33f9087..1787c1cc4 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_decoders.kt +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_decoders.kt @@ -2,7 +2,7 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind @@ -11,46 +11,49 @@ import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.encoding.CompositeDecoder import kotlin.js.Json -actual fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) { +internal actual fun FirebaseDecoderImpl.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder = when (descriptor.kind) { StructureKind.CLASS, StructureKind.OBJECT -> decodeAsMap(false) StructureKind.LIST -> decodeAsList() StructureKind.MAP -> (js("Object").entries(value) as Array>).let { FirebaseCompositeDecoder( it.size, - settings - ) { desc, index -> it[index / 2].run { if (index % 2 == 0) { - val key = get(0) as String - if (desc.getElementDescriptor(index).kind == PrimitiveKind.STRING) { - key - } else { - JSON.parse(key) + settings, + ) { desc, index -> + it[index / 2].run { + if (index % 2 == 0) { + val key = get(0) as String + if (desc.getElementDescriptor(index).kind == PrimitiveKind.STRING) { + key + } else { + JSON.parse(key) + } + } else { + get(1) + } } - } else get(1) } } - } - - is PolymorphicKind -> when (settings.polymorphicStructure) { - EncodeDecodeSettings.PolymorphicStructure.MAP -> decodeAsMap(polymorphicIsNested) - EncodeDecodeSettings.PolymorphicStructure.LIST -> decodeAsList() + } } + is PolymorphicKind -> decodeAsMap(polymorphicIsNested) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") -actual fun getPolymorphicType(value: Any?, discriminator: String): String = +internal actual fun getPolymorphicType(value: Any?, discriminator: String): String = (value as Json)[discriminator] as String -private fun FirebaseDecoder.decodeAsList(): CompositeDecoder = (value as Array<*>).let { +private fun FirebaseDecoderImpl.decodeAsList(): CompositeDecoder = (value as Array<*>).let { FirebaseCompositeDecoder(it.size, settings) { _, index -> it[index] } } + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") -private fun FirebaseDecoder.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as Json).let { json -> +private fun FirebaseDecoderImpl.decodeAsMap(isNestedPolymorphic: Boolean): CompositeDecoder = (value as Json).let { json -> FirebaseClassDecoder(js("Object").keys(value).length as Int, settings, { json[it] != undefined }) { desc, index -> if (isNestedPolymorphic) { - if (index == 0) { - json[desc.getElementName(index)] - } else { + if (desc.getElementName(index) == "value") { json + } else { + json[desc.getElementName(index)] } } else { json[desc.getElementName(index)] diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt similarity index 50% rename from firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt rename to firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt index 4d01b514d..7121128d0 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/_encoders.kt +++ b/firebase-common-internal/src/jsMain/kotlin/dev/gitlive/firebase/internal/_encoders.kt @@ -2,35 +2,36 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -package dev.gitlive.firebase +package dev.gitlive.firebase.internal import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlin.js.json -actual fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when(descriptor.kind) { +internal actual fun FirebaseEncoderImpl.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder = when (descriptor.kind) { StructureKind.LIST -> encodeAsList(descriptor) StructureKind.MAP -> { val map = json() var lastKey = "" value = map - FirebaseCompositeEncoder(settings) { _, index, value -> if(index % 2 == 0) { - lastKey = (value as? String) ?: JSON.stringify(value) - }else map[lastKey] = value } - } - StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor) - is PolymorphicKind -> when (settings.polymorphicStructure) { - EncodeDecodeSettings.PolymorphicStructure.MAP -> encodeAsMap(descriptor) - EncodeDecodeSettings.PolymorphicStructure.LIST -> encodeAsList(descriptor) + FirebaseCompositeEncoder(settings) { _, index, value -> + if (index % 2 == 0) { + lastKey = (value as? String) ?: JSON.stringify(value) + } else { + map[lastKey] = value + } + } } + StructureKind.CLASS, StructureKind.OBJECT -> encodeAsMap(descriptor) + is PolymorphicKind -> encodeAsMap(descriptor) else -> TODO("The firebase-kotlin-sdk does not support $descriptor for serialization yet") } -private fun FirebaseEncoder.encodeAsList(descriptor: SerialDescriptor): FirebaseCompositeEncoder = Array(descriptor.elementsCount) { null } +private fun FirebaseEncoderImpl.encodeAsList(descriptor: SerialDescriptor): FirebaseCompositeEncoder = Array(descriptor.elementsCount) { null } .also { value = it } .let { FirebaseCompositeEncoder(settings) { _, index, value -> it[index] = value } } -private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = json() +private fun FirebaseEncoderImpl.encodeAsMap(descriptor: SerialDescriptor): FirebaseCompositeEncoder = json() .also { value = it } .let { FirebaseCompositeEncoder( @@ -38,6 +39,6 @@ private fun FirebaseEncoder.encodeAsMap(descriptor: SerialDescriptor): FirebaseC setPolymorphicType = { discriminator, type -> it[discriminator] = type }, - set = { _, index, value -> it[descriptor.getElementName(index)] = value } + set = { _, index, value -> it[descriptor.getElementName(index)] = value }, ) } diff --git a/firebase-common/api/android/firebase-common.api b/firebase-common/api/android/firebase-common.api new file mode 100644 index 000000000..9724b86d4 --- /dev/null +++ b/firebase-common/api/android/firebase-common.api @@ -0,0 +1,70 @@ +public abstract interface class dev/gitlive/firebase/DecodeSettings : dev/gitlive/firebase/EncodeDecodeSettings { +} + +public abstract interface class dev/gitlive/firebase/DecodeSettings$Builder { + public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public abstract fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public abstract interface class dev/gitlive/firebase/EncodeDecodeSettings { + public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; +} + +public abstract interface class dev/gitlive/firebase/EncodeDecodeSettingsBuilder : dev/gitlive/firebase/DecodeSettings$Builder, dev/gitlive/firebase/EncodeSettings$Builder { +} + +public abstract interface class dev/gitlive/firebase/EncodeSettings : dev/gitlive/firebase/EncodeDecodeSettings { + public abstract fun getEncodeDefaults ()Z +} + +public abstract interface class dev/gitlive/firebase/EncodeSettings$Builder { + public abstract fun getEncodeDefaults ()Z + public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public abstract fun setEncodeDefaults (Z)V + public abstract fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public abstract interface annotation class dev/gitlive/firebase/FirebaseClassDiscriminator : java/lang/annotation/Annotation { + public abstract fun discriminator ()Ljava/lang/String; +} + +public synthetic class dev/gitlive/firebase/FirebaseClassDiscriminator$Impl : dev/gitlive/firebase/FirebaseClassDiscriminator { + public fun (Ljava/lang/String;)V + public final synthetic fun discriminator ()Ljava/lang/String; +} + +public abstract interface class dev/gitlive/firebase/FirebaseDecoder : kotlinx/serialization/encoding/Decoder { +} + +public final class dev/gitlive/firebase/FirebaseDecoder$DefaultImpls { + public static fun decodeNullableSerializableValue (Ldev/gitlive/firebase/FirebaseDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public static fun decodeSerializableValue (Ldev/gitlive/firebase/FirebaseDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +} + +public abstract interface class dev/gitlive/firebase/FirebaseEncoder : kotlinx/serialization/encoding/Encoder { +} + +public final class dev/gitlive/firebase/FirebaseEncoder$DefaultImpls { + public static fun beginCollection (Ldev/gitlive/firebase/FirebaseEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; + public static fun encodeNotNullMark (Ldev/gitlive/firebase/FirebaseEncoder;)V + public static fun encodeNullableSerializableValue (Ldev/gitlive/firebase/FirebaseEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public static fun encodeSerializableValue (Ldev/gitlive/firebase/FirebaseEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/ValueWithSerializer { + public fun (Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Lkotlinx/serialization/SerializationStrategy; + public final fun copy (Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;)Ldev/gitlive/firebase/ValueWithSerializer; + public static synthetic fun copy$default (Ldev/gitlive/firebase/ValueWithSerializer;Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;ILjava/lang/Object;)Ldev/gitlive/firebase/ValueWithSerializer; + public fun equals (Ljava/lang/Object;)Z + public final fun getSerializer ()Lkotlinx/serialization/SerializationStrategy; + public final fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/ValueWithSerializerKt { + public static final fun withSerializer (Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;)Ljava/lang/Object; +} + diff --git a/firebase-common/api/jvm/firebase-common.api b/firebase-common/api/jvm/firebase-common.api new file mode 100644 index 000000000..9724b86d4 --- /dev/null +++ b/firebase-common/api/jvm/firebase-common.api @@ -0,0 +1,70 @@ +public abstract interface class dev/gitlive/firebase/DecodeSettings : dev/gitlive/firebase/EncodeDecodeSettings { +} + +public abstract interface class dev/gitlive/firebase/DecodeSettings$Builder { + public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public abstract fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public abstract interface class dev/gitlive/firebase/EncodeDecodeSettings { + public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; +} + +public abstract interface class dev/gitlive/firebase/EncodeDecodeSettingsBuilder : dev/gitlive/firebase/DecodeSettings$Builder, dev/gitlive/firebase/EncodeSettings$Builder { +} + +public abstract interface class dev/gitlive/firebase/EncodeSettings : dev/gitlive/firebase/EncodeDecodeSettings { + public abstract fun getEncodeDefaults ()Z +} + +public abstract interface class dev/gitlive/firebase/EncodeSettings$Builder { + public abstract fun getEncodeDefaults ()Z + public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; + public abstract fun setEncodeDefaults (Z)V + public abstract fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +} + +public abstract interface annotation class dev/gitlive/firebase/FirebaseClassDiscriminator : java/lang/annotation/Annotation { + public abstract fun discriminator ()Ljava/lang/String; +} + +public synthetic class dev/gitlive/firebase/FirebaseClassDiscriminator$Impl : dev/gitlive/firebase/FirebaseClassDiscriminator { + public fun (Ljava/lang/String;)V + public final synthetic fun discriminator ()Ljava/lang/String; +} + +public abstract interface class dev/gitlive/firebase/FirebaseDecoder : kotlinx/serialization/encoding/Decoder { +} + +public final class dev/gitlive/firebase/FirebaseDecoder$DefaultImpls { + public static fun decodeNullableSerializableValue (Ldev/gitlive/firebase/FirebaseDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; + public static fun decodeSerializableValue (Ldev/gitlive/firebase/FirebaseDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +} + +public abstract interface class dev/gitlive/firebase/FirebaseEncoder : kotlinx/serialization/encoding/Encoder { +} + +public final class dev/gitlive/firebase/FirebaseEncoder$DefaultImpls { + public static fun beginCollection (Ldev/gitlive/firebase/FirebaseEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; + public static fun encodeNotNullMark (Ldev/gitlive/firebase/FirebaseEncoder;)V + public static fun encodeNullableSerializableValue (Ldev/gitlive/firebase/FirebaseEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V + public static fun encodeSerializableValue (Ldev/gitlive/firebase/FirebaseEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/ValueWithSerializer { + public fun (Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Lkotlinx/serialization/SerializationStrategy; + public final fun copy (Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;)Ldev/gitlive/firebase/ValueWithSerializer; + public static synthetic fun copy$default (Ldev/gitlive/firebase/ValueWithSerializer;Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;ILjava/lang/Object;)Ldev/gitlive/firebase/ValueWithSerializer; + public fun equals (Ljava/lang/Object;)Z + public final fun getSerializer ()Lkotlinx/serialization/SerializationStrategy; + public final fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/ValueWithSerializerKt { + public static final fun withSerializer (Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;)Ljava/lang/Object; +} + diff --git a/firebase-common/build.gradle.kts b/firebase-common/build.gradle.kts index 0586bfd3f..8b7d9f37c 100644 --- a/firebase-common/build.gradle.kts +++ b/firebase-common/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -10,6 +13,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("plugin.serialization") + id("testOptionsConvention") } android { @@ -24,15 +28,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") @@ -45,10 +45,22 @@ android { } kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -57,25 +69,9 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() val supportIosTarget = project.property("skipIosTarget") != "true" @@ -88,32 +84,26 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") optIn("kotlinx.serialization.ExperimentalSerializationApi") @@ -122,10 +112,8 @@ kotlin { } getByName("commonMain") { - val serializationVersion: String by project - dependencies { - api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") + api(libs.kotlinx.serialization.core) } } @@ -137,13 +125,13 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-common-ktx") + api(libs.google.firebase.common.ktx) } } getByName("jsMain") { dependencies { - api(npm("firebase", "10.6.0")) + api(npm("firebase", "10.12.2")) } } @@ -166,6 +154,12 @@ if (project.property("firebase-common.skipIosTests") == "true") { } } +if (project.property("firebase-common.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-common.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-common/package.json b/firebase-common/package.json index 163d7ec0f..ac1374d45 100644 --- a/firebase-common/package.json +++ b/firebase-common/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-common", - "version": "1.10.4", + "version": "2.0.0", "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", "main": "firebase-common.js", "scripts": { diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt index 79f580bb1..575b883a0 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt @@ -1,59 +1,42 @@ package dev.gitlive.firebase -import dev.gitlive.firebase.EncodeDecodeSettings.PolymorphicStructure -import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule /** * Settings used to configure encoding/decoding */ -sealed class EncodeDecodeSettings { - - /** - * The structure in which Polymorphic classes are to be serialized - */ - enum class PolymorphicStructure { - - /** - * A [PolymorphicStructure] where the polymorphic class is serialized as a Map, with a key for `type` reserved for the polymorphic discriminator - */ - MAP, - - /** - * A [PolymorphicStructure] where the polymorphic class is serialized as a List, with the polymorphic discriminator as its first element and the serialized object as its second element - */ - LIST - } +public sealed interface EncodeDecodeSettings { /** * The [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime */ - abstract val serializersModule: SerializersModule - - /** - * The [PolymorphicStructure] to use for encoding/decoding polymorphic classes - */ - abstract val polymorphicStructure: PolymorphicStructure + public val serializersModule: SerializersModule } /** * [EncodeDecodeSettings] used when encoding an object - * @property shouldEncodeElementDefault if `true` this will explicitly encode elements even if they are their default value - * @param serializersModule the [SerializersModule] to use for serialization. This allows for polymorphic serialization on runtime - * @param polymorphicStructure the [PolymorphicStructure] to use for encoding polymorphic classes + * @property encodeDefaults if `true` this will explicitly encode elements even if they are their default value */ -data class EncodeSettings( - val shouldEncodeElementDefault: Boolean = true, - override val serializersModule: SerializersModule = EmptySerializersModule(), - override val polymorphicStructure: PolymorphicStructure = PolymorphicStructure.MAP -) : EncodeDecodeSettings() +public interface EncodeSettings : EncodeDecodeSettings { + + public val encodeDefaults: Boolean + + public interface Builder { + public var encodeDefaults: Boolean + public var serializersModule: SerializersModule + } +} /** * [EncodeDecodeSettings] used when decoding an object - * @param serializersModule the [SerializersModule] to use for deserialization. This allows for polymorphic serialization on runtime - * @param polymorphicStructure the [PolymorphicStructure] to use for decoding polymorphic classes */ -data class DecodeSettings( - override val serializersModule: SerializersModule = EmptySerializersModule(), - override val polymorphicStructure: PolymorphicStructure = PolymorphicStructure.MAP -) : EncodeDecodeSettings() \ No newline at end of file +public interface DecodeSettings : EncodeDecodeSettings { + + public interface Builder { + public var serializersModule: SerializersModule + } +} + +public interface EncodeDecodeSettingsBuilder : + EncodeSettings.Builder, + DecodeSettings.Builder diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseClassDiscriminator.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseClassDiscriminator.kt index 2ed4bafa1..6a8fbbc4b 100644 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseClassDiscriminator.kt +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseClassDiscriminator.kt @@ -4,4 +4,4 @@ import kotlinx.serialization.InheritableSerialInfo @InheritableSerialInfo @Target(AnnotationTarget.CLASS) -annotation class FirebaseClassDiscriminator(val discriminator: String) \ No newline at end of file +public annotation class FirebaseClassDiscriminator(val discriminator: String) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseDecoder.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseDecoder.kt new file mode 100644 index 000000000..2dbf07b44 --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseDecoder.kt @@ -0,0 +1,5 @@ +package dev.gitlive.firebase + +import kotlinx.serialization.encoding.Decoder + +public interface FirebaseDecoder : Decoder diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseEncoder.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseEncoder.kt new file mode 100644 index 000000000..f33f69fbb --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/FirebaseEncoder.kt @@ -0,0 +1,5 @@ +package dev.gitlive.firebase + +import kotlinx.serialization.encoding.Encoder + +public interface FirebaseEncoder : Encoder diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/ValueWithSerializer.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/ValueWithSerializer.kt new file mode 100644 index 000000000..84ed4713b --- /dev/null +++ b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/ValueWithSerializer.kt @@ -0,0 +1,11 @@ +package dev.gitlive.firebase + +import kotlinx.serialization.SerializationStrategy + +/** + * An extension which which serializer to use for value. Handy in updating fields by name or path + * where using annotation is not possible + * @return a value with a custom serializer. + */ +public fun T.withSerializer(serializer: SerializationStrategy): Any = ValueWithSerializer(this, serializer) +public data class ValueWithSerializer(val value: T, val serializer: SerializationStrategy) diff --git a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt b/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt deleted file mode 100644 index b6cfec96b..000000000 --- a/firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt +++ /dev/null @@ -1,196 +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 kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.SerializersModule - -fun encode(strategy: SerializationStrategy, value: T, shouldEncodeElementDefault: Boolean): Any? = encode(strategy, value, EncodeSettings(shouldEncodeElementDefault)) - -fun encode(strategy: SerializationStrategy, value: T, settings: EncodeSettings): Any? = - FirebaseEncoder(settings).apply { encodeSerializableValue(strategy, value) }.value - -inline fun encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value, EncodeSettings(shouldEncodeElementDefault)) -inline fun encode(value: T, settings: EncodeSettings): Any? = value?.let { - FirebaseEncoder(settings).apply { - if (it is ValueWithSerializer<*> && it.value is T) { - @Suppress("UNCHECKED_CAST") - (it as ValueWithSerializer).let { - encodeSerializableValue(it.serializer, it.value) - } - } else { - encodeSerializableValue(it.firebaseSerializer(), it) - } - }.value -} - -/** - * An extension which which serializer to use for value. Handy in updating fields by name or path - * where using annotation is not possible - * @return a value with a custom serializer. - */ -fun T.withSerializer(serializer: SerializationStrategy): Any = ValueWithSerializer(this, serializer) -data class ValueWithSerializer(val value: T, val serializer: SerializationStrategy) - -expect fun FirebaseEncoder.structureEncoder(descriptor: SerialDescriptor): FirebaseCompositeEncoder - -class FirebaseEncoder( - internal val settings: EncodeSettings -) : Encoder { - -// constructor(shouldEncodeElementDefault: Boolean) : this(EncodeSettings(shouldEncodeElementDefault)) - - var value: Any? = null - - internal val shouldEncodeElementDefault = settings.shouldEncodeElementDefault - override val serializersModule: SerializersModule = settings.serializersModule - - private var polymorphicDiscriminator: String? = null - - override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { - val encoder = structureEncoder(descriptor) - if (polymorphicDiscriminator != null) { - encoder.encodePolymorphicClassDiscriminator(polymorphicDiscriminator!!, descriptor.serialName) - polymorphicDiscriminator = null - } - return encoder - } - - override fun encodeBoolean(value: Boolean) { - this.value = value - } - - override fun encodeByte(value: Byte) { - this.value = value - } - - override fun encodeChar(value: Char) { - this.value = value - } - - override fun encodeDouble(value: Double) { - this.value = value - } - - override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { - this.value = enumDescriptor.getElementName(index) - } - - override fun encodeFloat(value: Float) { - this.value = value - } - - override fun encodeInt(value: Int) { - this.value = value - } - - override fun encodeLong(value: Long) { - this.value = value - } - - override fun encodeNotNullMark() { - //no-op - } - - override fun encodeNull() { - this.value = null - } - - override fun encodeShort(value: Short) { - this.value = value - } - - override fun encodeString(value: String) { - this.value = value - } - - override fun encodeInline(descriptor: SerialDescriptor): Encoder = - FirebaseEncoder(settings) - - override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { - encodePolymorphically(serializer, value, settings) { - polymorphicDiscriminator = it - } - } -} - -open class FirebaseCompositeEncoder constructor( - private val settings: EncodeSettings, - private val end: () -> Unit = {}, - private val setPolymorphicType: (String, String) -> Unit = { _, _ -> }, - private val set: (descriptor: SerialDescriptor, index: Int, value: Any?) -> Unit, -): CompositeEncoder { - -// private fun SerializationStrategy.toFirebase(): SerializationStrategy = when(descriptor.kind) { -// StructureKind.MAP -> FirebaseMapSerializer(descriptor.getElementDescriptor(1)) as SerializationStrategy -// StructureKind.LIST -> FirebaseListSerializer(descriptor.getElementDescriptor(0)) as SerializationStrategy -// else -> this -// } - - override val serializersModule: SerializersModule = settings.serializersModule - - override fun endStructure(descriptor: SerialDescriptor) = end() - - override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int) = settings.shouldEncodeElementDefault - - override fun encodeNullableSerializableElement( - descriptor: SerialDescriptor, - index: Int, - serializer: SerializationStrategy, - value: T? - ) = set( - descriptor, - index, - value?.let { - FirebaseEncoder(settings).apply { - encodeSerializableValue(serializer, value) - }.value - } - ) - - override fun encodeSerializableElement( - descriptor: SerialDescriptor, - index: Int, - serializer: SerializationStrategy, - value: T - ) = set( - descriptor, - index, - FirebaseEncoder(settings).apply { - encodeSerializableValue(serializer, value) - }.value - ) - - fun encodeObject(descriptor: SerialDescriptor, index: Int, value: T) = set(descriptor, index, value) - - override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) = set(descriptor, index, value) - - override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) = set(descriptor, index, value) - - override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char) = set(descriptor, index, value) - - override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double) = set(descriptor, index, value) - - override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float) = set(descriptor, index, value) - - override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int) = set(descriptor, index, value) - - override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long) = set(descriptor, index, value) - - override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short) = set(descriptor, index, value) - - override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) = set(descriptor, index, value) - - @ExperimentalSerializationApi - override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder = - FirebaseEncoder(settings) - - fun encodePolymorphicClassDiscriminator(discriminator: String, type: String) { - setPolymorphicType(discriminator, type) - } -} - diff --git a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt b/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt deleted file mode 100644 index afa74d402..000000000 --- a/firebase-common/src/commonTest/kotlin/dev/gitlive/firebase/EncodersTest.kt +++ /dev/null @@ -1,231 +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 kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.modules.SerializersModule -import kotlin.test.Test -import kotlin.test.assertEquals - -@Serializable -object TestObject { - val map = mapOf("key" to "value", "key2" to 12, "key3" to null) - val bool = false - val nullableBool: Boolean? = null -} - -@Serializable -data class TestData(val map: Map, val otherMap: Map, val bool: Boolean = false, val nullableBool: Boolean? = null) - -@Serializable -sealed class SealedClass { - @Serializable - @SerialName("test") - data class Test(val value: String) : SealedClass() -} - -@Serializable -data class GenericClass( - val inner: T -) - -@Serializable -abstract class AbstractClass { - abstract val value: String -} - -@Serializable -@SerialName("implemented") -data class ImplementedClass(override val value: String, val otherValue: Boolean) : AbstractClass() - -@Serializable -data class NestedClass( - val sealed: SealedClass, - val abstract: AbstractClass, - val sealedList: List, - val abstractList: List, - val sealedMap: Map, - val abstractMap: Map -) - -class EncodersTest { - - @Test - fun encodeDecodeList() { - val list = listOf("One", "Two", "Three") - val encoded = encode(list, shouldEncodeElementDefault = true) - - nativeAssertEquals(nativeListOf("One", "Two", "Three"), encoded) - - val decoded = decode(ListSerializer(String.serializer()), encoded) - assertEquals(listOf("One", "Two", "Three"), decoded) - } - - @Test - fun encodeDecodeMap() { - val map = mapOf("key" to "value", "key2" to "value2", "key3" to "value3") - val encoded = encode(map, shouldEncodeElementDefault = true) - - nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3"), encoded) - - val decoded = decode(MapSerializer(String.serializer(), String.serializer()), encoded) - assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to "value3"), decoded) - } - - @Test - fun encodeDecodeObject() { - val encoded = encode(TestObject.serializer(), TestObject, shouldEncodeElementDefault = false) - nativeAssertEquals(nativeMapOf(), encoded) - - val decoded = decode(TestObject.serializer(), encoded) - assertEquals(TestObject, decoded) - } - - @Test - fun encodeDecodeClass() { - val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) - val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = false) - - nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), encoded) - - val decoded = decode(TestData.serializer(), encoded) - assertEquals(testDataClass, decoded) - } - - @Test - fun encodeDecodeClassNullableValue() { - val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true) - val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = true) - - nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), encoded) - - val decoded = decode(TestData.serializer(), encoded) - assertEquals(testDataClass, decoded) - } - - @Test - fun encodeDecodeGenericClass() { - val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true) - val genericClass = GenericClass(innerClass) - val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass, shouldEncodeElementDefault = true) - - nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null)), encoded) - - val decoded = decode(GenericClass.serializer(TestData.serializer()), encoded) - assertEquals(genericClass, decoded) - } - - @Test - fun encodeDecodeSealedClass() { - val sealedClass = SealedClass.Test("value") - val encoded = encode(SealedClass.serializer(), sealedClass, shouldEncodeElementDefault = true) - - nativeAssertEquals(nativeMapOf("type" to "test", "value" to "value"), encoded) - - val decoded = decode(SealedClass.serializer(), encoded) - assertEquals(sealedClass, decoded) - } - - @Test - fun encodeDecodeSealedClassAsList() { - val sealedClass = SealedClass.Test("value") - val encoded = encode(SealedClass.serializer(), sealedClass, EncodeSettings(shouldEncodeElementDefault = true, polymorphicStructure = EncodeDecodeSettings.PolymorphicStructure.LIST)) - - nativeAssertEquals(nativeListOf("test", nativeMapOf("value" to "value")), encoded) - - val decoded = decode(SealedClass.serializer(), encoded, DecodeSettings(polymorphicStructure = EncodeDecodeSettings.PolymorphicStructure.LIST)) - assertEquals(sealedClass, decoded) - } - - @Test - fun encodeDecodePolymorphicClass() { - val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) - } - val abstractClass: AbstractClass = ImplementedClass("value", true) - val encoded = encode(AbstractClass.serializer(), abstractClass, EncodeSettings(true, module)) - - nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true), encoded) - - val decoded = decode(AbstractClass.serializer(), encoded, DecodeSettings(module)) - assertEquals(abstractClass, decoded) - } - - @Test - fun encodeDecodePolymorphicClassAsList() { - val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) - } - val abstractClass: AbstractClass = ImplementedClass("value", true) - val encoded = encode(AbstractClass.serializer(), abstractClass, EncodeSettings(true, module, polymorphicStructure = EncodeDecodeSettings.PolymorphicStructure.LIST)) - - nativeAssertEquals(nativeListOf("implemented", nativeMapOf("value" to "value", "otherValue" to true)), encoded) - - val decoded = decode(AbstractClass.serializer(), encoded, DecodeSettings(module, polymorphicStructure = EncodeDecodeSettings.PolymorphicStructure.LIST)) - assertEquals(abstractClass, decoded) - } - - @Test - fun encodeDecodeNestedClass() { - val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) - } - - val sealedClass: SealedClass = SealedClass.Test("value") - val abstractClass: AbstractClass = ImplementedClass("value", true) - val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) - val encoded = encode(NestedClass.serializer(), nestedClass, EncodeSettings(true, module)) - - val sealedEncoded = nativeMapOf("type" to "test", "value" to "value") - val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true) - nativeAssertEquals( - nativeMapOf( - "sealed" to sealedEncoded, - "abstract" to abstractEncoded, - "sealedList" to nativeListOf(sealedEncoded), - "abstractList" to nativeListOf(abstractEncoded), - "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), - "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded) - ), - encoded - ) - - val decoded = decode(NestedClass.serializer(), encoded, DecodeSettings(module)) - assertEquals(nestedClass, decoded) - } - - @Test - fun encodeDecodeNestedClassAsList() { - val module = SerializersModule { - polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer()) - } - - val sealedClass: SealedClass = SealedClass.Test("value") - val abstractClass: AbstractClass = ImplementedClass("value", true) - val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass)) - val encoded = encode(NestedClass.serializer(), nestedClass, EncodeSettings(true, module, polymorphicStructure = EncodeDecodeSettings.PolymorphicStructure.LIST)) - - val sealedEncoded = nativeListOf("test", nativeMapOf("value" to "value")) - val abstractEncoded =nativeListOf("implemented", nativeMapOf("value" to "value", "otherValue" to true)) - nativeAssertEquals( - nativeMapOf( - "sealed" to sealedEncoded, - "abstract" to abstractEncoded, - "sealedList" to nativeListOf(sealedEncoded), - "abstractList" to nativeListOf(abstractEncoded), - "sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded), - "abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded) - ), - encoded - ) - - val decoded = decode(NestedClass.serializer(), encoded, DecodeSettings(module, polymorphicStructure = EncodeDecodeSettings.PolymorphicStructure.LIST)) - assertEquals(nestedClass, decoded) - } -} diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/Unsubscribe.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/Unsubscribe.kt index 087d4f86b..4a0ea36c9 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/Unsubscribe.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/Unsubscribe.kt @@ -4,4 +4,4 @@ package dev.gitlive.firebase -typealias Unsubscribe = () -> Unit +public typealias Unsubscribe = () -> Unit diff --git a/firebase-config/api/android/firebase-config.api b/firebase-config/api/android/firebase-config.api new file mode 100644 index 000000000..d9d718ce8 --- /dev/null +++ b/firebase-config/api/android/firebase-config.api @@ -0,0 +1,89 @@ +public final class dev/gitlive/firebase/remoteconfig/FetchStatus : java/lang/Enum { + public static final field Failure Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static final field NoFetchYet Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static final field Success Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static final field Throttled Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static fun values ()[Ldev/gitlive/firebase/remoteconfig/FetchStatus; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig { + public final fun activate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun ensureInitialized (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fetch-dnQKTGw (Lkotlin/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun fetch-dnQKTGw$default (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig;Lkotlin/time/Duration;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun fetchAndActivate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getAll ()Ljava/util/Map; + public final fun getInfo ()Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo; + public final fun getKeysByPrefix (Ljava/lang/String;)Ljava/util/Set; + public final fun getValue (Ljava/lang/String;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue; + public final fun reset (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setDefaults ([Lkotlin/Pair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun settings (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo { + public fun (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;Lkotlinx/datetime/Instant;Ldev/gitlive/firebase/remoteconfig/FetchStatus;)V + public final fun component1 ()Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public final fun component2 ()Lkotlinx/datetime/Instant; + public final fun component3 ()Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public final fun copy (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;Lkotlinx/datetime/Instant;Ldev/gitlive/firebase/remoteconfig/FetchStatus;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo; + public static synthetic fun copy$default (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo;Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;Lkotlinx/datetime/Instant;Ldev/gitlive/firebase/remoteconfig/FetchStatus;ILjava/lang/Object;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo; + public fun equals (Ljava/lang/Object;)Z + public final fun getConfigSettings ()Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public final fun getFetchTime ()Lkotlinx/datetime/Instant; + public final fun getFetchTimeMillis ()J + public final fun getLastFetchStatus ()Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigKt { + public static final fun fetch (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings { + public synthetic fun (JJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (JJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-UwyO8pc ()J + public final fun component2-UwyO8pc ()J + public final fun copy-QTBD994 (JJ)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public static synthetic fun copy-QTBD994$default (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;JJILjava/lang/Object;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getFetchTimeout-UwyO8pc ()J + public final fun getFetchTimeoutInSeconds ()J + public final fun getMinimumFetchInterval-UwyO8pc ()J + public final fun getMinimumFetchIntervalInSeconds ()J + public fun hashCode ()I + public final fun setFetchTimeout-LRDsOJo (J)V + public final fun setFetchTimeoutInSeconds (J)V + public final fun setMinimumFetchInterval-LRDsOJo (J)V + public final fun setMinimumFetchIntervalInSeconds (J)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue { + public final fun asBoolean ()Z + public final fun asByteArray ()[B + public final fun asDouble ()D + public final fun asLong ()J + public final fun asString ()Ljava/lang/String; + public final fun getSource ()Ldev/gitlive/firebase/remoteconfig/ValueSource; +} + +public final class dev/gitlive/firebase/remoteconfig/ValueSource : java/lang/Enum { + public static final field Default Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static final field Remote Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static final field Static Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static fun values ()[Ldev/gitlive/firebase/remoteconfig/ValueSource; +} + +public final class dev/gitlive/firebase/remoteconfig/android { + public static final fun getAndroid (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig;)Lcom/google/firebase/remoteconfig/FirebaseRemoteConfig; + public static final fun getRemoteConfig (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig; + public static final fun remoteConfig (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig; +} + diff --git a/firebase-config/api/jvm/firebase-config.api b/firebase-config/api/jvm/firebase-config.api new file mode 100644 index 000000000..d9d718ce8 --- /dev/null +++ b/firebase-config/api/jvm/firebase-config.api @@ -0,0 +1,89 @@ +public final class dev/gitlive/firebase/remoteconfig/FetchStatus : java/lang/Enum { + public static final field Failure Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static final field NoFetchYet Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static final field Success Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static final field Throttled Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public static fun values ()[Ldev/gitlive/firebase/remoteconfig/FetchStatus; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig { + public final fun activate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun ensureInitialized (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fetch-dnQKTGw (Lkotlin/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun fetch-dnQKTGw$default (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig;Lkotlin/time/Duration;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun fetchAndActivate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getAll ()Ljava/util/Map; + public final fun getInfo ()Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo; + public final fun getKeysByPrefix (Ljava/lang/String;)Ljava/util/Set; + public final fun getValue (Ljava/lang/String;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue; + public final fun reset (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setDefaults ([Lkotlin/Pair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun settings (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo { + public fun (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;Lkotlinx/datetime/Instant;Ldev/gitlive/firebase/remoteconfig/FetchStatus;)V + public final fun component1 ()Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public final fun component2 ()Lkotlinx/datetime/Instant; + public final fun component3 ()Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public final fun copy (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;Lkotlinx/datetime/Instant;Ldev/gitlive/firebase/remoteconfig/FetchStatus;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo; + public static synthetic fun copy$default (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo;Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;Lkotlinx/datetime/Instant;Ldev/gitlive/firebase/remoteconfig/FetchStatus;ILjava/lang/Object;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo; + public fun equals (Ljava/lang/Object;)Z + public final fun getConfigSettings ()Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public final fun getFetchTime ()Lkotlinx/datetime/Instant; + public final fun getFetchTimeMillis ()J + public final fun getLastFetchStatus ()Ldev/gitlive/firebase/remoteconfig/FetchStatus; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigKt { + public static final fun fetch (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings { + public synthetic fun (JJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (JJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-UwyO8pc ()J + public final fun component2-UwyO8pc ()J + public final fun copy-QTBD994 (JJ)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public static synthetic fun copy-QTBD994$default (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings;JJILjava/lang/Object;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getFetchTimeout-UwyO8pc ()J + public final fun getFetchTimeoutInSeconds ()J + public final fun getMinimumFetchInterval-UwyO8pc ()J + public final fun getMinimumFetchIntervalInSeconds ()J + public fun hashCode ()I + public final fun setFetchTimeout-LRDsOJo (J)V + public final fun setFetchTimeoutInSeconds (J)V + public final fun setMinimumFetchInterval-LRDsOJo (J)V + public final fun setMinimumFetchIntervalInSeconds (J)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue { + public final fun asBoolean ()Z + public final fun asByteArray ()[B + public final fun asDouble ()D + public final fun asLong ()J + public final fun asString ()Ljava/lang/String; + public final fun getSource ()Ldev/gitlive/firebase/remoteconfig/ValueSource; +} + +public final class dev/gitlive/firebase/remoteconfig/ValueSource : java/lang/Enum { + public static final field Default Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static final field Remote Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static final field Static Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/remoteconfig/ValueSource; + public static fun values ()[Ldev/gitlive/firebase/remoteconfig/ValueSource; +} + +public final class dev/gitlive/firebase/remoteconfig/android { + public static final fun getAndroid (Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig;)Lcom/google/firebase/remoteconfig/FirebaseRemoteConfig; + public static final fun getRemoteConfig (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig; + public static final fun remoteConfig (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig; +} + diff --git a/firebase-config/build.gradle.kts b/firebase-config/build.gradle.kts index a3f0bf36d..a28086463 100644 --- a/firebase-config/build.gradle.kts +++ b/firebase-config/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -10,7 +13,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") - //id("com.quittle.android-emulator") version "0.2.0" + id("testOptionsConvention") } android { @@ -26,15 +29,12 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -45,26 +45,29 @@ android { } } -// Optional configuration -//androidEmulator { -// emulator { -// name("givlive_emulator") -// sdkVersion(28) -// abi("x86_64") -// includeGoogleApis(true) // Defaults to false -// -// } -// headless(false) -// logEmulatorOutput(false) -//} +dependencies { + coreLibraryDesugaring(libs.android.desugarjdk) +} val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -73,38 +76,23 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseConfig" } noPodspec() pod("FirebaseRemoteConfig") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") } } } @@ -112,23 +100,19 @@ kotlin { js(IR) { useCommonJs() browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") if (name.lowercase().contains("ios")) { @@ -142,6 +126,7 @@ kotlin { dependencies { api(project(":firebase-app")) implementation(project(":firebase-common")) + api(libs.kotlinx.datetime) } } @@ -153,7 +138,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-config-ktx") + api(libs.google.firebase.config.ktx) } } @@ -169,6 +154,12 @@ if (project.property("firebase-config.skipIosTests") == "true") { } } +if (project.property("firebase-config.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-config.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-config/documentation.md b/firebase-config/documentation.md new file mode 100644 index 000000000..363b391cd --- /dev/null +++ b/firebase-config/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-config +This module is a direct forward of the Firebase Remote Config library. It provides the main functionality, like remote feature flags. \ No newline at end of file diff --git a/firebase-config/package.json b/firebase-config/package.json index 78d6c9b43..69582d9fc 100644 --- a/firebase-config/package.json +++ b/firebase-config/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-config", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-config/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt b/firebase-config/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt index 492af89bc..c8b9c6e1f 100644 --- a/firebase-config/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt +++ b/firebase-config/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.remoteconfig import androidx.test.platform.app.InstrumentationRegistry diff --git a/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt b/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt index 2e43b9a10..bfb420515 100644 --- a/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt +++ b/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt @@ -1,4 +1,5 @@ @file:JvmName("android") + package dev.gitlive.firebase.remoteconfig import com.google.firebase.remoteconfig.FirebaseRemoteConfigClientException @@ -6,57 +7,65 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledExcept import com.google.firebase.remoteconfig.FirebaseRemoteConfigServerException import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android import kotlinx.coroutines.tasks.await +import kotlinx.datetime.Instant +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds import com.google.firebase.remoteconfig.FirebaseRemoteConfig as AndroidFirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo as AndroidFirebaseRemoteConfigInfo import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings as AndroidFirebaseRemoteConfigSettings -actual val Firebase.remoteConfig: FirebaseRemoteConfig +public val FirebaseRemoteConfig.android: AndroidFirebaseRemoteConfig get() = AndroidFirebaseRemoteConfig.getInstance() + +public actual val Firebase.remoteConfig: FirebaseRemoteConfig get() = FirebaseRemoteConfig(com.google.firebase.remoteconfig.FirebaseRemoteConfig.getInstance()) -actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig = +public actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig = FirebaseRemoteConfig(com.google.firebase.remoteconfig.FirebaseRemoteConfig.getInstance(app.android)) -actual class FirebaseRemoteConfig internal constructor(val android: AndroidFirebaseRemoteConfig) { - actual val all: Map +public actual class FirebaseRemoteConfig internal constructor(internal val android: AndroidFirebaseRemoteConfig) { + public actual val all: Map get() = android.all.mapValues { FirebaseRemoteConfigValue(it.value) } - actual val info: FirebaseRemoteConfigInfo + public actual val info: FirebaseRemoteConfigInfo get() = android.info.asCommon() - actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) { + public actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) { val settings = FirebaseRemoteConfigSettings().apply(init) val androidSettings = com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings.Builder() - .setMinimumFetchIntervalInSeconds(settings.minimumFetchIntervalInSeconds) - .setFetchTimeoutInSeconds(settings.fetchTimeoutInSeconds) + .setMinimumFetchIntervalInSeconds(settings.minimumFetchInterval.inWholeSeconds) + .setFetchTimeoutInSeconds(settings.fetchTimeout.inWholeSeconds) .build() android.setConfigSettingsAsync(androidSettings).await() } - actual suspend fun setDefaults(vararg defaults: Pair) { + public actual suspend fun setDefaults(vararg defaults: Pair) { android.setDefaultsAsync(defaults.toMap()).await() } - actual suspend fun fetch(minimumFetchIntervalInSeconds: Long?) { - minimumFetchIntervalInSeconds - ?.also { android.fetch(it).await() } + public actual suspend fun fetch(minimumFetchInterval: Duration?) { + minimumFetchInterval + ?.also { android.fetch(it.inWholeSeconds).await() } ?: run { android.fetch().await() } } - actual suspend fun activate(): Boolean = android.activate().await() - actual suspend fun ensureInitialized() = android.ensureInitialized().await().let { } - actual suspend fun fetchAndActivate(): Boolean = android.fetchAndActivate().await() - actual fun getKeysByPrefix(prefix: String): Set = android.getKeysByPrefix(prefix) - actual fun getValue(key: String) = FirebaseRemoteConfigValue(android.getValue(key)) - actual suspend fun reset() = android.reset().await().let { } - - private fun AndroidFirebaseRemoteConfigSettings.asCommon(): FirebaseRemoteConfigSettings { - return FirebaseRemoteConfigSettings( - fetchTimeoutInSeconds = fetchTimeoutInSeconds, - minimumFetchIntervalInSeconds = minimumFetchIntervalInSeconds, - ) + public actual suspend fun activate(): Boolean = android.activate().await() + public actual suspend fun ensureInitialized() { + android.ensureInitialized().await() + } + public actual suspend fun fetchAndActivate(): Boolean = android.fetchAndActivate().await() + public actual fun getKeysByPrefix(prefix: String): Set = android.getKeysByPrefix(prefix) + public actual fun getValue(key: String): FirebaseRemoteConfigValue = FirebaseRemoteConfigValue(android.getValue(key)) + public actual suspend fun reset() { + android.reset().await() } + private fun AndroidFirebaseRemoteConfigSettings.asCommon(): FirebaseRemoteConfigSettings = FirebaseRemoteConfigSettings( + fetchTimeout = fetchTimeoutInSeconds.seconds, + minimumFetchInterval = minimumFetchIntervalInSeconds.seconds, + ) + private fun AndroidFirebaseRemoteConfigInfo.asCommon(): FirebaseRemoteConfigInfo { val lastFetchStatus = when (lastFetchStatus) { AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS -> FetchStatus.Success @@ -68,13 +77,13 @@ actual class FirebaseRemoteConfig internal constructor(val android: AndroidFireb return FirebaseRemoteConfigInfo( configSettings = configSettings.asCommon(), - fetchTimeMillis = fetchTimeMillis, - lastFetchStatus = lastFetchStatus + fetchTime = Instant.fromEpochMilliseconds(fetchTimeMillis), + lastFetchStatus = lastFetchStatus, ) } } -actual typealias FirebaseRemoteConfigException = com.google.firebase.remoteconfig.FirebaseRemoteConfigException -actual typealias FirebaseRemoteConfigClientException = FirebaseRemoteConfigClientException -actual typealias FirebaseRemoteConfigFetchThrottledException = FirebaseRemoteConfigFetchThrottledException -actual typealias FirebaseRemoteConfigServerException = FirebaseRemoteConfigServerException +public actual typealias FirebaseRemoteConfigException = com.google.firebase.remoteconfig.FirebaseRemoteConfigException +public actual typealias FirebaseRemoteConfigClientException = FirebaseRemoteConfigClientException +public actual typealias FirebaseRemoteConfigFetchThrottledException = FirebaseRemoteConfigFetchThrottledException +public actual typealias FirebaseRemoteConfigServerException = FirebaseRemoteConfigServerException diff --git a/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt b/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt index 89c470f0a..b0262cef3 100644 --- a/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt +++ b/firebase-config/src/androidMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt @@ -3,15 +3,15 @@ package dev.gitlive.firebase.remoteconfig import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue as AndroidFirebaseRemoteConfigValue -actual class FirebaseRemoteConfigValue internal constructor( - private val android: AndroidFirebaseRemoteConfigValue +public actual class FirebaseRemoteConfigValue internal constructor( + private val android: AndroidFirebaseRemoteConfigValue, ) { - actual fun asBoolean(): Boolean = android.asBoolean() - actual fun asByteArray(): ByteArray = android.asByteArray() - actual fun asDouble(): Double = android.asDouble() - actual fun asLong(): Long = android.asLong() - actual fun asString(): String = android.asString() - actual fun getSource(): ValueSource = when (android.source) { + public actual fun asBoolean(): Boolean = android.asBoolean() + public actual fun asByteArray(): ByteArray = android.asByteArray() + public actual fun asDouble(): Double = android.asDouble() + public actual fun asLong(): Long = android.asLong() + public actual fun asString(): String = android.asString() + public actual fun getSource(): ValueSource = when (android.source) { FirebaseRemoteConfig.VALUE_SOURCE_STATIC -> ValueSource.Static FirebaseRemoteConfig.VALUE_SOURCE_DEFAULT -> ValueSource.Default FirebaseRemoteConfig.VALUE_SOURCE_REMOTE -> ValueSource.Remote diff --git a/firebase-config/src/androidUnitTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt b/firebase-config/src/androidUnitTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt index 19d69bba3..59f922920 100644 --- a/firebase-config/src/androidUnitTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt +++ b/firebase-config/src/androidUnitTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.remoteconfig import org.junit.Ignore diff --git a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt index 2207f6618..a91888092 100644 --- a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt +++ b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt @@ -3,30 +3,142 @@ package dev.gitlive.firebase.remoteconfig import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds -expect val Firebase.remoteConfig: FirebaseRemoteConfig +/** Returns the [FirebaseRemoteConfig] instance of the default [FirebaseApp]. */ +public expect val Firebase.remoteConfig: FirebaseRemoteConfig -expect fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig +/** Returns the [FirebaseRemoteConfig] instance of a given [FirebaseApp]. */ +public expect fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig -expect class FirebaseRemoteConfig { - val all: Map - val info: FirebaseRemoteConfigInfo +/** + * Entry point for the Firebase Remote Config API. + * + * Callers should first get the singleton object using [Firebase.remoteConfig], and then call + * operations on that singleton object. The singleton contains the complete set of Remote Config + * parameter values available to your app. The singleton also stores values fetched from the Remote + * Config server until they are made available for use with a call to [activate]. + */ +public expect class FirebaseRemoteConfig { + /** + * Returns a [Map] of Firebase Remote Config key value pairs. + * + * Evaluates the values of the parameters in the following order: + * + * - The activated value, if the last successful [activate] contained the key. + * - The default value, if the key was set with [setDefaults]. + */ + public val all: Map - suspend fun activate(): Boolean - suspend fun ensureInitialized() - suspend fun fetch(minimumFetchIntervalInSeconds: Long? = null) - suspend fun fetchAndActivate(): Boolean - fun getKeysByPrefix(prefix: String): Set - fun getValue(key: String): FirebaseRemoteConfigValue - suspend fun reset() - suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) - suspend fun setDefaults(vararg defaults: Pair) + /** + * Returns the state of this [FirebaseRemoteConfig] instance as a [FirebaseRemoteConfigInfo]. + */ + public val info: FirebaseRemoteConfigInfo + + /** + * Asynchronously activates the most recently fetched configs, so that the fetched key value pairs + * take effect. + * + * @return true result if the current call activated the fetched + * configs; if the fetched configs were already activated by a previous call, it instead + * returns a false result. + */ + public suspend fun activate(): Boolean + + /** + * Ensures the last activated config are available to the app. + */ + public suspend fun ensureInitialized() + + /** + * Starts fetching configs, adhering to the specified minimum fetch interval. + * + * The fetched configs only take effect after the next [activate] call. + * + * Depending on the time elapsed since the last fetch from the Firebase Remote Config backend, + * configs are either served from local storage, or fetched from the backend. + * + * Note: Also initializes the Firebase installations SDK that creates installation IDs to + * identify Firebase installations and periodically sends data to Firebase servers. Remote Config + * requires installation IDs for Fetch requests. To stop the periodic sync, call [FirebaseInstallations.delete]. Sending a Fetch request + * after deletion will create a new installation ID for this Firebase installation and resume the + * periodic sync. + * + * @param minimumFetchInterval If configs in the local storage were fetched more than + * this long ago (rounded down to seconds), configs are served from the backend instead of local storage. + */ + public suspend fun fetch(minimumFetchInterval: Duration? = null) + + /** + * Asynchronously fetches and then activates the fetched configs. + * + * If the time elapsed since the last fetch from the Firebase Remote Config backend is more + * than the default minimum fetch interval, configs are fetched from the backend. + * + * After the fetch is complete, the configs are activated so that the fetched key value pairs + * take effect. + * + * @return [Boolean] with a true result if the current call activated the fetched + * configs; if no configs were fetched from the backend and the local fetched configs have + * already been activated, returns a [Boolean] with a false result. + */ + public suspend fun fetchAndActivate(): Boolean + + /** + * Returns a [Set] of all Firebase Remote Config parameter keys with the given prefix. + * + * @param prefix The key prefix to look for. If the prefix is empty, all keys are returned. + * @return [Set] of Remote Config parameter keys that start with the specified prefix. + */ + public fun getKeysByPrefix(prefix: String): Set + + /** + * Returns the parameter value for the given key as a [FirebaseRemoteConfigValue]. + * + * Evaluates the value of the parameter in the following order: + * + * - The activated value, if the last successful [activate] contained the key. + * - The default value, if the key was set with [setDefaults]. + * - A [FirebaseRemoteConfigValue] that returns the static value for each type. + * + * @param key A Firebase Remote Config parameter key. + * @return [FirebaseRemoteConfigValue] representing the value of the Firebase Remote Config + * parameter with the given key. + */ + public fun getValue(key: String): FirebaseRemoteConfigValue + + /** + * Deletes all activated, fetched and defaults configs and resets all Firebase Remote Config + * settings. + */ + public suspend fun reset() + + /** + * Asynchronously changes the settings for this [FirebaseRemoteConfig] instance. + * + * @param init A builder to set the settings. + */ + public suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) + + /** + * Asynchronously sets default configs using the given [Map]. + * + * @param defaults [Map] of key value pairs representing Firebase Remote Config parameter + * keys and values. + */ + public suspend fun setDefaults(vararg defaults: Pair) +} + +@Deprecated("Replaced with Kotlin Duration", replaceWith = ReplaceWith("fetch(minimumFetchIntervalInSeconds.seconds)")) +public suspend fun FirebaseRemoteConfig.fetch(minimumFetchIntervalInSeconds: Long) { + fetch(minimumFetchIntervalInSeconds.seconds) } @Suppress("IMPLICIT_CAST_TO_ANY") -inline operator fun FirebaseRemoteConfig.get(key: String): T { +public inline operator fun FirebaseRemoteConfig.get(key: String): T { val configValue = getValue(key) - return when(T::class) { + return when (T::class) { Boolean::class -> configValue.asBoolean() Double::class -> configValue.asDouble() Long::class -> configValue.asLong() @@ -36,7 +148,22 @@ inline operator fun FirebaseRemoteConfig.get(key: String): T { } as T } -expect open class FirebaseRemoteConfigException : FirebaseException -expect class FirebaseRemoteConfigClientException : FirebaseRemoteConfigException -expect class FirebaseRemoteConfigFetchThrottledException : FirebaseRemoteConfigException -expect class FirebaseRemoteConfigServerException : FirebaseRemoteConfigException +/** + * Exception that gets thrown when an operation on Firebase Remote Config fails. + */ +public expect open class FirebaseRemoteConfigException : FirebaseException + +/** + * Exception that gets thrown when an operation on Firebase Remote Config fails. + */ +public expect class FirebaseRemoteConfigClientException : FirebaseRemoteConfigException + +/** + * Exception that gets thrown when an operation on Firebase Remote Config fails. + */ +public expect class FirebaseRemoteConfigFetchThrottledException : FirebaseRemoteConfigException + +/** + * Exception that gets thrown when an operation on Firebase Remote Config fails. + */ +public expect class FirebaseRemoteConfigServerException : FirebaseRemoteConfigException diff --git a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo.kt b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo.kt index 0bfc2d1d0..8198a3543 100644 --- a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo.kt +++ b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigInfo.kt @@ -1,9 +1,58 @@ package dev.gitlive.firebase.remoteconfig -data class FirebaseRemoteConfigInfo( +import kotlinx.datetime.Instant + +/** Wraps the current state of the [FirebaseRemoteConfig] singleton object. */ +public data class FirebaseRemoteConfigInfo( + /** + * Gets the current settings of the [FirebaseRemoteConfig] singleton object. + * + * @return A [FirebaseRemoteConfig] object indicating the current settings. + */ val configSettings: FirebaseRemoteConfigSettings, - val fetchTimeMillis: Long, + + /** + * Gets the [Instant] of the last successful fetch, regardless of + * whether the fetch was activated or not. + * + * @return `Instant.fromEpochMilliseconds(-1)` if no fetch attempt has been made yet. Otherwise, returns the timestamp of the last + * successful fetch operation. + */ + val fetchTime: Instant, + + /** + * Gets the status of the most recent fetch attempt. + * + * @return Will return one of [FetchStatus.Success], [FetchStatus.Failure], [FetchStatus.Throttled], or [FetchStatus.NoFetchYet] + */ val lastFetchStatus: FetchStatus, -) +) { + @Deprecated("Replaced with Kotlin Duration", replaceWith = ReplaceWith("fetchTime")) + val fetchTimeMillis: Long get() = fetchTime.toEpochMilliseconds() +} + +public enum class FetchStatus { + /** + * Indicates that the most recent fetch of parameter values from the Firebase Remote Config server + * was completed successfully. + */ + Success, + + /** + * Indicates that the most recent attempt to fetch parameter values from the Firebase Remote + * Config server has failed. + */ + Failure, + + /** + * Indicates that the most recent attempt to fetch parameter values from the Firebase Remote + * Config server was throttled. + */ + Throttled, -enum class FetchStatus { Success, Failure, Throttled, NoFetchYet } + /** + * Indicates that the FirebaseRemoteConfig singleton object has not yet attempted to fetch + * parameter values from the Firebase Remote Config server. + */ + NoFetchYet, +} diff --git a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings.kt b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings.kt index 8eabe5f9f..46a3cbf3a 100644 --- a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings.kt +++ b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigSettings.kt @@ -1,11 +1,40 @@ package dev.gitlive.firebase.remoteconfig -private const val CONNECTION_TIMEOUT_IN_SECONDS = 60L +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +private val CONNECTION_TIMEOUT = 1.minutes // https://firebase.google.com/docs/remote-config/get-started?hl=en&platform=android#throttling -private const val DEFAULT_FETCH_INTERVAL_IN_SECONDS = 12 * 3600L +private val DEFAULT_FETCH_INTERVAL = 12.hours + +/** Wraps the settings for [FirebaseRemoteConfig] operations. */ +public data class FirebaseRemoteConfigSettings( + /** + * Returns the fetch timeout in seconds. + * + * The timeout specifies how long the client should wait for a connection to the Firebase + * Remote Config server. + */ + var fetchTimeout: Duration = CONNECTION_TIMEOUT, + + /** Returns the minimum interval between successive fetches calls in seconds. */ + var minimumFetchInterval: Duration = DEFAULT_FETCH_INTERVAL, +) { + + @Deprecated("Replaced with Kotlin Duration", replaceWith = ReplaceWith("fetchTimeout")) + public var fetchTimeoutInSeconds: Long + get() = fetchTimeout.inWholeSeconds + set(value) { + fetchTimeout = value.seconds + } -data class FirebaseRemoteConfigSettings( - var fetchTimeoutInSeconds: Long = CONNECTION_TIMEOUT_IN_SECONDS, - var minimumFetchIntervalInSeconds: Long = DEFAULT_FETCH_INTERVAL_IN_SECONDS, -) + @Deprecated("Replaced with Kotlin Duration", replaceWith = ReplaceWith("minimumFetchInterval")) + public var minimumFetchIntervalInSeconds: Long + get() = minimumFetchInterval.inWholeSeconds + set(value) { + minimumFetchInterval = value.seconds + } +} diff --git a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt index 8cf474661..fb255f315 100644 --- a/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt +++ b/firebase-config/src/commonMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt @@ -1,12 +1,57 @@ package dev.gitlive.firebase.remoteconfig -expect class FirebaseRemoteConfigValue { - fun asBoolean(): Boolean - fun asByteArray(): ByteArray - fun asDouble(): Double - fun asLong(): Long - fun asString(): String - fun getSource(): ValueSource +/** Wrapper for a Remote Config parameter value, with methods to get it as different types. */ +public expect class FirebaseRemoteConfigValue { + /** + * Gets the value as a [Boolean]. + * + * @return [Boolean] representation of this parameter value. + */ + public fun asBoolean(): Boolean + + /** + * Gets the value as a [ByteArray]. + * + * @return [ByteArray] representation of this parameter value. + */ + public fun asByteArray(): ByteArray + + /** + * Gets the value as a [Double]. + * + * @return [Double] representation of this parameter value. + */ + public fun asDouble(): Double + + /** + * Gets the value as a [Long]. + * + * @return [Long] representation of this parameter value. + */ + public fun asLong(): Long + + /** + * Gets the value as a [String]. + * + * @return [String] representation of this parameter value. + */ + public fun asString(): String + + /** + * Indicates at which source this value came from. + * + * @return [ValueSource.Remote] if the value was retrieved from the server, [ValueSource.Default] if the value was set as a default, or [ValueSource.Stataic] if no value was found and a static default value was returned instead. + */ + public fun getSource(): ValueSource } -enum class ValueSource { Static, Default, Remote } +public enum class ValueSource { + /** Indicates that the value returned is the static default value. */ + Static, + + /** Indicates that the value returned was retrieved from the defaults set by the client. */ + Default, + + /** Indicates that the value returned was retrieved from the Firebase Remote Config server. */ + Remote, +} 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 426a7f23e..215ffab90 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 @@ -9,11 +9,14 @@ import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize import dev.gitlive.firebase.runTest +import kotlinx.datetime.Instant import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds expect val context: Any expect annotation class IgnoreForAndroidUnitTest() @@ -39,8 +42,8 @@ class FirebaseRemoteConfigTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) remoteConfig = Firebase.remoteConfig(app) @@ -90,9 +93,9 @@ class FirebaseRemoteConfigTest { "test_default_boolean", "test_default_double", "test_default_long", - "test_default_string" + "test_default_string", ), - keys + keys, ) } @@ -101,22 +104,22 @@ class FirebaseRemoteConfigTest { assertEquals( FirebaseRemoteConfigInfo( configSettings = FirebaseRemoteConfigSettings(), - fetchTimeMillis = -1, - lastFetchStatus = FetchStatus.NoFetchYet + fetchTime = Instant.fromEpochMilliseconds(-1), + lastFetchStatus = FetchStatus.NoFetchYet, ).toString(), - remoteConfig.info.toString() + remoteConfig.info.toString(), ) } @Test fun testSetConfigSettings() = runTest { remoteConfig.settings { - fetchTimeoutInSeconds = 42 - minimumFetchIntervalInSeconds = 42 + fetchTimeout = 42.seconds + minimumFetchInterval = 42.seconds } val info = remoteConfig.info - assertEquals(42, info.configSettings.fetchTimeoutInSeconds) - assertEquals(42, info.configSettings.minimumFetchIntervalInSeconds) + assertEquals(42.seconds, info.configSettings.fetchTimeout) + assertEquals(42.seconds, info.configSettings.minimumFetchInterval) } // Unfortunately Firebase Remote Config is not implemented by Firebase emulator so it may be @@ -126,7 +129,7 @@ class FirebaseRemoteConfigTest { @Ignore fun testFetch() = runTest { remoteConfig.settings { - minimumFetchIntervalInSeconds = 60 + minimumFetchInterval = 1.minutes } remoteConfig.fetch() @@ -141,7 +144,7 @@ class FirebaseRemoteConfigTest { @Ignore fun testFetchAndActivate() = runTest { remoteConfig.settings { - minimumFetchIntervalInSeconds = 60 + minimumFetchInterval = 1.minutes } remoteConfig.fetchAndActivate() diff --git a/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt b/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt index 47e4db1df..fcbf1a198 100644 --- a/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt +++ b/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfig.kt @@ -12,21 +12,27 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.app +import dev.gitlive.firebase.ios import kotlinx.coroutines.CompletableDeferred +import kotlinx.datetime.Instant +import kotlinx.datetime.toKotlinInstant import platform.Foundation.NSError -import platform.Foundation.timeIntervalSince1970 +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit -actual val Firebase.remoteConfig: FirebaseRemoteConfig +public val FirebaseRemoteConfig.ios: FIRRemoteConfig get() = FIRRemoteConfig.remoteConfig() + +public actual val Firebase.remoteConfig: FirebaseRemoteConfig get() = FirebaseRemoteConfig(FIRRemoteConfig.remoteConfig()) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig = FirebaseRemoteConfig( - FIRRemoteConfig.remoteConfigWithApp(Firebase.app.ios as objcnames.classes.FIRApp) +public actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig = FirebaseRemoteConfig( + FIRRemoteConfig.remoteConfigWithApp(Firebase.app.ios as objcnames.classes.FIRApp), ) -actual class FirebaseRemoteConfig internal constructor(val ios: FIRRemoteConfig) { +public actual class FirebaseRemoteConfig internal constructor(internal val ios: FIRRemoteConfig) { @Suppress("UNCHECKED_CAST") - actual val all: Map + public actual val all: Map get() { return listOf( FIRRemoteConfigSource.FIRRemoteConfigSourceStatic, @@ -38,118 +44,111 @@ actual class FirebaseRemoteConfig internal constructor(val ios: FIRRemoteConfig) }.flatten().toMap() } - actual val info: FirebaseRemoteConfigInfo + public actual val info: FirebaseRemoteConfigInfo get() { return FirebaseRemoteConfigInfo( configSettings = ios.configSettings.asCommon(), - fetchTimeMillis = ios.lastFetchTime - ?.timeIntervalSince1970 - ?.let { it.toLong() * 1000 } - ?.takeIf { it > 0 } - ?: -1L, - lastFetchStatus = ios.lastFetchStatus.asCommon() + fetchTime = ios.lastFetchTime?.toKotlinInstant() + ?.takeIf { it.toEpochMilliseconds() > 0 } + ?: Instant.fromEpochMilliseconds(-1), + lastFetchStatus = ios.lastFetchStatus.asCommon(), ) } - actual suspend fun activate(): Boolean = ios.awaitResult { activateWithCompletion(it) } + public actual suspend fun activate(): Boolean = ios.awaitResult { activateWithCompletion(it) } - actual suspend fun ensureInitialized() = + public actual suspend fun ensureInitialized(): Unit = ios.await { ensureInitializedWithCompletionHandler(it) } - actual suspend fun fetch(minimumFetchIntervalInSeconds: Long?) { - if (minimumFetchIntervalInSeconds != null) { + public actual suspend fun fetch(minimumFetchInterval: Duration?) { + if (minimumFetchInterval != null) { ios.awaitResult { - fetchWithExpirationDuration(minimumFetchIntervalInSeconds.toDouble(), it) + fetchWithExpirationDuration(minimumFetchInterval.toDouble(DurationUnit.SECONDS), it) } } else { ios.awaitResult { fetchWithCompletionHandler(it) } } } - actual suspend fun fetchAndActivate(): Boolean { + public actual suspend fun fetchAndActivate(): Boolean { val status: FIRRemoteConfigFetchAndActivateStatus = ios.awaitResult { fetchAndActivateWithCompletionHandler(it) } return status == FIRRemoteConfigFetchAndActivateStatus.FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote } - actual fun getKeysByPrefix(prefix: String): Set = + public actual fun getKeysByPrefix(prefix: String): Set = all.keys.filter { it.startsWith(prefix) }.toSet() - actual fun getValue(key: String): FirebaseRemoteConfigValue = + public actual fun getValue(key: String): FirebaseRemoteConfigValue = FirebaseRemoteConfigValue(ios.configValueForKey(key)) - actual suspend fun reset() { + public actual suspend fun reset() { // not implemented for iOS target } - actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) { + public actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) { val settings = FirebaseRemoteConfigSettings().apply(init) val iosSettings = FIRRemoteConfigSettings().apply { - minimumFetchInterval = settings.minimumFetchIntervalInSeconds.toDouble() - fetchTimeout = settings.fetchTimeoutInSeconds.toDouble() + minimumFetchInterval = settings.minimumFetchInterval.toDouble(DurationUnit.SECONDS) + fetchTimeout = settings.fetchTimeout.toDouble(DurationUnit.SECONDS) } ios.setConfigSettings(iosSettings) } - actual suspend fun setDefaults(vararg defaults: Pair) { + public actual suspend fun setDefaults(vararg defaults: Pair) { ios.setDefaults(defaults.toMap()) } - private fun FIRRemoteConfigSettings.asCommon(): FirebaseRemoteConfigSettings { - return FirebaseRemoteConfigSettings( - fetchTimeoutInSeconds = fetchTimeout.toLong(), - minimumFetchIntervalInSeconds = minimumFetchInterval.toLong(), - ) - } - - private fun FIRRemoteConfigFetchStatus.asCommon(): FetchStatus { - return when (this) { - FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusSuccess -> FetchStatus.Success - FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusNoFetchYet -> FetchStatus.NoFetchYet - FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusFailure -> FetchStatus.Failure - FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusThrottled -> FetchStatus.Throttled - else -> FetchStatus.Failure - } + private fun FIRRemoteConfigSettings.asCommon(): FirebaseRemoteConfigSettings = FirebaseRemoteConfigSettings( + fetchTimeout = fetchTimeout.seconds, + minimumFetchInterval = minimumFetchInterval.seconds, + ) + + private fun FIRRemoteConfigFetchStatus.asCommon(): FetchStatus = when (this) { + FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusSuccess -> FetchStatus.Success + FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusNoFetchYet -> FetchStatus.NoFetchYet + FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusFailure -> FetchStatus.Failure + FIRRemoteConfigFetchStatus.FIRRemoteConfigFetchStatusThrottled -> FetchStatus.Throttled + else -> FetchStatus.Failure } } -private suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { +private suspend inline fun T.awaitResult( + function: T.(callback: (R?, NSError?) -> Unit) -> Unit, +): R { val job = CompletableDeferred() - val callback = { result: R?, error: NSError? -> - if(error == null) { + function { result: R?, error: NSError? -> + if (error == null) { job.complete(result) } else { job.completeExceptionally(error.toException()) } } - function(callback) return job.await() as R } private suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { val job = CompletableDeferred() - val callback = { error: NSError? -> - if(error == null) { + function { error: NSError? -> + if (error == null) { job.complete(Unit) } else { job.completeExceptionally(error.toException()) } } - function(callback) job.await() } - private fun NSError.toException() = when (domain) { FIRRemoteConfigErrorDomain -> { when (code) { FIRRemoteConfigErrorThrottled -> FirebaseRemoteConfigFetchThrottledException( - localizedDescription + localizedDescription, ) FIRRemoteConfigErrorInternalError -> FirebaseRemoteConfigServerException( - localizedDescription + localizedDescription, ) else -> FirebaseRemoteConfigClientException(localizedDescription) @@ -159,14 +158,10 @@ private fun NSError.toException() = when (domain) { else -> FirebaseException(localizedDescription) } +public actual open class FirebaseRemoteConfigException(message: String) : FirebaseException(message) -actual open class FirebaseRemoteConfigException(message: String) : FirebaseException(message) - -actual class FirebaseRemoteConfigClientException(message: String) : - FirebaseRemoteConfigException(message) +public actual class FirebaseRemoteConfigClientException(message: String) : FirebaseRemoteConfigException(message) -actual class FirebaseRemoteConfigFetchThrottledException(message: String) : - FirebaseRemoteConfigException(message) +public actual class FirebaseRemoteConfigFetchThrottledException(message: String) : FirebaseRemoteConfigException(message) -actual class FirebaseRemoteConfigServerException(message: String) : - FirebaseRemoteConfigException(message) +public actual class FirebaseRemoteConfigServerException(message: String) : FirebaseRemoteConfigException(message) diff --git a/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt b/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt index 1bf3e1f17..3d723d271 100644 --- a/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt +++ b/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/FirebaseRemoteConfigValue.kt @@ -3,15 +3,15 @@ package dev.gitlive.firebase.remoteconfig import cocoapods.FirebaseRemoteConfig.FIRRemoteConfigSource import cocoapods.FirebaseRemoteConfig.FIRRemoteConfigValue -actual class FirebaseRemoteConfigValue internal constructor(private val ios: FIRRemoteConfigValue) { +public actual class FirebaseRemoteConfigValue internal constructor(private val ios: FIRRemoteConfigValue) { @ExperimentalUnsignedTypes - actual fun asByteArray(): ByteArray = ios.dataValue.toByteArray() + public actual fun asByteArray(): ByteArray = ios.dataValue.toByteArray() - actual fun asBoolean(): Boolean = ios.boolValue - actual fun asDouble(): Double = ios.numberValue.doubleValue - actual fun asLong(): Long = ios.numberValue.longValue - actual fun asString(): String = ios.stringValue ?: "" - actual fun getSource(): ValueSource = when (ios.source) { + public actual fun asBoolean(): Boolean = ios.boolValue + public actual fun asDouble(): Double = ios.numberValue.doubleValue + public actual fun asLong(): Long = ios.numberValue.longValue + public actual fun asString(): String = ios.stringValue ?: "" + public actual fun getSource(): ValueSource = when (ios.source) { FIRRemoteConfigSource.FIRRemoteConfigSourceStatic -> ValueSource.Static FIRRemoteConfigSource.FIRRemoteConfigSourceDefault -> ValueSource.Default FIRRemoteConfigSource.FIRRemoteConfigSourceRemote -> ValueSource.Remote diff --git a/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/NSDataExtension.kt b/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/NSDataExtension.kt index 3ea2cf992..f39b5acea 100644 --- a/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/NSDataExtension.kt +++ b/firebase-config/src/iosMain/kotlin/dev/gitlive/firebase/remoteconfig/NSDataExtension.kt @@ -6,8 +6,6 @@ import platform.Foundation.NSData import platform.posix.memcpy @ExperimentalUnsignedTypes -fun NSData.toByteArray(): ByteArray { - return ByteArray(length.toInt()).apply { - usePinned { memcpy(it.addressOf(0), bytes, length) } - } +public fun NSData.toByteArray(): ByteArray = ByteArray(length.toInt()).apply { + usePinned { memcpy(it.addressOf(0), bytes, length) } } 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 bba268c12..01fb0b75a 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,97 +3,92 @@ package dev.gitlive.firebase.remoteconfig import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.js import dev.gitlive.firebase.remoteconfig.externals.* import kotlinx.coroutines.await +import kotlinx.datetime.Instant import kotlin.js.json +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds -actual val Firebase.remoteConfig: FirebaseRemoteConfig +public actual val Firebase.remoteConfig: FirebaseRemoteConfig get() = rethrow { FirebaseRemoteConfig(getRemoteConfig()) } -actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig = rethrow { +public actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig = rethrow { FirebaseRemoteConfig(getRemoteConfig(app.js)) } -actual class FirebaseRemoteConfig internal constructor(val js: RemoteConfig) { - actual val all: Map - get() = rethrow { getAllKeys().map { Pair(it, getValue(it)) }.toMap() } +public val FirebaseRemoteConfig.js get() = js - actual val info: FirebaseRemoteConfigInfo +public actual class FirebaseRemoteConfig internal constructor(internal val js: RemoteConfig) { + public actual val all: Map + get() = rethrow { getAllKeys().associateWith { getValue(it) } } + + public actual val info: FirebaseRemoteConfigInfo get() = rethrow { FirebaseRemoteConfigInfo( configSettings = js.settings.toFirebaseRemoteConfigSettings(), - fetchTimeMillis = js.fetchTimeMillis, - lastFetchStatus = js.lastFetchStatus.toFetchStatus() + fetchTime = Instant.fromEpochMilliseconds(js.fetchTimeMillis.toLong()), + lastFetchStatus = js.lastFetchStatus.toFetchStatus(), ) } - actual suspend fun activate(): Boolean = rethrow { activate(js).await() } - actual suspend fun ensureInitialized(): Unit = rethrow { ensureInitialized(js).await() } + public actual suspend fun activate(): Boolean = rethrow { activate(js).await() } + public actual suspend fun ensureInitialized(): Unit = rethrow { ensureInitialized(js).await() } - actual suspend fun fetch(minimumFetchIntervalInSeconds: Long?): Unit = + public actual suspend fun fetch(minimumFetchInterval: Duration?): Unit = rethrow { fetchConfig(js).await() } - actual suspend fun fetchAndActivate(): Boolean = rethrow { fetchAndActivate(js).await() } + public actual suspend fun fetchAndActivate(): Boolean = rethrow { fetchAndActivate(js).await() } - actual fun getValue(key: String): FirebaseRemoteConfigValue = rethrow { + public actual fun getValue(key: String): FirebaseRemoteConfigValue = rethrow { FirebaseRemoteConfigValue(getValue(js, key)) } - actual fun getKeysByPrefix(prefix: String): Set { - return getAllKeys().filter { it.startsWith(prefix) }.toSet() - } + public actual fun getKeysByPrefix(prefix: String): Set = getAllKeys().filter { it.startsWith(prefix) }.toSet() private fun getAllKeys(): Set { val objectKeys = js("Object.keys") return objectKeys(getAll(js)).unsafeCast>().toSet() } - actual suspend fun reset() { + public actual suspend fun reset() { // not implemented for JS target } - actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) { + public actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) { val settings = FirebaseRemoteConfigSettings().apply(init) js.settings.apply { - fetchTimeoutMillis = settings.fetchTimeoutInSeconds * 1000 - minimumFetchIntervalMillis = settings.minimumFetchIntervalInSeconds * 1000 + fetchTimeoutMillis = settings.fetchTimeout.inWholeMilliseconds + minimumFetchIntervalMillis = settings.minimumFetchInterval.inWholeMilliseconds } } - actual suspend fun setDefaults(vararg defaults: Pair) = rethrow { + public actual suspend fun setDefaults(vararg defaults: Pair): Unit = rethrow { js.defaultConfig = json(*defaults) } - private fun Settings.toFirebaseRemoteConfigSettings(): FirebaseRemoteConfigSettings { - return FirebaseRemoteConfigSettings( - fetchTimeoutInSeconds = fetchTimeoutMillis.toLong() / 1000, - minimumFetchIntervalInSeconds = minimumFetchIntervalMillis.toLong() / 1000 - ) - } - - private fun String.toFetchStatus(): FetchStatus { - return when (this) { - "no-fetch-yet" -> FetchStatus.NoFetchYet - "success" -> FetchStatus.Success - "failure" -> FetchStatus.Failure - "throttle" -> FetchStatus.Throttled - else -> error("Unknown FetchStatus: $this") - } + private fun Settings.toFirebaseRemoteConfigSettings(): FirebaseRemoteConfigSettings = FirebaseRemoteConfigSettings( + fetchTimeout = fetchTimeoutMillis.toLong().milliseconds, + minimumFetchInterval = minimumFetchIntervalMillis.toLong().milliseconds, + ) + + private fun String.toFetchStatus(): FetchStatus = when (this) { + "no-fetch-yet" -> FetchStatus.NoFetchYet + "success" -> FetchStatus.Success + "failure" -> FetchStatus.Failure + "throttle" -> FetchStatus.Throttled + else -> error("Unknown FetchStatus: $this") } } -actual open class FirebaseRemoteConfigException(code: String, cause: Throwable) : - FirebaseException(code, cause) - -actual class FirebaseRemoteConfigClientException(code: String, cause: Throwable) : - FirebaseRemoteConfigException(code, cause) +public actual open class FirebaseRemoteConfigException(code: String, cause: Throwable) : FirebaseException(code, cause) -actual class FirebaseRemoteConfigFetchThrottledException(code: String, cause: Throwable) : - FirebaseRemoteConfigException(code, cause) +public actual class FirebaseRemoteConfigClientException(code: String, cause: Throwable) : FirebaseRemoteConfigException(code, cause) -actual class FirebaseRemoteConfigServerException(code: String, cause: Throwable) : - FirebaseRemoteConfigException(code, cause) +public actual class FirebaseRemoteConfigFetchThrottledException(code: String, cause: Throwable) : FirebaseRemoteConfigException(code, cause) +public actual class FirebaseRemoteConfigServerException(code: String, cause: Throwable) : FirebaseRemoteConfigException(code, cause) internal inline fun rethrow(function: () -> R): R { try { @@ -112,7 +107,7 @@ internal fun errorToException(error: dynamic) = (error?.code ?: error?.message ? when { else -> { println("Unknown error code in ${JSON.stringify(error)}") - FirebaseRemoteConfigException(code, error) + FirebaseRemoteConfigException(code, error as Throwable) } } } 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 2891f926a..9cea39324 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 @@ -2,13 +2,15 @@ package dev.gitlive.firebase.remoteconfig import dev.gitlive.firebase.remoteconfig.externals.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() } - actual fun asLong(): Long = rethrow { js.asNumber().toLong() } - actual fun asString(): String = rethrow { js.asString() ?: "" } - actual fun getSource(): ValueSource = rethrow { js.getSource().toSource() } +public val FirebaseRemoteConfigValue.js get() = js + +public actual class FirebaseRemoteConfigValue(internal val js: Value) { + public actual fun asBoolean(): Boolean = rethrow { js.asBoolean() } + public actual fun asByteArray(): ByteArray = rethrow { js.asString()?.encodeToByteArray() ?: byteArrayOf() } + public actual fun asDouble(): Double = rethrow { js.asNumber().toDouble() } + public actual fun asLong(): Long = rethrow { js.asNumber().toLong() } + public actual fun asString(): String = rethrow { js.asString() ?: "" } + public actual fun getSource(): ValueSource = rethrow { js.getSource().toSource() } private fun String.toSource() = when (this) { "default" -> ValueSource.Default 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 index b2a42dc84..445c4d9c8 100644 --- 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 @@ -7,41 +7,41 @@ import dev.gitlive.firebase.externals.FirebaseApp import kotlin.js.Json import kotlin.js.Promise -external fun activate(remoteConfig: RemoteConfig): Promise +public external fun activate(remoteConfig: RemoteConfig): Promise -external fun ensureInitialized(remoteConfig: RemoteConfig): Promise +public external fun ensureInitialized(remoteConfig: RemoteConfig): Promise -external fun fetchAndActivate(remoteConfig: RemoteConfig): Promise +public external fun fetchAndActivate(remoteConfig: RemoteConfig): Promise -external fun fetchConfig(remoteConfig: RemoteConfig): Promise +public external fun fetchConfig(remoteConfig: RemoteConfig): Promise -external fun getAll(remoteConfig: RemoteConfig): Json +public external fun getAll(remoteConfig: RemoteConfig): Json -external fun getBoolean(remoteConfig: RemoteConfig, key: String): Boolean +public external fun getBoolean(remoteConfig: RemoteConfig, key: String): Boolean -external fun getNumber(remoteConfig: RemoteConfig, key: String): Number +public external fun getNumber(remoteConfig: RemoteConfig, key: String): Number -external fun getRemoteConfig(app: FirebaseApp? = definedExternally): RemoteConfig +public external fun getRemoteConfig(app: FirebaseApp? = definedExternally): RemoteConfig -external fun getString(remoteConfig: RemoteConfig, key: String): String? +public external fun getString(remoteConfig: RemoteConfig, key: String): String? -external fun getValue(remoteConfig: RemoteConfig, key: String): Value +public external fun getValue(remoteConfig: RemoteConfig, key: String): Value -external interface RemoteConfig { - var defaultConfig: Any - var fetchTimeMillis: Long - var lastFetchStatus: String - val settings: Settings +public external interface RemoteConfig { + public var defaultConfig: Any + public var fetchTimeMillis: Double + public var lastFetchStatus: String + public val settings: Settings } -external interface Settings { - var fetchTimeoutMillis: Number - var minimumFetchIntervalMillis: Number +public external interface Settings { + public var fetchTimeoutMillis: Number + public var minimumFetchIntervalMillis: Number } -external interface Value { - fun asBoolean(): Boolean - fun asNumber(): Number - fun asString(): String? - fun getSource(): String +public external interface Value { + public fun asBoolean(): Boolean + public fun asNumber(): Number + public fun asString(): String? + public fun getSource(): String } diff --git a/firebase-config/src/jvmTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt b/firebase-config/src/jvmTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt index 9c41518a7..172082cc1 100644 --- a/firebase-config/src/jvmTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt +++ b/firebase-config/src/jvmTest/kotlin/dev/gitlive/firebase/remoteconfig/RemoteConfig.kt @@ -3,13 +3,12 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.remoteconfig -import android.content.Context -import com.google.firebase.FirebasePlatform -import dev.gitlive.firebase.MockFirebasePlatform +import dev.gitlive.firebase.testContext -actual val context: Any get() = Context().also { FirebasePlatform.initializeFirebasePlatform(MockFirebasePlatform()) } +actual val context: Any = testContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-crashlytics/api/firebase-crashlytics.api b/firebase-crashlytics/api/firebase-crashlytics.api new file mode 100644 index 000000000..5246591bc --- /dev/null +++ b/firebase-crashlytics/api/firebase-crashlytics.api @@ -0,0 +1,27 @@ +public final class dev/gitlive/firebase/crashlytics/CrashlyticsKt { + public static final fun crashlytics (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/crashlytics/FirebaseCrashlytics; + public static final fun getAndroid (Ldev/gitlive/firebase/crashlytics/FirebaseCrashlytics;)Lcom/google/firebase/crashlytics/FirebaseCrashlytics; + public static final fun getCrashlytics (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/crashlytics/FirebaseCrashlytics; +} + +public final class dev/gitlive/firebase/crashlytics/FirebaseCrashlytics { + public final fun deleteUnsentReports ()V + public final fun didCrashOnPreviousExecution ()Z + public final fun log (Ljava/lang/String;)V + public final fun recordException (Ljava/lang/Throwable;)V + public final fun sendUnsentReports ()V + public final fun setCrashlyticsCollectionEnabled (Z)V + public final fun setCustomKey (Ljava/lang/String;D)V + public final fun setCustomKey (Ljava/lang/String;F)V + public final fun setCustomKey (Ljava/lang/String;I)V + public final fun setCustomKey (Ljava/lang/String;J)V + public final fun setCustomKey (Ljava/lang/String;Ljava/lang/String;)V + public final fun setCustomKey (Ljava/lang/String;Z)V + public final fun setCustomKeys (Ljava/util/Map;)V + public final fun setUserId (Ljava/lang/String;)V +} + +public class dev/gitlive/firebase/crashlytics/FirebaseCrashlyticsException : com/google/firebase/FirebaseException { + public fun (Ljava/lang/String;)V +} + diff --git a/firebase-crashlytics/build.gradle.kts b/firebase-crashlytics/build.gradle.kts index 76e1089f6..f6741a6b7 100644 --- a/firebase-crashlytics/build.gradle.kts +++ b/firebase-crashlytics/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -10,6 +13,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") + id("testOptionsConvention") } android { @@ -26,15 +30,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -48,10 +48,22 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -60,38 +72,22 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } -// jvm { -// compilations.getByName("main") { -// kotlinOptions { -// jvmTarget = "17" -// } -// } -// compilations.getByName("test") { -// kotlinOptions { -// jvmTarget = "17" -// } -// } -// } + // jvm() if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseCrashlytics" } noPodspec() pod("FirebaseCrashlytics") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() extraOpts += listOf("-compiler-option", "-fmodules") } } @@ -100,10 +96,8 @@ kotlin { sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") if (name.lowercase().contains("ios")) { @@ -127,7 +121,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-crashlytics-ktx") + api(libs.google.firebase.crashlytics.ktx) } } @@ -142,6 +136,18 @@ if (project.property("firebase-crashlytics.skipIosTests") == "true") { } } +if (project.property("firebase-crashlytics.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-crashlytics.skipJsTests") == "true") { + tasks.forEach { + if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + signing { val signingKey: String? by project val signingPassword: String? by project diff --git a/firebase-crashlytics/documentation.md b/firebase-crashlytics/documentation.md new file mode 100644 index 000000000..5139441b4 --- /dev/null +++ b/firebase-crashlytics/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-crashlytics +This module is a direct forward of the Firebase Crashlytics library. It provides the main functionality, like logging crashes to crashlytics. \ No newline at end of file diff --git a/firebase-crashlytics/package.json b/firebase-crashlytics/package.json index 845bef8a8..989eda9b0 100644 --- a/firebase-crashlytics/package.json +++ b/firebase-crashlytics/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-crashlytics", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-crashlytics/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index aa0ff306f..8f3160c49 100644 --- a/firebase-crashlytics/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -3,12 +3,11 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.crashlytics import androidx.test.platform.app.InstrumentationRegistry -actual val emulatorHost: String = "10.0.2.2" - actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) diff --git a/firebase-crashlytics/src/androidMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/androidMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index 680ebb8e0..0d56d2e1d 100644 --- a/firebase-crashlytics/src/androidMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/androidMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -4,29 +4,56 @@ import com.google.firebase.FirebaseException import com.google.firebase.crashlytics.CustomKeysAndValues.Builder import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android -actual val Firebase.crashlytics get() = +public val FirebaseCrashlytics.android: com.google.firebase.crashlytics.FirebaseCrashlytics get() = com.google.firebase.crashlytics.FirebaseCrashlytics.getInstance() + +public actual val Firebase.crashlytics: FirebaseCrashlytics get() = FirebaseCrashlytics(com.google.firebase.crashlytics.FirebaseCrashlytics.getInstance()) -actual fun Firebase.crashlytics(app: FirebaseApp) = +public actual fun Firebase.crashlytics(app: FirebaseApp): FirebaseCrashlytics = FirebaseCrashlytics(app.android.get(com.google.firebase.crashlytics.FirebaseCrashlytics::class.java)) -actual class FirebaseCrashlytics internal constructor(val android: com.google.firebase.crashlytics.FirebaseCrashlytics){ +public actual class FirebaseCrashlytics internal constructor(internal val android: com.google.firebase.crashlytics.FirebaseCrashlytics) { - actual fun recordException(exception: Throwable) = android.recordException(exception) - actual fun log(message: String) = android.log(message) - actual fun setUserId(userId: String) = android.setUserId(userId) - actual fun setCrashlyticsCollectionEnabled(enabled: Boolean) = android.setCrashlyticsCollectionEnabled(enabled) - actual fun sendUnsentReports() = android.sendUnsentReports() - actual fun deleteUnsentReports() = android.deleteUnsentReports() - actual fun didCrashOnPreviousExecution(): Boolean = android.didCrashOnPreviousExecution() - actual fun setCustomKey(key: String, value: String) = android.setCustomKey(key, value) - actual fun setCustomKey(key: String, value: Boolean) = android.setCustomKey(key, value) - actual fun setCustomKey(key: String, value: Double) = android.setCustomKey(key, value) - actual fun setCustomKey(key: String, value: Float) = android.setCustomKey(key, value) - actual fun setCustomKey(key: String, value: Int) = android.setCustomKey(key, value) - actual fun setCustomKey(key: String, value: Long) = android.setCustomKey(key, value) - actual fun setCustomKeys(customKeys: Map) = + public actual fun recordException(exception: Throwable) { + android.recordException(exception) + } + public actual fun log(message: String) { + android.log(message) + } + public actual fun setUserId(userId: String) { + android.setUserId(userId) + } + public actual fun setCrashlyticsCollectionEnabled(enabled: Boolean) { + android.setCrashlyticsCollectionEnabled(enabled) + } + public actual fun sendUnsentReports() { + android.sendUnsentReports() + } + public actual fun deleteUnsentReports() { + android.deleteUnsentReports() + } + public actual fun didCrashOnPreviousExecution(): Boolean = android.didCrashOnPreviousExecution() + public actual fun setCustomKey(key: String, value: String) { + android.setCustomKey(key, value) + } + public actual fun setCustomKey(key: String, value: Boolean) { + android.setCustomKey(key, value) + } + public actual fun setCustomKey(key: String, value: Double) { + android.setCustomKey(key, value) + } + public actual fun setCustomKey(key: String, value: Float) { + android.setCustomKey(key, value) + } + public actual fun setCustomKey(key: String, value: Int) { + android.setCustomKey(key, value) + } + public actual fun setCustomKey(key: String, value: Long) { + android.setCustomKey(key, value) + } + public actual fun setCustomKeys(customKeys: Map) { android.setCustomKeys( Builder().apply { customKeys.forEach { (key, value) -> @@ -39,8 +66,9 @@ actual class FirebaseCrashlytics internal constructor(val android: com.google.fi is Long -> putLong(key, value) } } - }.build() + }.build(), ) + } } -actual open class FirebaseCrashlyticsException(message: String) : FirebaseException(message) \ No newline at end of file +public actual open class FirebaseCrashlyticsException(message: String) : FirebaseException(message) diff --git a/firebase-crashlytics/src/androidUnitTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/androidUnitTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index 413cecca5..103d3d205 100644 --- a/firebase-crashlytics/src/androidUnitTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/androidUnitTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -3,12 +3,11 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.crashlytics import org.junit.Ignore -actual val emulatorHost: String = "10.0.2.2" - actual val context: Any = "" actual typealias IgnoreForAndroidUnitTest = Ignore diff --git a/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index 954991df7..32d99aa6c 100644 --- a/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/commonMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -5,26 +5,234 @@ import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException /** Returns the [FirebaseCrashlytics] instance of the default [FirebaseApp]. */ -expect val Firebase.crashlytics: FirebaseCrashlytics +public expect val Firebase.crashlytics: FirebaseCrashlytics /** Returns the [FirebaseCrashlytics] instance of a given [FirebaseApp]. */ -expect fun Firebase.crashlytics(app: FirebaseApp): FirebaseCrashlytics - -expect class FirebaseCrashlytics { - fun recordException(exception: Throwable) - fun log(message: String) - fun setUserId(userId: String) - fun setCustomKey(key: String, value: String) - fun setCustomKey(key: String, value: Boolean) - fun setCustomKey(key: String, value: Double) - fun setCustomKey(key: String, value: Float) - fun setCustomKey(key: String, value: Int) - fun setCustomKey(key: String, value: Long) - fun setCustomKeys(customKeys: Map) - fun setCrashlyticsCollectionEnabled(enabled: Boolean) - fun didCrashOnPreviousExecution(): Boolean - fun sendUnsentReports() - fun deleteUnsentReports() +public expect fun Firebase.crashlytics(app: FirebaseApp): FirebaseCrashlytics + +/** + * The Firebase Crashlytics API provides methods to annotate and manage fatal crashes, non-fatal + * errors, and ANRs captured and reported to Firebase Crashlytics. + * + * By default, Firebase Crashlytics is automatically initialized. + * + * Call [Firebase.crashlytics] to get the singleton instance of + * [FirebaseCrashlytics]. + */ +public expect class FirebaseCrashlytics { + /** + * Records a non-fatal report to send to Crashlytics. + * + * @param exception a [Throwable] to be recorded as a non-fatal event. + */ + public fun recordException(exception: Throwable) + + /** + * Logs a message that's included in the next fatal, non-fatal, or ANR report. + * + * Logs are visible in the session view on the Firebase Crashlytics console. + * + * Newline characters are stripped and extremely long messages are truncated. The maximum log + * size is 64k. If exceeded, the log rolls such that messages are removed, starting from the + * oldest. + * + * @param message the message to be logged + */ + public fun log(message: String) + + /** + * Records a user ID (identifier) that's associated with subsequent fatal, non-fatal, and ANR + * reports. + * + * The user ID is visible in the session view on the Firebase Crashlytics console. + * + * Identifiers longer than 1024 characters will be truncated. + * + * @param userId a unique identifier for the current user + */ + public fun setUserId(userId: String) + + /** + * Sets a custom key and value that are associated with subsequent fatal, non-fatal, and ANR + * reports. + * + * Multiple calls to this method with the same key update the value for that key. + * + * The value of any key at the time of a fatal, non-fatal, or ANR event is associated with that + * event. + * + * Keys and associated values are visible in the session view on the Firebase Crashlytics + * console. + * + * Accepts a maximum of 64 key/value pairs. New keys beyond that limit are ignored. Keys or + * values that exceed 1024 characters are truncated. + * + * @param key A unique key + * @param value A value to be associated with the given key + */ + public fun setCustomKey(key: String, value: String) + + /** + * Sets a custom key and value that are associated with subsequent fatal, non-fatal, and ANR + * reports. + * + * Multiple calls to this method with the same key update the value for that key. + * + * The value of any key at the time of a fatal, non-fatal, or ANR event is associated with that + * event. + * + * Keys and associated values are visible in the session view on the Firebase Crashlytics + * console. + * + * Accepts a maximum of 64 key/value pairs. New keys beyond that limit are ignored. Keys or + * values that exceed 1024 characters are truncated. + * + * @param key A unique key + * @param value A value to be associated with the given key + */ + public fun setCustomKey(key: String, value: Boolean) + + /** + * Sets a custom key and value that are associated with subsequent fatal, non-fatal, and ANR + * reports. + * + * Multiple calls to this method with the same key update the value for that key. + * + * The value of any key at the time of a fatal, non-fatal, or ANR event is associated with that + * event. + * + * Keys and associated values are visible in the session view on the Firebase Crashlytics + * console. + * + * Accepts a maximum of 64 key/value pairs. New keys beyond that limit are ignored. Keys or + * values that exceed 1024 characters are truncated. + * + * @param key A unique key + * @param value A value to be associated with the given key + */ + public fun setCustomKey(key: String, value: Double) + + /** + * Sets a custom key and value that are associated with subsequent fatal, non-fatal, and ANR + * reports. + * + * Multiple calls to this method with the same key update the value for that key. + * + * The value of any key at the time of a fatal, non-fatal, or ANR event is associated with that + * event. + * + * Keys and associated values are visible in the session view on the Firebase Crashlytics + * console. + * + * Accepts a maximum of 64 key/value pairs. New keys beyond that limit are ignored. Keys or + * values that exceed 1024 characters are truncated. + * + * @param key A unique key + * @param value A value to be associated with the given key + */ + public fun setCustomKey(key: String, value: Float) + + /** + * Sets a custom key and value that are associated with subsequent fatal, non-fatal, and ANR + * reports. + * + * Multiple calls to this method with the same key update the value for that key. + * + * The value of any key at the time of a fatal, non-fatal, or ANR event is associated with that + * event. + * + * Keys and associated values are visible in the session view on the Firebase Crashlytics + * console. + * + * Accepts a maximum of 64 key/value pairs. New keys beyond that limit are ignored. Keys or + * values that exceed 1024 characters are truncated. + * + * @param key A unique key + * @param value A value to be associated with the given key + */ + public fun setCustomKey(key: String, value: Int) + + /** + * Sets a custom key and value that are associated with subsequent fatal, non-fatal, and ANR + * reports. + * + * Multiple calls to this method with the same key update the value for that key. + * + * The value of any key at the time of a fatal, non-fatal, or ANR event is associated with that + * event. + * + * Keys and associated values are visible in the session view on the Firebase Crashlytics + * console. + * + * Accepts a maximum of 64 key/value pairs. New keys beyond that limit are ignored. Keys or + * values that exceed 1024 characters are truncated. + * + * @param key A unique key + * @param value A value to be associated with the given key + */ + public fun setCustomKey(key: String, value: Long) + + /** + * Sets multiple custom keys and values that are associated with subsequent fatal, non-fatal, and + * ANR reports. This method is intended as an alternative to [setCustomKey] in order to + * reduce the computational load of writing out multiple key/value pairs at the same time. + * + * Multiple calls to this method with the same key update the value for that key. + * + * The value of any key at the time of a fatal, non-fatal, or ANR event is associated with that + * event. + * + * Keys and associated values are visible in the session view on the Firebase Crashlytics + * console. + * + * Accepts a maximum of 64 key/value pairs. If calling this method results in the number of + * custom keys exceeding this limit, only some of the keys will be logged (however many are needed + * to get to 64). Which keys are logged versus dropped is unpredictable as there is no intrinsic + * sorting of keys. Keys or values that exceed 1024 characters are truncated. + * + * @param customKeys A dictionary of keys and the values to associate with each key + */ + public fun setCustomKeys(customKeys: Map) + + /** + * Enables or disables the automatic data collection configuration for Crashlytics. + * + * If this is set, it overrides any automatic data collection settings configured in the + * AndroidManifest.xml as well as any Firebase-wide settings. + * + * If automatic data collection is disabled for Crashlytics, crash reports are stored on the + * device. Use [sendUnsentReports] to upload existing reports even when automatic data collection is + * disabled. Use [deleteUnsentReports] to delete any reports stored on the device without + * sending them to Crashlytics. + * + * @param enabled whether to enable automatic data collection. When set to `false`, the new + * value does not apply until the next run of the app. To disable data collection by default + * for all app runs, add the `firebase_crashlytics_collection_enabled` flag to your + * app's AndroidManifest.xml. + */ + public fun setCrashlyticsCollectionEnabled(enabled: Boolean) + + /** + * Checks whether the app crashed on its previous run. + * + * @return true if a crash was recorded during the previous run of the app. + */ + public fun didCrashOnPreviousExecution(): Boolean + + /** + * If automatic data collection is disabled, this method queues up all the reports on a device to + * send to Crashlytics. Otherwise, this method is a no-op. + */ + public fun sendUnsentReports() + + /** + * If automatic data collection is disabled, this method queues up all the reports on a device for + * deletion. Otherwise, this method is a no-op. + */ + public fun deleteUnsentReports() } -expect open class FirebaseCrashlyticsException : FirebaseException \ No newline at end of file +/** + * Exception that gets thrown when an operation on Firebase Crashlytics fails. + */ +public expect open class FirebaseCrashlyticsException : FirebaseException 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 8c1933846..787587bc1 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 @@ -10,12 +10,13 @@ import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize import dev.gitlive.firebase.runBlockingTest import dev.gitlive.firebase.runTest +import kotlinx.coroutines.delay import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertFalse +import kotlin.time.Duration.Companion.seconds -expect val emulatorHost: String expect val context: Any expect annotation class IgnoreForAndroidUnitTest() @@ -34,8 +35,8 @@ class FirebaseCrashlyticsTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) crashlytics = Firebase.crashlytics(app) @@ -51,37 +52,57 @@ class FirebaseCrashlyticsTest { @Test fun testRecordException() = runTest { crashlytics.recordException(Exception("Test Exception")) + + // Delay to ensure Crashlytics completes + delay(1.seconds) } @Test fun testLog() = runTest { crashlytics.log("Test Log") + + // Delay to ensure Crashlytics completes + delay(1.seconds) } @Test fun testSetUserId() = runTest { crashlytics.setUserId("Test User Id") + // Delay to ensure Crashlytics completes + delay(1.seconds) } @Test fun testSendUnsentReports() = runTest { crashlytics.sendUnsentReports() + + // Delay to ensure Crashlytics completes + delay(1.seconds) } @Test fun testDeleteUnsentReports() = runTest { crashlytics.deleteUnsentReports() + + // Delay to ensure Crashlytics completes + delay(1.seconds) } @Test fun testDidCrashOnPreviousExecution() = runTest { val didCrash = crashlytics.didCrashOnPreviousExecution() assertFalse { didCrash } + + // Delay to ensure Crashlytics completes + delay(1.seconds) } @Test fun testSetCrashlyticsCollectionEnabled() = runTest { crashlytics.setCrashlyticsCollectionEnabled(true) + + // Delay to ensure Crashlytics completes + delay(1.seconds) } } diff --git a/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index 209e82f9f..6c6d16bec 100644 --- a/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/iosMain/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -7,34 +7,61 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -actual val Firebase.crashlytics get() = +public val FirebaseCrashlytics.ios: FIRCrashlytics get() = FIRCrashlytics.crashlytics() + +public actual val Firebase.crashlytics: FirebaseCrashlytics get() = FirebaseCrashlytics(FIRCrashlytics.crashlytics()) -actual fun Firebase.crashlytics(app: FirebaseApp) = +public actual fun Firebase.crashlytics(app: FirebaseApp): FirebaseCrashlytics = FirebaseCrashlytics(FIRCrashlytics.crashlytics()) -actual class FirebaseCrashlytics internal constructor(val ios: FIRCrashlytics) { +public actual class FirebaseCrashlytics internal constructor(internal val ios: FIRCrashlytics) { - actual fun recordException(exception: Throwable) { ios.recordError(exception.asNSError()) } - actual fun log(message: String) { ios.log(message) } - actual fun setUserId(userId: String) { ios.setUserID(userId) } - actual fun setCrashlyticsCollectionEnabled(enabled: Boolean) { ios.setCrashlyticsCollectionEnabled(enabled) } - actual fun sendUnsentReports() { ios.sendUnsentReports() } - actual fun deleteUnsentReports() { ios.deleteUnsentReports() } - actual fun didCrashOnPreviousExecution(): Boolean = ios.didCrashDuringPreviousExecution() - actual fun setCustomKey(key: String, value: String) { + public actual fun recordException(exception: Throwable) { + ios.recordError(exception.asNSError()) + } + public actual fun log(message: String) { + ios.log(message) + } + public actual fun setUserId(userId: String) { + ios.setUserID(userId) + } + public actual fun setCrashlyticsCollectionEnabled(enabled: Boolean) { + ios.setCrashlyticsCollectionEnabled(enabled) + } + public actual fun sendUnsentReports() { + ios.sendUnsentReports() + } + public actual fun deleteUnsentReports() { + ios.deleteUnsentReports() + } + public actual fun didCrashOnPreviousExecution(): Boolean = ios.didCrashDuringPreviousExecution() + public actual fun setCustomKey(key: String, value: String) { ios.setCustomValue(key, value) } - actual fun setCustomKey(key: String, value: Boolean) { ios.setCustomValue(key, value.toString()) } - actual fun setCustomKey(key: String, value: Double) { ios.setCustomValue(key, value.toString()) } - actual fun setCustomKey(key: String, value: Float) { ios.setCustomValue(key, value.toString()) } - actual fun setCustomKey(key: String, value: Int) { ios.setCustomValue(key, value.toString()) } - actual fun setCustomKey(key: String, value: Long) { ios.setCustomValue(key, value.toString()) } + public actual fun setCustomKey(key: String, value: Boolean) { + ios.setCustomValue(key, value.toString()) + } + public actual fun setCustomKey(key: String, value: Double) { + ios.setCustomValue(key, value.toString()) + } + public actual fun setCustomKey(key: String, value: Float) { + ios.setCustomValue(key, value.toString()) + } + public actual fun setCustomKey(key: String, value: Int) { + ios.setCustomValue(key, value.toString()) + } + public actual fun setCustomKey(key: String, value: Long) { + ios.setCustomValue(key, value.toString()) + } + @Suppress("UNCHECKED_CAST") - actual fun setCustomKeys(customKeys: Map) { ios.setCustomKeysAndValues(customKeys as Map) } + public actual fun setCustomKeys(customKeys: Map) { + ios.setCustomKeysAndValues(customKeys as Map) + } } -actual open class FirebaseCrashlyticsException internal constructor(message: String) : FirebaseException(message) +public actual open class FirebaseCrashlyticsException internal constructor(message: String) : FirebaseException(message) private fun Throwable.asNSError(): NSError { val userInfo = mutableMapOf() @@ -43,5 +70,5 @@ private fun Throwable.asNSError(): NSError { if (message != null) { userInfo[NSLocalizedDescriptionKey] = message } - return NSError.errorWithDomain("KotlinException", 0, userInfo) -} \ No newline at end of file + return NSError.errorWithDomain(this::class.qualifiedName, 0, userInfo) +} diff --git a/firebase-crashlytics/src/iosTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt b/firebase-crashlytics/src/iosTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt index 602cd1be6..25fea7595 100644 --- a/firebase-crashlytics/src/iosTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt +++ b/firebase-crashlytics/src/iosTest/kotlin/dev/gitlive/firebase/crashlytics/crashlytics.kt @@ -4,8 +4,6 @@ package dev.gitlive.firebase.crashlytics -actual val emulatorHost: String = "localhost" - actual val context: Any = Unit @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) diff --git a/firebase-database/api/android/firebase-database.api b/firebase-database/api/android/firebase-database.api new file mode 100644 index 000000000..8ea89b1bc --- /dev/null +++ b/firebase-database/api/android/firebase-database.api @@ -0,0 +1,147 @@ +public final class dev/gitlive/firebase/database/ChildEvent { + public final fun component1 ()Ldev/gitlive/firebase/database/DataSnapshot; + public final fun component2 ()Ldev/gitlive/firebase/database/ChildEvent$Type; + public final fun component3 ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public final fun getPreviousChildName ()Ljava/lang/String; + public final fun getSnapshot ()Ldev/gitlive/firebase/database/DataSnapshot; + public final fun getType ()Ldev/gitlive/firebase/database/ChildEvent$Type; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/database/ChildEvent$Type : java/lang/Enum { + public static final field ADDED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static final field CHANGED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static final field MOVED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static final field REMOVED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/database/ChildEvent$Type; + public static fun values ()[Ldev/gitlive/firebase/database/ChildEvent$Type; +} + +public final class dev/gitlive/firebase/database/DataSnapshot { + public final fun child (Ljava/lang/String;)Ldev/gitlive/firebase/database/DataSnapshot; + public final fun getChildren ()Ljava/lang/Iterable; + public final fun getExists ()Z + public final fun getHasChildren ()Z + public final fun getKey ()Ljava/lang/String; + public final fun getRef ()Ldev/gitlive/firebase/database/DatabaseReference; + public final fun getValue ()Ljava/lang/Object; + public final fun value (Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun value$default (Ldev/gitlive/firebase/database/DataSnapshot;Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/database/DatabaseReference : dev/gitlive/firebase/database/Query { + public final fun child (Ljava/lang/String;)Ldev/gitlive/firebase/database/DatabaseReference; + public final fun getKey ()Ljava/lang/String; + public final fun onDisconnect ()Ldev/gitlive/firebase/database/OnDisconnect; + public final fun push ()Ldev/gitlive/firebase/database/DatabaseReference; + public final fun removeValue (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun runTransaction (Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun runTransaction$default (Ldev/gitlive/firebase/database/DatabaseReference;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun setValue$default (Ldev/gitlive/firebase/database/DatabaseReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setValueEncoded (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateChildren$default (Ldev/gitlive/firebase/database/DatabaseReference;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateEncodedChildren (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/database/FirebaseDatabase { + public static final field Companion Ldev/gitlive/firebase/database/FirebaseDatabase$Companion; + public final fun goOffline ()V + public final fun goOnline ()V + public final fun reference ()Ldev/gitlive/firebase/database/DatabaseReference; + public final fun reference (Ljava/lang/String;)Ldev/gitlive/firebase/database/DatabaseReference; + public final fun setLoggingEnabled (Z)V + public final fun setPersistenceCacheSizeBytes (J)V + public final fun setPersistenceEnabled (Z)V + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/database/FirebaseDatabase$Companion { +} + +public final class dev/gitlive/firebase/database/OnDisconnect { + public final fun cancel (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun removeValue (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEncodedValue (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun setValue$default (Ldev/gitlive/firebase/database/OnDisconnect;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateChildren$default (Ldev/gitlive/firebase/database/OnDisconnect;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateEncodedChildren (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class dev/gitlive/firebase/database/Query { + public final fun childEvents ([Ldev/gitlive/firebase/database/ChildEvent$Type;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun childEvents$default (Ldev/gitlive/firebase/database/Query;[Ldev/gitlive/firebase/database/ChildEvent$Type;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public final fun endAt (DLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun endAt (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun endAt (ZLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun endAt$default (Ldev/gitlive/firebase/database/Query;DLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun endAt$default (Ldev/gitlive/firebase/database/Query;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun endAt$default (Ldev/gitlive/firebase/database/Query;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public final fun equalTo (DLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun equalTo (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun equalTo (ZLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun equalTo$default (Ldev/gitlive/firebase/database/Query;DLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun equalTo$default (Ldev/gitlive/firebase/database/Query;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun equalTo$default (Ldev/gitlive/firebase/database/Query;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public final fun getPersistenceEnabled ()Z + public final fun getValueEvents ()Lkotlinx/coroutines/flow/Flow; + public final fun limitToFirst (I)Ldev/gitlive/firebase/database/Query; + public final fun limitToLast (I)Ldev/gitlive/firebase/database/Query; + public final fun orderByChild (Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun orderByKey ()Ldev/gitlive/firebase/database/Query; + public final fun orderByValue ()Ldev/gitlive/firebase/database/Query; + public final fun startAt (DLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun startAt (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun startAt (ZLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun startAt$default (Ldev/gitlive/firebase/database/Query;DLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun startAt$default (Ldev/gitlive/firebase/database/Query;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun startAt$default (Ldev/gitlive/firebase/database/Query;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/database/ServerValue { + public static final field Companion Ldev/gitlive/firebase/database/ServerValue$Companion; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/database/ServerValue$Companion { + public final fun getTIMESTAMP ()Ldev/gitlive/firebase/database/ServerValue; + public final fun increment (D)Ldev/gitlive/firebase/database/ServerValue; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/database/ServerValueSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/database/ServerValueSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/database/ServerValue; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/database/ServerValue;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/database/databaseAndroid { + public static final fun database (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun database (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;Ljava/lang/String;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun database (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun getAndroid (Ldev/gitlive/firebase/database/DataSnapshot;)Lcom/google/firebase/database/DataSnapshot; + public static final fun getAndroid (Ldev/gitlive/firebase/database/DatabaseReference;)Lcom/google/firebase/database/DatabaseReference; + public static final fun getAndroid (Ldev/gitlive/firebase/database/FirebaseDatabase;)Lcom/google/firebase/database/FirebaseDatabase; + public static final fun getAndroid (Ldev/gitlive/firebase/database/OnDisconnect;)Lcom/google/firebase/database/OnDisconnect; + public static final fun getAndroid (Ldev/gitlive/firebase/database/Query;)Lcom/google/firebase/database/Query; + public static final fun getDatabase (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun getDatabase (Ldev/gitlive/firebase/database/OnDisconnect;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun getPersistenceEnabled (Ldev/gitlive/firebase/database/OnDisconnect;)Z +} + diff --git a/firebase-database/api/jvm/firebase-database.api b/firebase-database/api/jvm/firebase-database.api new file mode 100644 index 000000000..8ea89b1bc --- /dev/null +++ b/firebase-database/api/jvm/firebase-database.api @@ -0,0 +1,147 @@ +public final class dev/gitlive/firebase/database/ChildEvent { + public final fun component1 ()Ldev/gitlive/firebase/database/DataSnapshot; + public final fun component2 ()Ldev/gitlive/firebase/database/ChildEvent$Type; + public final fun component3 ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public final fun getPreviousChildName ()Ljava/lang/String; + public final fun getSnapshot ()Ldev/gitlive/firebase/database/DataSnapshot; + public final fun getType ()Ldev/gitlive/firebase/database/ChildEvent$Type; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/database/ChildEvent$Type : java/lang/Enum { + public static final field ADDED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static final field CHANGED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static final field MOVED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static final field REMOVED Ldev/gitlive/firebase/database/ChildEvent$Type; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/database/ChildEvent$Type; + public static fun values ()[Ldev/gitlive/firebase/database/ChildEvent$Type; +} + +public final class dev/gitlive/firebase/database/DataSnapshot { + public final fun child (Ljava/lang/String;)Ldev/gitlive/firebase/database/DataSnapshot; + public final fun getChildren ()Ljava/lang/Iterable; + public final fun getExists ()Z + public final fun getHasChildren ()Z + public final fun getKey ()Ljava/lang/String; + public final fun getRef ()Ldev/gitlive/firebase/database/DatabaseReference; + public final fun getValue ()Ljava/lang/Object; + public final fun value (Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun value$default (Ldev/gitlive/firebase/database/DataSnapshot;Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/database/DatabaseReference : dev/gitlive/firebase/database/Query { + public final fun child (Ljava/lang/String;)Ldev/gitlive/firebase/database/DatabaseReference; + public final fun getKey ()Ljava/lang/String; + public final fun onDisconnect ()Ldev/gitlive/firebase/database/OnDisconnect; + public final fun push ()Ldev/gitlive/firebase/database/DatabaseReference; + public final fun removeValue (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun runTransaction (Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun runTransaction$default (Ldev/gitlive/firebase/database/DatabaseReference;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun setValue$default (Ldev/gitlive/firebase/database/DatabaseReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setValueEncoded (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateChildren$default (Ldev/gitlive/firebase/database/DatabaseReference;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateEncodedChildren (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/database/FirebaseDatabase { + public static final field Companion Ldev/gitlive/firebase/database/FirebaseDatabase$Companion; + public final fun goOffline ()V + public final fun goOnline ()V + public final fun reference ()Ldev/gitlive/firebase/database/DatabaseReference; + public final fun reference (Ljava/lang/String;)Ldev/gitlive/firebase/database/DatabaseReference; + public final fun setLoggingEnabled (Z)V + public final fun setPersistenceCacheSizeBytes (J)V + public final fun setPersistenceEnabled (Z)V + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/database/FirebaseDatabase$Companion { +} + +public final class dev/gitlive/firebase/database/OnDisconnect { + public final fun cancel (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun removeValue (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setEncodedValue (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun setValue$default (Ldev/gitlive/firebase/database/OnDisconnect;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateChildren (Ljava/util/Map;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateChildren$default (Ldev/gitlive/firebase/database/OnDisconnect;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateEncodedChildren (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class dev/gitlive/firebase/database/Query { + public final fun childEvents ([Ldev/gitlive/firebase/database/ChildEvent$Type;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun childEvents$default (Ldev/gitlive/firebase/database/Query;[Ldev/gitlive/firebase/database/ChildEvent$Type;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public final fun endAt (DLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun endAt (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun endAt (ZLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun endAt$default (Ldev/gitlive/firebase/database/Query;DLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun endAt$default (Ldev/gitlive/firebase/database/Query;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun endAt$default (Ldev/gitlive/firebase/database/Query;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public final fun equalTo (DLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun equalTo (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun equalTo (ZLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun equalTo$default (Ldev/gitlive/firebase/database/Query;DLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun equalTo$default (Ldev/gitlive/firebase/database/Query;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun equalTo$default (Ldev/gitlive/firebase/database/Query;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public final fun getPersistenceEnabled ()Z + public final fun getValueEvents ()Lkotlinx/coroutines/flow/Flow; + public final fun limitToFirst (I)Ldev/gitlive/firebase/database/Query; + public final fun limitToLast (I)Ldev/gitlive/firebase/database/Query; + public final fun orderByChild (Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun orderByKey ()Ldev/gitlive/firebase/database/Query; + public final fun orderByValue ()Ldev/gitlive/firebase/database/Query; + public final fun startAt (DLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun startAt (Ljava/lang/String;Ljava/lang/String;)Ldev/gitlive/firebase/database/Query; + public final fun startAt (ZLjava/lang/String;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun startAt$default (Ldev/gitlive/firebase/database/Query;DLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun startAt$default (Ldev/gitlive/firebase/database/Query;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public static synthetic fun startAt$default (Ldev/gitlive/firebase/database/Query;ZLjava/lang/String;ILjava/lang/Object;)Ldev/gitlive/firebase/database/Query; + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/database/ServerValue { + public static final field Companion Ldev/gitlive/firebase/database/ServerValue$Companion; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/database/ServerValue$Companion { + public final fun getTIMESTAMP ()Ldev/gitlive/firebase/database/ServerValue; + public final fun increment (D)Ldev/gitlive/firebase/database/ServerValue; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/database/ServerValueSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/database/ServerValueSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/database/ServerValue; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/database/ServerValue;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/database/databaseAndroid { + public static final fun database (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun database (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;Ljava/lang/String;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun database (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun getAndroid (Ldev/gitlive/firebase/database/DataSnapshot;)Lcom/google/firebase/database/DataSnapshot; + public static final fun getAndroid (Ldev/gitlive/firebase/database/DatabaseReference;)Lcom/google/firebase/database/DatabaseReference; + public static final fun getAndroid (Ldev/gitlive/firebase/database/FirebaseDatabase;)Lcom/google/firebase/database/FirebaseDatabase; + public static final fun getAndroid (Ldev/gitlive/firebase/database/OnDisconnect;)Lcom/google/firebase/database/OnDisconnect; + public static final fun getAndroid (Ldev/gitlive/firebase/database/Query;)Lcom/google/firebase/database/Query; + public static final fun getDatabase (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun getDatabase (Ldev/gitlive/firebase/database/OnDisconnect;)Ldev/gitlive/firebase/database/FirebaseDatabase; + public static final fun getPersistenceEnabled (Ldev/gitlive/firebase/database/OnDisconnect;)Z +} + diff --git a/firebase-database/build.gradle.kts b/firebase-database/build.gradle.kts index aec53f473..625017011 100644 --- a/firebase-database/build.gradle.kts +++ b/firebase-database/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -11,11 +14,7 @@ plugins { kotlin("native.cocoapods") kotlin("multiplatform") kotlin("plugin.serialization") -} - -repositories { - google() - mavenCentral() + id("testOptionsConvention") } android { @@ -31,15 +30,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -53,10 +48,23 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + freeCompilerArgs.add("-Xconsistent-data-class-copy-visibility") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -65,38 +73,23 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseDatabase" } noPodspec() pod("FirebaseDatabase") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") } } } @@ -104,32 +97,26 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") optIn("kotlinx.coroutines.FlowPreview") @@ -143,7 +130,8 @@ kotlin { getByName("commonMain") { dependencies { api(project(":firebase-app")) - implementation(project(":firebase-common")) + api(project(":firebase-common")) + implementation(project(":firebase-common-internal")) } } @@ -159,7 +147,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-database-ktx") + api(libs.google.firebase.database.ktx) } } getByName("jvmMain") { @@ -174,6 +162,12 @@ if (project.property("firebase-database.skipIosTests") == "true") { } } +if (project.property("firebase-database.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-database.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-database/documentation.md b/firebase-database/documentation.md new file mode 100644 index 000000000..46c55e599 --- /dev/null +++ b/firebase-database/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-database +This module is a direct forward of the Firebase Database library. It provides the main functionality, like storing data. \ No newline at end of file diff --git a/firebase-database/package.json b/firebase-database/package.json index 2251ea0f5..228ba4282 100644 --- a/firebase-database/package.json +++ b/firebase-database/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-database", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt b/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt deleted file mode 100644 index ce3908c54..000000000 --- a/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.gitlive.firebase.database - -actual fun createFirebaseDatabaseTestSettings( - persistenceEnabled: Boolean, - persistenceCacheSizeBytes: Long?, -) = FirebaseDatabase.Settings(persistenceEnabled, persistenceCacheSizeBytes) diff --git a/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt index d0b8ea390..2cadfe271 100644 --- a/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -3,12 +3,17 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.database import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Ignore actual val emulatorHost: String = "10.0.2.2" actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest + +actual typealias IgnoreForAndroidTest = Ignore diff --git a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt index 6c6755ecd..f14b0b5b5 100644 --- a/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt +++ b/firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt @@ -6,12 +6,12 @@ private typealias NativeServerValue = com.google.firebase.database.ServerValue /** Represents a Firebase ServerValue. */ @Serializable(with = ServerValueSerializer::class) -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)) +public actual class ServerValue internal actual constructor( + internal actual val nativeValue: Any, +) { + public actual companion object { + public actual val TIMESTAMP: ServerValue get() = ServerValue(NativeServerValue.TIMESTAMP) + public actual fun increment(delta: Double): ServerValue = ServerValue(NativeServerValue.increment(delta)) } override fun equals(other: Any?): Boolean = 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 086087c4c..561f3215a 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 @@ -2,7 +2,8 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ -@file:JvmName("AndroidDatabase") +@file:JvmName("databaseAndroid") + package dev.gitlive.firebase.database import com.google.android.gms.tasks.Task @@ -13,25 +14,37 @@ import com.google.firebase.database.MutableData import com.google.firebase.database.Transaction import com.google.firebase.database.ValueEventListener import dev.gitlive.firebase.DecodeSettings -import dev.gitlive.firebase.EncodeSettings -import dev.gitlive.firebase.encode +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android import dev.gitlive.firebase.database.ChildEvent.Type -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode +import dev.gitlive.firebase.database.android as publicAndroid +import dev.gitlive.firebase.internal.android +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.reencodeTransformation import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationStrategy -import java.util.* +import java.util.WeakHashMap import kotlin.time.Duration.Companion.seconds -suspend fun Task.awaitWhileOnline(database: FirebaseDatabase): T = +public val FirebaseDatabase.android: com.google.firebase.database.FirebaseDatabase get() = com.google.firebase.database.FirebaseDatabase.getInstance() + +internal suspend fun Task.awaitWhileOnline(database: FirebaseDatabase): T = merge( flow { emit(await()) }, database @@ -39,135 +52,152 @@ suspend fun Task.awaitWhileOnline(database: FirebaseDatabase): T = .valueEvents .debounce(2.seconds) .filterNot { it.value() } - .map { throw DatabaseException("Database not connected", null) } + .map { throw DatabaseException("Database not connected", null) }, ) - .first() - + .first() -actual val Firebase.database - by lazy { FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance()) } +public actual val Firebase.database: FirebaseDatabase + by lazy { FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance()) } -actual fun Firebase.database(url: String) = +public actual fun Firebase.database(url: String): FirebaseDatabase = FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(url)) -actual fun Firebase.database(app: FirebaseApp) = +public actual fun Firebase.database(app: FirebaseApp): FirebaseDatabase = FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(app.android)) -actual fun Firebase.database(app: FirebaseApp, url: String) = +public actual fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase = FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(app.android, url)) -actual class FirebaseDatabase internal constructor(val android: com.google.firebase.database.FirebaseDatabase) { +public actual class FirebaseDatabase internal constructor(internal val android: com.google.firebase.database.FirebaseDatabase) { - companion object { + public companion object { private val instances = WeakHashMap() internal fun getInstance( - android: com.google.firebase.database.FirebaseDatabase - ) = instances.getOrPut(android) { dev.gitlive.firebase.database.FirebaseDatabase(android) } - } - - actual data class Settings( - actual val persistenceEnabled: Boolean = false, - actual val persistenceCacheSizeBytes: Long? = null, - ) { - - actual companion object { - actual fun createSettings(persistenceEnabled: Boolean, persistenceCacheSizeBytes: Long?) = Settings(persistenceEnabled, persistenceCacheSizeBytes) - } + android: com.google.firebase.database.FirebaseDatabase, + ) = instances.getOrPut(android) { FirebaseDatabase(android) } } private var persistenceEnabled = true - actual fun reference(path: String) = - DatabaseReference(android.getReference(path), persistenceEnabled) + public actual fun reference(path: String): DatabaseReference = + DatabaseReference(NativeDatabaseReference(android.getReference(path), persistenceEnabled)) + + public actual fun reference(): DatabaseReference = + DatabaseReference(NativeDatabaseReference(android.reference, persistenceEnabled)) - actual fun reference() = - DatabaseReference(android.reference, persistenceEnabled) + public actual fun setPersistenceEnabled(enabled: Boolean) { + android.setPersistenceEnabled(enabled) + persistenceEnabled = enabled + } - actual fun setSettings(settings: Settings) { - android.setPersistenceEnabled(settings.persistenceEnabled) - persistenceEnabled = settings.persistenceEnabled - settings.persistenceCacheSizeBytes?.let { android.setPersistenceCacheSizeBytes(it) } + public actual fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) { + android.setPersistenceCacheSizeBytes(cacheSizeInBytes) } - actual fun setLoggingEnabled(enabled: Boolean) = + public actual fun setLoggingEnabled(enabled: Boolean) { android.setLogLevel(Logger.Level.DEBUG.takeIf { enabled } ?: Logger.Level.NONE) + } - actual fun useEmulator(host: String, port: Int) = + public actual fun useEmulator(host: String, port: Int) { android.useEmulator(host, port) + } + + public actual fun goOffline() { + android.goOffline() + } + + public actual fun goOnline() { + android.goOnline() + } } -actual open class Query internal constructor( +internal actual open class NativeQuery( open val android: com.google.firebase.database.Query, - val persistenceEnabled: Boolean + val persistenceEnabled: Boolean, +) + +public val Query.android: com.google.firebase.database.Query get() = nativeQuery.android + +public actual open class Query internal actual constructor( + internal val nativeQuery: NativeQuery, ) { - actual fun orderByKey() = Query(android.orderByKey(), persistenceEnabled) - actual fun orderByValue() = Query(android.orderByValue(), persistenceEnabled) + internal constructor( + android: com.google.firebase.database.Query, + persistenceEnabled: Boolean, + ) : this(NativeQuery(android, persistenceEnabled)) + + internal open val android: com.google.firebase.database.Query = nativeQuery.android + public val persistenceEnabled: Boolean = nativeQuery.persistenceEnabled + + public actual fun orderByKey(): Query = Query(android.orderByKey(), persistenceEnabled) + + public actual fun orderByValue(): Query = Query(android.orderByValue(), persistenceEnabled) - actual fun orderByChild(path: String) = Query(android.orderByChild(path), persistenceEnabled) + public actual fun orderByChild(path: String): Query = Query(android.orderByChild(path), persistenceEnabled) - actual fun startAt(value: String, key: String?) = Query(android.startAt(value, key), persistenceEnabled) + public actual fun startAt(value: String, key: String?): Query = Query(android.startAt(value, key), persistenceEnabled) - actual fun startAt(value: Double, key: String?) = Query(android.startAt(value, key), persistenceEnabled) + public actual fun startAt(value: Double, key: String?): Query = Query(android.startAt(value, key), persistenceEnabled) - actual fun startAt(value: Boolean, key: String?) = Query(android.startAt(value, key), persistenceEnabled) + public actual fun startAt(value: Boolean, key: String?): Query = Query(android.startAt(value, key), persistenceEnabled) - actual fun endAt(value: String, key: String?) = Query(android.endAt(value, key), persistenceEnabled) + public actual fun endAt(value: String, key: String?): Query = Query(android.endAt(value, key), persistenceEnabled) - actual fun endAt(value: Double, key: String?) = Query(android.endAt(value, key), persistenceEnabled) + public actual fun endAt(value: Double, key: String?): Query = Query(android.endAt(value, key), persistenceEnabled) - actual fun endAt(value: Boolean, key: String?) = Query(android.endAt(value, key), persistenceEnabled) + public actual fun endAt(value: Boolean, key: String?): Query = Query(android.endAt(value, key), persistenceEnabled) - actual fun limitToFirst(limit: Int) = Query(android.limitToFirst(limit), persistenceEnabled) + public actual fun limitToFirst(limit: Int): Query = Query(android.limitToFirst(limit), persistenceEnabled) - actual fun limitToLast(limit: Int) = Query(android.limitToLast(limit), persistenceEnabled) + public actual fun limitToLast(limit: Int): Query = Query(android.limitToLast(limit), persistenceEnabled) - actual fun equalTo(value: String, key: String?) = Query(android.equalTo(value, key), persistenceEnabled) + public actual fun equalTo(value: String, key: String?): Query = Query(android.equalTo(value, key), persistenceEnabled) - actual fun equalTo(value: Double, key: String?) = Query(android.equalTo(value, key), persistenceEnabled) + public actual fun equalTo(value: Double, key: String?): Query = Query(android.equalTo(value, key), persistenceEnabled) - actual fun equalTo(value: Boolean, key: String?) = Query(android.equalTo(value, key), persistenceEnabled) + public actual fun equalTo(value: Boolean, key: String?): Query = Query(android.equalTo(value, key), persistenceEnabled) - actual val valueEvents: Flow + public actual val valueEvents: Flow get() = callbackFlow { - val listener = object : ValueEventListener { - override fun onDataChange(snapshot: com.google.firebase.database.DataSnapshot) { - trySendBlocking(DataSnapshot(snapshot, persistenceEnabled)) - } + val listener = object : ValueEventListener { + override fun onDataChange(snapshot: com.google.firebase.database.DataSnapshot) { + trySendBlocking(DataSnapshot(snapshot, persistenceEnabled)) + } - override fun onCancelled(error: com.google.firebase.database.DatabaseError) { - close(error.toException()) + override fun onCancelled(error: DatabaseError) { + close(error.toException()) + } } + android.addValueEventListener(listener) + awaitClose { android.removeEventListener(listener) } } - android.addValueEventListener(listener) - awaitClose { android.removeEventListener(listener) } - } - actual fun childEvents(vararg types: Type): Flow = callbackFlow { + public actual fun childEvents(vararg types: Type): Flow = callbackFlow { val listener = object : ChildEventListener { val moved by lazy { types.contains(Type.MOVED) } override fun onChildMoved(snapshot: com.google.firebase.database.DataSnapshot, previousChildName: String?) { - if(moved) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.MOVED, previousChildName)) + if (moved) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.MOVED, previousChildName)) } val changed by lazy { types.contains(Type.CHANGED) } override fun onChildChanged(snapshot: com.google.firebase.database.DataSnapshot, previousChildName: String?) { - if(changed) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.CHANGED, previousChildName)) + if (changed) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.CHANGED, previousChildName)) } val added by lazy { types.contains(Type.ADDED) } override fun onChildAdded(snapshot: com.google.firebase.database.DataSnapshot, previousChildName: String?) { - if(added) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.ADDED, previousChildName)) + if (added) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.ADDED, previousChildName)) } val removed by lazy { types.contains(Type.REMOVED) } override fun onChildRemoved(snapshot: com.google.firebase.database.DataSnapshot) { - if(removed) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.REMOVED, null)) + if (removed) trySend(ChildEvent(DataSnapshot(snapshot, persistenceEnabled), Type.REMOVED, null)) } - override fun onCancelled(error: com.google.firebase.database.DatabaseError) { + override fun onCancelled(error: DatabaseError) { close(error.toException()) } } @@ -175,48 +205,52 @@ actual open class Query internal constructor( awaitClose { android.removeEventListener(listener) } } - override fun toString() = android.toString() + override fun toString(): String = android.toString() } -actual class DatabaseReference internal constructor( +internal actual class NativeDatabaseReference internal constructor( override val android: com.google.firebase.database.DatabaseReference, - persistenceEnabled: Boolean -): Query(android, persistenceEnabled) { + persistenceEnabled: Boolean, +) : NativeQuery(android, persistenceEnabled) { actual val key get() = android.key val database = FirebaseDatabase(android.database) - actual fun child(path: String) = DatabaseReference(android.child(path), persistenceEnabled) - - actual fun push() = DatabaseReference(android.push(), persistenceEnabled) - actual fun onDisconnect() = OnDisconnect(android.onDisconnect(), persistenceEnabled, database) + actual fun child(path: String) = NativeDatabaseReference(android.child(path), persistenceEnabled) - actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) = android.setValue(encode(value, encodeSettings)) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + actual fun push() = NativeDatabaseReference(android.push(), persistenceEnabled) + actual fun onDisconnect() = NativeOnDisconnect(android.onDisconnect(), persistenceEnabled, database) - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - android.setValue(encode(strategy, value, encodeSettings)) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + actual suspend fun setValueEncoded(encodedValue: Any?) { + android.setValue(encodedValue) + .run { if (persistenceEnabled) await() else awaitWhileOnline(database) } + } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - android.updateChildren(encode(update, encodeSettings) as Map) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + android.updateChildren(encodedUpdate.android) + .run { if (persistenceEnabled) await() else awaitWhileOnline(database) } + } - actual suspend fun removeValue() = android.removeValue() - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + actual suspend fun removeValue() { + android.removeValue() + .run { if (persistenceEnabled) await() else awaitWhileOnline(database) } + } - actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { + @OptIn(ExperimentalSerializationApi::class) + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() android.runTransaction(object : Transaction.Handler { override fun doTransaction(currentData: MutableData): Transaction.Result { - currentData.value = currentData.value?.let { - transactionUpdate(decode(strategy, it, decodeSettings)) + val valueToReencode = currentData.value + // Value may be null initially, so only reencode if this is allowed + if (strategy.descriptor.isNullable || valueToReencode != null) { + currentData.value = reencodeTransformation( + strategy, + valueToReencode, + buildSettings, + transactionUpdate, + ) } return Transaction.success(currentData) } @@ -224,7 +258,7 @@ actual class DatabaseReference internal constructor( override fun onComplete( error: DatabaseError?, committed: Boolean, - snapshot: com.google.firebase.database.DataSnapshot? + snapshot: com.google.firebase.database.DataSnapshot?, ) { if (error != null) { deferred.completeExceptionally(error.toException()) @@ -232,64 +266,67 @@ actual class DatabaseReference internal constructor( deferred.complete(DataSnapshot(snapshot!!, persistenceEnabled)) } } - }) return deferred.await() } } -@Suppress("UNCHECKED_CAST") -actual class DataSnapshot internal constructor( - val android: com.google.firebase.database.DataSnapshot, - private val persistenceEnabled: Boolean + +public val DatabaseReference.android: com.google.firebase.database.DatabaseReference get() = nativeReference.android +public val DataSnapshot.android: com.google.firebase.database.DataSnapshot get() = android + +public actual class DataSnapshot internal constructor( + internal val android: com.google.firebase.database.DataSnapshot, + private val persistenceEnabled: Boolean, ) { - actual val exists get() = android.exists() + public actual val exists: Boolean get() = android.exists() - actual val key get() = android.key + public actual val key: String? get() = android.key - actual val ref: DatabaseReference get() = DatabaseReference(android.ref, persistenceEnabled) + public actual val ref: DatabaseReference get() = DatabaseReference(NativeDatabaseReference(android.ref, persistenceEnabled)) - actual val value get() = android.value + public actual val value: Any? get() = android.value - actual inline fun value() = - decode(value = android.value) + public actual inline fun value(): T = + decode(value = publicAndroid.value) - actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, android.value, decodeSettings) + public actual inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(strategy, publicAndroid.value, buildSettings) - actual fun child(path: String) = DataSnapshot(android.child(path), persistenceEnabled) - actual val hasChildren get() = android.hasChildren() - actual val children: Iterable get() = android.children.map { DataSnapshot(it, persistenceEnabled) } + public actual fun child(path: String): DataSnapshot = DataSnapshot(android.child(path), persistenceEnabled) + public actual val hasChildren: Boolean get() = android.hasChildren() + public actual val children: Iterable get() = android.children.map { DataSnapshot(it, persistenceEnabled) } } -actual class OnDisconnect internal constructor( +internal actual class NativeOnDisconnect internal constructor( val android: com.google.firebase.database.OnDisconnect, val persistenceEnabled: Boolean, val database: FirebaseDatabase, ) { - actual suspend fun removeValue() = android.removeValue() - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } - - actual suspend fun cancel() = android.cancel() - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + actual suspend fun removeValue() { + android.removeValue() + .run { if (persistenceEnabled) await() else awaitWhileOnline(database) } + } - actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) = - android.setValue(encode(value, encodeSettings)) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + actual suspend fun cancel() { + android.cancel() + .run { if (persistenceEnabled) await() else awaitWhileOnline(database) } + } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - android.setValue(encode(strategy, value, encodeSettings)) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit} + actual suspend fun setEncodedValue(encodedValue: Any?) { + android.setValue(encodedValue) + .run { if (persistenceEnabled) await() else awaitWhileOnline(database) } + } - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - android.updateChildren(update.mapValues { (_, it) -> encode(it, encodeSettings) }) - .run { if(persistenceEnabled) await() else awaitWhileOnline(database) } - .run { Unit } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + android.updateChildren(encodedUpdate.android) + .run { if (persistenceEnabled) await() else awaitWhileOnline(database) } + } } -actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) +public val OnDisconnect.android: com.google.firebase.database.OnDisconnect get() = native.android +public val OnDisconnect.persistenceEnabled: Boolean get() = native.persistenceEnabled +public val OnDisconnect.database: FirebaseDatabase get() = native.database + +public actual typealias DatabaseException = com.google.firebase.database.DatabaseException diff --git a/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt b/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt deleted file mode 100644 index ce3908c54..000000000 --- a/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.gitlive.firebase.database - -actual fun createFirebaseDatabaseTestSettings( - persistenceEnabled: Boolean, - persistenceCacheSizeBytes: Long?, -) = FirebaseDatabase.Settings(persistenceEnabled, persistenceCacheSizeBytes) diff --git a/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt index b0fbae1d3..dc4878866 100644 --- a/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/androidUnitTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.database import org.junit.Ignore @@ -11,3 +12,4 @@ actual val emulatorHost: String = "10.0.2.2" actual val context: Any = "" actual typealias IgnoreForAndroidUnitTest = Ignore +actual typealias IgnoreForAndroidTest = Ignore diff --git a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt index c93ba746a..e393d118b 100644 --- a/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt +++ b/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt @@ -1,28 +1,28 @@ package dev.gitlive.firebase.database -import dev.gitlive.firebase.FirebaseDecoder import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.FirebaseDecoder +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException /** Represents a Firebase ServerValue. */ @Serializable(with = ServerValueSerializer::class) -expect class ServerValue internal constructor(nativeValue: Any){ +public expect class ServerValue internal constructor(nativeValue: Any) { internal val nativeValue: Any - companion object { - val TIMESTAMP: ServerValue - fun increment(delta: Double): ServerValue + public companion object { + public val TIMESTAMP: ServerValue + public fun increment(delta: Double): ServerValue } } /** Serializer for [ServerValue]. Must be used with [FirebaseEncoder]/[FirebaseDecoder].*/ -object ServerValueSerializer: KSerializer by SpecialValueSerializer( +public object ServerValueSerializer : KSerializer by SpecialValueSerializer( serialName = "ServerValue", toNativeValue = ServerValue::nativeValue, fromNativeValue = { raw -> raw?.let(::ServerValue) ?: throw SerializationException("Cannot deserialize $raw") - } + }, ) 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 49d9e13f8..e72325282 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 @@ -5,110 +5,604 @@ package dev.gitlive.firebase.database import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.database.ChildEvent.Type.* +import dev.gitlive.firebase.database.ChildEvent.Type.ADDED +import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED +import dev.gitlive.firebase.database.ChildEvent.Type.MOVED +import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED +import dev.gitlive.firebase.internal.encode +import dev.gitlive.firebase.internal.encodeAsObject import kotlinx.coroutines.flow.Flow import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy /** Returns the [FirebaseDatabase] instance of the default [FirebaseApp]. */ -expect val Firebase.database: FirebaseDatabase +public expect val Firebase.database: FirebaseDatabase /** Returns the [FirebaseDatabase] instance for the specified [url]. */ -expect fun Firebase.database(url: String): FirebaseDatabase +public expect fun Firebase.database(url: String): FirebaseDatabase /** Returns the [FirebaseDatabase] instance of the given [FirebaseApp]. */ -expect fun Firebase.database(app: FirebaseApp): FirebaseDatabase +public expect fun Firebase.database(app: FirebaseApp): FirebaseDatabase /** Returns the [FirebaseDatabase] instance of the given [FirebaseApp] and [url]. */ -expect fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase +public expect fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase -expect class FirebaseDatabase { +/** + * The entry point for accessing a Firebase Database. You can get an instance by calling [Firebase.database]. To access a location in the database and read or write data, use [FirebaseDatabase.reference]. + */ +public expect class FirebaseDatabase { + /** + * Gets a DatabaseReference for the provided path. + * + * @param path Path to a location in your FirebaseDatabase. + * @return A DatabaseReference pointing to the specified path. + */ + public fun reference(path: String): DatabaseReference - class Settings { - val persistenceEnabled: Boolean - val persistenceCacheSizeBytes: Long? + /** + * Gets a DatabaseReference for the database root node. + * + * @return A DatabaseReference pointing to the root node. + */ + public fun reference(): DatabaseReference + public fun setLoggingEnabled(enabled: Boolean) - companion object { - fun createSettings(persistenceEnabled: Boolean = false, persistenceCacheSizeBytes: Long? = null): Settings - } - } + /** + * The Firebase Database client will cache synchronized data and keep track of all writes you've + * initiated while your application is running. It seamlessly handles intermittent network + * connections and re-sends write operations when the network connection is restored. + * + * However by default your write operations and cached data are only stored in-memory and will + * be lost when your app restarts. By setting this value to `true`, the data will be persisted to + * on-device (disk) storage and will thus be available again when the app is restarted (even when + * there is no network connectivity at that time). Note that this method must be called before + * creating your first Database reference and only needs to be called once per application. + * + * @param enabled Set to true to enable disk persistence, set to false to disable it. + */ + public fun setPersistenceEnabled(enabled: Boolean) - fun reference(path: String): DatabaseReference - fun reference(): DatabaseReference - fun setSettings(settings: Settings) - fun setLoggingEnabled(enabled: Boolean) - fun useEmulator(host: String, port: Int) -} + /** + * By default Firebase Database will use up to 10MB of disk space to cache data. If the cache + * grows beyond this size, Firebase Database will start removing data that hasn't been recently + * used. If you find that your application caches too little or too much data, call this method to + * change the cache size. This method must be called before creating your first Database reference + * and only needs to be called once per application. + * + * Note that the specified cache size is only an approximation and the size on disk may + * temporarily exceed it at times. Cache sizes smaller than 1 MB or greater than 100 MB are not + * supported. + * + * @param cacheSizeInBytes The new size of the cache in bytes. + */ + public fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) + + /** + * Modifies this FirebaseDatabase instance to communicate with the Realtime Database emulator. + * + *

Note: Call this method before using the instance to do any database operations. + * + * @param host the emulator host (for example, 10.0.2.2) + * @param port the emulator port (for example, 9000) + */ + public fun useEmulator(host: String, port: Int) -fun FirebaseDatabase.setPersistenceEnabled(enabled: Boolean) = setSettings(FirebaseDatabase.Settings.createSettings(persistenceEnabled = enabled)) + /** + * Shuts down our connection to the Firebase Database backend until [goOnline] is called. + */ + public fun goOffline() -data class ChildEvent internal constructor( + /** + * Resumes our connection to the Firebase Database backend after a previous [goOffline]. + * call. + */ + public fun goOnline() +} + +/** + * Used to emit events about changes in the child locations of a given [Query] when using the + * [childEvents] Flow. + */ +public data class ChildEvent internal constructor( val snapshot: DataSnapshot, val type: Type, - val previousChildName: String? + val previousChildName: String?, ) { - enum class Type { + public enum class Type { + /** + * Emitted when a new child is added to the location. + * + * @param snapshot An immutable snapshot of the data at the new child location + * @param previousChildName The key name of sibling location ordered before the new child. This + * ``` + * will be null for the first child node of a location. + * ``` + */ ADDED, + + /** + * Emitted when the data at a child location has changed. + * + * @param snapshot An immutable snapshot of the data at the new data at the child location + * @param previousChildName The key name of sibling location ordered before the child. This will + * ``` + * be null for the first child node of a location. + * ``` + */ CHANGED, + + /** + * Emitted when a child location's priority changes. + * + * @param snapshot An immutable snapshot of the data at the location that moved. + * @param previousChildName The key name of the sibling location ordered before the child + * ``` + * location. This will be null if this location is ordered first. + * ``` + */ MOVED, - REMOVED + + /** + * Emitted when a child is removed from the location. + * + * @param snapshot An immutable snapshot of the data at the child that was removed. + */ + REMOVED, } } -expect open class Query { - val valueEvents: Flow - fun childEvents(vararg types: ChildEvent.Type = arrayOf(ADDED, CHANGED, MOVED, REMOVED)): Flow - fun orderByKey(): Query - fun orderByValue(): Query - fun orderByChild(path: String): Query - fun startAt(value: String, key: String? = null): Query - fun startAt(value: Double, key: String? = null): Query - fun startAt(value: Boolean, key: String? = null): Query - fun endAt(value: String, key: String? = null): Query - fun endAt(value: Double, key: String? = null): Query - fun endAt(value: Boolean, key: String? = null): Query - fun limitToFirst(limit: Int): Query - fun limitToLast(limit: Int): Query - fun equalTo(value: String, key: String? = null): Query - fun equalTo(value: Double, key: String? = null): Query - fun equalTo(value: Boolean, key: String? = null): Query +internal expect open class NativeQuery + +/** + * The Query class (and its subclass, [DatabaseReference]) are used for reading data. + * Listeners are attached, and they will be triggered when the corresponding data changes. + * + * Instances of Query are obtained by calling [startAt], [endAt], or [limit] on a [DatabaseReference]. + */ +public expect open class Query internal constructor(nativeQuery: NativeQuery) { + public val valueEvents: Flow + public fun childEvents(vararg types: ChildEvent.Type = arrayOf(ADDED, CHANGED, MOVED, REMOVED)): Flow + + /** + * Creates a query in which child nodes are ordered by their keys. + * + * @return A query with the new constraint + */ + public fun orderByKey(): Query + + /** + * Creates a query in which nodes are ordered by their value + * + * @return A query with the new constraint + */ + public fun orderByValue(): Query + + /** + * Creates a query in which child nodes are ordered by the values of the specified path. + * + * @param path The path to the child node to use for sorting + * @return A query with the new constraint + */ + public fun orderByChild(path: String): Query + + /** + * Creates a query constrained to only return child nodes with a value greater than or equal to + * the given value, using the given `orderBy` directive or priority as default. + * + * @param value The value to start at, inclusive + * @return A query with the new constraint + */ + public fun startAt(value: String, key: String? = null): Query + + /** + * Creates a query constrained to only return child nodes with a value greater than or equal to + * the given value, using the given `orderBy` directive or priority as default. + * + * @param value The value to start at, inclusive + * @return A query with the new constraint + */ + public fun startAt(value: Double, key: String? = null): Query + + /** + * Creates a query constrained to only return child nodes with a value greater than or equal to + * the given value, using the given `orderBy` directive or priority as default. + * + * @param value The value to start at, inclusive + * @return A query with the new constraint + */ + public fun startAt(value: Boolean, key: String? = null): Query + + /** + * Creates a query constrained to only return child nodes with a value less than or equal to the + * given value, using the given `orderBy` directive or priority as default. + * + * @param value The value to end at, inclusive + * @return A query with the new constraint + */ + public fun endAt(value: String, key: String? = null): Query + + /** + * Creates a query constrained to only return child nodes with a value less than or equal to the + * given value, using the given `orderBy` directive or priority as default. + * + * @param value The value to end at, inclusive + * @return A query with the new constraint + */ + public fun endAt(value: Double, key: String? = null): Query + + /** + * Creates a query constrained to only return child nodes with a value less than or equal to the + * given value, using the given `orderBy` directive or priority as default. + * + * @param value The value to end at, inclusive + * @return A query with the new constraint + */ + public fun endAt(value: Boolean, key: String? = null): Query + + /** + * Creates a query with limit and anchor it to the start of the window. + * + * @param limit The maximum number of child nodes to return + * @return A query with the new constraint + */ + public fun limitToFirst(limit: Int): Query + + /** + * Creates a query with limit and anchor it to the end of the window. + * + * @param limit The maximum number of child nodes to return + * @return A query with the new constraint + */ + public fun limitToLast(limit: Int): Query + + /** + * Creates a query constrained to only return child nodes with the given value. + * + * @param value The value to query for + * @return A query with the new constraint + */ + public fun equalTo(value: String, key: String? = null): Query + + /** + * Creates a query constrained to only return child nodes with the given value. + * + * @param value The value to query for + * @return A query with the new constraint + */ + public fun equalTo(value: Double, key: String? = null): Query + + /** + * Creates a query constrained to only return child nodes with the given value. + * + * @param value The value to query for + * @return A query with the new constraint + */ + public fun equalTo(value: Boolean, key: String? = null): Query } -expect class DatabaseReference : Query { +internal expect class NativeDatabaseReference : NativeQuery { val key: String? - fun push(): DatabaseReference - fun child(path: String): DatabaseReference - fun onDisconnect(): OnDisconnect - suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) + fun push(): NativeDatabaseReference + suspend fun setValueEncoded(encodedValue: Any?) + suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) + fun child(path: String): NativeDatabaseReference + fun onDisconnect(): NativeOnDisconnect + suspend fun removeValue() - suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings = DecodeSettings(), transactionUpdate: (currentData: T) -> T): DataSnapshot + suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot } -expect class DataSnapshot { - val exists: Boolean - val key: String? - val ref: DatabaseReference - val value: Any? - inline fun value(): T - fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings()): T - fun child(path: String): DataSnapshot - val hasChildren: Boolean - val children: Iterable +/** + * A Firebase reference represents a particular location in your Database and can be used for + * reading or writing data to that Database location. + * + * This class is the starting point for all Database operations. After you've initialized it with + * a URL, you can use it to read data, write data, and to create new DatabaseReferences. + */ +public class DatabaseReference internal constructor(internal val nativeReference: NativeDatabaseReference) : Query(nativeReference) { + /** + * @return The last token in the location pointed to by this reference or null if this reference + * points to the database root + */ + public val key: String? = nativeReference.key + + /** + * Create a reference to an auto-generated child location. The child key is generated client-side + * and incorporates an estimate of the server's time for sorting purposes. Locations generated on + * a single client will be sorted in the order that they are created, and will be sorted + * approximately in order across all clients. + * + * @return A DatabaseReference pointing to the new location + */ + public fun push(): DatabaseReference = DatabaseReference(nativeReference.push()) + + /** + * Get a reference to location relative to this one + * + * @param path The relative path from this reference to the new one that should be created + * @return A new DatabaseReference to the given path + */ + public fun child(path: String): DatabaseReference = DatabaseReference(nativeReference.child(path)) + + /** + * Provides access to disconnect operations at this location + * + * @return An object for managing disconnect operations at this location + */ + public fun onDisconnect(): OnDisconnect = OnDisconnect(nativeReference.onDisconnect()) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }")) + public suspend inline fun setValue(value: T?, encodeDefaults: Boolean) { + setValue(value) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setValueEncoded(encode(value, buildSettings)) + } + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }")) + public suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) { + setValue(strategy, value) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setValueEncoded(encode(strategy, value, buildSettings)) + } + + @PublishedApi + internal suspend fun setValueEncoded(encodedValue: Any?) { + nativeReference.setValueEncoded(encodedValue) + } + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }")) + public suspend fun updateChildren(update: Map, encodeDefaults: Boolean) { + updateChildren(update) { + this.encodeDefaults = encodeDefaults + } + } + + /** + * Update the specific child keys to the specified values. Passing null in a map to + * updateChildren() will remove the value at the specified location. + * + * @param update The paths to update and their new values + * @return The {@link Task} for this operation. + */ + public suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + updateEncodedChildren( + encodeAsObject(update, buildSettings), + ) + } + + @PublishedApi + internal suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + nativeReference.updateEncodedChildren(encodedUpdate) + } + + /** + * Set the value at this location to 'null' + * + * @return The {@link Task} for this operation. + */ + public suspend fun removeValue() { + nativeReference.removeValue() + } + + /** + * Run a transaction on the data at this location. + * + * @param handler An object to handle running the transaction + */ + public suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot = nativeReference.runTransaction(strategy, buildSettings, transactionUpdate) +} + +/** + * A DataSnapshot instance contains data from a Firebase Database location. Any time you read + * Database data, you receive the data as a DataSnapshot. + * + * They are efficiently-generated immutable copies of the data at a Firebase Database location. They + * can't be modified and will never change. To modify data at a location, use a
+ * [DatabaseReference] reference (e.g. with [DatabaseReference.setValue]). + */ +public expect class DataSnapshot { + /** + * Returns true if the snapshot contains a non-null value. + * + * @return True if the snapshot contains a non-null value, otherwise false + */ + public val exists: Boolean + + /** + * @return The key name for the source location of this snapshot or null if this snapshot points + * to the database root. + */ + public val key: String? + + /** + * Used to obtain a reference to the source location for this snapshot. + * + * @return A DatabaseReference corresponding to the location that this snapshot came from + */ + public val ref: DatabaseReference + + /** + * [value] returns the data contained in this snapshot as native types. + * + * @return The data contained in this snapshot as native types or null if there is no data at this + * location. + */ + public val value: Any? + + /** + * [value] returns the data contained in this snapshot as native types. + * + * @return The data contained in this snapshot as native types or null if there is no data at this + * location. + */ + public inline fun value(): T + + /** + * [value] returns the data contained in this snapshot as native types. + * + * @return The data contained in this snapshot as native types or null if there is no data at this + * location. + */ + public inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T + + public fun child(path: String): DataSnapshot + + /** + * Indicates whether this snapshot has any children + * + * @return True if the snapshot has any children, otherwise false + */ + public val hasChildren: Boolean + + /** + * Gives access to all of the immediate children of this snapshot. Can be used in native for + * loops: + * + * ``` + * for (DataSnapshot child : parent.getChildren()) { + *     ... + * } + * ``` + * + * @return The immediate children of this snapshot + */ + public val children: Iterable } -expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException +/** + * Exception that gets thrown when an operation on Firebase Database fails. + */ +public expect class DatabaseException(message: String?, cause: Throwable?) : RuntimeException -expect class OnDisconnect { +internal expect class NativeOnDisconnect { suspend fun removeValue() suspend fun cancel() - suspend inline fun setValue(value: T, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings = EncodeSettings()) - suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings = EncodeSettings()) + suspend fun setEncodedValue(encodedValue: Any?) + suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) +} + +/** + * The OnDisconnect class is used to manage operations that will be run on the server when this + * client disconnects. It can be used to add or remove data based on a client's connection status. + * It is very useful in applications looking for 'presence' functionality. + * + * Instances of this class are obtained by calling [DatabaseReference.onDisconnect] + * on a Firebase Database ref. + */ +public class OnDisconnect internal constructor(internal val native: NativeOnDisconnect) { + /** + * Remove the value at this location when the client disconnects + * + * @return The {@link Task} for this operation. + */ + public suspend fun removeValue() { + native.removeValue() + } + + /** + * Cancel any disconnect operations that are queued up at this location + */ + public suspend fun cancel() { + native.cancel() + } + + /** + * Ensure the data at this location is set to the specified value when the client is disconnected + * (due to closing the browser, navigating to a new page, or network issues). + * + * This method is especially useful for implementing "presence" systems, where a value should be + * changed or cleared when a user disconnects so that they appear "offline" to other users. + * + * @param value The value to be set when a disconnect occurs or null to delete the existing value + */ + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(value) { this.encodeDefaults = encodeDefaults }")) + public suspend inline fun setValue(value: T?, encodeDefaults: Boolean) { + setValue(value) { this.encodeDefaults = encodeDefaults } + } + + /** + * Ensure the data at this location is set to the specified value when the client is disconnected + * (due to closing the browser, navigating to a new page, or network issues). + * + * This method is especially useful for implementing "presence" systems, where a value should be + * changed or cleared when a user disconnects so that they appear "offline" to other users. + * + * @param value The value to be set when a disconnect occurs or null to delete the existing value + */ + public suspend inline fun setValue(value: T?, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setEncodedValue(encode(value, buildSettings)) + } + + /** + * Ensure the data at this location is set to the specified value when the client is disconnected + * (due to closing the browser, navigating to a new page, or network issues). + * + * This method is especially useful for implementing "presence" systems, where a value should be + * changed or cleared when a user disconnects so that they appear "offline" to other users. + * + * @param value The value to be set when a disconnect occurs or null to delete the existing value + */ + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("setValue(strategy, value) { this.encodeDefaults = encodeDefaults }")) + public suspend fun setValue(strategy: SerializationStrategy, value: T, encodeDefaults: Boolean) { + setValue(strategy, value) { this.encodeDefaults = encodeDefaults } + } + + /** + * Ensure the data at this location is set to the specified value when the client is disconnected + * (due to closing the browser, navigating to a new page, or network issues). + * + * This method is especially useful for implementing "presence" systems, where a value should be + * changed or cleared when a user disconnects so that they appear "offline" to other users. + * + * @param value The value to be set when a disconnect occurs or null to delete the existing value + */ + public suspend inline fun setValue(strategy: SerializationStrategy, value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setValue(encode(strategy, value, buildSettings)) + } + + @PublishedApi + internal suspend fun setEncodedValue(encodedValue: Any?) { + native.setEncodedValue(encodedValue) + } + + /** + * Ensure the data has the specified child values updated when the client is disconnected + * + * @param update The paths to update, along with their desired values + */ + public suspend inline fun updateChildren(update: Map, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + updateEncodedChildren( + encodeAsObject(update, buildSettings), + ) + } + + /** + * Ensure the data has the specified child values updated when the client is disconnected + * + * @param update The paths to update, along with their desired values + */ + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("updateChildren(update) { this.encodeDefaults = encodeDefaults }")) + public suspend fun updateChildren(update: Map, encodeDefaults: Boolean) { + updateChildren(update) { + this.encodeDefaults = encodeDefaults + } + } + + @PublishedApi + internal suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + native.updateEncodedChildren(encodedUpdate) + } } diff --git a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt b/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt deleted file mode 100644 index 0ab16014b..000000000 --- a/firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.gitlive.firebase.database - -expect fun createFirebaseDatabaseTestSettings( - persistenceEnabled: Boolean = false, - persistenceCacheSizeBytes: Long? = null, -): FirebaseDatabase.Settings 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 00a8f85dc..a3f49fda8 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 @@ -1,18 +1,30 @@ package dev.gitlive.firebase.database -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy -import kotlin.test.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue import kotlin.time.Duration.Companion.minutes expect val emulatorHost: String expect val context: Any expect annotation class IgnoreForAndroidUnitTest() +expect annotation class IgnoreForAndroidTest() @IgnoreForAndroidUnitTest class FirebaseDatabaseTest { @@ -20,7 +32,7 @@ class FirebaseDatabaseTest { lateinit var database: FirebaseDatabase @Serializable - data class FirebaseDatabaseChildTest(val prop1: String? = null, val time: Double = 0.0) + data class FirebaseDatabaseChildTest(val prop1: String? = null, val time: Double = 0.0, val boolean: Boolean = true) @Serializable data class DatabaseTest(val title: String, val likes: Int = 0) @@ -32,11 +44,11 @@ class FirebaseDatabaseTest { FirebaseOptions( applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", - databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", + databaseUrl = "https://fir-kotlin-sdk-default-rtdb.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", - projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + projectId = "fir-kotlin-sdk-default-rtdb", + gcmSenderId = "846484016111", + ), ) database = Firebase.database(app).apply { @@ -75,55 +87,51 @@ class FirebaseDatabaseTest { .valueEvents .first() - val firebaseDatabaseChildCount = dataSnapshot.children.count() + val firebaseDatabaseChildCount = dataSnapshot.child("values").children.count() assertEquals(3, firebaseDatabaseChildCount) } -// @Test -// fun testBasicIncrementTransaction() = runTest { -// val data = DatabaseTest("PostOne", 2) -// val userRef = database.reference("users/user_1/post_id_1") -// setupDatabase(userRef, data, DatabaseTest.serializer()) -// -// // Check database before transaction -// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) -// assertEquals(data.title, userDocBefore.title) -// assertEquals(data.likes, userDocBefore.likes) -// -// // Run transaction -// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) } -// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) -// -// // Check the database after transaction -// assertEquals(data.title, userDocAfter.title) -// assertEquals(data.likes + 1, userDocAfter.likes) -// -// // cleanUp Firebase -// cleanUp() -// } -// -// @Test -// fun testBasicDecrementTransaction() = runTest { -// val data = DatabaseTest("PostTwo", 2) -// val userRef = database.reference("users/user_1/post_id_2") -// setupDatabase(userRef, data, DatabaseTest.serializer()) -// -// // Check database before transaction -// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) -// assertEquals(data.title, userDocBefore.title) -// assertEquals(data.likes, userDocBefore.likes) -// -// // Run transaction -// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) } -// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) -// -// // Check the database after transaction -// assertEquals(data.title, userDocAfter.title) -// assertEquals(data.likes - 1, userDocAfter.likes) -// -// // cleanUp Firebase -// cleanUp() -// } + @Test + fun testBasicIncrementTransaction() = runTest { + ensureDatabaseConnected() + val data = DatabaseTest("PostOne", 2) + val userRef = database.reference("users/user_1/post_id_1") + setupDatabase(userRef, data, DatabaseTest.serializer()) + + // Check database before transaction + val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) + assertEquals(data.title, userDocBefore.title) + assertEquals(data.likes, userDocBefore.likes) + + // Run transaction + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes + 1) } + val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) + + // Check the database after transaction + assertEquals(data.title, userDocAfter.title) + assertEquals(data.likes + 1, userDocAfter.likes) + } + + @Test + fun testBasicDecrementTransaction() = runTest { + ensureDatabaseConnected() + val data = DatabaseTest("PostTwo", 2) + val userRef = database.reference("users/user_1/post_id_2") + setupDatabase(userRef, data, DatabaseTest.serializer()) + + // Check database before transaction + val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer()) + assertEquals(data.title, userDocBefore.title) + assertEquals(data.likes, userDocBefore.likes) + + // Run transaction + val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { it.copy(likes = it.likes - 1) } + val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer()) + + // Check the database after transaction + assertEquals(data.title, userDocAfter.title) + assertEquals(data.likes - 1, userDocAfter.likes) + } @Test fun testSetServerTimestamp() = runTest { @@ -163,6 +171,72 @@ class FirebaseDatabaseTest { assertEquals(7.0, updatedValue) } + @Test + fun testBreakRules() = runTest { + ensureDatabaseConnected() + val reference = database + .reference("FirebaseRealtimeDatabaseTest") + val child = reference.child("lastActivity") + assertFailsWith { + child.setValue("stringNotAllowed") + } + child.setValue(2) + assertFailsWith { + reference.updateChildren(mapOf("lastActivity" to "stringNotAllowed")) + } + } + + @Test + fun testUpdateChildren() = runTest { + setupRealtimeData() + val reference = database + .reference("FirebaseRealtimeDatabaseTest") + val valueEvents = reference.child("lastActivity").valueEvents + assertTrue(valueEvents.first().exists) + reference.updateChildren(mapOf("test" to false, "nested" to mapOf("lastActivity" to null), "lastActivity" to null)) + assertFalse(valueEvents.first().exists) + } + + @Test + fun testBooleanValue() = runTest { + ensureDatabaseConnected() + val reference = database.reference("FirebaseRealtimeDatabaseBooleanTest") + val falseRef = reference.child("false") + val trueRef = reference.child("true") + falseRef.setValue(false) + trueRef.setValue(true) + val falseValue = falseRef.valueEvents.first().value() + val trueValue = trueRef.valueEvents.first().value() + assertFalse(falseValue) + assertTrue(trueValue) + } + + @Test + fun testBooleanValueInChild() = runTest { + ensureDatabaseConnected() + val reference = database.reference("FirebaseRealtimeDatabaseBooleanInChildTest") + reference.setValue(FirebaseDatabaseChildTest()) + val value = reference.valueEvents.first().value() + assertEquals(FirebaseDatabaseChildTest(), value) + } + + // Ignoring on Android Instrumented Tests due to bug in Firebase: https://github.com/firebase/firebase-android-sdk/issues/5870 + @IgnoreForAndroidTest + @Test + fun testUpdateChildrenOnDisconnect() = runTest { + setupRealtimeData() + val reference = database + .reference("FirebaseRealtimeDatabaseTest") + val valueEvents = reference.child("lastActivity").valueEvents + assertTrue(valueEvents.first().exists) + reference.onDisconnect().updateChildren(mapOf("test" to false, "nested" to mapOf("lastActivity" to null), "lastActivity" to null)) + database.goOffline() + + database.goOnline() + ensureDatabaseConnected() + assertFalse(valueEvents.first().exists) + } + private suspend fun setupRealtimeData() { ensureDatabaseConnected() val firebaseDatabaseTestReference = database @@ -172,9 +246,13 @@ class FirebaseDatabaseTest { val firebaseDatabaseChildTest2 = FirebaseDatabaseChildTest("bbb") val firebaseDatabaseChildTest3 = FirebaseDatabaseChildTest("ccc") - firebaseDatabaseTestReference.child("1").setValue(firebaseDatabaseChildTest1) - firebaseDatabaseTestReference.child("2").setValue(firebaseDatabaseChildTest2) - firebaseDatabaseTestReference.child("3").setValue(firebaseDatabaseChildTest3) + val values = firebaseDatabaseTestReference.child("values") + values.child("1").setValue(firebaseDatabaseChildTest1) + values.child("2").setValue(firebaseDatabaseChildTest2) + values.child("3").setValue(firebaseDatabaseChildTest3) + firebaseDatabaseTestReference.child("lastActivity").setValue(1) + firebaseDatabaseTestReference.child("test").setValue(true) + firebaseDatabaseTestReference.child("nested").setValue(mapOf("lastActivity" to 0)) } private suspend fun setupDatabase(ref: DatabaseReference, data: T, strategy: SerializationStrategy) { diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt index 7d9ad55ee..2ce33a91d 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/ServerValue.kt @@ -8,13 +8,14 @@ private typealias NativeServerValue = FIRServerValue /** Represents a Firebase ServerValue. */ @Serializable(with = ServerValueSerializer::class) -actual class ServerValue internal actual constructor( - internal actual val nativeValue: Any -){ - actual companion object { - actual val TIMESTAMP: ServerValue get() = ServerValue(NativeServerValue.timestamp()) +public actual class ServerValue internal actual constructor( + internal actual val nativeValue: Any, +) { + public actual companion object { + public actual val TIMESTAMP: ServerValue get() = ServerValue(NativeServerValue.timestamp()) + @Suppress("CAST_NEVER_SUCCEEDS") - actual fun increment(delta: Double): ServerValue = ServerValue(NativeServerValue.increment(delta as NSNumber)) + public actual fun increment(delta: Double): ServerValue = ServerValue(NativeServerValue.increment(delta as NSNumber)) } override fun equals(other: Any?): Boolean = diff --git a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt index ef67c46b9..c75290154 100644 --- a/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt @@ -4,17 +4,32 @@ package dev.gitlive.firebase.database -import cocoapods.FirebaseDatabase.* -import cocoapods.FirebaseDatabase.FIRDataEventType.* +import cocoapods.FirebaseDatabase.FIRDataEventType +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildAdded +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildChanged +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildMoved +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeChildRemoved +import cocoapods.FirebaseDatabase.FIRDataEventType.FIRDataEventTypeValue +import cocoapods.FirebaseDatabase.FIRDataSnapshot +import cocoapods.FirebaseDatabase.FIRDatabase +import cocoapods.FirebaseDatabase.FIRDatabaseQuery +import cocoapods.FirebaseDatabase.FIRDatabaseReference +import cocoapods.FirebaseDatabase.FIRTransactionResult import dev.gitlive.firebase.DecodeSettings -import dev.gitlive.firebase.EncodeSettings -import dev.gitlive.firebase.encode +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.database.ChildEvent.Type -import dev.gitlive.firebase.database.ChildEvent.Type.* -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode +import dev.gitlive.firebase.database.ChildEvent.Type.ADDED +import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED +import dev.gitlive.firebase.database.ChildEvent.Type.MOVED +import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED +import dev.gitlive.firebase.database.ios as publicIos +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.ios +import dev.gitlive.firebase.internal.reencodeTransformation +import dev.gitlive.firebase.ios import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose @@ -22,120 +37,130 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.produceIn +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.selects.select import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationStrategy import platform.Foundation.NSError import platform.Foundation.allObjects -import platform.darwin.dispatch_queue_t -import kotlin.collections.component1 -import kotlin.collections.component2 -actual val Firebase.database - by lazy { FirebaseDatabase(FIRDatabase.database()) } +public val FirebaseDatabase.ios: FIRDatabase get() = FIRDatabase.database() -actual fun Firebase.database(url: String) = +public actual val Firebase.database: FirebaseDatabase + by lazy { FirebaseDatabase(FIRDatabase.database()) } + +public actual fun Firebase.database(url: String): FirebaseDatabase = FirebaseDatabase(FIRDatabase.databaseWithURL(url)) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.database(app: FirebaseApp): FirebaseDatabase = FirebaseDatabase( - FIRDatabase.databaseForApp(app.ios as objcnames.classes.FIRApp) +public actual fun Firebase.database(app: FirebaseApp): FirebaseDatabase = FirebaseDatabase( + FIRDatabase.databaseForApp(app.ios as objcnames.classes.FIRApp), ) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase = FirebaseDatabase( - FIRDatabase.databaseForApp(app.ios as objcnames.classes.FIRApp, url) +public actual fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase = FirebaseDatabase( + FIRDatabase.databaseForApp(app.ios as objcnames.classes.FIRApp, url), ) -actual class FirebaseDatabase internal constructor(val ios: FIRDatabase) { - - actual data class Settings( - actual val persistenceEnabled: Boolean = false, - actual val persistenceCacheSizeBytes: Long? = null, - val callbackQueue: dispatch_queue_t = null - ) { +public actual class FirebaseDatabase internal constructor(internal val ios: FIRDatabase) { - actual companion object { - actual fun createSettings(persistenceEnabled: Boolean, persistenceCacheSizeBytes: Long?) = Settings(persistenceEnabled, persistenceCacheSizeBytes) - } - } + public actual fun reference(path: String): DatabaseReference = + DatabaseReference(NativeDatabaseReference(ios.referenceWithPath(path), ios.persistenceEnabled)) - actual fun reference(path: String) = - DatabaseReference(ios.referenceWithPath(path), ios.persistenceEnabled) + public actual fun reference(): DatabaseReference = + DatabaseReference(NativeDatabaseReference(ios.reference(), ios.persistenceEnabled)) - actual fun reference() = - DatabaseReference(ios.reference(), ios.persistenceEnabled) + public actual fun setPersistenceEnabled(enabled: Boolean) { + ios.persistenceEnabled = enabled + } - actual fun setSettings(settings: Settings) { - ios.persistenceEnabled = settings.persistenceEnabled - settings.persistenceCacheSizeBytes?.let { ios.setPersistenceCacheSizeBytes(it.toULong()) } - settings.callbackQueue?.let { ios.callbackQueue = it } + public actual fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) { + ios.setPersistenceCacheSizeBytes(cacheSizeInBytes.toULong()) } - actual fun setLoggingEnabled(enabled: Boolean) = + public actual fun setLoggingEnabled(enabled: Boolean) { FIRDatabase.setLoggingEnabled(enabled) + } - actual fun useEmulator(host: String, port: Int) = + public actual fun useEmulator(host: String, port: Int) { ios.useEmulatorWithHost(host, port.toLong()) + } + + public actual fun goOffline() { + ios.goOffline() + } + + public actual fun goOnline() { + ios.goOnline() + } } -fun Type.toEventType() = when(this) { +public fun Type.toEventType(): FIRDataEventType = when (this) { ADDED -> FIRDataEventTypeChildAdded CHANGED -> FIRDataEventTypeChildChanged MOVED -> FIRDataEventTypeChildMoved REMOVED -> FIRDataEventTypeChildRemoved } -actual open class Query internal constructor( +internal actual open class NativeQuery( open val ios: FIRDatabaseQuery, - val persistenceEnabled: Boolean + val persistenceEnabled: Boolean, +) + +public val Query.ios: FIRDatabaseQuery get() = nativeQuery.ios + +public actual open class Query internal actual constructor( + internal val nativeQuery: NativeQuery, ) { - actual fun orderByKey() = Query(ios.queryOrderedByKey(), persistenceEnabled) - actual fun orderByValue() = Query(ios.queryOrderedByValue(), persistenceEnabled) + internal constructor(ios: FIRDatabaseQuery, persistenceEnabled: Boolean) : this(NativeQuery(ios, persistenceEnabled)) - actual fun orderByChild(path: String) = Query(ios.queryOrderedByChild(path), persistenceEnabled) + internal open val ios: FIRDatabaseQuery = nativeQuery.ios + public val persistenceEnabled: Boolean = nativeQuery.persistenceEnabled - actual fun startAt(value: String, key: String?) = Query(ios.queryStartingAtValue(value, key), persistenceEnabled) + public actual fun orderByKey(): Query = Query(ios.queryOrderedByKey(), persistenceEnabled) - actual fun startAt(value: Double, key: String?) = Query(ios.queryStartingAtValue(value, key), persistenceEnabled) + public actual fun orderByValue(): Query = Query(ios.queryOrderedByValue(), persistenceEnabled) - actual fun startAt(value: Boolean, key: String?) = Query(ios.queryStartingAtValue(value, key), persistenceEnabled) + public actual fun orderByChild(path: String): Query = Query(ios.queryOrderedByChild(path), persistenceEnabled) - actual fun endAt(value: String, key: String?) = Query(ios.queryEndingAtValue(value, key), persistenceEnabled) + public actual fun startAt(value: String, key: String?): Query = Query(ios.queryStartingAtValue(value, key), persistenceEnabled) - actual fun endAt(value: Double, key: String?) = Query(ios.queryEndingAtValue(value, key), persistenceEnabled) + public actual fun startAt(value: Double, key: String?): Query = Query(ios.queryStartingAtValue(value, key), persistenceEnabled) - actual fun endAt(value: Boolean, key: String?) = Query(ios.queryEndingAtValue(value, key), persistenceEnabled) + public actual fun startAt(value: Boolean, key: String?): Query = Query(ios.queryStartingAtValue(value, key), persistenceEnabled) - actual fun limitToFirst(limit: Int) = Query(ios.queryLimitedToFirst(limit.toULong()), persistenceEnabled) + public actual fun endAt(value: String, key: String?): Query = Query(ios.queryEndingAtValue(value, key), persistenceEnabled) - actual fun limitToLast(limit: Int) = Query(ios.queryLimitedToLast(limit.toULong()), persistenceEnabled) + public actual fun endAt(value: Double, key: String?): Query = Query(ios.queryEndingAtValue(value, key), persistenceEnabled) - actual fun equalTo(value: String, key: String?) = Query(ios.queryEqualToValue(value, key), persistenceEnabled) + public actual fun endAt(value: Boolean, key: String?): Query = Query(ios.queryEndingAtValue(value, key), persistenceEnabled) - actual fun equalTo(value: Double, key: String?) = Query(ios.queryEqualToValue(value, key), persistenceEnabled) + public actual fun limitToFirst(limit: Int): Query = Query(ios.queryLimitedToFirst(limit.toULong()), persistenceEnabled) - actual fun equalTo(value: Boolean, key: String?) = Query(ios.queryEqualToValue(value, key), persistenceEnabled) + public actual fun limitToLast(limit: Int): Query = Query(ios.queryLimitedToLast(limit.toULong()), persistenceEnabled) - actual val valueEvents get() = callbackFlow { + public actual fun equalTo(value: String, key: String?): Query = Query(ios.queryEqualToValue(value, key), persistenceEnabled) + + public actual fun equalTo(value: Double, key: String?): Query = Query(ios.queryEqualToValue(value, key), persistenceEnabled) + + public actual fun equalTo(value: Boolean, key: String?): Query = Query(ios.queryEqualToValue(value, key), persistenceEnabled) + + public actual val valueEvents: Flow get() = callbackFlow { val handle = ios.observeEventType( FIRDataEventTypeValue, withBlock = { snapShot -> trySend(DataSnapshot(snapShot!!, persistenceEnabled)) - } + }, ) { close(DatabaseException(it.toString(), null)) } awaitClose { ios.removeObserverWithHandle(handle) } } - actual fun childEvents(vararg types: Type) = callbackFlow { + public actual fun childEvents(vararg types: Type): Flow = callbackFlow { val handles = types.map { type -> ios.observeEventType( type.toEventType(), andPreviousSiblingKeyWithBlock = { snapShot, key -> trySend(ChildEvent(DataSnapshot(snapShot!!, persistenceEnabled), type, key)) - } + }, ) { close(DatabaseException(it.toString(), null)) } } awaitClose { @@ -143,45 +168,38 @@ actual open class Query internal constructor( } } - override fun toString() = ios.toString() + override fun toString(): String = ios.toString() } -actual class DatabaseReference internal constructor( +internal actual class NativeDatabaseReference internal constructor( override val ios: FIRDatabaseReference, - persistenceEnabled: Boolean -): Query(ios, persistenceEnabled) { + persistenceEnabled: Boolean, +) : NativeQuery(ios, persistenceEnabled) { actual val key get() = ios.key - actual fun child(path: String) = DatabaseReference(ios.child(path), persistenceEnabled) - - actual fun push() = DatabaseReference(ios.childByAutoId(), persistenceEnabled) - actual fun onDisconnect() = OnDisconnect(ios, persistenceEnabled) + actual fun child(path: String) = NativeDatabaseReference(ios.child(path), persistenceEnabled) - actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { setValue(encode(value, encodeSettings), it) } - } + actual fun push() = NativeDatabaseReference(ios.childByAutoId(), persistenceEnabled) + actual fun onDisconnect() = NativeOnDisconnect(ios, persistenceEnabled) - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { setValue(encode(strategy, value, encodeSettings), it) } + actual suspend fun setValueEncoded(encodedValue: Any?) { + ios.await(persistenceEnabled) { setValue(encodedValue, it) } } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { updateChildValues(encode(update, encodeSettings) as Map, it) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + ios.await(persistenceEnabled) { updateChildValues(encodedUpdate.ios, it) } } actual suspend fun removeValue() { ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) } } - actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot { val deferred = CompletableDeferred() ios.runTransactionBlock( block = { firMutableData -> - firMutableData?.value = firMutableData?.value?.let { - transactionUpdate(decode(strategy, it, decodeSettings)) - } + firMutableData?.value = reencodeTransformation(strategy, firMutableData?.value, buildSettings, transactionUpdate) FIRTransactionResult.successWithValue(firMutableData!!) }, andCompletionBlock = { error, _, snapshot -> @@ -191,40 +209,42 @@ actual class DatabaseReference internal constructor( deferred.complete(DataSnapshot(snapshot!!, persistenceEnabled)) } }, - withLocalEvents = false + withLocalEvents = false, ) return deferred.await() } } -@Suppress("UNCHECKED_CAST") -actual class DataSnapshot internal constructor( - val ios: FIRDataSnapshot, - private val persistenceEnabled: Boolean +public val DatabaseReference.ios: FIRDatabaseReference get() = nativeReference.ios +public val DataSnapshot.ios: FIRDataSnapshot get() = ios + +public actual class DataSnapshot internal constructor( + internal val ios: FIRDataSnapshot, + private val persistenceEnabled: Boolean, ) { - actual val exists get() = ios.exists() + public actual val exists: Boolean get() = ios.exists() - actual val key: String? get() = ios.key + public actual val key: String? get() = ios.key - actual val ref: DatabaseReference get() = DatabaseReference(ios.ref, persistenceEnabled) + public actual val ref: DatabaseReference get() = DatabaseReference(NativeDatabaseReference(ios.ref, persistenceEnabled)) - actual val value get() = ios.value + public actual val value: Any? get() = ios.value - actual inline fun value() = - decode(value = ios.value) + public actual inline fun value(): T = + decode(value = publicIos.value) - actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, ios.value, decodeSettings) + public actual inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(strategy, publicIos.value, buildSettings) - actual fun child(path: String) = DataSnapshot(ios.childSnapshotForPath(path), persistenceEnabled) - actual val hasChildren get() = ios.hasChildren() - actual val children: Iterable get() = ios.children.allObjects.map { DataSnapshot(it as FIRDataSnapshot, persistenceEnabled) } + public actual fun child(path: String): DataSnapshot = DataSnapshot(ios.childSnapshotForPath(path), persistenceEnabled) + public actual val hasChildren: Boolean get() = ios.hasChildren() + public actual val children: Iterable get() = ios.children.allObjects.map { DataSnapshot(it as FIRDataSnapshot, persistenceEnabled) } } -actual class OnDisconnect internal constructor( +internal actual class NativeOnDisconnect internal constructor( val ios: FIRDatabaseReference, - val persistenceEnabled: Boolean + val persistenceEnabled: Boolean, ) { actual suspend fun removeValue() { ios.await(persistenceEnabled) { onDisconnectRemoveValueWithCompletionBlock(it) } @@ -234,58 +254,53 @@ actual class OnDisconnect internal constructor( ios.await(persistenceEnabled) { cancelDisconnectOperationsWithCompletionBlock(it) } } - actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { onDisconnectSetValue(encode(value, encodeSettings), it) } - } - - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { onDisconnectSetValue(encode(strategy, value, encodeSettings), it) } + actual suspend fun setEncodedValue(encodedValue: Any?) { + ios.await(persistenceEnabled) { onDisconnectSetValue(encodedValue, it) } } - @Suppress("UNCHECKED_CAST") - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) { - ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(update.mapValues { (_, it) -> encode(it, encodeSettings) } as Map, it) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) { + ios.await(persistenceEnabled) { onDisconnectUpdateChildValues(encodedUpdate.ios, it) } } } -actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) +public val OnDisconnect.ios: FIRDatabaseReference get() = native.ios +public val OnDisconnect.persistenceEnabled: Boolean get() = native.persistenceEnabled + +public actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) -private suspend inline fun T.awaitResult(whileOnline: Boolean, function: T.(callback: (NSError?, R?) -> Unit) -> Unit): R { +internal suspend inline fun T.awaitResult(whileOnline: Boolean, function: T.(callback: (NSError?, R?) -> Unit) -> Unit): R { val job = CompletableDeferred() - val callback = { error: NSError?, result: R? -> - if(error == null) { + function { error, result -> + if (error == null) { job.complete(result) } else { job.completeExceptionally(DatabaseException(error.toString(), null)) } } - function(callback) - return job.run { if(whileOnline) awaitWhileOnline() else await() } as R + return job.run { if (whileOnline) awaitWhileOnline() else await() } as R } -suspend inline fun T.await(whileOnline: Boolean, function: T.(callback: (NSError?, FIRDatabaseReference?) -> Unit) -> Unit) { +internal suspend inline fun T.await(whileOnline: Boolean, function: T.(callback: (NSError?, FIRDatabaseReference?) -> Unit) -> Unit) { val job = CompletableDeferred() - val callback = { error: NSError?, _: FIRDatabaseReference? -> - if(error == null) { + function { error, _ -> + if (error == null) { job.complete(Unit) } else { job.completeExceptionally(DatabaseException(error.toString(), null)) } } - function(callback) - job.run { if(whileOnline) awaitWhileOnline() else await() } + job.run { if (whileOnline) awaitWhileOnline() else await() } } @FlowPreview -suspend fun CompletableDeferred.awaitWhileOnline(): T = coroutineScope { - +internal suspend fun CompletableDeferred.awaitWhileOnline(): T = coroutineScope { val notConnected = Firebase.database .reference(".info/connected") .valueEvents .filter { !it.value() } .produceIn(this) - select { + select { onAwait { it.also { notConnected.cancel() } } notConnected.onReceive { throw DatabaseException("Database not connected", null) } } diff --git a/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt b/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt deleted file mode 100644 index ebacd2e64..000000000 --- a/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.gitlive.firebase.database - -import platform.darwin.DISPATCH_QUEUE_CONCURRENT -import platform.darwin.dispatch_queue_create - -private val firebaseDatabaseTestQueue = dispatch_queue_create("FirebaseDatabaseQueue", DISPATCH_QUEUE_CONCURRENT) - -actual fun createFirebaseDatabaseTestSettings( - persistenceEnabled: Boolean, - persistenceCacheSizeBytes: Long?, -) = FirebaseDatabase.Settings(persistenceEnabled, persistenceCacheSizeBytes, firebaseDatabaseTestQueue) diff --git a/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt index 410cf18bb..244b31c82 100644 --- a/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/iosTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -10,3 +10,6 @@ actual val context: Any = Unit @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidTest 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 cfb14b69b..d2af58f7a 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 @@ -6,12 +6,12 @@ import dev.gitlive.firebase.database.externals.increment as jsIncrement /** Represents a Firebase ServerValue. */ @Serializable(with = ServerValueSerializer::class) -actual class ServerValue internal actual constructor( - internal actual val nativeValue: Any -){ - actual companion object { - actual val TIMESTAMP: ServerValue get() = ServerValue(serverTimestamp()) - actual fun increment(delta: Double): ServerValue = ServerValue(jsIncrement(delta)) +public actual class ServerValue internal actual constructor( + internal actual val nativeValue: Any, +) { + public actual companion object { + public actual val TIMESTAMP: ServerValue get() = ServerValue(serverTimestamp()) + public 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 e94a7af78..f4f38d8f7 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 @@ -4,18 +4,44 @@ package dev.gitlive.firebase.database -import dev.gitlive.firebase.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.EncodeDecodeSettingsBuilder +import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.database.externals.* -import kotlinx.coroutines.* +import dev.gitlive.firebase.database.externals.CancelCallback +import dev.gitlive.firebase.database.externals.ChangeSnapshotCallback +import dev.gitlive.firebase.database.externals.Database +import dev.gitlive.firebase.database.externals.child +import dev.gitlive.firebase.database.externals.connectDatabaseEmulator +import dev.gitlive.firebase.database.externals.enableLogging +import dev.gitlive.firebase.database.externals.getDatabase +import dev.gitlive.firebase.database.externals.onChildAdded +import dev.gitlive.firebase.database.externals.onChildChanged +import dev.gitlive.firebase.database.externals.onChildMoved +import dev.gitlive.firebase.database.externals.onChildRemoved +import dev.gitlive.firebase.database.externals.onDisconnect +import dev.gitlive.firebase.database.externals.onValue +import dev.gitlive.firebase.database.externals.push +import dev.gitlive.firebase.database.externals.query +import dev.gitlive.firebase.database.externals.ref +import dev.gitlive.firebase.database.externals.remove +import dev.gitlive.firebase.database.externals.set +import dev.gitlive.firebase.database.externals.update +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.js +import dev.gitlive.firebase.internal.reencodeTransformation +import dev.gitlive.firebase.js +import kotlinx.coroutines.asDeferred import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.produceIn import kotlinx.coroutines.selects.select 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 @@ -24,76 +50,86 @@ 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.goOffline as jsGoOffline +import dev.gitlive.firebase.database.externals.goOnline as jsGoOnline 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 import dev.gitlive.firebase.database.externals.runTransaction as jsRunTransaction +import dev.gitlive.firebase.database.externals.startAt as jsStartAt +import dev.gitlive.firebase.database.js as publicJs -actual val Firebase.database +public actual val Firebase.database: FirebaseDatabase get() = rethrow { FirebaseDatabase(getDatabase()) } -actual fun Firebase.database(app: FirebaseApp) = +public actual fun Firebase.database(app: FirebaseApp): FirebaseDatabase = rethrow { FirebaseDatabase(getDatabase(app = app.js)) } -actual fun Firebase.database(url: String) = +public actual fun Firebase.database(url: String): FirebaseDatabase = rethrow { FirebaseDatabase(getDatabase(url = url)) } -actual fun Firebase.database(app: FirebaseApp, url: String) = +public actual fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase = rethrow { FirebaseDatabase(getDatabase(app = app.js, url = url)) } -actual class FirebaseDatabase internal constructor(val js: Database) { +public val FirebaseDatabase.js get() = js - actual data class Settings( - actual val persistenceEnabled: Boolean = false, - actual val persistenceCacheSizeBytes: Long? = null, - ) { +public actual class FirebaseDatabase internal constructor(internal val js: Database) { - actual companion object { - actual fun createSettings(persistenceEnabled: Boolean, persistenceCacheSizeBytes: Long?) = Settings(persistenceEnabled, persistenceCacheSizeBytes) - } - } + public actual fun reference(path: String): DatabaseReference = rethrow { DatabaseReference(NativeDatabaseReference(ref(js, path), js)) } + public actual fun reference(): DatabaseReference = rethrow { DatabaseReference(NativeDatabaseReference(ref(js), js)) } + public actual fun setPersistenceEnabled(enabled: Boolean) {} + public actual fun setPersistenceCacheSizeBytes(cacheSizeInBytes: Long) {} + public actual fun setLoggingEnabled(enabled: Boolean): Unit = rethrow { enableLogging(enabled) } + public actual fun useEmulator(host: String, port: Int): Unit = rethrow { connectDatabaseEmulator(js, host, port) } + + public actual fun goOffline(): Unit = rethrow { jsGoOffline(js) } - actual fun reference(path: String) = rethrow { DatabaseReference(ref(js, path), js) } - actual fun reference() = rethrow { DatabaseReference(ref(js), js) } - actual fun setSettings(settings: Settings) {} - actual fun setLoggingEnabled(enabled: Boolean) = rethrow { enableLogging(enabled) } - actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) } + public actual fun goOnline(): Unit = rethrow { jsGoOnline(js) } } -actual open class Query internal constructor( +internal actual open class NativeQuery( open val js: JsQuery, - val database: Database + val database: Database, +) + +public val Query.js: JsQuery get() = nativeQuery.js + +public actual open class Query internal actual constructor( + internal val nativeQuery: NativeQuery, ) { - 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) + internal constructor(js: JsQuery, database: Database) : this(NativeQuery(js, database)) + + public val database: Database = nativeQuery.database - actual val valueEvents - get() = callbackFlow { + public actual fun orderByKey(): Query = Query(query(publicJs, jsOrderByKey()), database) + public actual fun orderByValue(): Query = Query(query(publicJs, jsOrderByValue()), database) + public actual fun orderByChild(path: String): Query = Query(query(publicJs, jsOrderByChild(path)), database) + + public actual val valueEvents: Flow + get() = callbackFlow { val unsubscribe = rethrow { onValue( - query = js, + query = publicJs, callback = { trySend(DataSnapshot(it, database)) }, - cancelCallback = { close(DatabaseException(it)).run { } } + cancelCallback = { close(DatabaseException(it)).run { } }, ) } awaitClose { rethrow { unsubscribe() } } } - actual fun childEvents(vararg types: ChildEvent.Type) = callbackFlow { + public actual fun childEvents(vararg types: ChildEvent.Type): Flow = callbackFlow { val unsubscribes = rethrow { types.map { type -> val callback: ChangeSnapshotCallback = { snapshot, previousChildName -> trySend( ChildEvent( - DataSnapshot(snapshot, database), + DataSnapshot(snapshot, database), type, - previousChildName - ) + previousChildName, + ), ) } @@ -102,124 +138,129 @@ actual open class Query internal constructor( } 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) + ChildEvent.Type.ADDED -> onChildAdded(publicJs, callback, cancelCallback) + ChildEvent.Type.CHANGED -> onChildChanged(publicJs, callback, cancelCallback) + ChildEvent.Type.MOVED -> onChildMoved(publicJs, callback, cancelCallback) + ChildEvent.Type.REMOVED -> onChildRemoved(publicJs, callback, cancelCallback) } } } awaitClose { rethrow { unsubscribes.forEach { it.invoke() } } } } - actual fun startAt(value: String, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database) - - actual fun startAt(value: Double, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database) + public actual fun startAt(value: String, key: String?): Query = Query(query(publicJs, jsStartAt(value, key ?: undefined)), database) - actual fun startAt(value: Boolean, key: String?) = Query(query(js, jsStartAt(value, key ?: undefined)), database) + public actual fun startAt(value: Double, key: String?): Query = Query(query(publicJs, jsStartAt(value, key ?: undefined)), database) - actual fun endAt(value: String, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database) + public actual fun startAt(value: Boolean, key: String?): Query = Query(query(publicJs, jsStartAt(value, key ?: undefined)), database) - actual fun endAt(value: Double, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database) + public actual fun endAt(value: String, key: String?): Query = Query(query(publicJs, jsEndAt(value, key ?: undefined)), database) - actual fun endAt(value: Boolean, key: String?) = Query(query(js, jsEndAt(value, key ?: undefined)), database) + public actual fun endAt(value: Double, key: String?): Query = Query(query(publicJs, jsEndAt(value, key ?: undefined)), database) - actual fun limitToFirst(limit: Int) = Query(query(js, jsLimitToFirst(limit)), database) + public actual fun endAt(value: Boolean, key: String?): Query = Query(query(publicJs, jsEndAt(value, key ?: undefined)), database) - actual fun limitToLast(limit: Int) = Query(query(js, jsLimitToLast(limit)), database) + public actual fun limitToFirst(limit: Int): Query = Query(query(publicJs, jsLimitToFirst(limit)), database) - actual fun equalTo(value: String, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database) + public actual fun limitToLast(limit: Int): Query = Query(query(publicJs, jsLimitToLast(limit)), database) - actual fun equalTo(value: Double, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database) + public actual fun equalTo(value: String, key: String?): Query = Query(query(publicJs, jsEqualTo(value, key ?: undefined)), database) - actual fun equalTo(value: Boolean, key: String?) = Query(query(js, jsEqualTo(value, key ?: undefined)), database) + public actual fun equalTo(value: Double, key: String?): Query = Query(query(publicJs, jsEqualTo(value, key ?: undefined)), database) - override fun toString() = js.toString() + public actual fun equalTo(value: Boolean, key: String?): Query = Query(query(publicJs, jsEqualTo(value, key ?: undefined)), database) + override fun toString(): String = publicJs.toString() } -actual class DatabaseReference internal constructor( +internal actual class NativeDatabaseReference internal constructor( override val js: JsDatabaseReference, - database: Database -) : Query(js, database) { + database: Database, +) : NativeQuery(js, database) { actual val key get() = rethrow { js.key } - actual fun push() = rethrow { DatabaseReference(push(js), database) } - actual fun child(path: String) = rethrow { DatabaseReference(child(js, path), database) } + actual fun push() = rethrow { NativeDatabaseReference(push(js), database) } + actual fun child(path: String) = rethrow { NativeDatabaseReference(child(js, path), database) } - actual fun onDisconnect() = rethrow { OnDisconnect(onDisconnect(js), database) } - - actual suspend fun updateChildren(update: Map, encodeSettings: EncodeSettings) = - rethrow { update(js, encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } + actual fun onDisconnect() = rethrow { NativeOnDisconnect(onDisconnect(js), database) } actual suspend fun removeValue() = rethrow { remove(js).awaitWhileOnline(database) } - actual suspend inline fun setValue(value: T?, encodeSettings: EncodeSettings) = rethrow { - set(js, encode(value, encodeSettings)).awaitWhileOnline(database) + actual suspend fun setValueEncoded(encodedValue: Any?) = rethrow { + set(js, encodedValue).awaitWhileOnline(database) } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - rethrow { set(js, encode(strategy, value, encodeSettings)).awaitWhileOnline(database) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = + rethrow { update(js, encodedUpdate.js).awaitWhileOnline(database) } - actual suspend fun runTransaction(strategy: KSerializer, decodeSettings: DecodeSettings, transactionUpdate: (currentData: T) -> T): DataSnapshot { - return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database) - } + actual suspend fun runTransaction(strategy: KSerializer, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot = DataSnapshot( + jsRunTransaction(js, transactionUpdate = { currentData -> + reencodeTransformation(strategy, currentData ?: json(), buildSettings, transactionUpdate) + }).awaitWhileOnline(database).snapshot, + database, + ) } -actual class DataSnapshot internal constructor( - val js: JsDataSnapshot, - val database: Database +public val DataSnapshot.js: JsDataSnapshot get() = js + +public actual class DataSnapshot internal constructor( + internal val js: JsDataSnapshot, + public val database: Database, ) { - actual val value get(): Any? { + public actual val value: Any? get() { check(!hasChildren) { "DataSnapshot.value can only be used for primitive values (snapshots without children)" } return js.`val`() } - actual inline fun value() = - rethrow { decode(value = js.`val`()) } + public actual inline fun value(): T = + rethrow { decode(value = publicJs.`val`()) } - actual fun value(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - rethrow { decode(strategy, js.`val`(), decodeSettings) } + public actual inline fun value(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit): T = + rethrow { decode(strategy, publicJs.`val`(), buildSettings) } - actual val exists get() = rethrow { js.exists() } - actual val key get() = rethrow { js.key } - actual fun child(path: String) = DataSnapshot(js.child(path), database) - actual val hasChildren get() = js.hasChildren() - actual val children: Iterable = rethrow { + public actual val exists: Boolean get() = rethrow { js.exists() } + public actual val key: String? get() = rethrow { js.key } + public actual fun child(path: String): DataSnapshot = DataSnapshot(js.child(path), database) + public actual val hasChildren: Boolean get() = js.hasChildren() + public actual val children: Iterable = rethrow { ArrayList(js.size).also { - js.forEach { snapshot -> it.add(DataSnapshot(snapshot, database)); false /* don't cancel enumeration */ } + js.forEach { snapshot -> + it.add(DataSnapshot(snapshot, database)) + false // don't cancel enumeration + } } } - actual val ref: DatabaseReference - get() = DatabaseReference(js.ref, database) - + public actual val ref: DatabaseReference + get() = DatabaseReference(NativeDatabaseReference(js.ref, database)) } -actual class OnDisconnect internal constructor( +internal actual class NativeOnDisconnect internal constructor( val js: JsOnDisconnect, - val database: Database + 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, encodeSettings: EncodeSettings) = - rethrow { js.update(encode(update, encodeSettings) ?: json()).awaitWhileOnline(database) } + actual suspend fun cancel() = rethrow { js.cancel().awaitWhileOnline(database) } - actual suspend inline fun setValue(value: T, encodeSettings: EncodeSettings) = - rethrow { js.set(encode(value, encodeSettings)).awaitWhileOnline(database) } + actual suspend fun setEncodedValue(encodedValue: Any?) = + rethrow { js.set(encodedValue).awaitWhileOnline(database) } - actual suspend fun setValue(strategy: SerializationStrategy, value: T, encodeSettings: EncodeSettings) = - rethrow { js.set(encode(strategy, value, encodeSettings)).awaitWhileOnline(database) } + actual suspend fun updateEncodedChildren(encodedUpdate: EncodedObject) = + rethrow { js.update(encodedUpdate.js).awaitWhileOnline(database) } } -actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) { - constructor(error: dynamic) : this("${error.code ?: "UNKNOWN"}: ${error.message}", error.unsafeCast()) +public val OnDisconnect.js: dev.gitlive.firebase.database.externals.OnDisconnect get() = native.js +public val OnDisconnect.database: Database get() = native.database + +public actual class DatabaseException actual constructor(message: String?, cause: Throwable?) : RuntimeException(message, cause) { + public constructor(error: dynamic) : this("${error.code ?: "UNKNOWN"}: ${error.message}", error.unsafeCast()) } -inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.database.rethrow { function() } +@PublishedApi +internal inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.database.rethrow { function() } -inline fun rethrow(function: () -> R): R { +@PublishedApi +internal inline fun rethrow(function: () -> R): R { try { return function() } catch (e: Exception) { @@ -229,17 +270,16 @@ inline fun rethrow(function: () -> R): R { } } -suspend fun Promise.awaitWhileOnline(database: Database): T = coroutineScope { - +@PublishedApi +internal suspend fun Promise.awaitWhileOnline(database: Database): T = coroutineScope { val notConnected = FirebaseDatabase(database) .reference(".info/connected") .valueEvents .filter { !it.value() } .produceIn(this) - select { + select { this@awaitWhileOnline.asDeferred().onAwait { it.also { notConnected.cancel() } } notConnected.onReceive { throw DatabaseException("Database not connected", null) } } - } 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 index 86a592432..9d706b15a 100644 --- 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 @@ -4,6 +4,6 @@ package dev.gitlive.firebase.database.externals -typealias ChangeSnapshotCallback = (data: DataSnapshot, previousChildName: String?) -> Unit -typealias ValueSnapshotCallback = (data: DataSnapshot) -> Unit -typealias CancelCallback = (error: Throwable) -> Unit +public typealias ChangeSnapshotCallback = (data: DataSnapshot, previousChildName: String?) -> Unit +public typealias ValueSnapshotCallback = (data: DataSnapshot) -> Unit +public 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 index 52f71422b..f8955f400 100644 --- 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 @@ -3,145 +3,145 @@ package dev.gitlive.firebase.database.externals -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Unsubscribe import dev.gitlive.firebase.externals.FirebaseApp import kotlin.js.Promise -external fun child(parent: DatabaseReference, path: String): DatabaseReference +public external fun child(parent: DatabaseReference, path: String): DatabaseReference -external fun connectDatabaseEmulator( +public external fun connectDatabaseEmulator( db: Database, host: String, port: Int, - options: Any? = definedExternally + options: Any? = definedExternally, ) -external fun enableLogging(enabled: Boolean?, persistent: Boolean? = definedExternally) +public external fun enableLogging(enabled: Boolean?, persistent: Boolean? = definedExternally) -external fun endAt(value: Any?, key: String? = definedExternally): QueryConstraint +public external fun endAt(value: Any?, key: String? = definedExternally): QueryConstraint -external fun endBefore(value: Any?, key: String? = definedExternally): QueryConstraint +public external fun endBefore(value: Any?, key: String? = definedExternally): QueryConstraint -external fun equalTo(value: Any?, key: String? = definedExternally): QueryConstraint +public external fun equalTo(value: Any?, key: String? = definedExternally): QueryConstraint -external fun get(query: Query): Promise +public external fun get(query: Query): Promise -external fun getDatabase( +public external fun getDatabase( app: FirebaseApp? = definedExternally, - url: String? = definedExternally + url: String? = definedExternally, ): Database -external fun increment(delta: Double): Any +public external fun increment(delta: Double): Any -external fun limitToFirst(limit: Int): QueryConstraint +public external fun limitToFirst(limit: Int): QueryConstraint -external fun limitToLast(limit: Int): QueryConstraint +public external fun limitToLast(limit: Int): QueryConstraint -external fun off(query: Query, eventType: String?, callback: Any?) +public external fun off(query: Query, eventType: String?, callback: Any?) -external fun goOffline(db: Database) +public external fun goOffline(db: Database) -external fun goOnline(db: Database) +public external fun goOnline(db: Database) -external fun onChildAdded( +public external fun onChildAdded( query: Query, callback: ChangeSnapshotCallback, cancelCallback: CancelCallback? = definedExternally, ): Unsubscribe -external fun onChildChanged( +public external fun onChildChanged( query: Query, callback: ChangeSnapshotCallback, cancelCallback: CancelCallback? = definedExternally, ): Unsubscribe -external fun onChildMoved( +public external fun onChildMoved( query: Query, callback: ChangeSnapshotCallback, cancelCallback: CancelCallback? = definedExternally, ): Unsubscribe -external fun onChildRemoved( +public external fun onChildRemoved( query: Query, callback: ChangeSnapshotCallback, cancelCallback: CancelCallback? = definedExternally, ): Unsubscribe -external fun onValue( +public external fun onValue( query: Query, callback: ValueSnapshotCallback, cancelCallback: CancelCallback? = definedExternally, ): Unsubscribe -external fun onDisconnect(ref: DatabaseReference): OnDisconnect +public external fun onDisconnect(ref: DatabaseReference): OnDisconnect -external fun orderByChild(path: String): QueryConstraint +public external fun orderByChild(path: String): QueryConstraint -external fun orderByKey(): QueryConstraint +public external fun orderByKey(): QueryConstraint -external fun orderByValue(): QueryConstraint +public external fun orderByValue(): QueryConstraint -external fun push(parent: DatabaseReference, value: Any? = definedExternally): ThenableReference +public external fun push(parent: DatabaseReference, value: Any? = definedExternally): ThenableReference -external fun query(query: Query, vararg queryConstraints: QueryConstraint): Query +public external fun query(query: Query, vararg queryConstraints: QueryConstraint): Query -external fun ref(db: Database, path: String? = definedExternally): DatabaseReference +public external fun ref(db: Database, path: String? = definedExternally): DatabaseReference -external fun remove(ref: DatabaseReference): Promise +public external fun remove(ref: DatabaseReference): Promise -external fun serverTimestamp(): Any +public external fun serverTimestamp(): Any -external fun set(ref: DatabaseReference, value: Any?): Promise +public external fun set(ref: DatabaseReference, value: Any?): Promise -external fun startAfter(value: Any?, key: String? = definedExternally): QueryConstraint +public external fun startAfter(value: Any?, key: String? = definedExternally): QueryConstraint -external fun startAt(value: Any?, key: String? = definedExternally): QueryConstraint +public external fun startAt(value: Any?, key: String? = definedExternally): QueryConstraint -external fun update(ref: DatabaseReference, values: Any): Promise +public external fun update(ref: DatabaseReference, values: Any): Promise -external fun runTransaction( +public external fun runTransaction( ref: DatabaseReference, transactionUpdate: (currentData: T) -> T, - options: Any? = definedExternally + options: Any? = definedExternally, ): Promise -external interface Database { - val app: FirebaseApp +public external interface Database { + public val app: FirebaseApp } -external interface Query { - val ref: DatabaseReference +public external interface Query { + public val ref: DatabaseReference } -external interface QueryConstraint +public external interface QueryConstraint -external interface DatabaseReference : Query { - val key: String? - val parent: DatabaseReference? - val root: DatabaseReference +public external interface DatabaseReference : Query { + public val key: String? + public val parent: DatabaseReference? + public 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; +public external interface ThenableReference : DatabaseReference + +public external interface DataSnapshot { + public val key: String? + public val size: Int + public val ref: DatabaseReference + public fun `val`(): Any + public fun exists(): Boolean + public fun forEach(action: (a: DataSnapshot) -> Boolean): Boolean + public fun child(path: String): DataSnapshot + public fun hasChildren(): Boolean } -external interface OnDisconnect { - fun cancel(): Promise - fun remove(): Promise - fun set(value: Any?): Promise - fun update(value: Any): Promise +public external interface OnDisconnect { + public fun cancel(): Promise + public fun remove(): Promise + public fun set(value: Any?): Promise + public fun update(value: Any): Promise } -external interface TransactionResult { - val committed: Boolean - val snapshot: DataSnapshot +public external interface TransactionResult { + public val committed: Boolean + public val snapshot: DataSnapshot } diff --git a/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt b/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt deleted file mode 100644 index ce3908c54..000000000 --- a/firebase-database/src/jsTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.gitlive.firebase.database - -actual fun createFirebaseDatabaseTestSettings( - persistenceEnabled: Boolean, - persistenceCacheSizeBytes: Long?, -) = FirebaseDatabase.Settings(persistenceEnabled, persistenceCacheSizeBytes) 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 48e4649bb..e16889d97 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 @@ -6,3 +6,6 @@ actual val context: Any = Unit @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidTest diff --git a/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt b/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt deleted file mode 100644 index ce3908c54..000000000 --- a/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/createFirebaseDatabaseTestSettings.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.gitlive.firebase.database - -actual fun createFirebaseDatabaseTestSettings( - persistenceEnabled: Boolean, - persistenceCacheSizeBytes: Long?, -) = FirebaseDatabase.Settings(persistenceEnabled, persistenceCacheSizeBytes) diff --git a/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt b/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt index 48cc1ba9d..2ba970b4e 100644 --- a/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt +++ b/firebase-database/src/jvmTest/kotlin/dev/gitlive/firebase/database/database.kt @@ -3,15 +3,17 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.database -import android.content.Context -import com.google.firebase.FirebasePlatform -import dev.gitlive.firebase.MockFirebasePlatform +import dev.gitlive.firebase.testContext -actual val emulatorHost: String = "10.0.2.2" +actual val emulatorHost: String = "localhost" -actual val context: Any get() = Context().also { FirebasePlatform.initializeFirebasePlatform(MockFirebasePlatform()) } +actual val context: Any = testContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreForAndroidTest diff --git a/firebase-firestore/api/android/firebase-firestore.api b/firebase-firestore/api/android/firebase-firestore.api new file mode 100644 index 000000000..8a0aa2e9a --- /dev/null +++ b/firebase-firestore/api/android/firebase-firestore.api @@ -0,0 +1,798 @@ +public abstract class dev/gitlive/firebase/firestore/BaseTimestamp { + public static final field Companion Ldev/gitlive/firebase/firestore/BaseTimestamp$Companion; +} + +public final class dev/gitlive/firebase/firestore/BaseTimestamp$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/BaseTimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/BaseTimestampSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/BaseTimestamp; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/BaseTimestamp;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/CollectionReference : dev/gitlive/firebase/firestore/Query { + public static final field Companion Ldev/gitlive/firebase/firestore/CollectionReference$Companion; + public final fun add (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun add (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun add$default (Ldev/gitlive/firebase/firestore/CollectionReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun addEncoded (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun document (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/DocumentReference; + public fun equals (Ljava/lang/Object;)Z + public final fun getDocument ()Ldev/gitlive/firebase/firestore/DocumentReference; + public synthetic fun getNative$firebase_firestore_release ()Lcom/google/firebase/firestore/Query; + public final fun getParent ()Ldev/gitlive/firebase/firestore/DocumentReference; + public final fun getPath ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/CollectionReference$Companion { +} + +public final class dev/gitlive/firebase/firestore/DocumentChange { + public fun (Lcom/google/firebase/firestore/DocumentChange;)V + public final fun getDocument ()Ldev/gitlive/firebase/firestore/DocumentSnapshot; + public final fun getNewIndex ()I + public final fun getOldIndex ()I + public final fun getType ()Lcom/google/firebase/firestore/DocumentChange$Type; +} + +public final class dev/gitlive/firebase/firestore/DocumentReference { + public static final field Companion Ldev/gitlive/firebase/firestore/DocumentReference$Companion; + public final fun collection (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/CollectionReference; + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getId ()Ljava/lang/String; + public final fun getParent ()Ldev/gitlive/firebase/firestore/CollectionReference; + public final fun getPath ()Ljava/lang/String; + public final fun getSnapshots ()Lkotlinx/coroutines/flow/Flow; + public fun hashCode ()I + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setEncoded (Ldev/gitlive/firebase/internal/EncodedObject;Ldev/gitlive/firebase/firestore/internal/SetOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun snapshots (Z)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun snapshots$default (Ldev/gitlive/firebase/firestore/DocumentReference;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public fun toString ()Ljava/lang/String; + public final fun update (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun update (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateEncoded (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateEncodedFieldPathsAndValues (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateEncodedFieldsAndValues (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateFieldPaths ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateFieldPaths$default (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateFields ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateFields$default (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/DocumentReference$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/DocumentReferenceSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/DocumentReferenceSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/DocumentReference; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/DocumentReference;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/DocumentSnapshot { + public static final field Companion Ldev/gitlive/firebase/firestore/DocumentSnapshot$Companion; + public final fun contains (Ldev/gitlive/firebase/firestore/FieldPath;)Z + public final fun contains (Ljava/lang/String;)Z + public final fun data (Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun data$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public final fun encodedData (Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;)Ljava/lang/Object; + public static synthetic fun encodedData$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;ILjava/lang/Object;)Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Ldev/gitlive/firebase/firestore/FieldPath;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public final fun get (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ldev/gitlive/firebase/firestore/FieldPath;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getEncoded (Ldev/gitlive/firebase/firestore/FieldPath;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;)Ljava/lang/Object; + public final fun getEncoded (Ljava/lang/String;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;)Ljava/lang/Object; + public static synthetic fun getEncoded$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ldev/gitlive/firebase/firestore/FieldPath;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun getEncoded$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ljava/lang/String;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getExists ()Z + public final fun getId ()Ljava/lang/String; + public final fun getMetadata ()Ldev/gitlive/firebase/firestore/SnapshotMetadata; + public final fun getReference ()Ldev/gitlive/firebase/firestore/DocumentReference; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/DocumentSnapshot$Companion { +} + +public final class dev/gitlive/firebase/firestore/DoubleAsTimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/DoubleAsTimestampSerializer; + public static final field SERVER_TIMESTAMP D + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Double; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;D)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/FieldPath { + public static final field Companion Ldev/gitlive/firebase/firestore/FieldPath$Companion; + public fun ([Ljava/lang/String;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getDocumentId ()Ldev/gitlive/firebase/firestore/FieldPath; + public final fun getEncoded ()Lcom/google/firebase/firestore/FieldPath; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/FieldPath$Companion { + public final fun getDocumentId ()Ldev/gitlive/firebase/firestore/FieldPath; +} + +public final class dev/gitlive/firebase/firestore/FieldValue { + public static final field Companion Ldev/gitlive/firebase/firestore/FieldValue$Companion; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/FieldValue$Companion { + public final fun arrayRemove ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/FieldValue; + public final fun arrayUnion ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/FieldValue; + public final fun getDelete ()Ldev/gitlive/firebase/firestore/FieldValue; + public final fun getServerTimestamp ()Ldev/gitlive/firebase/firestore/FieldValue; + public final fun increment (I)Ldev/gitlive/firebase/firestore/FieldValue; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/FieldValueSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/FieldValueSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/FieldValue; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/FieldValue;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public abstract class dev/gitlive/firebase/firestore/Filter { +} + +public final class dev/gitlive/firebase/firestore/Filter$And : dev/gitlive/firebase/firestore/Filter { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public final fun getFilters ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Filter$Field : dev/gitlive/firebase/firestore/Filter$WithConstraint { + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public fun equals (Ljava/lang/Object;)Z + public fun getConstraint ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public final fun getField ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Filter$Or : dev/gitlive/firebase/firestore/Filter { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public final fun getFilters ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Filter$Path : dev/gitlive/firebase/firestore/Filter$WithConstraint { + public final fun component1 ()Ldev/gitlive/firebase/firestore/FieldPath; + public final fun component2 ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public fun equals (Ljava/lang/Object;)Z + public fun getConstraint ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public final fun getPath ()Ldev/gitlive/firebase/firestore/FieldPath; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class dev/gitlive/firebase/firestore/Filter$WithConstraint : dev/gitlive/firebase/firestore/Filter { + public abstract fun getConstraint ()Ldev/gitlive/firebase/firestore/WhereConstraint; +} + +public final class dev/gitlive/firebase/firestore/FilterBuilder { + public final fun all ([Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter; + public final fun and (Ldev/gitlive/firebase/firestore/Filter;Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter$And; + public final fun any ([Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter; + public final fun contains (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun contains (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun containsAny (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun containsAny (Ljava/lang/String;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun equalTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun equalTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThan (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThan (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThanOrEqualTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThanOrEqualTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun inArray (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun inArray (Ljava/lang/String;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThan (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThan (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThanOrEqualTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThanOrEqualTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notEqualTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notEqualTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notInArray (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notInArray (Ljava/lang/String;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun or (Ldev/gitlive/firebase/firestore/Filter;Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter$Or; +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestore { + public static final field Companion Ldev/gitlive/firebase/firestore/FirebaseFirestore$Companion; + public final fun batch ()Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun clearPersistence (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun collection (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/CollectionReference; + public final fun collectionGroup (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Query; + public final fun disableNetwork (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun document (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/DocumentReference; + public final fun enableNetwork (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getSettings ()Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public final fun runTransaction (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setLoggingEnabled (Z)V + public final fun setSettings (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;)V + public final fun setSettings (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Long;)V + public static synthetic fun setSettings$default (Ldev/gitlive/firebase/firestore/FirebaseFirestore;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Long;ILjava/lang/Object;)V + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestore$Companion { +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestoreSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings$Companion; + public fun (ZLjava/lang/String;Ldev/gitlive/firebase/firestore/LocalCacheSettings;Ljava/util/concurrent/Executor;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ldev/gitlive/firebase/firestore/LocalCacheSettings; + public final fun component4 ()Ljava/util/concurrent/Executor; + public final fun copy (ZLjava/lang/String;Ldev/gitlive/firebase/firestore/LocalCacheSettings;Ljava/util/concurrent/Executor;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public static synthetic fun copy$default (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;ZLjava/lang/String;Ldev/gitlive/firebase/firestore/LocalCacheSettings;Ljava/util/concurrent/Executor;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getCacheSettings ()Ldev/gitlive/firebase/firestore/LocalCacheSettings; + public final fun getCallbackExecutor ()Ljava/util/concurrent/Executor; + public final fun getHost ()Ljava/lang/String; + public final fun getSslEnabled ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestoreSettings$Builder { + public fun ()V + public fun (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;)V + public final fun build ()Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public final fun getCacheSettings ()Ldev/gitlive/firebase/firestore/LocalCacheSettings; + public final fun getCallbackExecutor ()Ljava/util/concurrent/Executor; + public final fun getHost ()Ljava/lang/String; + public final fun getSslEnabled ()Z + public final fun setCacheSettings (Ldev/gitlive/firebase/firestore/LocalCacheSettings;)V + public final fun setCallbackExecutor (Ljava/util/concurrent/Executor;)V + public final fun setHost (Ljava/lang/String;)V + public final fun setSslEnabled (Z)V +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestoreSettings$Companion { + public final fun getCACHE_SIZE_UNLIMITED ()J +} + +public final class dev/gitlive/firebase/firestore/FirestoreKt { + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; +} + +public final class dev/gitlive/firebase/firestore/GeoPoint { + public static final field Companion Ldev/gitlive/firebase/firestore/GeoPoint$Companion; + public fun (DD)V + public fun equals (Ljava/lang/Object;)Z + public final fun getLatitude ()D + public final fun getLongitude ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/GeoPoint$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/GeoPointSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/GeoPointSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/GeoPoint; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/GeoPoint;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/HelpersKt { + public static final fun encodeFieldAndValue ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun performUpdateFieldPaths ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun performUpdateFields ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ljava/util/List; +} + +public abstract interface class dev/gitlive/firebase/firestore/LocalCacheSettings { +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Memory : dev/gitlive/firebase/firestore/LocalCacheSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Companion; + public final fun component1 ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getGarbaseCollectorSettings ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory; + public final fun getGcSettings ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings; + public final fun setGcSettings (Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings;)V +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Companion { + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Builder; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Persistent : dev/gitlive/firebase/firestore/LocalCacheSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Companion; + public final fun component1 ()J + public fun equals (Ljava/lang/Object;)Z + public final fun getSizeBytes ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent; + public final fun getSizeBytes ()J + public final fun setSizeBytes (J)V +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Companion { + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Builder; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettingsKt { + public static final fun memoryCacheSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory; + public static final fun memoryEagerGcSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager; + public static final fun memoryLruGcSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC; + public static final fun persistentCacheSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent; +} + +public abstract interface class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings { +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager : dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager$Builder; + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager; +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC : dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Companion; + public final fun component1 ()J + public fun equals (Ljava/lang/Object;)Z + public final fun getSizeBytes ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC; + public final fun getSizeBytes ()J + public final fun setSizeBytes (J)V +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Companion { + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Builder; +} + +public class dev/gitlive/firebase/firestore/Query { + public static final field Companion Ldev/gitlive/firebase/firestore/Query$Companion; + public final fun endAt (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun endAt ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun endBefore (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun endBefore ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun get (Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getSnapshots ()Lkotlinx/coroutines/flow/Flow; + public final fun limit (Ljava/lang/Number;)Ldev/gitlive/firebase/firestore/Query; + public final fun orderBy (Ldev/gitlive/firebase/firestore/FieldPath;Lcom/google/firebase/firestore/Query$Direction;)Ldev/gitlive/firebase/firestore/Query; + public final fun orderBy (Ljava/lang/String;Lcom/google/firebase/firestore/Query$Direction;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun orderBy$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Lcom/google/firebase/firestore/Query$Direction;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun orderBy$default (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Lcom/google/firebase/firestore/Query$Direction;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun snapshots (Z)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun snapshots$default (Ldev/gitlive/firebase/firestore/Query;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public final fun startAfter (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun startAfter ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun startAt (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun startAt ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun where (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Query; +} + +public final class dev/gitlive/firebase/firestore/Query$Companion { +} + +public final class dev/gitlive/firebase/firestore/QuerySnapshot { + public fun (Lcom/google/firebase/firestore/QuerySnapshot;)V + public final fun getDocumentChanges ()Ljava/util/List; + public final fun getDocuments ()Ljava/util/List; + public final fun getMetadata ()Ldev/gitlive/firebase/firestore/SnapshotMetadata; +} + +public final class dev/gitlive/firebase/firestore/ServerTimestampBehavior : java/lang/Enum { + public static final field ESTIMATE Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static final field NONE Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static final field PREVIOUS Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static fun values ()[Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; +} + +public final class dev/gitlive/firebase/firestore/ServerTimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/ServerTimestampSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/Timestamp$ServerTimestamp; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/Timestamp$ServerTimestamp;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/SnapshotMetadata { + public fun (Lcom/google/firebase/firestore/SnapshotMetadata;)V + public final fun getHasPendingWrites ()Z + public final fun isFromCache ()Z +} + +public final class dev/gitlive/firebase/firestore/Source : java/lang/Enum { + public static final field CACHE Ldev/gitlive/firebase/firestore/Source; + public static final field DEFAULT Ldev/gitlive/firebase/firestore/Source; + public static final field SERVER Ldev/gitlive/firebase/firestore/Source; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Source; + public static fun values ()[Ldev/gitlive/firebase/firestore/Source; +} + +public final class dev/gitlive/firebase/firestore/Timestamp : dev/gitlive/firebase/firestore/BaseTimestamp { + public static final field Companion Ldev/gitlive/firebase/firestore/Timestamp$Companion; + public fun (JI)V + public fun equals (Ljava/lang/Object;)Z + public final fun getNanoseconds ()I + public final fun getSeconds ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Timestamp$Companion { + public final fun now ()Ldev/gitlive/firebase/firestore/Timestamp; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/Timestamp$ServerTimestamp : dev/gitlive/firebase/firestore/BaseTimestamp { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/Timestamp$ServerTimestamp; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/TimestampKt { + public static final fun fromDuration-HG0u8IE (Ldev/gitlive/firebase/firestore/Timestamp$Companion;J)Ldev/gitlive/firebase/firestore/Timestamp; + public static final fun fromMilliseconds (Ldev/gitlive/firebase/firestore/Timestamp$Companion;D)Ldev/gitlive/firebase/firestore/Timestamp; + public static final fun toDuration (Ldev/gitlive/firebase/firestore/Timestamp;)J + public static final fun toMilliseconds (Ldev/gitlive/firebase/firestore/Timestamp;)D +} + +public final class dev/gitlive/firebase/firestore/TimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/TimestampSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/Timestamp; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/Timestamp;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/Transaction { + public static final field Companion Ldev/gitlive/firebase/firestore/Transaction$Companion; + public final fun delete (Ldev/gitlive/firebase/firestore/DocumentReference;)Ldev/gitlive/firebase/firestore/Transaction; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun hashCode ()I + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZZ)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Z[Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZ)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZZILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun setEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;Ldev/gitlive/firebase/firestore/internal/SetOptions;)Ldev/gitlive/firebase/firestore/Transaction; + public fun toString ()Ljava/lang/String; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Z)Ldev/gitlive/firebase/firestore/Transaction; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateEncodedFieldPathsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateEncodedFieldsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateFieldPaths (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun updateFieldPaths$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateFields (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun updateFields$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; +} + +public final class dev/gitlive/firebase/firestore/Transaction$Companion { +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint { +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ArrayContains : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ArrayContainsAny : dev/gitlive/firebase/firestore/WhereConstraint$ForArray { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValues ()Ljava/util/List; + public fun getValues ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$EqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint$ForArray : dev/gitlive/firebase/firestore/WhereConstraint { + public abstract fun getSafeValues ()Ljava/util/List; + public abstract fun getValues ()Ljava/util/List; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ForArray$DefaultImpls { + public static fun getSafeValues (Ldev/gitlive/firebase/firestore/WhereConstraint$ForArray;)Ljava/util/List; +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject : dev/gitlive/firebase/firestore/WhereConstraint { + public abstract fun getSafeValue ()Ljava/lang/Object; + public abstract fun getValue ()Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject$DefaultImpls { + public static fun getSafeValue (Ldev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject;)Ljava/lang/Object; +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint$ForObject : dev/gitlive/firebase/firestore/WhereConstraint { + public abstract fun getSafeValue ()Ljava/lang/Object; + public abstract fun getValue ()Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ForObject$DefaultImpls { + public static fun getSafeValue (Ldev/gitlive/firebase/firestore/WhereConstraint$ForObject;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$GreaterThan : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$GreaterThanOrEqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$InArray : dev/gitlive/firebase/firestore/WhereConstraint$ForArray { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValues ()Ljava/util/List; + public fun getValues ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$LessThan : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$LessThanOrEqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$NotEqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$NotInArray : dev/gitlive/firebase/firestore/WhereConstraint$ForArray { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValues ()Ljava/util/List; + public fun getValues ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WriteBatch { + public static final field Companion Ldev/gitlive/firebase/firestore/WriteBatch$Companion; + public final fun commit (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun delete (Ldev/gitlive/firebase/firestore/DocumentReference;)Ldev/gitlive/firebase/firestore/WriteBatch; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZ)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ljava/lang/String;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun setEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;Ldev/gitlive/firebase/firestore/internal/SetOptions;)Ldev/gitlive/firebase/firestore/WriteBatch; + public fun toString ()Ljava/lang/String; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateEncodedFieldPathsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateEncodedFieldsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateField (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun updateField$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateFieldPath (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun updateFieldPath$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; +} + +public final class dev/gitlive/firebase/firestore/WriteBatch$Companion { +} + +public final class dev/gitlive/firebase/firestore/_encodersKt { + public static final fun isSpecialValue (Ljava/lang/Object;)Z +} + +public final class dev/gitlive/firebase/firestore/android { + public static final fun firestore (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/firestore/FirebaseFirestore; + public static final fun firestoreSettings (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public static synthetic fun firestoreSettings$default (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/CollectionReference;)Lcom/google/firebase/firestore/CollectionReference; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/DocumentChange;)Lcom/google/firebase/firestore/DocumentChange; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/DocumentReference;)Lcom/google/firebase/firestore/DocumentReference; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Lcom/google/firebase/firestore/DocumentSnapshot; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/FieldPath;)Lcom/google/firebase/firestore/FieldPath; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/FirebaseFirestore;)Lcom/google/firebase/firestore/FirebaseFirestore; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/LocalCacheSettings;)Lcom/google/firebase/firestore/LocalCacheSettings; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/Query;)Lcom/google/firebase/firestore/Query; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/QuerySnapshot;)Lcom/google/firebase/firestore/QuerySnapshot; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/SnapshotMetadata;)Lcom/google/firebase/firestore/SnapshotMetadata; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/Transaction;)Lcom/google/firebase/firestore/Transaction; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/WriteBatch;)Lcom/google/firebase/firestore/WriteBatch; + public static final fun getCode (Lcom/google/firebase/firestore/FirebaseFirestoreException;)Lcom/google/firebase/firestore/FirebaseFirestoreException$Code; + public static final fun getFirestore (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/firestore/FirebaseFirestore; + public static final fun invoke (Ldev/gitlive/firebase/firestore/CollectionReference$Companion;Lcom/google/firebase/firestore/CollectionReference;)Ldev/gitlive/firebase/firestore/CollectionReference; + public static final fun invoke (Ldev/gitlive/firebase/firestore/DocumentReference$Companion;Lcom/google/firebase/firestore/DocumentReference;)Ldev/gitlive/firebase/firestore/DocumentReference; + public static final fun invoke (Ldev/gitlive/firebase/firestore/DocumentSnapshot$Companion;Lcom/google/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/DocumentSnapshot; + public static final fun invoke (Ldev/gitlive/firebase/firestore/FirebaseFirestore$Companion;Lcom/google/firebase/firestore/FirebaseFirestore;)Ldev/gitlive/firebase/firestore/FirebaseFirestore; + public static final fun invoke (Ldev/gitlive/firebase/firestore/Query$Companion;Lcom/google/firebase/firestore/Query;)Ldev/gitlive/firebase/firestore/Query; + public static final fun invoke (Ldev/gitlive/firebase/firestore/Transaction$Companion;Lcom/google/firebase/firestore/Transaction;)Ldev/gitlive/firebase/firestore/Transaction; + public static final fun invoke (Ldev/gitlive/firebase/firestore/WriteBatch$Companion;Lcom/google/firebase/firestore/WriteBatch;)Ldev/gitlive/firebase/firestore/WriteBatch; +} + +public abstract class dev/gitlive/firebase/firestore/internal/SetOptions { +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$Merge : dev/gitlive/firebase/firestore/internal/SetOptions { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/internal/SetOptions$Merge; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths : dev/gitlive/firebase/firestore/internal/SetOptions { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths; + public static synthetic fun copy$default (Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths; + public fun equals (Ljava/lang/Object;)Z + public final fun getEncodedFieldPaths ()Ljava/util/List; + public final fun getFieldPaths ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$MergeFields : dev/gitlive/firebase/firestore/internal/SetOptions { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFields; + public static synthetic fun copy$default (Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFields;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFields; + public fun equals (Ljava/lang/Object;)Z + public final fun getFields ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$Overwrite : dev/gitlive/firebase/firestore/internal/SetOptions { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/internal/SetOptions$Overwrite; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/firebase-firestore/api/jvm/firebase-firestore.api b/firebase-firestore/api/jvm/firebase-firestore.api new file mode 100644 index 000000000..33a64aef7 --- /dev/null +++ b/firebase-firestore/api/jvm/firebase-firestore.api @@ -0,0 +1,798 @@ +public abstract class dev/gitlive/firebase/firestore/BaseTimestamp { + public static final field Companion Ldev/gitlive/firebase/firestore/BaseTimestamp$Companion; +} + +public final class dev/gitlive/firebase/firestore/BaseTimestamp$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/BaseTimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/BaseTimestampSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/BaseTimestamp; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/BaseTimestamp;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/CollectionReference : dev/gitlive/firebase/firestore/Query { + public static final field Companion Ldev/gitlive/firebase/firestore/CollectionReference$Companion; + public final fun add (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun add (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun add$default (Ldev/gitlive/firebase/firestore/CollectionReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun addEncoded (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun document (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/DocumentReference; + public fun equals (Ljava/lang/Object;)Z + public final fun getDocument ()Ldev/gitlive/firebase/firestore/DocumentReference; + public synthetic fun getNative$firebase_firestore ()Lcom/google/firebase/firestore/Query; + public final fun getParent ()Ldev/gitlive/firebase/firestore/DocumentReference; + public final fun getPath ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/CollectionReference$Companion { +} + +public final class dev/gitlive/firebase/firestore/DocumentChange { + public fun (Lcom/google/firebase/firestore/DocumentChange;)V + public final fun getDocument ()Ldev/gitlive/firebase/firestore/DocumentSnapshot; + public final fun getNewIndex ()I + public final fun getOldIndex ()I + public final fun getType ()Lcom/google/firebase/firestore/DocumentChange$Type; +} + +public final class dev/gitlive/firebase/firestore/DocumentReference { + public static final field Companion Ldev/gitlive/firebase/firestore/DocumentReference$Companion; + public final fun collection (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/CollectionReference; + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getId ()Ljava/lang/String; + public final fun getParent ()Ldev/gitlive/firebase/firestore/CollectionReference; + public final fun getPath ()Ljava/lang/String; + public final fun getSnapshots ()Lkotlinx/coroutines/flow/Flow; + public fun hashCode ()I + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun set (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setEncoded (Ldev/gitlive/firebase/internal/EncodedObject;Ldev/gitlive/firebase/firestore/internal/SetOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun snapshots (Z)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun snapshots$default (Ldev/gitlive/firebase/firestore/DocumentReference;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public fun toString ()Ljava/lang/String; + public final fun update (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun update (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateEncoded (Ldev/gitlive/firebase/internal/EncodedObject;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateEncodedFieldPathsAndValues (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateEncodedFieldsAndValues (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateFieldPaths ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateFieldPaths$default (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun updateFields ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun updateFields$default (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/DocumentReference$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/DocumentReferenceSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/DocumentReferenceSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/DocumentReference; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/DocumentReference;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/DocumentSnapshot { + public static final field Companion Ldev/gitlive/firebase/firestore/DocumentSnapshot$Companion; + public final fun contains (Ldev/gitlive/firebase/firestore/FieldPath;)Z + public final fun contains (Ljava/lang/String;)Z + public final fun data (Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun data$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public final fun encodedData (Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;)Ljava/lang/Object; + public static synthetic fun encodedData$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;ILjava/lang/Object;)Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Ldev/gitlive/firebase/firestore/FieldPath;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public final fun get (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ldev/gitlive/firebase/firestore/FieldPath;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getEncoded (Ldev/gitlive/firebase/firestore/FieldPath;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;)Ljava/lang/Object; + public final fun getEncoded (Ljava/lang/String;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;)Ljava/lang/Object; + public static synthetic fun getEncoded$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ldev/gitlive/firebase/firestore/FieldPath;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun getEncoded$default (Ldev/gitlive/firebase/firestore/DocumentSnapshot;Ljava/lang/String;Ldev/gitlive/firebase/firestore/ServerTimestampBehavior;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getExists ()Z + public final fun getId ()Ljava/lang/String; + public final fun getMetadata ()Ldev/gitlive/firebase/firestore/SnapshotMetadata; + public final fun getReference ()Ldev/gitlive/firebase/firestore/DocumentReference; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/DocumentSnapshot$Companion { +} + +public final class dev/gitlive/firebase/firestore/DoubleAsTimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/DoubleAsTimestampSerializer; + public static final field SERVER_TIMESTAMP D + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Double; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;D)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/FieldPath { + public static final field Companion Ldev/gitlive/firebase/firestore/FieldPath$Companion; + public fun ([Ljava/lang/String;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getDocumentId ()Ldev/gitlive/firebase/firestore/FieldPath; + public final fun getEncoded ()Lcom/google/firebase/firestore/FieldPath; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/FieldPath$Companion { + public final fun getDocumentId ()Ldev/gitlive/firebase/firestore/FieldPath; +} + +public final class dev/gitlive/firebase/firestore/FieldValue { + public static final field Companion Ldev/gitlive/firebase/firestore/FieldValue$Companion; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/FieldValue$Companion { + public final fun arrayRemove ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/FieldValue; + public final fun arrayUnion ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/FieldValue; + public final fun getDelete ()Ldev/gitlive/firebase/firestore/FieldValue; + public final fun getServerTimestamp ()Ldev/gitlive/firebase/firestore/FieldValue; + public final fun increment (I)Ldev/gitlive/firebase/firestore/FieldValue; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/FieldValueSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/FieldValueSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/FieldValue; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/FieldValue;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public abstract class dev/gitlive/firebase/firestore/Filter { +} + +public final class dev/gitlive/firebase/firestore/Filter$And : dev/gitlive/firebase/firestore/Filter { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public final fun getFilters ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Filter$Field : dev/gitlive/firebase/firestore/Filter$WithConstraint { + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public fun equals (Ljava/lang/Object;)Z + public fun getConstraint ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public final fun getField ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Filter$Or : dev/gitlive/firebase/firestore/Filter { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public final fun getFilters ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Filter$Path : dev/gitlive/firebase/firestore/Filter$WithConstraint { + public final fun component1 ()Ldev/gitlive/firebase/firestore/FieldPath; + public final fun component2 ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public fun equals (Ljava/lang/Object;)Z + public fun getConstraint ()Ldev/gitlive/firebase/firestore/WhereConstraint; + public final fun getPath ()Ldev/gitlive/firebase/firestore/FieldPath; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class dev/gitlive/firebase/firestore/Filter$WithConstraint : dev/gitlive/firebase/firestore/Filter { + public abstract fun getConstraint ()Ldev/gitlive/firebase/firestore/WhereConstraint; +} + +public final class dev/gitlive/firebase/firestore/FilterBuilder { + public final fun all ([Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter; + public final fun and (Ldev/gitlive/firebase/firestore/Filter;Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter$And; + public final fun any ([Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter; + public final fun contains (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun contains (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun containsAny (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun containsAny (Ljava/lang/String;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun equalTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun equalTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThan (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThan (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThanOrEqualTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun greaterThanOrEqualTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun inArray (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun inArray (Ljava/lang/String;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThan (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThan (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThanOrEqualTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun lessThanOrEqualTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notEqualTo (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notEqualTo (Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notInArray (Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun notInArray (Ljava/lang/String;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Filter$WithConstraint; + public final fun or (Ldev/gitlive/firebase/firestore/Filter;Ldev/gitlive/firebase/firestore/Filter;)Ldev/gitlive/firebase/firestore/Filter$Or; +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestore { + public static final field Companion Ldev/gitlive/firebase/firestore/FirebaseFirestore$Companion; + public final fun batch ()Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun clearPersistence (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun collection (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/CollectionReference; + public final fun collectionGroup (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Query; + public final fun disableNetwork (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun document (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/DocumentReference; + public final fun enableNetwork (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getSettings ()Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public final fun runTransaction (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setLoggingEnabled (Z)V + public final fun setSettings (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;)V + public final fun setSettings (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Long;)V + public static synthetic fun setSettings$default (Ldev/gitlive/firebase/firestore/FirebaseFirestore;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Long;ILjava/lang/Object;)V + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestore$Companion { +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestoreSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings$Companion; + public fun (ZLjava/lang/String;Ldev/gitlive/firebase/firestore/LocalCacheSettings;Ljava/util/concurrent/Executor;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ldev/gitlive/firebase/firestore/LocalCacheSettings; + public final fun component4 ()Ljava/util/concurrent/Executor; + public final fun copy (ZLjava/lang/String;Ldev/gitlive/firebase/firestore/LocalCacheSettings;Ljava/util/concurrent/Executor;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public static synthetic fun copy$default (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;ZLjava/lang/String;Ldev/gitlive/firebase/firestore/LocalCacheSettings;Ljava/util/concurrent/Executor;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getCacheSettings ()Ldev/gitlive/firebase/firestore/LocalCacheSettings; + public final fun getCallbackExecutor ()Ljava/util/concurrent/Executor; + public final fun getHost ()Ljava/lang/String; + public final fun getSslEnabled ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestoreSettings$Builder { + public fun ()V + public fun (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;)V + public final fun build ()Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public final fun getCacheSettings ()Ldev/gitlive/firebase/firestore/LocalCacheSettings; + public final fun getCallbackExecutor ()Ljava/util/concurrent/Executor; + public final fun getHost ()Ljava/lang/String; + public final fun getSslEnabled ()Z + public final fun setCacheSettings (Ldev/gitlive/firebase/firestore/LocalCacheSettings;)V + public final fun setCallbackExecutor (Ljava/util/concurrent/Executor;)V + public final fun setHost (Ljava/lang/String;)V + public final fun setSslEnabled (Z)V +} + +public final class dev/gitlive/firebase/firestore/FirebaseFirestoreSettings$Companion { + public final fun getCACHE_SIZE_UNLIMITED ()J +} + +public final class dev/gitlive/firebase/firestore/FirestoreKt { + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static final fun where (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun where$default (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; +} + +public final class dev/gitlive/firebase/firestore/GeoPoint { + public static final field Companion Ldev/gitlive/firebase/firestore/GeoPoint$Companion; + public fun (DD)V + public fun equals (Ljava/lang/Object;)Z + public final fun getLatitude ()D + public final fun getLongitude ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/GeoPoint$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/GeoPointSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/GeoPointSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/GeoPoint; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/GeoPoint;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/HelpersKt { + public static final fun encodeFieldAndValue ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun performUpdateFieldPaths ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun performUpdateFields ([Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ljava/util/List; +} + +public abstract interface class dev/gitlive/firebase/firestore/LocalCacheSettings { +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Memory : dev/gitlive/firebase/firestore/LocalCacheSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Companion; + public final fun component1 ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings; + public fun equals (Ljava/lang/Object;)Z + public final fun getGarbaseCollectorSettings ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory; + public final fun getGcSettings ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings; + public final fun setGcSettings (Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings;)V +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Companion { + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory$Builder; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Persistent : dev/gitlive/firebase/firestore/LocalCacheSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Companion; + public final fun component1 ()J + public fun equals (Ljava/lang/Object;)Z + public final fun getSizeBytes ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent; + public final fun getSizeBytes ()J + public final fun setSizeBytes (J)V +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Companion { + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent$Builder; +} + +public final class dev/gitlive/firebase/firestore/LocalCacheSettingsKt { + public static final fun memoryCacheSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/LocalCacheSettings$Memory; + public static final fun memoryEagerGcSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager; + public static final fun memoryLruGcSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC; + public static final fun persistentCacheSettings (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/LocalCacheSettings$Persistent; +} + +public abstract interface class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings { +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager : dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager$Builder; + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$Eager; +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC : dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings { + public static final field Companion Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Companion; + public final fun component1 ()J + public fun equals (Ljava/lang/Object;)Z + public final fun getSizeBytes ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Builder { + public final fun build ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC; + public final fun getSizeBytes ()J + public final fun setSizeBytes (J)V +} + +public final class dev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Companion { + public final fun newBuilder ()Ldev/gitlive/firebase/firestore/MemoryGarbageCollectorSettings$LRUGC$Builder; +} + +public class dev/gitlive/firebase/firestore/Query { + public static final field Companion Ldev/gitlive/firebase/firestore/Query$Companion; + public final fun endAt (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun endAt ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun endBefore (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun endBefore ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun get (Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun get$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/Source;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getSnapshots ()Lkotlinx/coroutines/flow/Flow; + public final fun limit (Ljava/lang/Number;)Ldev/gitlive/firebase/firestore/Query; + public final fun orderBy (Ldev/gitlive/firebase/firestore/FieldPath;Lcom/google/firebase/firestore/Query$Direction;)Ldev/gitlive/firebase/firestore/Query; + public final fun orderBy (Ljava/lang/String;Lcom/google/firebase/firestore/Query$Direction;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun orderBy$default (Ldev/gitlive/firebase/firestore/Query;Ldev/gitlive/firebase/firestore/FieldPath;Lcom/google/firebase/firestore/Query$Direction;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public static synthetic fun orderBy$default (Ldev/gitlive/firebase/firestore/Query;Ljava/lang/String;Lcom/google/firebase/firestore/Query$Direction;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun snapshots (Z)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun snapshots$default (Ldev/gitlive/firebase/firestore/Query;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public final fun startAfter (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun startAfter ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun startAt (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/Query; + public final fun startAt ([Ljava/lang/Object;)Ldev/gitlive/firebase/firestore/Query; + public final fun where (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Query; +} + +public final class dev/gitlive/firebase/firestore/Query$Companion { +} + +public final class dev/gitlive/firebase/firestore/QuerySnapshot { + public fun (Lcom/google/firebase/firestore/QuerySnapshot;)V + public final fun getDocumentChanges ()Ljava/util/List; + public final fun getDocuments ()Ljava/util/List; + public final fun getMetadata ()Ldev/gitlive/firebase/firestore/SnapshotMetadata; +} + +public final class dev/gitlive/firebase/firestore/ServerTimestampBehavior : java/lang/Enum { + public static final field ESTIMATE Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static final field NONE Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static final field PREVIOUS Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; + public static fun values ()[Ldev/gitlive/firebase/firestore/ServerTimestampBehavior; +} + +public final class dev/gitlive/firebase/firestore/ServerTimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/ServerTimestampSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/Timestamp$ServerTimestamp; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/Timestamp$ServerTimestamp;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/SnapshotMetadata { + public fun (Lcom/google/firebase/firestore/SnapshotMetadata;)V + public final fun getHasPendingWrites ()Z + public final fun isFromCache ()Z +} + +public final class dev/gitlive/firebase/firestore/Source : java/lang/Enum { + public static final field CACHE Ldev/gitlive/firebase/firestore/Source; + public static final field DEFAULT Ldev/gitlive/firebase/firestore/Source; + public static final field SERVER Ldev/gitlive/firebase/firestore/Source; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Source; + public static fun values ()[Ldev/gitlive/firebase/firestore/Source; +} + +public final class dev/gitlive/firebase/firestore/Timestamp : dev/gitlive/firebase/firestore/BaseTimestamp { + public static final field Companion Ldev/gitlive/firebase/firestore/Timestamp$Companion; + public fun (JI)V + public fun equals (Ljava/lang/Object;)Z + public final fun getNanoseconds ()I + public final fun getSeconds ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/Timestamp$Companion { + public final fun now ()Ldev/gitlive/firebase/firestore/Timestamp; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/gitlive/firebase/firestore/Timestamp$ServerTimestamp : dev/gitlive/firebase/firestore/BaseTimestamp { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/Timestamp$ServerTimestamp; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/TimestampKt { + public static final fun fromDuration-HG0u8IE (Ldev/gitlive/firebase/firestore/Timestamp$Companion;J)Ldev/gitlive/firebase/firestore/Timestamp; + public static final fun fromMilliseconds (Ldev/gitlive/firebase/firestore/Timestamp$Companion;D)Ldev/gitlive/firebase/firestore/Timestamp; + public static final fun toDuration (Ldev/gitlive/firebase/firestore/Timestamp;)J + public static final fun toMilliseconds (Ldev/gitlive/firebase/firestore/Timestamp;)D +} + +public final class dev/gitlive/firebase/firestore/TimestampSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/TimestampSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/gitlive/firebase/firestore/Timestamp; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/gitlive/firebase/firestore/Timestamp;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class dev/gitlive/firebase/firestore/Transaction { + public static final field Companion Ldev/gitlive/firebase/firestore/Transaction$Companion; + public final fun delete (Ldev/gitlive/firebase/firestore/DocumentReference;)Ldev/gitlive/firebase/firestore/Transaction; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun hashCode ()I + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZZ)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Z[Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZ)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ljava/lang/String;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;ZZILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun setEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;Ldev/gitlive/firebase/firestore/internal/SetOptions;)Ldev/gitlive/firebase/firestore/Transaction; + public fun toString ()Ljava/lang/String; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Z)Ldev/gitlive/firebase/firestore/Transaction; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateEncodedFieldPathsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateEncodedFieldsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateFieldPaths (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun updateFieldPaths$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; + public final fun updateFields (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/Transaction; + public static synthetic fun updateFields$default (Ldev/gitlive/firebase/firestore/Transaction;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/Transaction; +} + +public final class dev/gitlive/firebase/firestore/Transaction$Companion { +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint { +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ArrayContains : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ArrayContainsAny : dev/gitlive/firebase/firestore/WhereConstraint$ForArray { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValues ()Ljava/util/List; + public fun getValues ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$EqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint$ForArray : dev/gitlive/firebase/firestore/WhereConstraint { + public abstract fun getSafeValues ()Ljava/util/List; + public abstract fun getValues ()Ljava/util/List; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ForArray$DefaultImpls { + public static fun getSafeValues (Ldev/gitlive/firebase/firestore/WhereConstraint$ForArray;)Ljava/util/List; +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject : dev/gitlive/firebase/firestore/WhereConstraint { + public abstract fun getSafeValue ()Ljava/lang/Object; + public abstract fun getValue ()Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject$DefaultImpls { + public static fun getSafeValue (Ldev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject;)Ljava/lang/Object; +} + +public abstract interface class dev/gitlive/firebase/firestore/WhereConstraint$ForObject : dev/gitlive/firebase/firestore/WhereConstraint { + public abstract fun getSafeValue ()Ljava/lang/Object; + public abstract fun getValue ()Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$ForObject$DefaultImpls { + public static fun getSafeValue (Ldev/gitlive/firebase/firestore/WhereConstraint$ForObject;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$GreaterThan : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$GreaterThanOrEqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$InArray : dev/gitlive/firebase/firestore/WhereConstraint$ForArray { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValues ()Ljava/util/List; + public fun getValues ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$LessThan : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$LessThanOrEqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$NotEqualTo : dev/gitlive/firebase/firestore/WhereConstraint$ForNullableObject { + public final fun component1 ()Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValue ()Ljava/lang/Object; + public fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WhereConstraint$NotInArray : dev/gitlive/firebase/firestore/WhereConstraint$ForArray { + public final fun component1 ()Ljava/util/List; + public fun equals (Ljava/lang/Object;)Z + public fun getSafeValues ()Ljava/util/List; + public fun getValues ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/WriteBatch { + public static final field Companion Ldev/gitlive/firebase/firestore/WriteBatch$Companion; + public final fun commit (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun delete (Ldev/gitlive/firebase/firestore/DocumentReference;)Ldev/gitlive/firebase/firestore/WriteBatch; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZ)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ldev/gitlive/firebase/firestore/FieldPath;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z[Ljava/lang/String;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun set (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZZILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ldev/gitlive/firebase/firestore/FieldPath;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun set$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun setEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;Ldev/gitlive/firebase/firestore/internal/SetOptions;)Ldev/gitlive/firebase/firestore/WriteBatch; + public fun toString ()Ljava/lang/String; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun update (Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Z)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun update$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateEncoded (Ldev/gitlive/firebase/firestore/DocumentReference;Ldev/gitlive/firebase/internal/EncodedObject;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateEncodedFieldPathsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateEncodedFieldsAndValues (Ldev/gitlive/firebase/firestore/DocumentReference;Ljava/util/List;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateField (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun updateField$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; + public final fun updateFieldPath (Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/WriteBatch; + public static synthetic fun updateFieldPath$default (Ldev/gitlive/firebase/firestore/WriteBatch;Ldev/gitlive/firebase/firestore/DocumentReference;[Lkotlin/Pair;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/WriteBatch; +} + +public final class dev/gitlive/firebase/firestore/WriteBatch$Companion { +} + +public final class dev/gitlive/firebase/firestore/_encodersKt { + public static final fun isSpecialValue (Ljava/lang/Object;)Z +} + +public final class dev/gitlive/firebase/firestore/android { + public static final fun firestore (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/firestore/FirebaseFirestore; + public static final fun firestoreSettings (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public static synthetic fun firestoreSettings$default (Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/FirebaseFirestoreSettings; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/CollectionReference;)Lcom/google/firebase/firestore/CollectionReference; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/DocumentChange;)Lcom/google/firebase/firestore/DocumentChange; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/DocumentReference;)Lcom/google/firebase/firestore/DocumentReference; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/DocumentSnapshot;)Lcom/google/firebase/firestore/DocumentSnapshot; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/FieldPath;)Lcom/google/firebase/firestore/FieldPath; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/FirebaseFirestore;)Lcom/google/firebase/firestore/FirebaseFirestore; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/LocalCacheSettings;)Lcom/google/firebase/firestore/LocalCacheSettings; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/Query;)Lcom/google/firebase/firestore/Query; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/QuerySnapshot;)Lcom/google/firebase/firestore/QuerySnapshot; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/SnapshotMetadata;)Lcom/google/firebase/firestore/SnapshotMetadata; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/Transaction;)Lcom/google/firebase/firestore/Transaction; + public static final fun getAndroid (Ldev/gitlive/firebase/firestore/WriteBatch;)Lcom/google/firebase/firestore/WriteBatch; + public static final fun getCode (Lcom/google/firebase/firestore/FirebaseFirestoreException;)Lcom/google/firebase/firestore/FirebaseFirestoreException$Code; + public static final fun getFirestore (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/firestore/FirebaseFirestore; + public static final fun invoke (Ldev/gitlive/firebase/firestore/CollectionReference$Companion;Lcom/google/firebase/firestore/CollectionReference;)Ldev/gitlive/firebase/firestore/CollectionReference; + public static final fun invoke (Ldev/gitlive/firebase/firestore/DocumentReference$Companion;Lcom/google/firebase/firestore/DocumentReference;)Ldev/gitlive/firebase/firestore/DocumentReference; + public static final fun invoke (Ldev/gitlive/firebase/firestore/DocumentSnapshot$Companion;Lcom/google/firebase/firestore/DocumentSnapshot;)Ldev/gitlive/firebase/firestore/DocumentSnapshot; + public static final fun invoke (Ldev/gitlive/firebase/firestore/FirebaseFirestore$Companion;Lcom/google/firebase/firestore/FirebaseFirestore;)Ldev/gitlive/firebase/firestore/FirebaseFirestore; + public static final fun invoke (Ldev/gitlive/firebase/firestore/Query$Companion;Lcom/google/firebase/firestore/Query;)Ldev/gitlive/firebase/firestore/Query; + public static final fun invoke (Ldev/gitlive/firebase/firestore/Transaction$Companion;Lcom/google/firebase/firestore/Transaction;)Ldev/gitlive/firebase/firestore/Transaction; + public static final fun invoke (Ldev/gitlive/firebase/firestore/WriteBatch$Companion;Lcom/google/firebase/firestore/WriteBatch;)Ldev/gitlive/firebase/firestore/WriteBatch; +} + +public abstract class dev/gitlive/firebase/firestore/internal/SetOptions { +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$Merge : dev/gitlive/firebase/firestore/internal/SetOptions { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/internal/SetOptions$Merge; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths : dev/gitlive/firebase/firestore/internal/SetOptions { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths; + public static synthetic fun copy$default (Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFieldPaths; + public fun equals (Ljava/lang/Object;)Z + public final fun getEncodedFieldPaths ()Ljava/util/List; + public final fun getFieldPaths ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$MergeFields : dev/gitlive/firebase/firestore/internal/SetOptions { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFields; + public static synthetic fun copy$default (Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFields;Ljava/util/List;ILjava/lang/Object;)Ldev/gitlive/firebase/firestore/internal/SetOptions$MergeFields; + public fun equals (Ljava/lang/Object;)Z + public final fun getFields ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/firestore/internal/SetOptions$Overwrite : dev/gitlive/firebase/firestore/internal/SetOptions { + public static final field INSTANCE Ldev/gitlive/firebase/firestore/internal/SetOptions$Overwrite; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/firebase-firestore/build.gradle.kts b/firebase-firestore/build.gradle.kts index 921ccd57f..56d85981a 100644 --- a/firebase-firestore/build.gradle.kts +++ b/firebase-firestore/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -11,6 +14,7 @@ plugins { kotlin("native.cocoapods") kotlin("multiplatform") kotlin("plugin.serialization") + id("testOptionsConvention") } android { @@ -27,15 +31,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -50,10 +50,23 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + freeCompilerArgs.add("-Xconsistent-data-class-copy-visibility") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -62,32 +75,16 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseFirestore" } @@ -95,10 +92,11 @@ kotlin { // As of Firebase 10.17 Firestore has moved all ObjC headers to FirebaseFirestoreInternal and the kotlin cocoapods plugin does not handle this well // Adding it manually seems to resolve the issue pod("FirebaseFirestoreInternal") { - version = "10.19.0" + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") } pod("FirebaseFirestore") { - version = "10.19.0" + version = libs.versions.firebase.cocoapods.get() extraOpts += listOf("-compiler-option", "-fmodules") useInteropBindingFrom("FirebaseFirestoreInternal") } @@ -108,40 +106,34 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - // Explicitly specify Mocha here since it seems to be throwing random errors otherwise - useMocha { - timeout = "180s" - } + testTask { + useKarma { + useChromeHeadless() + // Explicitly specify Mocha here since it seems to be throwing random errors otherwise + useMocha { + timeout = "180s" } } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - // Explicitly specify Mocha here since it seems to be throwing random errors otherwise - useMocha { - timeout = "180s" - } + testTask { + useKarma { + useChromeHeadless() + // Explicitly specify Mocha here since it seems to be throwing random errors otherwise + useMocha { + timeout = "180s" } } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") optIn("kotlinx.serialization.InternalSerializationApi") @@ -156,7 +148,8 @@ kotlin { getByName("commonMain") { dependencies { api(project(":firebase-app")) - implementation(project(":firebase-common")) + api(project(":firebase-common")) + implementation(project(":firebase-common-internal")) } } @@ -168,9 +161,14 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-firestore") + api(libs.google.firebase.firestore) } } + + getByName("jvmMain") { + kotlin.srcDir("src/androidMain/kotlin") + } + } } @@ -180,6 +178,12 @@ if (project.property("firebase-firestore.skipIosTests") == "true") { } } +if (project.property("firebase-firestore.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-firestore.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-firestore/documentation.md b/firebase-firestore/documentation.md new file mode 100644 index 000000000..91158ac94 --- /dev/null +++ b/firebase-firestore/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-firestore +This module is a direct forward of the Firebase Firestore library. It provides the main functionality, like storing data. \ No newline at end of file diff --git a/firebase-firestore/package.json b/firebase-firestore/package.json index 02a96298c..8efd2dd91 100644 --- a/firebase-firestore/package.json +++ b/firebase-firestore/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-firestore", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt index 416d1a374..e484ad9f6 100644 --- a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt +++ b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -2,5 +2,6 @@ package dev.gitlive.firebase.firestore @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreJs + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -actual annotation class IgnoreForAndroidUnitTest \ No newline at end of file +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt deleted file mode 100644 index dcb2c5452..000000000 --- a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.gitlive.firebase.firestore - -actual fun createFirestoreTestSettings( - sslEnabled: Boolean?, - host: String?, - cacheSettings: LocalCacheSettings? -) = FirebaseFirestore.Settings( - sslEnabled, host, cacheSettings -) diff --git a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 8c6035f28..ca3a160c8 100644 --- a/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.firestore import androidx.test.platform.app.InstrumentationRegistry @@ -11,5 +12,6 @@ actual val emulatorHost: String = "10.0.2.2" actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext +@Suppress("UNCHECKED_CAST") actual fun encodedAsMap(encoded: Any?): Map = encoded as Map actual fun Map.asEncoded(): Any = this diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt index f5f2cee34..2c324b45f 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -3,11 +3,11 @@ package dev.gitlive.firebase.firestore import kotlinx.serialization.Serializable /** Represents a platform specific Firebase FieldValue. */ -typealias NativeFieldValue = com.google.firebase.firestore.FieldValue +public typealias NativeFieldValue = com.google.firebase.firestore.FieldValue /** Represents a Firebase FieldValue. */ @Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { +public actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { init { require(nativeValue is NativeFieldValue) } @@ -16,11 +16,11 @@ actual class FieldValue internal actual constructor(internal actual val nativeVa override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() - actual companion object { - actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.serverTimestamp()) - actual val delete: FieldValue get() = FieldValue(NativeFieldValue.delete()) - actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.increment(value.toDouble())) - actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayUnion(*elements)) - actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayRemove(*elements)) + public actual companion object { + public actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.serverTimestamp()) + public actual val delete: FieldValue get() = FieldValue(NativeFieldValue.delete()) + public actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.increment(value.toDouble())) + public actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayUnion(*elements)) + public actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayRemove(*elements)) } } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt index 7523619f5..dcf73b20d 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt @@ -3,14 +3,14 @@ package dev.gitlive.firebase.firestore import kotlinx.serialization.Serializable /** A class representing a platform specific Firebase GeoPoint. */ -actual typealias NativeGeoPoint = com.google.firebase.firestore.GeoPoint +public actual typealias NativeGeoPoint = com.google.firebase.firestore.GeoPoint /** A class representing a Firebase GeoPoint. */ @Serializable(with = GeoPointSerializer::class) -actual class GeoPoint internal actual constructor(internal actual val nativeValue: NativeGeoPoint) { - actual constructor(latitude: Double, longitude: Double) : this(NativeGeoPoint(latitude, longitude)) - actual val latitude: Double = nativeValue.latitude - actual val longitude: Double = nativeValue.longitude +public actual class GeoPoint internal actual constructor(internal actual val nativeValue: NativeGeoPoint) { + public actual constructor(latitude: Double, longitude: Double) : this(NativeGeoPoint(latitude, longitude)) + public actual val latitude: Double = nativeValue.latitude + public actual val longitude: Double = nativeValue.longitude override fun equals(other: Any?): Boolean = this === other || other is GeoPoint && nativeValue == other.nativeValue diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt index cc9a2ddb9..61388745b 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt @@ -1,35 +1,36 @@ @file:JvmName("androidTimestamp") + package dev.gitlive.firebase.firestore import kotlinx.serialization.Serializable /** A class representing a platform specific Firebase Timestamp. */ -actual typealias NativeTimestamp = com.google.firebase.Timestamp +public actual typealias NativeTimestamp = com.google.firebase.Timestamp /** A base class that could be used to combine [Timestamp] and [Timestamp.ServerTimestamp] in the same field. */ @Serializable(with = BaseTimestampSerializer::class) -actual sealed class BaseTimestamp +public actual sealed class BaseTimestamp /** A class representing a Firebase Timestamp. */ @Serializable(with = TimestampSerializer::class) -actual class Timestamp internal actual constructor( - internal actual val nativeValue: NativeTimestamp -): BaseTimestamp() { - actual constructor(seconds: Long, nanoseconds: Int) : this(NativeTimestamp(seconds, nanoseconds)) +public actual class Timestamp internal actual constructor( + internal actual val nativeValue: NativeTimestamp, +) : BaseTimestamp() { + public actual constructor(seconds: Long, nanoseconds: Int) : this(NativeTimestamp(seconds, nanoseconds)) - actual val seconds: Long = nativeValue.seconds - actual val nanoseconds: Int = nativeValue.nanoseconds + public actual val seconds: Long = nativeValue.seconds + public actual val nanoseconds: Int = nativeValue.nanoseconds override fun equals(other: Any?): Boolean = this === other || other is Timestamp && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() - actual companion object { - actual fun now(): Timestamp = Timestamp(NativeTimestamp.now()) + public actual companion object { + public actual fun now(): Timestamp = Timestamp(NativeTimestamp.now()) } /** A server time timestamp. */ @Serializable(with = ServerTimestampSerializer::class) - actual object ServerTimestamp: BaseTimestamp() + public actual data object ServerTimestamp : BaseTimestamp() } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt index d12bda859..a1e313dd6 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -1,10 +1,11 @@ package dev.gitlive.firebase.firestore @PublishedApi -internal actual fun isSpecialValue(value: Any) = when(value) { +internal actual fun isSpecialValue(value: Any): Boolean = when (value) { is NativeFieldValue, is NativeGeoPoint, is NativeTimestamp, - is NativeDocumentReference -> true + is NativeDocumentReferenceType, + -> true else -> false } diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 1b0a65b8f..01106caae 100644 --- a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -3,498 +3,199 @@ */ @file:JvmName("android") -package dev.gitlive.firebase.firestore -import com.google.android.gms.tasks.Task -import com.google.firebase.firestore.* -import dev.gitlive.firebase.* -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.tasks.asDeferred -import kotlinx.coroutines.tasks.await -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy - -actual val Firebase.firestore get() = - FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance()) - -actual fun Firebase.firestore(app: FirebaseApp) = - FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance(app.android)) - -@Suppress("DeferredIsResult") -@PublishedApi -internal fun Task.asUnitDeferred(): Deferred = CompletableDeferred() - .apply { - asDeferred().invokeOnCompletion { exception -> - if (exception == null) complete(Unit) else completeExceptionally(exception) - } - } +package dev.gitlive.firebase.firestore -val LocalCacheSettings.android: com.google.firebase.firestore.LocalCacheSettings get() = when (this) { - is LocalCacheSettings.Persistent -> persistentCacheSettings { - sizeBytes?.let { setSizeBytes(it) } - } - is LocalCacheSettings.Memory -> memoryCacheSettings { +import com.google.android.gms.tasks.TaskExecutors +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import java.util.concurrent.Executor +import com.google.firebase.firestore.CollectionReference as AndroidCollectionReference +import com.google.firebase.firestore.DocumentChange as AndroidDocumentChange +import com.google.firebase.firestore.DocumentReference as AndroidDocumentReference +import com.google.firebase.firestore.DocumentSnapshot as AndroidDocumentSnapshot +import com.google.firebase.firestore.FieldPath as AndroidFieldPath +import com.google.firebase.firestore.FirebaseFirestore as AndroidFirebaseFirestore +import com.google.firebase.firestore.FirebaseFirestoreException as AndroidFirebaseFirestoreException +import com.google.firebase.firestore.LocalCacheSettings as AndroidLocalCacheSettings +import com.google.firebase.firestore.Query as AndroidQuery +import com.google.firebase.firestore.QuerySnapshot as AndroidQuerySnapshot +import com.google.firebase.firestore.SnapshotMetadata as AndroidSnapshotMetadata +import com.google.firebase.firestore.Source as AndroidSource +import com.google.firebase.firestore.Transaction as AndroidTransaction +import com.google.firebase.firestore.WriteBatch as AndroidWriteBatch +import com.google.firebase.firestore.memoryCacheSettings as androidMemoryCacheSettings +import com.google.firebase.firestore.memoryEagerGcSettings as androidMemoryEagerGcSettings +import com.google.firebase.firestore.memoryLruGcSettings as androidMemoryLruGcSettings +import com.google.firebase.firestore.persistentCacheSettings as androidPersistentCacheSettings + +public val FirebaseFirestore.android: AndroidFirebaseFirestore get() = AndroidFirebaseFirestore.getInstance() + +public actual val Firebase.firestore: FirebaseFirestore get() = + FirebaseFirestore(AndroidFirebaseFirestore.getInstance()) + +public actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore = + FirebaseFirestore(AndroidFirebaseFirestore.getInstance(app.android)) + +public val LocalCacheSettings.android: AndroidLocalCacheSettings get() = when (this) { + is LocalCacheSettings.Persistent -> androidPersistentCacheSettings { + setSizeBytes(sizeBytes) + } + is LocalCacheSettings.Memory -> androidMemoryCacheSettings { setGcSettings( when (garbaseCollectorSettings) { - is LocalCacheSettings.Memory.GarbageCollectorSettings.Eager -> memoryEagerGcSettings { } - is LocalCacheSettings.Memory.GarbageCollectorSettings.LRUGC -> memoryLruGcSettings { - garbaseCollectorSettings.sizeBytes?.let { - setSizeBytes(it) - } + is MemoryGarbageCollectorSettings.Eager -> androidMemoryEagerGcSettings { } + is MemoryGarbageCollectorSettings.LRUGC -> androidMemoryLruGcSettings { + setSizeBytes(garbaseCollectorSettings.sizeBytes) } - } + }, ) } } -actual data class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { - - actual data class Settings( - actual val sslEnabled: Boolean? = null, - actual val host: String? = null, - actual val cacheSettings: LocalCacheSettings? = null - ) { - actual companion object { - actual fun create(sslEnabled: Boolean?, host: String?, cacheSettings: LocalCacheSettings?) = Settings(sslEnabled, host, cacheSettings) - } - } - - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) - - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - - actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId)) - - actual fun batch() = WriteBatch(android.batch()) - - actual fun setLoggingEnabled(loggingEnabled: Boolean) = - com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) - - actual suspend fun runTransaction(func: suspend Transaction.() -> T): T = - android.runTransaction { runBlocking { Transaction(it).func() } }.await() - - actual suspend fun clearPersistence() = - android.clearPersistence().await().run { } - - actual fun useEmulator(host: String, port: Int) { - android.useEmulator(host, port) - android.firestoreSettings = firestoreSettings { } - } - - actual fun setSettings(settings: Settings) { - android.firestoreSettings = firestoreSettings { - settings.sslEnabled?.let { isSslEnabled = it } - settings.host?.let { host = it } - settings.cacheSettings?.let { setLocalCacheSettings(it.android) } - } - } - - @Suppress("DEPRECATION") - actual fun updateSettings(settings: Settings) { - android.firestoreSettings = firestoreSettings { - isSslEnabled = settings.sslEnabled ?: android.firestoreSettings.isSslEnabled - host = settings.host ?: android.firestoreSettings.host - val cacheSettings = settings.cacheSettings?.android ?: android.firestoreSettings.cacheSettings - cacheSettings?.let { - setLocalCacheSettings(it) - } ?: kotlin.run { - isPersistenceEnabled = android.firestoreSettings.isPersistenceEnabled - setCacheSizeBytes(android.firestoreSettings.cacheSizeBytes) - } - } - } - - actual suspend fun disableNetwork() = - android.disableNetwork().await().run { } - - actual suspend fun enableNetwork() = - android.enableNetwork().await().run { } - -} - -val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { - is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() - is SetOptions.Overwrite -> null - is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) - is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) -} - -actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : BaseWriteBatch() { +internal actual typealias NativeFirebaseFirestore = AndroidFirebaseFirestore - actual val async = Async(android) +public operator fun FirebaseFirestore.Companion.invoke(android: AndroidFirebaseFirestore): FirebaseFirestore = FirebaseFirestore(android) - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseWriteBatch = (setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData)).let { - this - } +public actual data class FirebaseFirestoreSettings( + actual val sslEnabled: Boolean, + actual val host: String, + actual val cacheSettings: LocalCacheSettings, + val callbackExecutor: Executor, +) { - @Suppress("UNCHECKED_CAST") - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List>, - merge: Boolean - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - if (merge) { - android.set(documentRef.android, result, com.google.firebase.firestore.SetOptions.merge()) - } else { - android.set(documentRef.android, result) - } - return this + public actual companion object { + public actual val CACHE_SIZE_UNLIMITED: Long = -1L + internal actual val DEFAULT_HOST: String = "firestore.googleapis.com" + internal actual val MINIMUM_CACHE_BYTES: Long = 1 * 1024 * 1024 + internal actual val DEFAULT_CACHE_SIZE_BYTES: Long = 100 * 1024 * 1024 } - @Suppress("UNCHECKED_CAST") - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = android.update(documentRef.android, encodedData as Map).let { this } - - @Suppress("UNCHECKED_CAST") - override fun updateEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List> - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - return android.update(documentRef.android, result).let { this } - } - - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } + public actual class Builder internal constructor( + public actual var sslEnabled: Boolean, + public actual var host: String, + public actual var cacheSettings: LocalCacheSettings, + public var callbackExecutor: Executor, + ) { - actual suspend fun commit() = async.commit().await() + public actual constructor() : this( + true, + DEFAULT_HOST, + persistentCacheSettings { }, + TaskExecutors.MAIN_THREAD, + ) + public actual constructor(settings: FirebaseFirestoreSettings) : this(settings.sslEnabled, settings.host, settings.cacheSettings, settings.callbackExecutor) - @Suppress("DeferredIsResult") - actual class Async(private val android: com.google.firebase.firestore.WriteBatch) { - actual fun commit(): Deferred = android.commit().asUnitDeferred() + public actual fun build(): FirebaseFirestoreSettings = FirebaseFirestoreSettings(sslEnabled, host, cacheSettings, callbackExecutor) } } -actual class Transaction(val android: com.google.firebase.firestore.Transaction) : BaseTransaction() { - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseTransaction { - setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData) - return this +public actual fun firestoreSettings( + settings: FirebaseFirestoreSettings?, + builder: FirebaseFirestoreSettings.Builder.() -> Unit, +): FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().apply { + settings?.let { + sslEnabled = it.sslEnabled + host = it.host + cacheSettings = it.cacheSettings + callbackExecutor = it.callbackExecutor } +}.apply(builder).build() - @Suppress("UNCHECKED_CAST") - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = android.update(documentRef.android, encodedData as Map).let { this } +internal actual typealias NativeWriteBatch = AndroidWriteBatch - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseTransaction = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } +public operator fun WriteBatch.Companion.invoke(android: AndroidWriteBatch): WriteBatch = WriteBatch(android) +public val WriteBatch.android: AndroidWriteBatch get() = native - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } +internal actual typealias NativeTransaction = AndroidTransaction - actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - DocumentSnapshot(android.get(documentRef.android)) -} +public operator fun Transaction.Companion.invoke(android: AndroidTransaction): Transaction = Transaction(android) +public val Transaction.android: AndroidTransaction get() = native /** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = com.google.firebase.firestore.DocumentReference - -@Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - val android: NativeDocumentReference by ::nativeValue - actual val id: String - get() = android.id - - actual val path: String - get() = android.path - - actual val parent: CollectionReference - get() = CollectionReference(android.parent) - - override val async = Async(android) - - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) - - actual suspend fun get() = - DocumentSnapshot(android.get().await()) +internal actual typealias NativeDocumentReferenceType = AndroidDocumentReference - actual val snapshots: Flow get() = snapshots() +public operator fun DocumentReference.Companion.invoke(android: AndroidDocumentReference): DocumentReference = DocumentReference(android) +public val DocumentReference.android: AndroidDocumentReference get() = native.android - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(DocumentSnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - @Suppress("DeferredIsResult") - class Async(@PublishedApi internal val android: NativeDocumentReference) : BaseDocumentReference.Async() { - - override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = (setOptions.android?.let { - android.set(encodedData, it) - } ?: android.set(encodedData)).asUnitDeferred() - - @Suppress("UNCHECKED_CAST") - override fun updateEncoded(encodedData: Any): Deferred = android.update(encodedData as Map).asUnitDeferred() - - override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { - android.update(encodedFieldsAndValues.toMap()) - }?.asUnitDeferred() ?: CompletableDeferred(Unit) - - override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.asUnitDeferred() ?: CompletableDeferred(Unit) - - override fun delete() = - android.delete().asUnitDeferred() - } -} - -actual open class Query(open val android: com.google.firebase.firestore.Query) { - - actual suspend fun get() = QuerySnapshot(android.get().await()) - - actual fun limit(limit: Number) = Query(android.limit(limit.toLong())) - - actual val snapshots get() = callbackFlow { - val listener = android.addSnapshotListener { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - internal actual fun _where(field: String, equalTo: Any?) = Query(android.whereEqualTo(field, equalTo)) - internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(android.whereEqualTo(path.android, equalTo)) - - internal actual fun _where(field: String, equalTo: DocumentReference) = Query(android.whereEqualTo(field, equalTo.android)) - internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(android.whereEqualTo(path.android, equalTo.android)) - - internal actual fun _where( - field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = Query( - when { - lessThan != null -> android.whereLessThan(field, lessThan) - greaterThan != null -> android.whereGreaterThan(field, greaterThan) - arrayContains != null -> android.whereArrayContains(field, arrayContains) - notEqualTo != null -> android.whereNotEqualTo(field, notEqualTo) - lessThanOrEqualTo != null -> android.whereLessThanOrEqualTo(field, lessThanOrEqualTo) - greaterThanOrEqualTo != null -> android.whereGreaterThanOrEqualTo(field, greaterThanOrEqualTo) - else -> android - } - ) - - internal actual fun _where( - path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = Query( - when { - lessThan != null -> android.whereLessThan(path.android, lessThan) - greaterThan != null -> android.whereGreaterThan(path.android, greaterThan) - arrayContains != null -> android.whereArrayContains(path.android, arrayContains) - notEqualTo != null -> android.whereNotEqualTo(path.android, notEqualTo) - lessThanOrEqualTo != null -> android.whereLessThanOrEqualTo(path.android, lessThanOrEqualTo) - greaterThanOrEqualTo != null -> android.whereGreaterThanOrEqualTo(path.android, greaterThanOrEqualTo) - else -> android - } - ) - - internal actual fun _where( - field: String, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = Query( - when { - inArray != null -> android.whereIn(field, inArray) - arrayContainsAny != null -> android.whereArrayContainsAny(field, arrayContainsAny) - notInArray != null -> android.whereNotIn(field, notInArray) - else -> android - } - ) - - internal actual fun _where( - path: FieldPath, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = Query( - when { - inArray != null -> android.whereIn(path.android, inArray) - arrayContainsAny != null -> android.whereArrayContainsAny(path.android, arrayContainsAny) - notInArray != null -> android.whereNotIn(path.android, notInArray) - else -> android - } - ) +internal actual typealias NativeQuery = AndroidQuery - internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction)) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(android.orderBy(field.android, direction)) - - internal actual fun _startAfter(document: DocumentSnapshot) = Query(android.startAfter(document.android)) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(android.startAfter(*fieldValues)) - internal actual fun _startAt(document: DocumentSnapshot) = Query(android.startAt(document.android)) - internal actual fun _startAt(vararg fieldValues: Any) = Query(android.startAt(*fieldValues)) - - internal actual fun _endBefore(document: DocumentSnapshot) = Query(android.endBefore(document.android)) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues)) - internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android)) - internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues)) -} +public operator fun Query.Companion.invoke(android: AndroidQuery): Query = Query(android) +public val Query.android: AndroidQuery get() = native -actual typealias Direction = com.google.firebase.firestore.Query.Direction -actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type +public actual typealias Direction = AndroidQuery.Direction +public actual typealias ChangeType = AndroidDocumentChange.Type -actual class CollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : Query(android) { +internal actual typealias NativeCollectionReference = AndroidCollectionReference - actual val path: String - get() = android.path - actual val async = Async(android) +public operator fun CollectionReference.Companion.invoke(android: AndroidCollectionReference): CollectionReference = CollectionReference(android) +public val CollectionReference.android: AndroidCollectionReference get() = native - actual val document: DocumentReference - get() = DocumentReference(android.document()) - - actual val parent: DocumentReference? - get() = android.parent?.let{DocumentReference(it)} - - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - - actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = - DocumentReference(android.add(encode(data, encodeSettings)!!).await()) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - DocumentReference(android.add(encode(strategy, data, encodeSettings)!!).await()) - - @Suppress("DeferredIsResult") - actual class Async(@PublishedApi internal val android: com.google.firebase.firestore.CollectionReference) { - actual inline fun add(data: T, encodeSettings: EncodeSettings) = - android.add(encode(data, encodeSettings)!!).asDeferred().convert(::DocumentReference) - actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - android.add(encode(strategy, data, encodeSettings)!!).asDeferred().convert(::DocumentReference) - } -} +public actual typealias FirebaseFirestoreException = AndroidFirebaseFirestoreException -actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException +@Suppress("ConflictingExtensionProperty") +public actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code -actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code +public actual typealias FirestoreExceptionCode = AndroidFirebaseFirestoreException.Code -actual typealias FirestoreExceptionCode = com.google.firebase.firestore.FirebaseFirestoreException.Code +public val QuerySnapshot.android: AndroidQuerySnapshot get() = android -actual class QuerySnapshot(val android: com.google.firebase.firestore.QuerySnapshot) { - actual val documents - get() = android.documents.map { DocumentSnapshot(it) } - actual val documentChanges +public actual class QuerySnapshot(internal val android: AndroidQuerySnapshot) { + public actual val documents: List + get() = android.documents.map { DocumentSnapshot(NativeDocumentSnapshotWrapper(it)) } + public actual val documentChanges: List get() = android.documentChanges.map { DocumentChange(it) } - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) + public actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) } -actual class DocumentChange(val android: com.google.firebase.firestore.DocumentChange) { - actual val document: DocumentSnapshot - get() = DocumentSnapshot(android.document) - actual val newIndex: Int +public val DocumentChange.android: AndroidDocumentChange get() = android + +public actual class DocumentChange(internal val android: AndroidDocumentChange) { + public actual val document: DocumentSnapshot + get() = DocumentSnapshot(NativeDocumentSnapshotWrapper(android.document)) + public actual val newIndex: Int get() = android.newIndex - actual val oldIndex: Int + public actual val oldIndex: Int get() = android.oldIndex - actual val type: ChangeType + public actual val type: ChangeType get() = android.type } -@Suppress("UNCHECKED_CAST") -actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) { - - actual val id get() = android.id - actual val reference get() = DocumentReference(android.reference) +internal actual typealias NativeDocumentSnapshot = AndroidDocumentSnapshot - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.getData(serverTimestampBehavior.toAndroid())) +public operator fun DocumentSnapshot.Companion.invoke(android: AndroidDocumentSnapshot): DocumentSnapshot = DocumentSnapshot(android) +public val DocumentSnapshot.android: AndroidDocumentSnapshot get() = native - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.getData(serverTimestampBehavior.toAndroid()), decodeSettings) +public val SnapshotMetadata.android: AndroidSnapshotMetadata get() = android - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.get(field, serverTimestampBehavior.toAndroid())) - - actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.get(field, serverTimestampBehavior.toAndroid()), decodeSettings) - - actual fun contains(field: String) = android.contains(field) +public actual class SnapshotMetadata(internal val android: AndroidSnapshotMetadata) { + public actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() + public actual val isFromCache: Boolean get() = android.isFromCache +} - actual val exists get() = android.exists() +public val FieldPath.android: AndroidFieldPath get() = android - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) +public actual class FieldPath private constructor(internal val android: AndroidFieldPath) { - fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE - ServerTimestampBehavior.NONE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.NONE - ServerTimestampBehavior.PREVIOUS -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.PREVIOUS + public actual companion object { + public actual val documentId: FieldPath = FieldPath(AndroidFieldPath.documentId()) } -} - -actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { - actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() - actual val isFromCache: Boolean get() = android.isFromCache -} -actual class FieldPath private constructor(val android: com.google.firebase.firestore.FieldPath) { - actual constructor(vararg fieldNames: String) : this( - com.google.firebase.firestore.FieldPath.of( - *fieldNames - ) + public actual constructor(vararg fieldNames: String) : this( + AndroidFieldPath.of( + *fieldNames, + ), ) - actual val documentId: FieldPath get() = FieldPath(com.google.firebase.firestore.FieldPath.documentId()) - actual val encoded: EncodedFieldPath = android + public actual val documentId: FieldPath get() = FieldPath.documentId + public actual val encoded: EncodedFieldPath = android override fun equals(other: Any?): Boolean = other is FieldPath && android == other.android override fun hashCode(): Int = android.hashCode() override fun toString(): String = android.toString() } -actual typealias EncodedFieldPath = com.google.firebase.firestore.FieldPath +public actual typealias EncodedFieldPath = AndroidFieldPath + +internal typealias NativeSource = AndroidSource diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..d6b73afb0 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,24 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.tasks.await + +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + actual val path: String + get() = native.path + + actual val document: NativeDocumentReference + get() = NativeDocumentReference(native.document()) + + actual val parent: NativeDocumentReference? + get() = native.parent?.let { NativeDocumentReference(it) } + + actual fun document(documentPath: String) = + NativeDocumentReference(native.document(documentPath)) + + actual suspend fun addEncoded(data: EncodedObject) = + NativeDocumentReference(native.add(data.android).await()) +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..cb91ffd74 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,89 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.MetadataChanges +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.tasks.await + +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + val android: NativeDocumentReferenceType by ::nativeValue + actual val id: String + get() = android.id + + actual val path: String + get() = android.path + + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(android.parent) + + actual fun collection(collectionPath: String) = android.collection(collectionPath) + + actual suspend fun get(source: Source) = + android.get(source.toAndroidSource()).await() + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { + val task = ( + setOptions.android?.let { + android.set(encodedData.android, it) + } ?: android.set(encodedData.android) + ) + task.await() + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) { + android.update(encodedData.android).await() + } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { + android.update(encodedFieldsAndValues.toMap()) + }?.await() + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + android.update(field, value, *moreFieldsAndValues) + }?.await() + } + + actual suspend fun delete() { + android.delete().await() + } + + actual val snapshots: Flow get() = snapshots() + + actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception -> + snapshot?.let { trySend(snapshot) } + exception?.let { close(exception) } + } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.DocumentSnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit, + ) = callbackFlow { + val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = + android.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..931720dba --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,28 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata + +internal actual class NativeDocumentSnapshotWrapper internal actual constructor(actual val native: com.google.firebase.firestore.DocumentSnapshot) { + + actual val id get() = native.id + actual val reference get() = NativeDocumentReference(native.reference) + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(field, serverTimestampBehavior.toAndroid()) + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = native.get(fieldPath, serverTimestampBehavior.toAndroid()) + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = native.getData(serverTimestampBehavior.toAndroid()) + + actual fun contains(field: String) = native.contains(field) + actual fun contains(fieldPath: EncodedFieldPath) = native.contains(fieldPath) + + actual val exists get() = native.exists() + + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) + + fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { + ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE + ServerTimestampBehavior.NONE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.NONE + ServerTimestampBehavior.PREVIOUS -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.PREVIOUS + } +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..c3f08216a --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,49 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.android +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.tasks.await + +internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native: NativeFirebaseFirestore) { + + actual fun collection(collectionPath: String) = native.collection(collectionPath) + + actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId) + + actual fun document(documentPath: String) = + NativeDocumentReference(native.document(documentPath)) + + actual fun batch() = native.batch() + + actual fun setLoggingEnabled(loggingEnabled: Boolean) = + com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) + + actual fun applySettings(settings: FirebaseFirestoreSettings) { + native.firestoreSettings = firestoreSettings { + isSslEnabled = settings.sslEnabled + host = settings.host + setLocalCacheSettings(settings.cacheSettings.android) + } + callbackExecutorMap[native] = settings.callbackExecutor + } + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T = + native.runTransaction { runBlocking { it.func() } }.await() + + actual suspend fun clearPersistence() = + native.clearPersistence().await().run { } + + actual fun useEmulator(host: String, port: Int) { + native.useEmulator(host, port) + } + + actual suspend fun disableNetwork() = + native.disableNetwork().await().run { } + + actual suspend fun enableNetwork() = + native.enableNetwork().await().run { } +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..5dc17f72b --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,141 @@ +package dev.gitlive.firebase.firestore.internal + +import com.google.android.gms.tasks.TaskExecutors +import com.google.firebase.firestore.FieldPath +import com.google.firebase.firestore.MetadataChanges +import com.google.firebase.firestore.Query +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.tasks.await + +internal actual open class NativeQueryWrapper internal actual constructor(actual open val native: Query) { + + actual fun limit(limit: Number) = native.limit(limit.toLong()) + + actual val snapshots get() = callbackFlow { + val listener = native.addSnapshotListener { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } + } + awaitClose { listener.remove() } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val listener = native.addSnapshotListener(metadataChanges) { snapshot, exception -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + exception?.let { close(exception) } + } + awaitClose { listener.remove() } + } + + actual suspend fun get(source: Source): QuerySnapshot = + QuerySnapshot(native.get(source.toAndroidSource()).await()) + + actual fun where(filter: Filter) = native.where(filter.toAndroidFilter()) + + private fun Filter.toAndroidFilter(): com.google.firebase.firestore.Filter = when (this) { + is Filter.And -> com.google.firebase.firestore.Filter.and( + *filters.map { it.toAndroidFilter() } + .toTypedArray(), + ) + is Filter.Or -> com.google.firebase.firestore.Filter.or( + *filters.map { it.toAndroidFilter() } + .toTypedArray(), + ) + is Filter.Field -> { + when (constraint) { + is WhereConstraint.ForNullableObject -> { + val modifier: (String, Any?) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.EqualTo -> com.google.firebase.firestore.Filter::equalTo + is WhereConstraint.NotEqualTo -> com.google.firebase.firestore.Filter::notEqualTo + } + modifier.invoke(field, constraint.safeValue) + } + is WhereConstraint.ForObject -> { + val modifier: (String, Any) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.LessThan -> com.google.firebase.firestore.Filter::lessThan + is WhereConstraint.GreaterThan -> com.google.firebase.firestore.Filter::greaterThan + is WhereConstraint.LessThanOrEqualTo -> com.google.firebase.firestore.Filter::lessThanOrEqualTo + is WhereConstraint.GreaterThanOrEqualTo -> com.google.firebase.firestore.Filter::greaterThanOrEqualTo + is WhereConstraint.ArrayContains -> com.google.firebase.firestore.Filter::arrayContains + } + modifier.invoke(field, constraint.safeValue) + } + is WhereConstraint.ForArray -> { + val modifier: (String, List) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.InArray -> com.google.firebase.firestore.Filter::inArray + is WhereConstraint.ArrayContainsAny -> com.google.firebase.firestore.Filter::arrayContainsAny + is WhereConstraint.NotInArray -> com.google.firebase.firestore.Filter::notInArray + } + modifier.invoke(field, constraint.safeValues) + } + } + } + is Filter.Path -> { + when (constraint) { + is WhereConstraint.ForNullableObject -> { + val modifier: (FieldPath, Any?) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.EqualTo -> com.google.firebase.firestore.Filter::equalTo + is WhereConstraint.NotEqualTo -> com.google.firebase.firestore.Filter::notEqualTo + } + modifier.invoke(path.android, constraint.safeValue) + } + is WhereConstraint.ForObject -> { + val modifier: (FieldPath, Any) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.LessThan -> com.google.firebase.firestore.Filter::lessThan + is WhereConstraint.GreaterThan -> com.google.firebase.firestore.Filter::greaterThan + is WhereConstraint.LessThanOrEqualTo -> com.google.firebase.firestore.Filter::lessThanOrEqualTo + is WhereConstraint.GreaterThanOrEqualTo -> com.google.firebase.firestore.Filter::greaterThanOrEqualTo + is WhereConstraint.ArrayContains -> com.google.firebase.firestore.Filter::arrayContains + } + modifier.invoke(path.android, constraint.safeValue) + } + is WhereConstraint.ForArray -> { + val modifier: (FieldPath, List) -> com.google.firebase.firestore.Filter = when (constraint) { + is WhereConstraint.InArray -> com.google.firebase.firestore.Filter::inArray + is WhereConstraint.ArrayContainsAny -> com.google.firebase.firestore.Filter::arrayContainsAny + is WhereConstraint.NotInArray -> com.google.firebase.firestore.Filter::notInArray + } + modifier.invoke(path.android, constraint.safeValues) + } + } + } + } + + actual fun orderBy(field: String, direction: Direction) = native.orderBy(field, direction) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.orderBy(field, direction) + + actual fun startAfter(document: NativeDocumentSnapshot) = native.startAfter(document) + actual fun startAfter(vararg fieldValues: Any) = native.startAfter(*fieldValues) + actual fun startAt(document: NativeDocumentSnapshot) = native.startAt(document) + actual fun startAt(vararg fieldValues: Any) = native.startAt(*fieldValues) + + actual fun endBefore(document: NativeDocumentSnapshot) = native.endBefore(document) + actual fun endBefore(vararg fieldValues: Any) = native.endBefore(*fieldValues) + actual fun endAt(document: NativeDocumentSnapshot) = native.endAt(document) + actual fun endAt(vararg fieldValues: Any) = native.endAt(*fieldValues) + + private fun addSnapshotListener( + includeMetadataChanges: Boolean = false, + listener: ProducerScope.(com.google.firebase.firestore.QuerySnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit, + ) = callbackFlow { + val executor = callbackExecutorMap[native.firestore] ?: TaskExecutors.MAIN_THREAD + val metadataChanges = + if (includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE + val registration = + native.addSnapshotListener(executor, metadataChanges) { snapshots, exception -> + listener(snapshots, exception) + } + awaitClose { registration.remove() } + } +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..4b8219703 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,45 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.android +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android + +internal actual class NativeTransactionWrapper internal actual constructor(actual val native: NativeTransaction) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions, + ): NativeTransactionWrapper { + setOptions.android?.let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android) + return this + } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + native.delete(documentRef.android).let { this } + + actual suspend fun get(documentRef: DocumentReference) = + NativeDocumentSnapshotWrapper(native.get(documentRef.android)) +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..9aa34f700 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,48 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.android +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.android +import kotlinx.coroutines.tasks.await + +internal actual class NativeWriteBatchWrapper internal actual constructor(actual val native: NativeWriteBatch) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions, + ): NativeWriteBatchWrapper = ( + setOptions.android?.let { + native.set(documentRef.android, encodedData.android, it) + } ?: native.set(documentRef.android, encodedData.android) + ).let { + this + } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject) = native.update(documentRef.android, encodedData.android).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + native.update(documentRef.android, field, value, *moreFieldsAndValues) + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + native.delete(documentRef.android).let { this } + + actual suspend fun commit() { + native.commit().await() + } +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..8b44d4799 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,8 @@ +package dev.gitlive.firebase.firestore.internal + +internal val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { + is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() + is SetOptions.Overwrite -> null + is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) + is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt new file mode 100644 index 000000000..4ef12d369 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeSource +import dev.gitlive.firebase.firestore.Source + +internal fun Source.toAndroidSource() = when (this) { + Source.CACHE -> NativeSource.CACHE + Source.SERVER -> NativeSource.SERVER + Source.DEFAULT -> NativeSource.DEFAULT +} diff --git a/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt new file mode 100644 index 000000000..386ef0505 --- /dev/null +++ b/firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/callbackExecutorMap.kt @@ -0,0 +1,7 @@ +package dev.gitlive.firebase.firestore.internal + +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executor + +// Since on iOS Callback threads are set as settings, we store the settings explicitly here as well +internal val callbackExecutorMap = ConcurrentHashMap() diff --git a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt index 2fc601b0e..6deceefb3 100644 --- a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt +++ b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -4,4 +4,4 @@ import org.junit.Ignore @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreJs -actual typealias IgnoreForAndroidUnitTest = Ignore \ No newline at end of file +actual typealias IgnoreForAndroidUnitTest = Ignore diff --git a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt deleted file mode 100644 index dcb2c5452..000000000 --- a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.gitlive.firebase.firestore - -actual fun createFirestoreTestSettings( - sslEnabled: Boolean?, - host: String?, - cacheSettings: LocalCacheSettings? -) = FirebaseFirestore.Settings( - sslEnabled, host, cacheSettings -) diff --git a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index e56e244c6..c935a03f4 100644 --- a/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/androidUnitTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -3,11 +3,13 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.firestore actual val emulatorHost: String = "10.0.2.2" -actual val context: Any = Unit +actual val context: Any = "" +@Suppress("UNCHECKED_CAST") actual fun encodedAsMap(encoded: Any?): Map = encoded as Map actual fun Map.asEncoded(): Any = this diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt deleted file mode 100644 index 00cae66af..000000000 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DeferredExtensions.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.gitlive.firebase.firestore - -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred - -@PublishedApi -internal fun Deferred.convert(converter: (T) -> R): Deferred { - val deferred = CompletableDeferred() - invokeOnCompletion { exception -> - if (exception == null) { - deferred.complete(converter(getCompleted())) - } else { - deferred.completeExceptionally(exception) - } - } - return deferred -} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt index 038edc434..10fa9341f 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/DocumentReferenceSerializer.kt @@ -1,20 +1,21 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.firestore.internal.NativeDocumentReference +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException /** * A serializer for [DocumentReference]. If used with [FirebaseEncoder] performs serialization using native Firebase mechanisms. */ -object DocumentReferenceSerializer : KSerializer by SpecialValueSerializer( +public object DocumentReferenceSerializer : KSerializer by SpecialValueSerializer( serialName = "DocumentReference", - toNativeValue = DocumentReference::nativeValue, + toNativeValue = { it.native.nativeValue }, fromNativeValue = { value -> when (value) { - is NativeDocumentReference -> DocumentReference(value) + is NativeDocumentReferenceType -> DocumentReference(NativeDocumentReference(value)) else -> throw SerializationException("Cannot deserialize $value") } - } + }, ) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt index a091c4c05..61b5860ad 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -4,14 +4,14 @@ import kotlinx.serialization.Serializable /** Represents a Firebase FieldValue. */ @Serializable(with = FieldValueSerializer::class) -expect class FieldValue internal constructor(nativeValue: Any) { +public expect class FieldValue internal constructor(nativeValue: Any) { internal val nativeValue: Any - companion object { - val serverTimestamp: FieldValue - val delete: FieldValue - fun increment(value: Int): FieldValue - fun arrayUnion(vararg elements: Any): FieldValue - fun arrayRemove(vararg elements: Any): FieldValue + public companion object { + public val serverTimestamp: FieldValue + public val delete: FieldValue + public fun increment(value: Int): FieldValue + public fun arrayUnion(vararg elements: Any): FieldValue + public fun arrayRemove(vararg elements: Any): FieldValue } } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt index 2dc95492f..d2717474d 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/FieldValueSerializer.kt @@ -1,15 +1,15 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException /** A serializer for [FieldValue]. Must be used in conjunction with [FirebaseEncoder]. */ -object FieldValueSerializer : KSerializer by SpecialValueSerializer( +public object FieldValueSerializer : KSerializer by SpecialValueSerializer( serialName = "FieldValue", toNativeValue = FieldValue::nativeValue, fromNativeValue = { raw -> raw?.let(::FieldValue) ?: throw SerializationException("Cannot deserialize $raw") - } + }, ) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt new file mode 100644 index 000000000..f9e8558e7 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Filter.kt @@ -0,0 +1,116 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.firestore.internal.safeValue + +public sealed interface WhereConstraint { + + public sealed interface ForNullableObject : WhereConstraint { + public val value: Any? + public val safeValue: Any? get() = value?.safeValue + } + + public sealed interface ForObject : WhereConstraint { + public val value: Any + public val safeValue: Any get() = value.safeValue + } + public sealed interface ForArray : WhereConstraint { + public val values: List + public val safeValues: List get() = values.map { it.safeValue } + } + + public data class EqualTo internal constructor(override val value: Any?) : ForNullableObject + public data class NotEqualTo internal constructor(override val value: Any?) : ForNullableObject + public data class LessThan internal constructor(override val value: Any) : ForObject + public data class GreaterThan internal constructor(override val value: Any) : ForObject + public data class LessThanOrEqualTo internal constructor(override val value: Any) : ForObject + public data class GreaterThanOrEqualTo internal constructor(override val value: Any) : ForObject + public data class ArrayContains internal constructor(override val value: Any) : ForObject + public data class ArrayContainsAny internal constructor(override val values: List) : ForArray + public data class InArray internal constructor(override val values: List) : ForArray + public data class NotInArray internal constructor(override val values: List) : ForArray +} + +public sealed class Filter { + public data class And internal constructor(val filters: List) : Filter() + public data class Or internal constructor(val filters: List) : Filter() + public sealed class WithConstraint : Filter() { + public abstract val constraint: WhereConstraint + } + + public data class Field internal constructor(val field: String, override val constraint: WhereConstraint) : WithConstraint() + public data class Path internal constructor(val path: FieldPath, override val constraint: WhereConstraint) : WithConstraint() +} + +public class FilterBuilder internal constructor() { + + public infix fun String.equalTo(value: Any?): Filter.WithConstraint = Filter.Field(this, WhereConstraint.EqualTo(value)) + + public infix fun FieldPath.equalTo(value: Any?): Filter.WithConstraint = Filter.Path(this, WhereConstraint.EqualTo(value)) + + public infix fun String.notEqualTo(value: Any?): Filter.WithConstraint = Filter.Field(this, WhereConstraint.NotEqualTo(value)) + + public infix fun FieldPath.notEqualTo(value: Any?): Filter.WithConstraint = Filter.Path(this, WhereConstraint.NotEqualTo(value)) + + public infix fun String.lessThan(value: Any): Filter.WithConstraint = Filter.Field(this, WhereConstraint.LessThan(value)) + + public infix fun FieldPath.lessThan(value: Any): Filter.WithConstraint = Filter.Path(this, WhereConstraint.LessThan(value)) + + public infix fun String.greaterThan(value: Any): Filter.WithConstraint = Filter.Field(this, WhereConstraint.GreaterThan(value)) + + public infix fun FieldPath.greaterThan(value: Any): Filter.WithConstraint = Filter.Path(this, WhereConstraint.GreaterThan(value)) + + public infix fun String.lessThanOrEqualTo(value: Any): Filter.WithConstraint = Filter.Field(this, WhereConstraint.LessThanOrEqualTo(value)) + + public infix fun FieldPath.lessThanOrEqualTo(value: Any): Filter.WithConstraint = Filter.Path(this, WhereConstraint.LessThanOrEqualTo(value)) + + public infix fun String.greaterThanOrEqualTo(value: Any): Filter.WithConstraint = Filter.Field(this, WhereConstraint.GreaterThanOrEqualTo(value)) + + public infix fun FieldPath.greaterThanOrEqualTo(value: Any): Filter.WithConstraint = Filter.Path(this, WhereConstraint.GreaterThanOrEqualTo(value)) + + public infix fun String.contains(value: Any): Filter.WithConstraint = Filter.Field(this, WhereConstraint.ArrayContains(value)) + + public infix fun FieldPath.contains(value: Any): Filter.WithConstraint = Filter.Path(this, WhereConstraint.ArrayContains(value)) + + public infix fun String.containsAny(values: List): Filter.WithConstraint = Filter.Field(this, WhereConstraint.ArrayContainsAny(values)) + + public infix fun FieldPath.containsAny(values: List): Filter.WithConstraint = Filter.Path(this, WhereConstraint.ArrayContainsAny(values)) + + public infix fun String.inArray(values: List): Filter.WithConstraint = Filter.Field(this, WhereConstraint.InArray(values)) + + public infix fun FieldPath.inArray(values: List): Filter.WithConstraint = Filter.Path(this, WhereConstraint.InArray(values)) + + public infix fun String.notInArray(values: List): Filter.WithConstraint = Filter.Field(this, WhereConstraint.NotInArray(values)) + + public infix fun FieldPath.notInArray(values: List): Filter.WithConstraint = Filter.Path(this, WhereConstraint.NotInArray(values)) + + public infix fun Filter.and(right: Filter): Filter.And { + val leftList = when (this) { + is Filter.And -> filters + else -> listOf(this) + } + val rightList = when (right) { + is Filter.And -> right.filters + else -> listOf(right) + } + return Filter.And(leftList + rightList) + } + + public infix fun Filter.or(right: Filter): Filter.Or { + val leftList = when (this) { + is Filter.Or -> filters + else -> listOf(this) + } + val rightList = when (right) { + is Filter.Or -> right.filters + else -> listOf(right) + } + return Filter.Or(leftList + rightList) + } + + public fun all(vararg filters: Filter): Filter? = filters.toList().combine { left, right -> left and right } + public fun any(vararg filters: Filter): Filter? = filters.toList().combine { left, right -> left or right } + + private fun Collection.combine(over: (Filter, Filter) -> Filter): Filter? = fold(null) { acc, filter -> + acc?.let { over(acc, filter) } ?: filter + } +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt index 77093b9cb..e5c48d171 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt @@ -3,13 +3,13 @@ package dev.gitlive.firebase.firestore import kotlinx.serialization.Serializable /** A class representing a platform specific Firebase GeoPoint. */ -expect class NativeGeoPoint +public expect class NativeGeoPoint /** A class representing a Firebase GeoPoint. */ @Serializable(with = GeoPointSerializer::class) -expect class GeoPoint internal constructor(nativeValue: NativeGeoPoint) { - constructor(latitude: Double, longitude: Double) - val latitude: Double - val longitude: Double +public expect class GeoPoint internal constructor(nativeValue: NativeGeoPoint) { + public constructor(latitude: Double, longitude: Double) + public val latitude: Double + public val longitude: Double internal val nativeValue: NativeGeoPoint } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt index e44e2ed04..7228704bc 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/GeoPointSerializer.kt @@ -1,12 +1,12 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer +import dev.gitlive.firebase.internal.SpecialValueSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException /** Serializer for [GeoPoint]. If used with [FirebaseEncoder] performs serialization using native Firebase mechanisms. */ -object GeoPointSerializer : KSerializer by SpecialValueSerializer( +public object GeoPointSerializer : KSerializer by SpecialValueSerializer( serialName = "GeoPoint", toNativeValue = GeoPoint::nativeValue, fromNativeValue = { value -> @@ -14,5 +14,5 @@ object GeoPointSerializer : KSerializer by SpecialValueSerializer( is NativeGeoPoint -> GeoPoint(value) else -> throw SerializationException("Cannot deserialize $value") } - } + }, ) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/LocalCacheSettings.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/LocalCacheSettings.kt new file mode 100644 index 000000000..04096c94a --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/LocalCacheSettings.kt @@ -0,0 +1,69 @@ +package dev.gitlive.firebase.firestore + +public sealed interface LocalCacheSettings { + + public data class Persistent internal constructor(public val sizeBytes: Long) : LocalCacheSettings { + + public companion object { + public fun newBuilder(): Builder = Builder() + } + + public class Builder internal constructor() { + public var sizeBytes: Long = FirebaseFirestoreSettings.DEFAULT_CACHE_SIZE_BYTES + public fun build(): Persistent = Persistent(sizeBytes) + } + } + public data class Memory internal constructor(val garbaseCollectorSettings: MemoryGarbageCollectorSettings) : LocalCacheSettings { + + public companion object { + public fun newBuilder(): Builder = Builder() + } + + public class Builder internal constructor() { + + public var gcSettings: MemoryGarbageCollectorSettings = MemoryGarbageCollectorSettings.Eager.newBuilder().build() + + public fun build(): Memory = Memory(gcSettings) + } + } +} + +public typealias PersistentCacheSettings = LocalCacheSettings.Persistent +public typealias MemoryCacheSettings = LocalCacheSettings.Memory + +public sealed interface MemoryGarbageCollectorSettings { + public data object Eager : MemoryGarbageCollectorSettings { + + public fun newBuilder(): Builder = Builder() + + public class Builder internal constructor() { + public fun build(): Eager = Eager + } + } + public data class LRUGC internal constructor(val sizeBytes: Long) : MemoryGarbageCollectorSettings { + + public companion object { + public fun newBuilder(): Builder = Builder() + } + + public class Builder internal constructor() { + public var sizeBytes: Long = FirebaseFirestoreSettings.DEFAULT_CACHE_SIZE_BYTES + public fun build(): LRUGC = LRUGC(sizeBytes) + } + } +} + +public typealias MemoryEagerGcSettings = MemoryGarbageCollectorSettings.Eager +public typealias MemoryLruGcSettings = MemoryGarbageCollectorSettings.LRUGC + +public fun memoryCacheSettings(builder: LocalCacheSettings.Memory.Builder.() -> Unit): LocalCacheSettings.Memory = + LocalCacheSettings.Memory.newBuilder().apply(builder).build() + +public fun memoryEagerGcSettings(builder: MemoryGarbageCollectorSettings.Eager.Builder.() -> Unit): MemoryGarbageCollectorSettings.Eager = + MemoryGarbageCollectorSettings.Eager.newBuilder().apply(builder).build() + +public fun memoryLruGcSettings(builder: MemoryGarbageCollectorSettings.LRUGC.Builder.() -> Unit): MemoryGarbageCollectorSettings.LRUGC = + MemoryGarbageCollectorSettings.LRUGC.newBuilder().apply(builder).build() + +public fun persistentCacheSettings(builder: LocalCacheSettings.Persistent.Builder.() -> Unit): LocalCacheSettings.Persistent = + LocalCacheSettings.Persistent.newBuilder().apply(builder).build() diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt index c6df088af..13a37a7e7 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt @@ -8,35 +8,36 @@ import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit /** A class representing a platform specific Firebase Timestamp. */ -expect class NativeTimestamp +public expect class NativeTimestamp /** A base class that could be used to combine [Timestamp] and [Timestamp.ServerTimestamp] in the same field. */ @Serializable(with = BaseTimestampSerializer::class) -expect sealed class BaseTimestamp +public expect sealed class BaseTimestamp /** A class representing a Firebase Timestamp. */ @Serializable(with = TimestampSerializer::class) -expect class Timestamp internal constructor(nativeValue: NativeTimestamp): BaseTimestamp { - constructor(seconds: Long, nanoseconds: Int) - val seconds: Long - val nanoseconds: Int +public expect class Timestamp internal constructor(nativeValue: NativeTimestamp) : BaseTimestamp { + public constructor(seconds: Long, nanoseconds: Int) + public val seconds: Long + public val nanoseconds: Int internal val nativeValue: NativeTimestamp - companion object { + public companion object { /** @return a local time timestamp. */ - fun now(): Timestamp + public fun now(): Timestamp } + /** A server time timestamp. */ @Serializable(with = ServerTimestampSerializer::class) - object ServerTimestamp: BaseTimestamp + public data object ServerTimestamp : BaseTimestamp } -fun Timestamp.Companion.fromDuration(duration: Duration): Timestamp = +public fun Timestamp.Companion.fromDuration(duration: Duration): Timestamp = duration.toComponents { seconds, nanoseconds -> Timestamp(seconds, nanoseconds) } -fun Timestamp.toDuration(): Duration = seconds.seconds + nanoseconds.nanoseconds +public fun Timestamp.toDuration(): Duration = seconds.seconds + nanoseconds.nanoseconds -fun Timestamp.Companion.fromMilliseconds(milliseconds: Double): Timestamp = fromDuration(milliseconds.milliseconds) -fun Timestamp.toMilliseconds(): Double = toDuration().toDouble(DurationUnit.MILLISECONDS) +public fun Timestamp.Companion.fromMilliseconds(milliseconds: Double): Timestamp = fromDuration(milliseconds.milliseconds) +public fun Timestamp.toMilliseconds(): Double = toDuration().toDouble(DurationUnit.MILLISECONDS) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt index 8e462e17c..791e4d6f5 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/TimestampSerializer.kt @@ -1,13 +1,14 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.FirebaseEncoder -import dev.gitlive.firebase.SpecialValueSerializer -import dev.gitlive.firebase.firestore.DoubleAsTimestampSerializer.serverTimestamp +import dev.gitlive.firebase.FirebaseDecoder +import dev.gitlive.firebase.internal.SpecialValueSerializer +import dev.gitlive.firebase.firestore.DoubleAsTimestampSerializer.SERVER_TIMESTAMP import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException /** A serializer for [BaseTimestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ -object BaseTimestampSerializer : KSerializer by SpecialValueSerializer( +public object BaseTimestampSerializer : KSerializer by SpecialValueSerializer( serialName = "Timestamp", toNativeValue = { value -> when (value) { @@ -22,11 +23,11 @@ object BaseTimestampSerializer : KSerializer by SpecialValueSeria FieldValue.serverTimestamp.nativeValue -> Timestamp.ServerTimestamp else -> throw SerializationException("Cannot deserialize $value") } - } + }, ) /** A serializer for [Timestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ -object TimestampSerializer : KSerializer by SpecialValueSerializer( +public object TimestampSerializer : KSerializer by SpecialValueSerializer( serialName = "Timestamp", toNativeValue = Timestamp::nativeValue, fromNativeValue = { value -> @@ -34,11 +35,11 @@ object TimestampSerializer : KSerializer by SpecialValueSerializer( is NativeTimestamp -> Timestamp(value) else -> throw SerializationException("Cannot deserialize $value") } - } + }, ) /** A serializer for [Timestamp.ServerTimestamp]. Must be used with [FirebaseEncoder]/[FirebaseDecoder]. */ -object ServerTimestampSerializer : KSerializer by SpecialValueSerializer( +public object ServerTimestampSerializer : KSerializer by SpecialValueSerializer( serialName = "Timestamp", toNativeValue = { FieldValue.serverTimestamp.nativeValue }, fromNativeValue = { value -> @@ -46,26 +47,26 @@ object ServerTimestampSerializer : KSerializer by Spe FieldValue.serverTimestamp.nativeValue -> Timestamp.ServerTimestamp else -> throw SerializationException("Cannot deserialize $value") } - } + }, ) /** A serializer for a Double field which is stored as a Timestamp. */ -object DoubleAsTimestampSerializer : KSerializer by SpecialValueSerializer( +public object DoubleAsTimestampSerializer : KSerializer by SpecialValueSerializer( serialName = "Timestamp", toNativeValue = { value -> - when(value) { - serverTimestamp -> FieldValue.serverTimestamp.nativeValue + when (value) { + SERVER_TIMESTAMP -> FieldValue.serverTimestamp.nativeValue else -> Timestamp.fromMilliseconds(value).nativeValue } }, fromNativeValue = { value -> - when(value) { - FieldValue.serverTimestamp.nativeValue -> serverTimestamp + when (value) { + FieldValue.serverTimestamp.nativeValue -> SERVER_TIMESTAMP is NativeTimestamp -> Timestamp(value).toMilliseconds() is Double -> value else -> throw SerializationException("Cannot deserialize $value") } - } + }, ) { - const val serverTimestamp = Double.POSITIVE_INFINITY + public const val SERVER_TIMESTAMP: Double = Double.POSITIVE_INFINITY } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt index 21e9bbc64..54410b4ef 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/encoders.kt @@ -7,9 +7,9 @@ import dev.gitlive.firebase.EncodeSettings internal expect fun isSpecialValue(value: Any): Boolean @PublishedApi -internal inline fun encode(value: T, encodeSettings: EncodeSettings) = +internal inline fun encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = if (value?.let(::isSpecialValue) == true) { value } else { - dev.gitlive.firebase.encode(value, encodeSettings) + dev.gitlive.firebase.internal.encode(value, buildSettings) } diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 6174bd462..bf4723e49 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -6,339 +6,588 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.EncodeSettings +import dev.gitlive.firebase.internal.EncodedObject import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.encode -import kotlinx.coroutines.Deferred +import dev.gitlive.firebase.firestore.internal.NativeCollectionReferenceWrapper +import dev.gitlive.firebase.firestore.internal.NativeDocumentReference +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import dev.gitlive.firebase.firestore.internal.NativeFirebaseFirestoreWrapper +import dev.gitlive.firebase.firestore.internal.NativeQueryWrapper +import dev.gitlive.firebase.firestore.internal.NativeTransactionWrapper +import dev.gitlive.firebase.firestore.internal.NativeWriteBatchWrapper +import dev.gitlive.firebase.firestore.internal.SetOptions +import dev.gitlive.firebase.firestore.internal.safeValue +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.encodeAsObject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationStrategy import kotlin.jvm.JvmName /** Returns the [FirebaseFirestore] instance of the default [FirebaseApp]. */ -expect val Firebase.firestore: FirebaseFirestore +public expect val Firebase.firestore: FirebaseFirestore /** Returns the [FirebaseFirestore] instance of a given [FirebaseApp]. */ -expect fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore - -sealed class LocalCacheSettings { - data class Persistent(val sizeBytes: Long? = null) : LocalCacheSettings() - data class Memory(val garbaseCollectorSettings: GarbageCollectorSettings) : LocalCacheSettings() { - sealed class GarbageCollectorSettings { - object Eager : GarbageCollectorSettings() - data class LRUGC(val sizeBytes: Long? = null) : GarbageCollectorSettings() +public expect fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore + +internal expect class NativeFirebaseFirestore + +public class FirebaseFirestore internal constructor(private val wrapper: NativeFirebaseFirestoreWrapper) { + + public companion object {} + + internal constructor(native: NativeFirebaseFirestore) : this(NativeFirebaseFirestoreWrapper(native)) + + // Important to leave this as a get property since on JS it is initialized lazily + internal val native: NativeFirebaseFirestore get() = wrapper.native + + public var settings: FirebaseFirestoreSettings + @Deprecated("Property can only be written.", level = DeprecationLevel.ERROR) + get() = throw NotImplementedError() + set(value) { + wrapper.applySettings(value) + } + + public fun collection(collectionPath: String): CollectionReference = CollectionReference(wrapper.collection(collectionPath)) + public fun collectionGroup(collectionId: String): Query = Query(wrapper.collectionGroup(collectionId)) + public fun document(documentPath: String): DocumentReference = DocumentReference(wrapper.document(documentPath)) + public fun batch(): WriteBatch = WriteBatch(wrapper.batch()) + public fun setLoggingEnabled(loggingEnabled: Boolean) { + wrapper.setLoggingEnabled(loggingEnabled) + } + public suspend fun clearPersistence() { + wrapper.clearPersistence() + } + public suspend fun runTransaction(func: suspend Transaction.() -> T): T = wrapper.runTransaction { func(Transaction(this)) } + public fun useEmulator(host: String, port: Int) { + wrapper.useEmulator(host, port) + } + + @Deprecated("Use SettingsBuilder instead", replaceWith = ReplaceWith("settings = firestoreSettings { }", "dev.gitlive.firebase.firestore.firestoreSettings")) + public fun setSettings( + persistenceEnabled: Boolean? = null, + sslEnabled: Boolean? = null, + host: String? = null, + cacheSizeBytes: Long? = null, + ) { + settings = firestoreSettings { + this.sslEnabled = sslEnabled ?: true + this.host = host ?: FirebaseFirestoreSettings.DEFAULT_HOST + this.cacheSettings = if (persistenceEnabled != false) { + LocalCacheSettings.Persistent( + cacheSizeBytes ?: FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED, + ) + } else { + val cacheSize = cacheSizeBytes ?: FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED + val garbageCollectionSettings = + if (cacheSize == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED) { + MemoryGarbageCollectorSettings.Eager + } else { + MemoryGarbageCollectorSettings.LRUGC(cacheSize) + } + LocalCacheSettings.Memory(garbageCollectionSettings) + } } } + + public suspend fun disableNetwork() { + wrapper.disableNetwork() + } + public suspend fun enableNetwork() { + wrapper.enableNetwork() + } } -expect class FirebaseFirestore { +public expect class FirebaseFirestoreSettings { + + public companion object { + public val CACHE_SIZE_UNLIMITED: Long + internal val DEFAULT_HOST: String + internal val MINIMUM_CACHE_BYTES: Long + internal val DEFAULT_CACHE_SIZE_BYTES: Long + } - class Settings { + public class Builder { + public constructor() + public constructor(settings: FirebaseFirestoreSettings) - companion object { - fun create(sslEnabled: Boolean? = null, host: String? = null, cacheSettings: LocalCacheSettings? = null): Settings - } + public var sslEnabled: Boolean + public var host: String + public var cacheSettings: LocalCacheSettings - val sslEnabled: Boolean? - val host: String? - val cacheSettings: LocalCacheSettings? - } - - fun collection(collectionPath: String): CollectionReference - fun document(documentPath: String): DocumentReference - fun collectionGroup(collectionId: String): Query - fun batch(): WriteBatch - fun setLoggingEnabled(loggingEnabled: Boolean) - suspend fun clearPersistence() - suspend fun runTransaction(func: suspend Transaction.() -> T): T - fun useEmulator(host: String, port: Int) - fun setSettings(settings: Settings) - fun updateSettings(settings: Settings) - suspend fun disableNetwork() - suspend fun enableNetwork() + public fun build(): FirebaseFirestoreSettings + } + + public val sslEnabled: Boolean + public val host: String + public val cacheSettings: LocalCacheSettings } -fun FirebaseFirestore.setSettings( - sslEnabled: Boolean? = null, - host: String? = null, - cacheSettings: LocalCacheSettings? = null -) = FirebaseFirestore.Settings.create(sslEnabled, host, cacheSettings) +public expect fun firestoreSettings(settings: FirebaseFirestoreSettings? = null, builder: FirebaseFirestoreSettings.Builder.() -> Unit): FirebaseFirestoreSettings + +internal expect class NativeTransaction + +public data class Transaction internal constructor(internal val nativeWrapper: NativeTransactionWrapper) { + + public companion object {} -sealed class SetOptions { - object Merge : SetOptions() - object Overwrite : SetOptions() - data class MergeFields(val fields: List) : SetOptions() - data class MergeFieldPaths(val fieldPaths: List) : SetOptions() { - val encodedFieldPaths = fieldPaths.map { it.encoded } + internal constructor(native: NativeTransaction) : this(NativeTransactionWrapper(native)) + + internal val native: NativeTransaction = nativeWrapper.native + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, merge: Boolean = false): Transaction = set(documentRef, data, merge) { + this.encodeDefaults = encodeDefaults } -} + public inline fun set(documentRef: DocumentReference, data: Any, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) -abstract class BaseTransaction { + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFields: String): Transaction = set(documentRef, data, *mergeFields) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath): Transaction = set(documentRef, data, *mergeFieldPaths) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, data: Any, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false): Transaction = set(documentRef, strategy, data, merge) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String): Transaction = set(documentRef, strategy, data, *mergeFields) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath): BaseTransaction = setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath): Transaction = set(documentRef, strategy, data, *mergeFieldPaths) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - protected abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseTransaction + @PublishedApi + internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): Transaction = Transaction(nativeWrapper.setEncoded(documentRef, encodedData, setOptions)) - fun update(documentRef: DocumentReference, data: Any, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(data, encodeSettings)!!) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) + public fun update(documentRef: DocumentReference, data: Any, encodeDefaults: Boolean): Transaction = update(documentRef, data) { + this.encodeDefaults = encodeDefaults + } + public inline fun update(documentRef: DocumentReference, data: Any, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = updateEncoded(documentRef, encodeAsObject(data, buildSettings)) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { this.encodeDefaults = encodeDefaults }")) + public fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): Transaction = update(documentRef, strategy, data) { + this.encodeDefaults = encodeDefaults + } + public inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = updateEncoded(documentRef, encodeAsObject(strategy, data, buildSettings)) @JvmName("updateFields") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + public inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + @JvmName("updateFieldPaths") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + public inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}): Transaction = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) + + @PublishedApi + internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): Transaction = Transaction(nativeWrapper.updateEncoded(documentRef, encodedData)) + + @PublishedApi + internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(nativeWrapper.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) + + @PublishedApi + internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): Transaction = Transaction(nativeWrapper.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) - protected abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction - protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction - protected abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseTransaction + public fun delete(documentRef: DocumentReference): Transaction = Transaction(nativeWrapper.delete(documentRef)) + public suspend fun get(documentRef: DocumentReference): DocumentSnapshot = DocumentSnapshot(nativeWrapper.get(documentRef)) } -expect class Transaction : BaseTransaction { - fun delete(documentRef: DocumentReference): Transaction - suspend fun get(documentRef: DocumentReference): DocumentSnapshot +internal expect open class NativeQuery + +public open class Query internal constructor(internal val nativeQuery: NativeQueryWrapper) { + + public companion object {} + + internal constructor(native: NativeQuery) : this(NativeQueryWrapper(native)) + + internal open val native: NativeQuery = nativeQuery.native + + public fun limit(limit: Number): Query = Query(nativeQuery.limit(limit)) + public val snapshots: Flow = nativeQuery.snapshots + public fun snapshots(includeMetadataChanges: Boolean = false): Flow = nativeQuery.snapshots(includeMetadataChanges) + public suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot = nativeQuery.get(source) + + public fun where(builder: FilterBuilder.() -> Filter?): Query = builder(FilterBuilder())?.let { Query(nativeQuery.where(it)) } ?: this + + public fun orderBy(field: String, direction: Direction = Direction.ASCENDING): Query = Query(nativeQuery.orderBy(field, direction)) + public fun orderBy(field: FieldPath, direction: Direction = Direction.ASCENDING): Query = Query(nativeQuery.orderBy(field.encoded, direction)) + + public fun startAfter(document: DocumentSnapshot): Query = Query(nativeQuery.startAfter(document.native)) + public fun startAfter(vararg fieldValues: Any): Query = Query(nativeQuery.startAfter(*(fieldValues.map { it.safeValue }.toTypedArray()))) + public fun startAt(document: DocumentSnapshot): Query = Query(nativeQuery.startAt(document.native)) + public fun startAt(vararg fieldValues: Any): Query = Query(nativeQuery.startAt(*(fieldValues.map { it.safeValue }.toTypedArray()))) + + public fun endBefore(document: DocumentSnapshot): Query = Query(nativeQuery.endBefore(document.native)) + public fun endBefore(vararg fieldValues: Any): Query = Query(nativeQuery.endBefore(*(fieldValues.map { it.safeValue }.toTypedArray()))) + public fun endAt(document: DocumentSnapshot): Query = Query(nativeQuery.endAt(document.native)) + public fun endAt(vararg fieldValues: Any): Query = Query(nativeQuery.endAt(*(fieldValues.map { it.safeValue }.toTypedArray()))) } -expect open class Query { - fun limit(limit: Number): Query - val snapshots: Flow - fun snapshots(includeMetadataChanges: Boolean = false): Flow - suspend fun get(): QuerySnapshot - internal fun _where(field: String, equalTo: Any?): Query - internal fun _where(path: FieldPath, equalTo: Any?): Query - internal fun _where(field: String, equalTo: DocumentReference): Query - internal fun _where(path: FieldPath, equalTo: DocumentReference): Query - internal fun _where(field: String, lessThan: Any? = null, greaterThan: Any? = null, - arrayContains: Any? = null, notEqualTo: Any? = null, - lessThanOrEqualTo: Any? = null, greaterThanOrEqualTo: Any? = null): Query - internal fun _where(path: FieldPath, lessThan: Any? = null, greaterThan: Any? = null, - arrayContains: Any? = null, notEqualTo: Any? = null, - lessThanOrEqualTo: Any? = null, greaterThanOrEqualTo: Any? = null): Query - internal fun _where(field: String, inArray: List? = null, - arrayContainsAny: List? = null, notInArray: List? = null): Query - internal fun _where(path: FieldPath, inArray: List? = null, - arrayContainsAny: List? = null, notInArray: List? = null): Query - internal fun _orderBy(field: String, direction: Direction): Query - internal fun _orderBy(field: FieldPath, direction: Direction): Query - - internal fun _startAfter(document: DocumentSnapshot): Query - internal fun _startAfter(vararg fieldValues: Any): Query - internal fun _startAt(document: DocumentSnapshot): Query - internal fun _startAt(vararg fieldValues: Any): Query - - internal fun _endBefore(document: DocumentSnapshot): Query - internal fun _endBefore(vararg fieldValues: Any): Query - internal fun _endAt(document: DocumentSnapshot): Query - internal fun _endAt(vararg fieldValues: Any): Query +@Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { field equalTo equalTo }", "dev.gitlive.firebase.firestore")) +public fun Query.where(field: String, equalTo: Any?): Query = where { + field equalTo equalTo } -private val Any?.value get() = when (this) { - is Timestamp -> nativeValue - is GeoPoint -> nativeValue - is DocumentReference -> nativeValue - else -> this +@Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { path equalTo equalTo }", "dev.gitlive.firebase.firestore")) +public fun Query.where(path: FieldPath, equalTo: Any?): Query = where { + path equalTo equalTo } -fun Query.where(field: String, equalTo: Any?) = _where(field, equalTo.value) -fun Query.where(path: FieldPath, equalTo: Any?) = _where(path, equalTo.value) -fun Query.where(field: String, equalTo: DocumentReference) = _where(field, equalTo) -fun Query.where(path: FieldPath, equalTo: DocumentReference) = _where(path, equalTo) -fun Query.where(field: String, lessThan: Any? = null, greaterThan: Any? = null, - arrayContains: Any? = null, notEqualTo: Any? = null, - lessThanOrEqualTo: Any? = null, greaterThanOrEqualTo: Any? = null) = - _where(field, lessThan.value, greaterThan.value, arrayContains.value, notEqualTo.value, lessThanOrEqualTo.value, greaterThanOrEqualTo.value) -fun Query.where(path: FieldPath, lessThan: Any? = null, greaterThan: Any? = null, - arrayContains: Any? = null, notEqualTo: Any? = null, - lessThanOrEqualTo: Any? = null, greaterThanOrEqualTo: Any? = null) = - _where(path, lessThan.value, greaterThan.value, arrayContains.value, notEqualTo.value, lessThanOrEqualTo.value, greaterThanOrEqualTo.value) -fun Query.where(field: String, inArray: List? = null, arrayContainsAny: List? = null, - notInArray: List? = null) = - _where(field, inArray.value, arrayContainsAny.value, notInArray.value) -fun Query.where(path: FieldPath, inArray: List? = null, arrayContainsAny: List? = null, - notInArray: List? = null) = - _where(path, inArray.value, arrayContainsAny.value, notInArray.value) -fun Query.orderBy(field: String, direction: Direction = Direction.ASCENDING) = _orderBy(field, direction) -fun Query.orderBy(field: FieldPath, direction: Direction = Direction.ASCENDING) = _orderBy(field, direction) - -fun Query.startAfter(document: DocumentSnapshot) = _startAfter(document) -fun Query.startAfter(vararg fieldValues: Any) = _startAfter(*(fieldValues.mapNotNull { it.value }.toTypedArray())) -fun Query.startAt(document: DocumentSnapshot) = _startAt(document) -fun Query.startAt(vararg fieldValues: Any) = _startAt(*(fieldValues.mapNotNull { it.value }.toTypedArray())) - -fun Query.endBefore(document: DocumentSnapshot) = _endBefore(document) -fun Query.endBefore(vararg fieldValues: Any) = _endBefore(*(fieldValues.mapNotNull { it.value }.toTypedArray())) -fun Query.endAt(document: DocumentSnapshot) = _endAt(document) -fun Query.endAt(vararg fieldValues: Any) = _endAt(*(fieldValues.mapNotNull { it.value }.toTypedArray())) - -abstract class BaseWriteBatch { - inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - setEncoded(documentRef, encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = - setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - inline fun set(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - setEncoded(documentRef, encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String)= - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) - fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false, vararg fieldsAndValues: Pair) = - setEncoded(documentRef, encode(strategy, data, encodeSettings)!!, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty(), merge) - - abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, setOptions: SetOptions): BaseWriteBatch - abstract fun setEncoded(documentRef: DocumentReference, encodedData: Any, encodedFieldsAndValues: List>, merge: Boolean): BaseWriteBatch - - inline fun update(documentRef: DocumentReference, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - updateEncoded(documentRef, encode(data, encodeSettings)!!) - fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!) - inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg fieldsAndValues: Pair) = - updateEncoded(documentRef, encode(strategy, data, encodeSettings)!!, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) +@Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { }", "dev.gitlive.firebase.firestore")) +public fun Query.where(field: String, lessThan: Any? = null, greaterThan: Any? = null, arrayContains: Any? = null): Query = where { + all( + *listOfNotNull( + lessThan?.let { field lessThan it }, + greaterThan?.let { field greaterThan it }, + arrayContains?.let { field contains it }, + ).toTypedArray(), + ) +} - @JvmName("updateField") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) - @JvmName("updateFieldPath") - fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) +@Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { }", "dev.gitlive.firebase.firestore")) +public fun Query.where(path: FieldPath, lessThan: Any? = null, greaterThan: Any? = null, arrayContains: Any? = null): Query = where { + all( + *listOfNotNull( + lessThan?.let { path lessThan it }, + greaterThan?.let { path greaterThan it }, + arrayContains?.let { path contains it }, + ).toTypedArray(), + ) +} - abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch - abstract fun updateEncoded(documentRef: DocumentReference, encodedData: Any, encodedFieldsAndValues: List>): BaseWriteBatch +@Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { }", "dev.gitlive.firebase.firestore")) +public fun Query.where(field: String, inArray: List? = null, arrayContainsAny: List? = null): Query = where { + all( + *listOfNotNull( + inArray?.let { field inArray it }, + arrayContainsAny?.let { field containsAny it }, + ).toTypedArray(), + ) +} - protected abstract fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch - protected abstract fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): BaseWriteBatch +@Deprecated("Deprecated in favor of using a [FilterBuilder]", replaceWith = ReplaceWith("where { }", "dev.gitlive.firebase.firestore")) +public fun Query.where(path: FieldPath, inArray: List? = null, arrayContainsAny: List? = null): Query = where { + all( + *listOfNotNull( + inArray?.let { path inArray it }, + arrayContainsAny?.let { path containsAny it }, + ).toTypedArray(), + ) } -expect class WriteBatch : BaseWriteBatch { - val async: Async +internal expect class NativeWriteBatch + +public data class WriteBatch internal constructor(internal val nativeWrapper: NativeWriteBatchWrapper) { + + public companion object {} + + internal constructor(native: NativeWriteBatch) : this(NativeWriteBatchWrapper(native)) - fun delete(documentRef: DocumentReference): WriteBatch - suspend fun commit() + internal val native: NativeWriteBatch = nativeWrapper.native - @Suppress("DeferredIsResult") - class Async { - fun commit(): Deferred + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, merge) { this.encodeDefaults = encodeDefaults }")) + public inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, merge: Boolean = false): WriteBatch = set(documentRef, data, merge) { + this.encodeDefaults = encodeDefaults } -} + public inline fun set(documentRef: DocumentReference, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + setEncoded(documentRef, encodeAsObject(data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) -/** A class representing a platform specific Firebase DocumentReference. */ -expect class NativeDocumentReference + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) + public inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFields: String): WriteBatch = set(documentRef, data, *mergeFields) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) + public inline fun set(documentRef: DocumentReference, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath): WriteBatch = set(documentRef, data, *mergeFieldPaths) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + setEncoded(documentRef, encodeAsObject(data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, merge) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false): WriteBatch = set(documentRef, strategy, data, merge) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), if (merge) SetOptions.Merge else SetOptions.Overwrite) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String): WriteBatch = set(documentRef, strategy, data, *mergeFields) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFields(mergeFields.asList())) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(documentRef, strategy, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) + public fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath): WriteBatch = set(documentRef, strategy, data, *mergeFieldPaths) { + this.encodeDefaults = encodeDefaults + } + public inline fun set(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + setEncoded(documentRef, encodeAsObject(strategy, data, buildSettings), SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + + @PublishedApi + internal fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): WriteBatch = WriteBatch(nativeWrapper.setEncoded(documentRef, encodedData, setOptions)) -abstract class BaseDocumentReference { + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, data) { this.encodeDefaults = encodeDefaults }")) + public inline fun update(documentRef: DocumentReference, data: T, encodeDefaults: Boolean): WriteBatch = update(documentRef, data) { + this.encodeDefaults = encodeDefaults + } + public inline fun update(documentRef: DocumentReference, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + updateEncoded(documentRef, encodeAsObject(data, buildSettings)) - abstract class Async { - inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded(encode(data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = setEncoded(encode(data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded(encode(data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(documentRef, strategy, data) { this.encodeDefaults = encodeDefaults }")) + public fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): WriteBatch = update(documentRef, strategy, data) { + this.encodeDefaults = encodeDefaults + } + public inline fun update(documentRef: DocumentReference, strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = + updateEncoded(documentRef, encodeAsObject(strategy, data, buildSettings)) - fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = setEncoded( - encode(strategy, data, encodeSettings)!!, if (merge) SetOptions.Merge else SetOptions.Overwrite) - fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String)= setEncoded( - encode(strategy, data, encodeSettings)!!, SetOptions.MergeFields(mergeFields.asList())) - fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = setEncoded( - encode(strategy, data, encodeSettings)!!, SetOptions.MergeFieldPaths(mergeFieldPaths.asList())) + @JvmName("updateField") + public inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = updateEncodedFieldsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) - abstract fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred + @JvmName("updateFieldPath") + public inline fun update(documentRef: DocumentReference, vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}): WriteBatch = updateEncodedFieldPathsAndValues(documentRef, encodeFieldAndValue(fieldsAndValues, buildSettings).orEmpty()) - inline fun update(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncoded(encode(data, encodeSettings)!!) - fun update(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = update(encode(strategy, data, encodeSettings)) + @PublishedApi + internal fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): WriteBatch = WriteBatch(nativeWrapper.updateEncoded(documentRef, encodedData)) - @JvmName("updateFields") - fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldsAndValues(encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) - @JvmName("updateFieldPaths") - fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = updateEncodedFieldPathsAndValues(encodeFieldAndValue(fieldsAndValues, encodeSettings).orEmpty()) + @PublishedApi + internal fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): WriteBatch = WriteBatch(nativeWrapper.updateEncodedFieldsAndValues(documentRef, encodedFieldsAndValues)) - abstract fun updateEncoded(encodedData: Any): Deferred - protected abstract fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred - protected abstract fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred + @PublishedApi + internal fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): WriteBatch = WriteBatch(nativeWrapper.updateEncodedFieldPathsAndValues(documentRef, encodedFieldsAndValues)) - abstract fun delete(): Deferred + public fun delete(documentRef: DocumentReference): WriteBatch = WriteBatch(nativeWrapper.delete(documentRef)) + public suspend fun commit() { + nativeWrapper.commit() } +} - abstract val async: Async +/** A class representing a platform specific Firebase DocumentReference. */ +internal expect class NativeDocumentReferenceType + +/** A class representing a Firebase DocumentReference. */ +@Serializable(with = DocumentReferenceSerializer::class) +public data class DocumentReference internal constructor(internal val native: NativeDocumentReference) { - suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - async.set(data, encodeSettings, merge).await() + public companion object {} - suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = - async.set(data, encodeSettings, mergeFields = mergeFields).await() + internal val nativeValue get() = native.nativeValue - suspend inline fun set(data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - async.set(data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() + val id: String get() = native.id + val path: String get() = native.path + val snapshots: Flow get() = native.snapshots.map(::DocumentSnapshot) + val parent: CollectionReference get() = CollectionReference(native.parent) + public fun snapshots(includeMetadataChanges: Boolean = false): Flow = native.snapshots(includeMetadataChanges).map(::DocumentSnapshot) - suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), merge: Boolean = false) = - async.set(strategy, data, encodeSettings, merge).await() + public fun collection(collectionPath: String): CollectionReference = CollectionReference(native.collection(collectionPath)) + public suspend fun get(source: Source = Source.DEFAULT): DocumentSnapshot = DocumentSnapshot(native.get(source)) - suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFields: String) = - async.set(strategy, data, encodeSettings, mergeFields = mergeFields).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, merge) { this.encodeDefaults = encodeDefaults }")) + public suspend inline fun set(data: T, encodeDefaults: Boolean, merge: Boolean = false) { + set(data, merge) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun set(data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setEncoded( + encodeAsObject(data, buildSettings), + if (merge) SetOptions.Merge else SetOptions.Overwrite, + ) + } - suspend fun set(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings(), vararg mergeFieldPaths: FieldPath) = - async.set(strategy, data, encodeSettings, mergeFieldPaths = mergeFieldPaths).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFields) { this.encodeDefaults = encodeDefaults }")) + public suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFields: String) { + set(data, *mergeFields) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun set(data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setEncoded( + encodeAsObject(data, buildSettings), + SetOptions.MergeFields(mergeFields.asList()), + ) + } - @Suppress("UNCHECKED_CAST") - suspend inline fun update(data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(data, encodeSettings).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) + public suspend inline fun set(data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) { + set(data, *mergeFieldPaths) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun set(data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setEncoded( + encodeAsObject(data, buildSettings), + SetOptions.MergeFieldPaths(mergeFieldPaths.asList()), + ) + } - @Suppress("UNCHECKED_CAST") - suspend fun update(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(strategy, data, encodeSettings).await() + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, merge) { this.encodeDefaults = encodeDefaults }")) + public suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, merge: Boolean = false) { + set(strategy, data, merge) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun set(strategy: SerializationStrategy, data: T, merge: Boolean = false, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setEncoded( + encodeAsObject(strategy, data, buildSettings), + if (merge) SetOptions.Merge else SetOptions.Overwrite, + ) + } + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFields) { this.encodeDefaults = encodeDefaults }")) + public suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFields: String) { + set(strategy, data, *mergeFields) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFields: String, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setEncoded( + encodeAsObject(strategy, data, buildSettings), + SetOptions.MergeFields(mergeFields.asList()), + ) + } + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("set(strategy, data, mergeFieldPaths) { this.encodeDefaults = encodeDefaults }")) + public suspend fun set(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean, vararg mergeFieldPaths: FieldPath) { + set(strategy, data, *mergeFieldPaths) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun set(strategy: SerializationStrategy, data: T, vararg mergeFieldPaths: FieldPath, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + setEncoded( + encodeAsObject(strategy, data, buildSettings), + SetOptions.MergeFieldPaths(mergeFieldPaths.asList()), + ) + } + + @PublishedApi + internal suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) { + native.setEncoded(encodedData, setOptions) + } + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(data) { this.encodeDefaults = encodeDefaults }")) + public suspend inline fun update(data: T, encodeDefaults: Boolean) { + update(data) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun update(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + updateEncoded(encodeAsObject(data, buildSettings)) + } + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("update(strategy, data) { this.encodeDefaults = encodeDefaults }")) + public suspend fun update(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean) { + update(strategy, data) { + this.encodeDefaults = encodeDefaults + } + } + public suspend inline fun update(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + updateEncoded( + encodeAsObject(strategy, data, buildSettings), + ) + } + + @PublishedApi + internal suspend fun updateEncoded(encodedData: EncodedObject) { + native.updateEncoded(encodedData) + } @JvmName("updateFields") - suspend fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(fieldsAndValues = fieldsAndValues, encodeSettings).await() + public suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + updateEncodedFieldsAndValues( + encodeFieldAndValue( + fieldsAndValues, + buildSettings, + ).orEmpty(), + ) + } + + @PublishedApi + internal suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + native.updateEncodedFieldsAndValues(encodedFieldsAndValues) + } @JvmName("updateFieldPaths") - suspend fun update(vararg fieldsAndValues: Pair, encodeSettings: EncodeSettings = EncodeSettings()) = - async.update(fieldsAndValues = fieldsAndValues, encodeSettings).await() + public suspend inline fun update(vararg fieldsAndValues: Pair, buildSettings: EncodeSettings.Builder.() -> Unit = {}) { + updateEncodedFieldPathsAndValues( + encodeFieldAndValue( + fieldsAndValues, + buildSettings, + ).orEmpty(), + ) + } + + @PublishedApi + internal suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + native.updateEncodedFieldPathsAndValues(encodedFieldsAndValues) + } - suspend fun delete() = - async.delete().await() + public suspend fun delete() { + native.delete() + } } -/** A class representing a Firebase DocumentReference. */ -@Serializable(with = DocumentReferenceSerializer::class) -expect class DocumentReference internal constructor(nativeValue: NativeDocumentReference) : BaseDocumentReference { - internal val nativeValue: NativeDocumentReference +internal expect class NativeCollectionReference : NativeQuery - val id: String - val path: String - val snapshots: Flow - val parent: CollectionReference - fun snapshots(includeMetadataChanges: Boolean = false): Flow +public data class CollectionReference internal constructor(internal val nativeWrapper: NativeCollectionReferenceWrapper) : Query(nativeWrapper) { - fun collection(collectionPath: String): CollectionReference - suspend fun get(): DocumentSnapshot -} + public companion object {} + + internal constructor(native: NativeCollectionReference) : this(NativeCollectionReferenceWrapper(native)) -expect class CollectionReference : Query { - val path: String - val async: Async - val document: DocumentReference - val parent: DocumentReference? + override val native: NativeCollectionReference = nativeWrapper.native - fun document(documentPath: String): DocumentReference - suspend inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()): DocumentReference - suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): DocumentReference - @Suppress("DeferredIsResult") - class Async { - inline fun add(data: T, encodeSettings: EncodeSettings = EncodeSettings()): Deferred - fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): Deferred + val path: String get() = nativeWrapper.path + val document: DocumentReference get() = DocumentReference(nativeWrapper.document) + val parent: DocumentReference? get() = nativeWrapper.parent?.let(::DocumentReference) + + public fun document(documentPath: String): DocumentReference = DocumentReference(nativeWrapper.document(documentPath)) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(data) { this.encodeDefaults = encodeDefaults }")) + public suspend inline fun add(data: T, encodeDefaults: Boolean): DocumentReference = add(data) { + this.encodeDefaults = encodeDefaults } + public suspend inline fun add(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): DocumentReference = addEncoded( + encodeAsObject(data, buildSettings), + ) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("add(strategy, data) { this.encodeDefaults = encodeDefaults }")) + public suspend fun add(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): DocumentReference = add(strategy, data) { + this.encodeDefaults = encodeDefaults + } + public suspend inline fun add(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): DocumentReference = addEncoded( + encodeAsObject(strategy, data, buildSettings), + ) + + @PublishedApi + internal suspend fun addEncoded(data: EncodedObject): DocumentReference = DocumentReference(nativeWrapper.addEncoded(data)) } -expect class FirebaseFirestoreException : FirebaseException +public expect class FirebaseFirestoreException : FirebaseException -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") -expect val FirebaseFirestoreException.code: FirestoreExceptionCode +public expect val FirebaseFirestoreException.code: FirestoreExceptionCode -expect enum class FirestoreExceptionCode { +public expect enum class FirestoreExceptionCode { OK, CANCELLED, UNKNOWN, @@ -355,63 +604,95 @@ expect enum class FirestoreExceptionCode { INTERNAL, UNAVAILABLE, DATA_LOSS, - UNAUTHENTICATED + UNAUTHENTICATED, } -expect enum class Direction { +public expect enum class Direction { ASCENDING, - DESCENDING + DESCENDING, } -expect class QuerySnapshot { - val documents: List - val documentChanges: List - val metadata: SnapshotMetadata +public expect class QuerySnapshot { + public val documents: List + public val documentChanges: List + public val metadata: SnapshotMetadata } -expect enum class ChangeType { - ADDED , +public expect enum class ChangeType { + ADDED, MODIFIED, - REMOVED + REMOVED, } -expect class DocumentChange { - val document: DocumentSnapshot - val newIndex: Int - val oldIndex: Int - val type: ChangeType +public expect class DocumentChange { + public val document: DocumentSnapshot + public val newIndex: Int + public val oldIndex: Int + public val type: ChangeType } -expect class DocumentSnapshot { +internal expect class NativeDocumentSnapshot + +public data class DocumentSnapshot internal constructor(internal val nativeWrapper: NativeDocumentSnapshotWrapper) { + + public companion object {} + + internal constructor(native: NativeDocumentSnapshot) : this(NativeDocumentSnapshotWrapper(native)) + + internal val native: NativeDocumentSnapshot = nativeWrapper.native - inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T - fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings(), serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T + val exists: Boolean get() = nativeWrapper.exists + val id: String get() = nativeWrapper.id + val reference: DocumentReference get() = DocumentReference(nativeWrapper.reference) + val metadata: SnapshotMetadata get() = nativeWrapper.metadata - fun contains(field: String): Boolean + public fun contains(field: String): Boolean = nativeWrapper.contains(field) + public fun contains(fieldPath: FieldPath): Boolean = nativeWrapper.contains(fieldPath.encoded) - inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T - fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings(), serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): T + public inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(field, serverTimestampBehavior), buildSettings) + public inline fun get(field: String, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(field, serverTimestampBehavior), buildSettings) - val exists: Boolean - val id: String - val reference: DocumentReference - val metadata: SnapshotMetadata + @PublishedApi + internal fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(field, serverTimestampBehavior) + + public inline fun get(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(value = getEncoded(fieldPath, serverTimestampBehavior), buildSettings) + public inline fun get(fieldPath: FieldPath, strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, getEncoded(fieldPath, serverTimestampBehavior), buildSettings) + + @PublishedApi + internal fun getEncoded(fieldPath: FieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.getEncoded(fieldPath.encoded, serverTimestampBehavior) + + public inline fun data(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(encodedData(serverTimestampBehavior), buildSettings) + public inline fun data(strategy: DeserializationStrategy, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T = decode(strategy, encodedData(serverTimestampBehavior), buildSettings) + + @PublishedApi + internal fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? = nativeWrapper.encodedData(serverTimestampBehavior) } -enum class ServerTimestampBehavior { +public enum class ServerTimestampBehavior { ESTIMATE, NONE, - PREVIOUS + PREVIOUS, } -expect class SnapshotMetadata { - val hasPendingWrites: Boolean - val isFromCache: Boolean +public expect class SnapshotMetadata { + public val hasPendingWrites: Boolean + public val isFromCache: Boolean } -expect class FieldPath(vararg fieldNames: String) { - val documentId: FieldPath - val encoded: EncodedFieldPath +public expect class FieldPath(vararg fieldNames: String) { + public companion object { + public val documentId: FieldPath + } + + @Deprecated("Use companion object instead", replaceWith = ReplaceWith("FieldPath.documentId")) + public val documentId: FieldPath + public val encoded: EncodedFieldPath } -expect class EncodedFieldPath +public expect class EncodedFieldPath + +public enum class Source { + CACHE, + SERVER, + DEFAULT, +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt index c3401d926..766836a76 100644 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/helpers.kt @@ -3,33 +3,36 @@ package dev.gitlive.firebase.firestore import dev.gitlive.firebase.EncodeSettings import kotlin.jvm.JvmName -/** Helper method to perform an update operation. */ +// ** Helper method to perform an update operation. */ @JvmName("performUpdateFields") -fun encodeFieldAndValue( +@PublishedApi +internal inline fun encodeFieldAndValue( fieldsAndValues: Array>, - encodeSettings: EncodeSettings -) = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, encodeSettings) }) + buildSettings: EncodeSettings.Builder.() -> Unit, +): List>? = encodeFieldAndValue(fieldsAndValues, encodeField = { it }, encodeValue = { encode(it, buildSettings) }) /** Helper method to perform an update operation. */ @JvmName("performUpdateFieldPaths") -fun encodeFieldAndValue( +@PublishedApi +internal inline fun encodeFieldAndValue( fieldsAndValues: Array>, - encodeSettings: EncodeSettings, -) = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, encodeSettings) }) + buildSettings: EncodeSettings.Builder.() -> Unit, +): List>? = encodeFieldAndValue(fieldsAndValues, { it.encoded }, { encode(it, buildSettings) }) /** Helper method to perform an update operation in Android and JS. */ -internal fun encodeFieldAndValue( +@PublishedApi +internal inline fun encodeFieldAndValue( fieldsAndValues: Array>, encodeField: (T) -> K, - encodeValue: (Any?) -> Any? -) : List>? = + encodeValue: (Any?) -> Any?, +): List>? = fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() } ?.map { (field, value) -> encodeField(field) to value?.let { encodeValue(it) } } internal fun List>.performUpdate( - update: (K, Any?, Array) -> R + update: (K, Any?, Array) -> R, ) = update( this[0].first, this[0].second, - this.drop(1).flatMap { (field, value) -> listOf(field, value) }.toTypedArray() -) \ No newline at end of file + this.drop(1).flatMap { (field, value) -> listOf(field, value) }.toTypedArray(), +) diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..825a37ae9 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,16 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.internal.EncodedObject + +internal expect class NativeCollectionReferenceWrapper internal constructor(native: NativeCollectionReference) : NativeQueryWrapper { + + override val native: NativeCollectionReference + + val path: String + val document: NativeDocumentReference + val parent: NativeDocumentReference? + + fun document(documentPath: String): NativeDocumentReference + suspend fun addEncoded(data: EncodedObject): NativeDocumentReference +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..baa706419 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,26 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.internal.EncodedObject +import kotlinx.coroutines.flow.Flow + +internal expect class NativeDocumentReference(nativeValue: NativeDocumentReferenceType) { + val nativeValue: NativeDocumentReferenceType + val id: String + val path: String + val snapshots: Flow + val parent: NativeCollectionReferenceWrapper + fun snapshots(includeMetadataChanges: Boolean = false): Flow + + fun collection(collectionPath: String): NativeCollectionReference + suspend fun get(source: Source = Source.DEFAULT): NativeDocumentSnapshot + suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) + suspend fun updateEncoded(encodedData: EncodedObject) + suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) + suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) + suspend fun delete() +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..0cacfd9ab --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,23 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata + +internal expect class NativeDocumentSnapshotWrapper internal constructor(native: NativeDocumentSnapshot) { + + val native: NativeDocumentSnapshot + + val exists: Boolean + val id: String + val reference: NativeDocumentReference + val metadata: SnapshotMetadata + + fun contains(field: String): Boolean + fun contains(fieldPath: EncodedFieldPath): Boolean + + fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? + fun encodedData(serverTimestampBehavior: ServerTimestampBehavior = ServerTimestampBehavior.NONE): Any? +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..b617deb45 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,24 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.NativeWriteBatch + +internal expect class NativeFirebaseFirestoreWrapper internal constructor(native: NativeFirebaseFirestore) { + val native: NativeFirebaseFirestore + + fun collection(collectionPath: String): NativeCollectionReference + fun collectionGroup(collectionId: String): NativeQuery + fun document(documentPath: String): NativeDocumentReference + fun batch(): NativeWriteBatch + fun setLoggingEnabled(loggingEnabled: Boolean) + fun applySettings(settings: FirebaseFirestoreSettings) + suspend fun clearPersistence() + suspend fun runTransaction(func: suspend NativeTransaction.() -> T): T + fun useEmulator(host: String, port: Int) + suspend fun disableNetwork() + suspend fun enableNetwork() +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..5d1d1e67c --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,35 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import kotlinx.coroutines.flow.Flow + +internal expect open class NativeQueryWrapper internal constructor(native: NativeQuery) { + + open val native: NativeQuery + + fun limit(limit: Number): NativeQuery + val snapshots: Flow + fun snapshots(includeMetadataChanges: Boolean = false): Flow + suspend fun get(source: Source = Source.DEFAULT): QuerySnapshot + + fun where(filter: Filter): NativeQuery + + fun orderBy(field: String, direction: Direction): NativeQuery + fun orderBy(field: EncodedFieldPath, direction: Direction): NativeQuery + + fun startAfter(document: NativeDocumentSnapshot): NativeQuery + fun startAfter(vararg fieldValues: Any): NativeQuery + fun startAt(document: NativeDocumentSnapshot): NativeQuery + fun startAt(vararg fieldValues: Any): NativeQuery + + fun endBefore(document: NativeDocumentSnapshot): NativeQuery + fun endBefore(vararg fieldValues: Any): NativeQuery + fun endAt(document: NativeDocumentSnapshot): NativeQuery + fun endAt(vararg fieldValues: Any): NativeQuery +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..6bb72eedf --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,18 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.internal.EncodedObject + +internal expect class NativeTransactionWrapper internal constructor(native: NativeTransaction) { + + val native: NativeTransaction + + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeTransactionWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeTransactionWrapper + fun delete(documentRef: DocumentReference): NativeTransactionWrapper + suspend fun get(documentRef: DocumentReference): NativeDocumentSnapshotWrapper +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..bea3d0071 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,16 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.internal.EncodedObject + +internal expect class NativeWriteBatchWrapper internal constructor(native: NativeWriteBatch) { + val native: NativeWriteBatch + fun setEncoded(documentRef: DocumentReference, encodedData: EncodedObject, setOptions: SetOptions): NativeWriteBatchWrapper + fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper + fun updateEncodedFieldsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun updateEncodedFieldPathsAndValues(documentRef: DocumentReference, encodedFieldsAndValues: List>): NativeWriteBatchWrapper + fun delete(documentRef: DocumentReference): NativeWriteBatchWrapper + suspend fun commit() +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt new file mode 100644 index 000000000..afca32060 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SafeValue.kt @@ -0,0 +1,14 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.GeoPoint +import dev.gitlive.firebase.firestore.Timestamp + +internal val Any.safeValue: Any get() = when (this) { + is Timestamp -> nativeValue + is GeoPoint -> nativeValue + is DocumentReference -> native.nativeValue + is Map<*, *> -> this.mapNotNull { (key, value) -> key?.let { it.safeValue to value?.safeValue } } + is Collection<*> -> this.mapNotNull { it?.safeValue } + else -> this +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..badaba656 --- /dev/null +++ b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,13 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.FieldPath + +@PublishedApi +internal sealed class SetOptions { + data object Merge : SetOptions() + data object Overwrite : SetOptions() + data class MergeFields(val fields: List) : SetOptions() + data class MergeFieldPaths(val fieldPaths: List) : SetOptions() { + val encodedFieldPaths = fieldPaths.map { it.encoded } + } +} diff --git a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt b/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt deleted file mode 100644 index 2ea299201..000000000 --- a/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/serializers.kt +++ /dev/null @@ -1,29 +0,0 @@ -package dev.gitlive.firebase.firestore - -import kotlinx.serialization.descriptors.ClassSerialDescriptorBuilder -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.nullable - -/** - * Builder for a [SerialDescriptor] which fixes an nullability issue in [kotlinx.serialization.descriptors.buildClassSerialDescriptor] - * @return a class [SerialDescriptor]. */ -fun buildClassSerialDescriptor( - serialName: String, - vararg typeParameters: SerialDescriptor, - isNullable: Boolean, - builderAction: ClassSerialDescriptorBuilder.() -> Unit = {} -): SerialDescriptor { - val descriptor = kotlinx.serialization.descriptors.buildClassSerialDescriptor( - serialName = serialName, - typeParameters = typeParameters, - builderAction = builderAction - ) - - return if (isNullable && !descriptor.isNullable) { - // bug https://github.com/Kotlin/kotlinx.serialization/issues/1929 - descriptor.nullable - } else { - descriptor - } -} - diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt deleted file mode 100644 index 147a1c9d8..000000000 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/DeferredExtensionsTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.gitlive.firebase.firestore - -import dev.gitlive.firebase.runTest -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class DeferredExtensionsTest { - - @Test - fun testConvert() = runTest { - val original = CompletableDeferred() - val converted = original.convert { it.toString() } - - original.complete(1) - assertEquals("1", converted.await()) - } - - @Test - fun testConvertCompleted() = runTest { - val original = CompletableDeferred(1) - val converted = original.convert { it.toString() } - - assertEquals("1", converted.await()) - } - - @Test - fun testCanceled() = runTest { - val original = CompletableDeferred() - val converted = original.convert { it.toString() } - - val result = runCatching { - original.cancel() - - converted.await() - } - assertTrue(result.isFailure) - assertTrue(result.exceptionOrNull() is CancellationException) - } -} diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt index 138a1e070..96c5350e8 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FieldValueTests.kt @@ -1,6 +1,6 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.firebaseSerializer +import dev.gitlive.firebase.internal.firebaseSerializer import dev.gitlive.firebase.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -20,4 +20,4 @@ class FieldValueTests { fun serializers() = runTest { assertEquals(FieldValueSerializer, FieldValue.delete.firebaseSerializer()) } -} \ No newline at end of file +} diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt deleted file mode 100644 index dacd663bf..000000000 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreAsync.kt +++ /dev/null @@ -1,84 +0,0 @@ -package dev.gitlive.firebase.firestore - -import dev.gitlive.firebase.* -import kotlinx.serialization.Serializable -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test - -@IgnoreJs -@IgnoreForAndroidUnitTest -class FirestoreAsync { - - lateinit var firestore: FirebaseFirestore - - @BeforeTest - fun initializeFirebase() { - val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize( - context, - FirebaseOptions( - applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", - apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", - databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", - storageBucket = "fir-kotlin-sdk.appspot.com", - projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) - ) - - firestore = Firebase.firestore(app).apply { - useEmulator(emulatorHost, 8080) - setSettings(FirebaseFirestore.Settings.create(cacheSettings = LocalCacheSettings.Memory(LocalCacheSettings.Memory.GarbageCollectorSettings.Eager))) - } - } - - @AfterTest - fun deinitializeFirebase() = runBlockingTest { - Firebase.apps(context).forEach { - it.delete() - } - } - - @Serializable - data class TestData(val value: Int) - - @Test - fun asyncDocumentReferenceTest() = runTest { - fun getDocument() = firestore.collection("asyncDocumentReferenceTest") - .document("asyncDocumentReferenceTest") - - firestore.disableNetwork() - val update1 = getDocument().async.set(TestData(1)) - val update2 = getDocument().async.update(TestData(2)) - firestore.enableNetwork() - update1.await() - update2.await() - } - - @Test - fun asyncBatchTest() = runTest { - val batch = firestore.batch() - - fun getDocument() = firestore.collection("asyncBatchTest") - .document("asyncBatchTest") - - firestore.disableNetwork() - batch.set(getDocument(), TestData(1)) - batch.update(getDocument(), TestData(1)) - - val result = batch.async.commit() - firestore.enableNetwork() - - result.await() - } - - @Test - fun asyncCollectionAddTest() = runTest { - firestore.disableNetwork() - val result = firestore.collection("asyncCollectionAddTest").async - .add(TestData(1)) - firestore.enableNetwork() - - result.await() - } -} diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt new file mode 100644 index 000000000..2a5d74d90 --- /dev/null +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt @@ -0,0 +1,124 @@ +package dev.gitlive.firebase.firestore + +import dev.gitlive.firebase.* +import kotlin.test.* + +/** + * These tests are separated from other tests because + * testing Firestore Source requires toggling persistence settings per test. + */ +@IgnoreForAndroidUnitTest +class FirestoreSourceTest { + lateinit var firestore: FirebaseFirestore + + companion object { + val testDoc = FirebaseFirestoreTest.FirestoreTest( + "aaa", + 0.0, + 1, + listOf("a", "aa", "aaa"), + "notNull", + ) + } + + private suspend fun setDoc() { + firestore.collection("testFirestoreQuerying").document("one").set(testDoc) + } + + private fun initializeFirebase(persistenceEnabled: Boolean = false) { + val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize( + context, + FirebaseOptions( + applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", + apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", + databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", + storageBucket = "fir-kotlin-sdk.appspot.com", + projectId = "fir-kotlin-sdk", + gcmSenderId = "846484016111", + ), + ) + + firestore = Firebase.firestore(app).apply { + settings = firestoreSettings { + cacheSettings = if (persistenceEnabled) { + persistentCacheSettings { } + } else { + memoryCacheSettings { } + } + } + useEmulator(emulatorHost, 8080) + } + } + + @AfterTest + fun deinitializeFirebase() = runBlockingTest { + Firebase.apps(context).forEach { + it.delete() + } + } + + @Test + fun testGetFromServer_withPersistence() = runTest { + initializeFirebase(persistenceEnabled = true) + setDoc() + val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.SERVER) + assertTrue(doc.exists) + assertFalse(doc.metadata.isFromCache) + } + + @Test + fun testGetFromServer_withoutPersistence() = runTest { + initializeFirebase(persistenceEnabled = false) + setDoc() + val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.SERVER) + assertTrue(doc.exists) + assertFalse(doc.metadata.isFromCache) + } + + @Test + fun testGetFromCache() = runTest { + initializeFirebase(persistenceEnabled = true) + + // Warm up cache by setting a document + setDoc() + + val cachedDoc = firestore.collection("testFirestoreQuerying").document("one").get(Source.CACHE) + assertTrue(cachedDoc.exists) + assertTrue(cachedDoc.metadata.isFromCache) + } + + @Test + fun testGetFromCache_withoutPersistence() = runTest { + initializeFirebase(persistenceEnabled = false) + setDoc() + assertFailsWith(FirebaseFirestoreException::class) { + firestore.collection("testFirestoreQuerying").document("one").get(Source.CACHE) + } + } + + @Test + fun testGetDefault_withPersistence() = runTest { + initializeFirebase(persistenceEnabled = false) + val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.DEFAULT) + assertTrue(doc.exists) + assertFalse(doc.metadata.isFromCache) + } + + @Test + fun testGet() = runTest { + initializeFirebase(persistenceEnabled = false) + val doc = firestore.collection("testFirestoreQuerying").document("one").get() + assertTrue(doc.exists) + assertFalse(doc.metadata.isFromCache) + } + + @Test + fun testGetDefault_withoutPersistence() = runTest { + initializeFirebase(persistenceEnabled = true) + setDoc() + val doc = firestore.collection("testFirestoreQuerying").document("one").get(Source.DEFAULT) + assertTrue(doc.exists) + // Firebase defaults to first fetching from server + assertFalse(doc.metadata.isFromCache) + } +} diff --git a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt index b6d2bcc26..755787664 100644 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt +++ b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/GeoPointTests.kt @@ -1,6 +1,8 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.firebaseSerializer +import dev.gitlive.firebase.runTest import kotlinx.serialization.Serializable import kotlin.test.Test import kotlin.test.assertEquals @@ -8,17 +10,20 @@ import kotlin.test.assertEquals @Serializable data class TestDataWithGeoPoint( val uid: String, - val location: GeoPoint + val location: GeoPoint, ) -@Suppress("UNCHECKED_CAST") class GeoPointTests { @Test fun encodeGeoPointObject() = runTest { val geoPoint = GeoPoint(12.3, 45.6) val item = TestDataWithGeoPoint("123", geoPoint) - val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + val encoded = encodedAsMap( + encode(item) { + encodeDefaults = false + }, + ) assertEquals("123", encoded["uid"]) // check GeoPoint is encoded to a platform representation assertEquals(geoPoint.nativeValue, encoded["location"]) @@ -29,7 +34,7 @@ class GeoPointTests { val geoPoint = GeoPoint(12.3, 45.6) val obj = mapOf( "uid" to "123", - "location" to geoPoint.nativeValue + "location" to geoPoint.nativeValue, ).asEncoded() val decoded: TestDataWithGeoPoint = decode(obj) assertEquals("123", decoded.uid) @@ -40,6 +45,6 @@ class GeoPointTests { @Test @IgnoreJs fun serializers() = runTest { - assertEquals(GeoPointSerializer, GeoPoint(0.0,0.0).firebaseSerializer()) + assertEquals(GeoPointSerializer, GeoPoint(0.0, 0.0).firebaseSerializer()) } } 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 98292048a..10d8c1a63 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 @@ -1,22 +1,25 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode -import dev.gitlive.firebase.firebaseSerializer +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.internal.encode +import dev.gitlive.firebase.internal.firebaseSerializer import dev.gitlive.firebase.nativeAssertEquals import dev.gitlive.firebase.nativeMapOf import dev.gitlive.firebase.runTest import kotlinx.serialization.Serializable -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.DurationUnit @Serializable data class TestData( val uid: String, val createdAt: Timestamp, var updatedAt: BaseTimestamp, - val deletedAt: BaseTimestamp? + val deletedAt: BaseTimestamp?, ) class TimestampTests { @@ -39,9 +42,9 @@ class TimestampTests { "uid" to "uid123", "createdAt" to timestamp.nativeValue, "updatedAt" to timestamp.nativeValue, - "deletedAt" to null + "deletedAt" to null, ), - encode(item, shouldEncodeElementDefault = false) + encode(item) { encodeDefaults = false }, ) } @@ -54,9 +57,9 @@ class TimestampTests { "uid" to "uid123", "createdAt" to timestamp.nativeValue, "updatedAt" to FieldValue.serverTimestamp.nativeValue, - "deletedAt" to FieldValue.serverTimestamp.nativeValue + "deletedAt" to FieldValue.serverTimestamp.nativeValue, ), - encode(item, shouldEncodeElementDefault = false) + encode(item) { encodeDefaults = false }, ) } @@ -67,7 +70,7 @@ class TimestampTests { "uid" to "uid123", "createdAt" to timestamp.nativeValue, "updatedAt" to timestamp.nativeValue, - "deletedAt" to timestamp.nativeValue + "deletedAt" to timestamp.nativeValue, ) val decoded: TestData = decode(TestData.serializer(), obj) assertEquals("uid123", decoded.uid) @@ -94,7 +97,7 @@ class TimestampTests { "uid" to "uid123", "createdAt" to Timestamp.now().nativeValue, "updatedAt" to Timestamp.now().nativeValue, - "deletedAt" to null + "deletedAt" to null, ) val decoded: TestData = decode(TestData.serializer(), obj) assertEquals("uid123", decoded.uid) @@ -104,11 +107,10 @@ class TimestampTests { @Test fun serializers() = runTest { - //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()) + 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/createFirestoreTestSettings.kt b/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt deleted file mode 100644 index 24b92dab7..000000000 --- a/firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.gitlive.firebase.firestore - -expect fun createFirestoreTestSettings( - sslEnabled: Boolean? = null, - host: String? = null, - cacheSettings: LocalCacheSettings? = null -): FirebaseFirestore.Settings 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 6495948a0..50b90c44c 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 @@ -4,16 +4,22 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* -import dev.gitlive.firebase.firestore.encode -import kotlinx.coroutines.CoroutineScope +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest +import dev.gitlive.firebase.withSerializer 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.test.TestResult import kotlinx.coroutines.withContext +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.nullable @@ -27,12 +33,15 @@ import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds expect val emulatorHost: String expect val context: Any /** @return a map extracted from the encoded data. */ expect fun encodedAsMap(encoded: Any?): Map + /** @return pairs as raw encoded data. */ expect fun Map.asEncoded(): Any @@ -45,14 +54,39 @@ class FirebaseFirestoreTest { val time: Double = 0.0, val count: Int = 0, val list: List = emptyList(), + val optional: String? = null, ) @Serializable data class FirestoreTimeTest( val prop1: String, - val time: BaseTimestamp? + val time: BaseTimestamp?, ) + companion object { + val testOne = FirestoreTest( + "aaa", + 0.0, + 1, + listOf("a", "aa", "aaa"), + "notNull", + ) + val testTwo = FirestoreTest( + "bbb", + 0.0, + 2, + listOf("b", "bb", "ccc"), + ) + val testThree = FirestoreTest( + "ccc", + 1.0, + 3, + listOf("c", "cc", "ccc"), + "notNull", + ) + } + + lateinit var firebaseApp: FirebaseApp lateinit var firestore: FirebaseFirestore @BeforeTest @@ -65,13 +99,18 @@ class FirebaseFirestoreTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) + firebaseApp = app firestore = Firebase.firestore(app).apply { + settings = firestoreSettings { + cacheSettings = memoryCacheSettings { + gcSettings = memoryEagerGcSettings { } + } + } useEmulator(emulatorHost, 8080) - setSettings(FirebaseFirestore.Settings.create(cacheSettings = LocalCacheSettings.Memory(LocalCacheSettings.Memory.GarbageCollectorSettings.Eager))) } } @@ -182,11 +221,11 @@ class FirebaseFirestoreTest { val deferredPendingWritesSnapshot = async { doc.snapshots.filter { it.exists }.first() } - nonSkippedDelay(100) // makes possible to catch pending writes snapshot + nonSkippedDelay(100.milliseconds) // makes possible to catch pending writes snapshot doc.set( FirestoreTimeTest.serializer(), - FirestoreTimeTest("ServerTimestampBehavior", Timestamp.ServerTimestamp) + FirestoreTimeTest("ServerTimestampBehavior", Timestamp.ServerTimestamp), ) val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() @@ -195,7 +234,7 @@ class FirebaseFirestoreTest { } @Test - fun testExtendedSetBatch() = runTest { + fun testSetBatch() = runTest { val doc = firestore .collection("testServerTestSetBatch") .document("test") @@ -205,17 +244,12 @@ class FirebaseFirestoreTest { strategy = FirestoreTest.serializer(), data = FirestoreTest( prop1 = "prop1", - time = 123.0 + time = 123.0, ), - fieldsAndValues = arrayOf( - "time" to 124.0 - ) ) batch.commit() - assertEquals(124.0, doc.get().get("time")) assertEquals("prop1", doc.get().data(FirestoreTest.serializer()).prop1) - } @Test @@ -227,7 +261,7 @@ class FirebaseFirestoreTest { val deferredPendingWritesSnapshot = async { doc.snapshots.filter { it.exists }.first() } - nonSkippedDelay(100) // makes possible to catch pending writes snapshot + nonSkippedDelay(100.milliseconds) // makes possible to catch pending writes snapshot doc.set(FirestoreTimeTest.serializer(), FirestoreTimeTest("ServerTimestampBehavior", Timestamp.ServerTimestamp)) @@ -246,7 +280,7 @@ class FirebaseFirestoreTest { val deferredPendingWritesSnapshot = async { doc.snapshots.filter { it.exists }.first() } - nonSkippedDelay(100) // makes possible to catch pending writes snapshot + nonSkippedDelay(100.milliseconds) // makes possible to catch pending writes snapshot doc.set(FirestoreTimeTest.serializer(), FirestoreTimeTest("ServerTimestampBehavior", Timestamp.ServerTimestamp)) @@ -473,31 +507,6 @@ class FirebaseFirestoreTest { assertEquals(listOf("first"), dataAfter.list) } - @Test - fun testLegacyDoubleTimestamp() = runTest { - @Serializable - data class DoubleTimestamp( - @Serializable(with = DoubleAsTimestampSerializer::class) - val time: Double? - ) - - val doc = firestore - .collection("testLegacyDoubleTimestamp") - .document("test${Random.nextInt()}") - - val deferredPendingWritesSnapshot = async { - doc.snapshots.filter { it.exists }.first() - } - nonSkippedDelay(100) // makes possible to catch pending writes snapshot - - doc.set(DoubleTimestamp.serializer(), DoubleTimestamp(DoubleAsTimestampSerializer.serverTimestamp)) - - val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() - assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) - assertNotNull(pendingWritesSnapshot.get("time", DoubleAsTimestampSerializer, serverTimestampBehavior = ServerTimestampBehavior.ESTIMATE )) - assertNotEquals(DoubleAsTimestampSerializer.serverTimestamp, pendingWritesSnapshot.data(DoubleTimestamp.serializer(), serverTimestampBehavior = ServerTimestampBehavior.ESTIMATE).time) - } - @Test fun testSetBatchDoesNotEncodeEmptyValues() = runTest { val doc = firestore @@ -509,9 +518,8 @@ class FirebaseFirestoreTest { strategy = FirestoreTest.serializer(), data = FirestoreTest( prop1 = "prop1-set", - time = 125.0 + time = 125.0, ), - fieldsAndValues = arrayOf>() ) batch.commit() @@ -520,35 +528,31 @@ class FirebaseFirestoreTest { } @Test - fun testExtendedUpdateBatch() = runTest { + fun testUpdateBatch() = runTest { val doc = firestore .collection("testServerTestSetBatch") .document("test").apply { set( FirestoreTest( prop1 = "prop1", - time = 123.0 - ) + time = 123.0, + ), ) } - val batch = firestore.batch() batch.update( documentRef = doc, strategy = FirestoreTest.serializer(), data = FirestoreTest( prop1 = "prop1-updated", - time = 123.0 + time = 123.0, ), - encodeSettings = EncodeSettings(shouldEncodeElementDefault = false), - fieldsAndValues = arrayOf( - "time" to FieldValue.delete - ) - ) + ) { + encodeDefaults = false + } batch.commit() - assertEquals(null, doc.get().get("time") as Double?) assertEquals("prop1-updated", doc.get().data(FirestoreTest.serializer()).prop1) } @@ -560,8 +564,8 @@ class FirebaseFirestoreTest { set( FirestoreTest( prop1 = "prop1", - time = 123.0 - ) + time = 123.0, + ), ) } val batch = firestore.batch() @@ -570,29 +574,53 @@ class FirebaseFirestoreTest { strategy = FirestoreTest.serializer(), data = FirestoreTest( prop1 = "prop1-set", - time = 126.0 + time = 126.0, ), - encodeSettings = EncodeSettings(shouldEncodeElementDefault = false), - fieldsAndValues = arrayOf>() - ) + ) { + encodeDefaults = false + } batch.commit() assertEquals(126.0, doc.get().get("time") as Double?) assertEquals("prop1-set", doc.get().data(FirestoreTest.serializer()).prop1) } + @Test + fun testLegacyDoubleTimestamp() = runTest { + @Serializable + data class DoubleTimestamp( + @Serializable(with = DoubleAsTimestampSerializer::class) + val time: Double?, + ) + + val doc = firestore + .collection("testLegacyDoubleTimestamp") + .document("test${Random.nextInt()}") + + val deferredPendingWritesSnapshot = async { + doc.snapshots.filter { it.exists }.first() + } + nonSkippedDelay(100.milliseconds) // makes possible to catch pending writes snapshot + + doc.set(DoubleTimestamp.serializer(), DoubleTimestamp(DoubleAsTimestampSerializer.SERVER_TIMESTAMP)) + + val pendingWritesSnapshot = deferredPendingWritesSnapshot.await() + assertTrue(pendingWritesSnapshot.metadata.hasPendingWrites) + assertNotNull(pendingWritesSnapshot.get("time", DoubleAsTimestampSerializer, serverTimestampBehavior = ServerTimestampBehavior.ESTIMATE)) + assertNotEquals(DoubleAsTimestampSerializer.SERVER_TIMESTAMP, pendingWritesSnapshot.data(DoubleTimestamp.serializer(), serverTimestampBehavior = ServerTimestampBehavior.ESTIMATE).time) + } @Test fun testLegacyDoubleTimestampWriteNewFormatRead() = runTest { @Serializable data class LegacyDocument( @Serializable(with = DoubleAsTimestampSerializer::class) - val time: Double + val time: Double, ) @Serializable data class NewDocument( - val time: Timestamp + val time: Timestamp, ) val doc = firestore @@ -611,7 +639,7 @@ class FirebaseFirestoreTest { fun testQueryByTimestamp() = runTest { @Serializable data class DocumentWithTimestamp( - val time: Timestamp + val time: Timestamp, ) val collection = firestore @@ -625,33 +653,19 @@ class FirebaseFirestoreTest { 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(DocumentWithTimestamp.serializer()) }.toSet() + val equalityQueryResult = collection.where { + FieldPath(DocumentWithTimestamp::time.name) equalTo pastTimestamp + }.get().documents.map { it.data(DocumentWithTimestamp.serializer()) }.toSet() assertEquals(setOf(DocumentWithTimestamp(pastTimestamp)), equalityQueryResult) - val gtQueryResult = collection.where( - path = FieldPath(DocumentWithTimestamp::time.name), - greaterThan = timestamp - ).get().documents.map { it.data(DocumentWithTimestamp.serializer()) }.toSet() + val gtQueryResult = collection.where { + FieldPath(DocumentWithTimestamp::time.name) greaterThan timestamp + }.get().documents.map { it.data(DocumentWithTimestamp.serializer()) }.toSet() assertEquals(setOf(DocumentWithTimestamp(futureTimestamp)), gtQueryResult) } - private suspend fun setupFirestoreData() { - firestore.collection("testFirestoreQuerying") - .document("one") - .set(FirestoreTest.serializer(), FirestoreTest("aaa")) - firestore.collection("testFirestoreQuerying") - .document("two") - .set(FirestoreTest.serializer(), FirestoreTest("bbb")) - firestore.collection("testFirestoreQuerying") - .document("three") - .set(FirestoreTest.serializer(), FirestoreTest("ccc")) - } - @Test fun testGeoPointSerialization() = runTest { @Serializable @@ -679,7 +693,7 @@ class FirebaseFirestoreTest { fun testDocumentReferenceSerialization() = runTest { @Serializable data class DataWithDocumentReference( - val documentReference: DocumentReference + val documentReference: DocumentReference, ) fun getCollection() = firestore.collection("documentReferenceSerialization") @@ -702,7 +716,7 @@ class FirebaseFirestoreTest { // update data val updatedData = DataWithDocumentReference(documentRef2) getDocument().update( - FieldPath(DataWithDocumentReference::documentReference.name) to updatedData.documentReference.withSerializer(DocumentReferenceSerializer) + FieldPath(DataWithDocumentReference::documentReference.name) to updatedData.documentReference.withSerializer(DocumentReferenceSerializer), ) // verify update val updatedSavedData = getDocument().get().data(DataWithDocumentReference.serializer()) @@ -713,19 +727,23 @@ class FirebaseFirestoreTest { data class TestDataWithDocumentReference( val uid: String, val reference: DocumentReference, - val optionalReference: DocumentReference? + val optionalReference: DocumentReference?, ) @Serializable data class TestDataWithOptionalDocumentReference( - val optionalReference: DocumentReference? + val optionalReference: DocumentReference?, ) @Test fun encodeDocumentReference() = runTest { val doc = firestore.document("a/b") val item = TestDataWithDocumentReference("123", doc, doc) - val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + val encoded = encodedAsMap( + encode(item) { + encodeDefaults = false + }, + ) assertEquals("123", encoded["uid"]) assertEquals(doc.nativeValue, encoded["reference"]) assertEquals(doc.nativeValue, encoded["optionalReference"]) @@ -734,7 +752,11 @@ class FirebaseFirestoreTest { @Test fun encodeNullDocumentReference() = runTest { val item = TestDataWithOptionalDocumentReference(null) - val encoded = encodedAsMap(encode(item, shouldEncodeElementDefault = false)) + val encoded = encodedAsMap( + encode(item) { + encodeDefaults = false + }, + ) assertNull(encoded["optionalReference"]) } @@ -744,7 +766,7 @@ class FirebaseFirestoreTest { val obj = mapOf( "uid" to "123", "reference" to doc.nativeValue, - "optionalReference" to doc.nativeValue + "optionalReference" to doc.nativeValue, ).asEncoded() val decoded: TestDataWithDocumentReference = decode(obj) assertEquals("123", decoded.uid) @@ -788,7 +810,261 @@ class FirebaseFirestoreTest { assertNull(deletedList) } - private suspend fun nonSkippedDelay(timeout: Long) = withContext(Dispatchers.Default) { + @Test + fun testQueryEqualTo() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "prop1" equalTo testOne.prop1 } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::prop1.name) equalTo testTwo.prop1 } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testTwo) + + val nullableQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::optional.name) equalTo null } + + nullableQuery.assertDocuments(FirestoreTest.serializer(), testTwo) + } + + @Test + fun testQueryNotEqualTo() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "prop1" notEqualTo testOne.prop1 } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testTwo, testThree) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::prop1.name) notEqualTo testTwo.prop1 } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testOne, testThree) + + val nullableQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::optional.name) notEqualTo null } + + nullableQuery.assertDocuments(FirestoreTest.serializer(), testOne, testThree) + } + + @Test + fun testQueryLessThan() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "count" lessThan testThree.count } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne, testTwo) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::count.name) lessThan testTwo.count } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testOne) + } + + @Test + fun testQueryGreaterThan() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "count" greaterThan testOne.count } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testTwo, testThree) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::count.name) greaterThan testTwo.count } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testThree) + } + + @Test + fun testQueryLessThanOrEqualTo() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "count" lessThanOrEqualTo testOne.count } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::count.name) lessThanOrEqualTo testTwo.count } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testOne, testTwo) + } + + @Test + fun testQueryGreaterThanOrEqualTo() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "count" greaterThanOrEqualTo testThree.count } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testThree) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::count.name) greaterThanOrEqualTo testTwo.count } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testTwo, testThree) + } + + @Test + fun testQueryArrayContains() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "list" contains "a" } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::list.name) contains "ccc" } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testThree, testTwo) + } + + @Test + fun testQueryArrayContainsAny() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "list" containsAny listOf("a", "b") } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne, testTwo) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::list.name) containsAny listOf("c", "d") } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testThree) + } + + @Test + fun testQueryInArray() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "prop1" inArray listOf("aaa", "bbb") } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne, testTwo) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::prop1.name) inArray listOf("ccc", "ddd") } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testThree) + } + + @Test + fun testQueryNotInArray() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { "prop1" notInArray listOf("aaa", "bbb") } + + fieldQuery.assertDocuments(FirestoreTest.serializer(), testThree) + + val pathQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath(FirestoreTest::prop1.name) notInArray listOf("ccc", "ddd") } + + pathQuery.assertDocuments(FirestoreTest.serializer(), testOne, testTwo) + } + + @Test + fun testCompoundQuery() = runTest { + setupFirestoreData() + + val andQuery = firestore + .collection("testFirestoreQuerying") + .where { + FieldPath(FirestoreTest::prop1.name) inArray listOf("aaa", "bbb") and (FieldPath(FirestoreTest::count.name) equalTo 1) + } + andQuery.assertDocuments(FirestoreTest.serializer(), testOne) + + val orQuery = firestore + .collection("testFirestoreQuerying") + .where { + FieldPath(FirestoreTest::prop1.name) equalTo "aaa" or (FieldPath(FirestoreTest::count.name) equalTo 2) + } + orQuery.assertDocuments(FirestoreTest.serializer(), testOne, testTwo) + + val andOrQuery = firestore + .collection("testFirestoreQuerying") + .where { + all( + any( + FieldPath(FirestoreTest::prop1.name) equalTo "aaa", + FieldPath(FirestoreTest::count.name) equalTo 2, + )!!, + FieldPath(FirestoreTest::list.name) contains "a", + ) + } + andOrQuery.assertDocuments(FirestoreTest.serializer(), testOne) + } + + @Test + fun testQueryByDocumentId() = runTest { + setupFirestoreData() + + val fieldQuery = firestore + .collection("testFirestoreQuerying") + .where { FieldPath.documentId equalTo "one" } + fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne) + } + + @Test + fun testMultiple() = runTest { + Firebase.firestore(firebaseApp).disableNetwork() + Firebase.firestore(firebaseApp).enableNetwork() + } + + private suspend fun setupFirestoreData( + documentOne: FirestoreTest = testOne, + documentTwo: FirestoreTest = testTwo, + documentThree: FirestoreTest = testThree, + ) { + firestore.collection("testFirestoreQuerying") + .document("one") + .set(FirestoreTest.serializer(), documentOne) + firestore.collection("testFirestoreQuerying") + .document("two") + .set(FirestoreTest.serializer(), documentTwo) + firestore.collection("testFirestoreQuerying") + .document("three") + .set(FirestoreTest.serializer(), documentThree) + } + + private suspend fun Query.assertDocuments(serializer: KSerializer, vararg expected: T) { + val documents = get().documents + assertEquals(expected.size, documents.size) + documents.forEachIndexed { index, documentSnapshot -> + assertEquals(expected[index], documentSnapshot.data(serializer)) + } + } + + private suspend fun nonSkippedDelay(timeout: Duration) = withContext(Dispatchers.Default) { delay(timeout) } } diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt index f3c0efd0a..9eb6a8589 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -8,7 +8,7 @@ private typealias NativeFieldValue = FIRFieldValue /** Represents a Firebase FieldValue. */ @Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { +public actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { init { require(nativeValue is NativeFieldValue) } @@ -17,11 +17,11 @@ actual class FieldValue internal actual constructor(internal actual val nativeVa override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() - actual companion object { - actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForServerTimestamp()) - actual val delete: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForDelete()) - actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.fieldValueForIntegerIncrement(value.toLong())) - actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayUnion(elements.asList())) - actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayRemove(elements.asList())) + public actual companion object { + public actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForServerTimestamp()) + public actual val delete: FieldValue get() = FieldValue(NativeFieldValue.fieldValueForDelete()) + public actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.fieldValueForIntegerIncrement(value.toLong())) + public actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayUnion(elements.asList())) + public actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.fieldValueForArrayRemove(elements.asList())) } } diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt index f82ee3156..5dc0fa8aa 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/GeoPoint.kt @@ -3,16 +3,16 @@ package dev.gitlive.firebase.firestore import cocoapods.FirebaseFirestoreInternal.FIRGeoPoint import kotlinx.serialization.Serializable - /** A class representing a platform specific Firebase GeoPoint. */ -actual typealias NativeGeoPoint = FIRGeoPoint +public actual typealias NativeGeoPoint = FIRGeoPoint /** A class representing a Firebase GeoPoint. */ @Serializable(with = GeoPointSerializer::class) -actual class GeoPoint internal actual constructor(internal actual val nativeValue: NativeGeoPoint) { - actual constructor(latitude: Double, longitude: Double) : this(NativeGeoPoint(latitude, longitude)) - actual val latitude: Double = nativeValue.latitude - actual val longitude: Double = nativeValue.longitude +public actual class GeoPoint internal actual constructor(internal actual val nativeValue: NativeGeoPoint) { + public actual constructor(latitude: Double, longitude: Double) : this(NativeGeoPoint(latitude, longitude)) + public actual val latitude: Double = nativeValue.latitude + public actual val longitude: Double = nativeValue.longitude + override fun equals(other: Any?): Boolean = this === other || other is GeoPoint && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt index aeedf8d01..d0eb95602 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt @@ -4,32 +4,32 @@ import cocoapods.FirebaseFirestoreInternal.FIRTimestamp import kotlinx.serialization.Serializable /** A class representing a platform specific Firebase Timestamp. */ -actual typealias NativeTimestamp = FIRTimestamp +public actual typealias NativeTimestamp = FIRTimestamp /** A base class that could be used to combine [Timestamp] and [Timestamp.ServerTimestamp] in the same field. */ @Serializable(with = BaseTimestampSerializer::class) -actual sealed class BaseTimestamp +public actual sealed class BaseTimestamp /** A class representing a Firebase Timestamp. */ @Serializable(with = TimestampSerializer::class) -actual class Timestamp internal actual constructor( - internal actual val nativeValue: NativeTimestamp -): BaseTimestamp() { - actual constructor(seconds: Long, nanoseconds: Int) : this(NativeTimestamp(seconds, nanoseconds)) +public actual class Timestamp internal actual constructor( + internal actual val nativeValue: NativeTimestamp, +) : BaseTimestamp() { + public actual constructor(seconds: Long, nanoseconds: Int) : this(NativeTimestamp(seconds, nanoseconds)) - actual val seconds: Long = nativeValue.seconds - actual val nanoseconds: Int = nativeValue.nanoseconds + public actual val seconds: Long = nativeValue.seconds + public actual val nanoseconds: Int = nativeValue.nanoseconds override fun equals(other: Any?): Boolean = this === other || other is Timestamp && nativeValue == other.nativeValue override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() - actual companion object { - actual fun now(): Timestamp = Timestamp(NativeTimestamp.timestamp()) + public actual companion object { + public actual fun now(): Timestamp = Timestamp(NativeTimestamp.timestamp()) } /** A server time timestamp. */ @Serializable(with = ServerTimestampSerializer::class) - actual object ServerTimestamp: BaseTimestamp() + public actual object ServerTimestamp : BaseTimestamp() } diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt index ab06e2eed..c2907944a 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -3,10 +3,11 @@ package dev.gitlive.firebase.firestore import cocoapods.FirebaseFirestoreInternal.FIRFieldValue @PublishedApi -internal actual fun isSpecialValue(value: Any) = when(value) { +internal actual fun isSpecialValue(value: Any): Boolean = when (value) { is FIRFieldValue, is NativeGeoPoint, is NativeTimestamp, - is NativeDocumentReference -> true + is NativeDocumentReferenceType, + -> true else -> false } diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 44fb88d81..605281ec0 100644 --- a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -2,423 +2,136 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ +@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER") + package dev.gitlive.firebase.firestore import cocoapods.FirebaseFirestoreInternal.* import cocoapods.FirebaseFirestoreInternal.FIRDocumentChangeType.* -import dev.gitlive.firebase.* -import kotlinx.cinterop.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import dev.gitlive.firebase.ios import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy import platform.Foundation.NSError -import platform.Foundation.NSNull import platform.Foundation.NSNumber +import platform.Foundation.numberWithLong +import platform.darwin.dispatch_get_main_queue import platform.darwin.dispatch_queue_t -actual val Firebase.firestore get() = +public val FirebaseFirestore.ios: FIRFirestore get() = FIRFirestore.firestore() + +public actual val Firebase.firestore: FirebaseFirestore get() = FirebaseFirestore(FIRFirestore.firestore()) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore = FirebaseFirestore( - FIRFirestore.firestoreForApp(app.ios as objcnames.classes.FIRApp) +public actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore = FirebaseFirestore( + FIRFirestore.firestoreForApp(app.ios as objcnames.classes.FIRApp), ) -@Suppress("CAST_NEVER_SUCCEEDS") -val LocalCacheSettings.ios: FIRLocalCacheSettingsProtocol get() = when (this) { - is LocalCacheSettings.Persistent -> sizeBytes?.let { FIRPersistentCacheSettings(it as NSNumber) } ?: FIRPersistentCacheSettings() +public val LocalCacheSettings.ios: FIRLocalCacheSettingsProtocol get() = when (this) { + is LocalCacheSettings.Persistent -> FIRPersistentCacheSettings(NSNumber.numberWithLong(sizeBytes)) is LocalCacheSettings.Memory -> FIRMemoryCacheSettings( when (garbaseCollectorSettings) { - is LocalCacheSettings.Memory.GarbageCollectorSettings.Eager -> FIRMemoryEagerGCSettings() - is LocalCacheSettings.Memory.GarbageCollectorSettings.LRUGC -> garbaseCollectorSettings.sizeBytes?.let { FIRMemoryLRUGCSettings(it as NSNumber) } ?: FIRMemoryLRUGCSettings() - } + is MemoryGarbageCollectorSettings.Eager -> FIRMemoryEagerGCSettings() + is MemoryGarbageCollectorSettings.LRUGC -> FIRMemoryLRUGCSettings(NSNumber.numberWithLong(garbaseCollectorSettings.sizeBytes)) + }, ) } -@Suppress("UNCHECKED_CAST") -actual data class FirebaseFirestore(val ios: FIRFirestore) { - - actual data class Settings( - actual val sslEnabled: Boolean? = null, - actual val host: String? = null, - actual val cacheSettings: LocalCacheSettings? = null, - val dispatchQueue: dispatch_queue_t = null - ) { - actual companion object { - actual fun create(sslEnabled: Boolean?, host: String?, cacheSettings: LocalCacheSettings?) = Settings(sslEnabled, host, cacheSettings) - } - } - - actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) - - actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) - - actual fun collectionGroup(collectionId: String) = Query(ios.collectionGroupWithID(collectionId)) - - actual fun batch() = WriteBatch(ios.batch()) +internal actual typealias NativeFirebaseFirestore = FIRFirestore - actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = - FIRFirestore.enableLogging(loggingEnabled) +public operator fun FirebaseFirestore.Companion.invoke(ios: FIRFirestore): FirebaseFirestore = FirebaseFirestore(ios) - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - awaitResult { ios.runTransactionWithBlock({ transaction, _ -> runBlocking { Transaction(transaction!!).func() } }, it) } as T +public actual data class FirebaseFirestoreSettings( + actual val sslEnabled: Boolean, + actual val host: String, + actual val cacheSettings: LocalCacheSettings, + val dispatchQueue: dispatch_queue_t, +) { - actual suspend fun clearPersistence() = - await { ios.clearPersistenceWithCompletion(it) } - - actual fun useEmulator(host: String, port: Int) { - ios.useEmulatorWithHost(host, port.toLong()) - ios.settings = ios.settings.apply { - cacheSettings = FIRMemoryCacheSettings() - sslEnabled = false - } + public actual companion object { + public actual val CACHE_SIZE_UNLIMITED: Long = -1L + internal actual val DEFAULT_HOST: String = "firestore.googleapis.com" + internal actual val MINIMUM_CACHE_BYTES: Long = 1 * 1024 * 1024 + internal actual val DEFAULT_CACHE_SIZE_BYTES: Long = 100 * 1024 * 1024 } - actual fun setSettings(settings: Settings) { - ios.settings = FIRFirestoreSettings().applySettings(settings) - } + public actual class Builder( + public actual var sslEnabled: Boolean, + public actual var host: String, + public actual var cacheSettings: LocalCacheSettings, + public var dispatchQueue: dispatch_queue_t, + ) { - actual fun updateSettings(settings: Settings) { - ios.settings = ios.settings.applySettings(settings) - } + public actual constructor() : this( + true, + DEFAULT_HOST, + persistentCacheSettings { }, + dispatch_get_main_queue(), + ) - private fun FIRFirestoreSettings.applySettings(settings: Settings): FIRFirestoreSettings = apply { - settings.cacheSettings?.let { cacheSettings = it.ios } - settings.sslEnabled?.let { sslEnabled = it } - settings.host?.let { host = it } - settings.dispatchQueue?.let { dispatchQueue = it } - } + public actual constructor(settings: FirebaseFirestoreSettings) : this( + settings.sslEnabled, + settings.host, + settings.cacheSettings, + settings.dispatchQueue, + ) - actual suspend fun disableNetwork() { - await { ios.disableNetworkWithCompletion(it) } + public actual fun build(): FirebaseFirestoreSettings = FirebaseFirestoreSettings(sslEnabled, host, cacheSettings, dispatchQueue) } - actual suspend fun enableNetwork() { - await { ios.enableNetworkWithCompletion(it) } + val ios: FIRFirestoreSettings get() = FIRFirestoreSettings().apply { + cacheSettings = this@FirebaseFirestoreSettings.cacheSettings.ios + sslEnabled = this@FirebaseFirestoreSettings.sslEnabled + host = this@FirebaseFirestoreSettings.host + dispatchQueue = this@FirebaseFirestoreSettings.dispatchQueue } - - override fun toString(): String = ios.app.toString() } -@Suppress("UNCHECKED_CAST") -actual class WriteBatch(val ios: FIRWriteBatch) : BaseWriteBatch() { - - actual val async = Async(ios) - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseWriteBatch = when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData as Map, documentRef.ios, true) - is SetOptions.Overwrite -> ios.setData(encodedData as Map, documentRef.ios, false) - is SetOptions.MergeFields -> ios.setData(encodedData as Map, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData as Map, documentRef.ios, setOptions.encodedFieldPaths) - }.let { this } - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List>, - merge: Boolean - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - ios.setData(result as Map, documentRef.ios, merge) - return this +public actual fun firestoreSettings( + settings: FirebaseFirestoreSettings?, + builder: FirebaseFirestoreSettings.Builder.() -> Unit, +): FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().apply { + settings?.let { + sslEnabled = it.sslEnabled + host = it.host + cacheSettings = it.cacheSettings + dispatchQueue = it.dispatchQueue } +}.apply(builder).build() - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = ios.updateData(encodedData as Map, documentRef.ios).let { this } - - override fun updateEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List> - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() +internal actual typealias NativeWriteBatch = FIRWriteBatch - val result = serializedItem + serializedFieldAndValues - return ios.updateData(result as Map, documentRef.ios).let { this } - } +public operator fun WriteBatch.Companion.invoke(ios: FIRWriteBatch): WriteBatch = WriteBatch(ios) +public val WriteBatch.ios: FIRWriteBatch get() = native - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } - - actual suspend fun commit() = async.commit().await() - - actual class Async(@PublishedApi internal val ios: FIRWriteBatch) { - actual fun commit() = deferred { ios.commitWithCompletion(it) } - } -} +internal actual typealias NativeTransaction = FIRTransaction -@Suppress("UNCHECKED_CAST") -actual class Transaction(val ios: FIRTransaction) : BaseTransaction() { - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseTransaction = when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData as Map, documentRef.ios, true) - is SetOptions.Overwrite -> ios.setData(encodedData as Map, documentRef.ios, false) - is SetOptions.MergeFields -> ios.setData(encodedData as Map, documentRef.ios, setOptions.fields) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData as Map, documentRef.ios, setOptions.encodedFieldPaths) - }.let { this } - - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = ios.updateData(encodedData as Map, documentRef.ios).let { this } - - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseTransaction = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseTransaction = ios.updateData( - encodedFieldsAndValues.toMap(), - documentRef.ios - ).let { this } - - actual fun delete(documentRef: DocumentReference) = - ios.deleteDocument(documentRef.ios).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - throwError { DocumentSnapshot(ios.getDocument(documentRef.ios, it)!!) } - -} +public operator fun Transaction.Companion.invoke(ios: FIRTransaction): Transaction = Transaction(ios) +public val Transaction.ios: FIRTransaction get() = native /** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = FIRDocumentReference - -@Suppress("UNCHECKED_CAST") -@Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - - class Async(@PublishedApi internal val ios: NativeDocumentReference) : BaseDocumentReference.Async() { - - override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = deferred { - when (setOptions) { - is SetOptions.Merge -> ios.setData(encodedData as Map, true, it) - is SetOptions.Overwrite -> ios.setData(encodedData as Map, false, it) - is SetOptions.MergeFields -> ios.setData(encodedData as Map, setOptions.fields, it) - is SetOptions.MergeFieldPaths -> ios.setData(encodedData as Map, setOptions.encodedFieldPaths, it) - } - } - - override fun updateEncoded(encodedData: Any): Deferred = deferred { - ios.updateData(encodedData as Map, it) - } - - override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = deferred { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = deferred { - ios.updateData(encodedFieldsAndValues.toMap(), it) - } - - override fun delete() = - deferred { ios.deleteDocumentWithCompletion(it) } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(DocumentSnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - val ios: NativeDocumentReference by ::nativeValue - - actual val id: String - get() = ios.documentID - - actual val path: String - get() = ios.path - - actual val parent: CollectionReference - get() = CollectionReference(ios.parent) - - override val async = Async(nativeValue) - - actual fun collection(collectionPath: String) = CollectionReference(ios.collectionWithPath(collectionPath)) - - actual suspend fun get() = - DocumentSnapshot(awaitResult { ios.getDocumentWithCompletion(it) }) - - actual val snapshots get() = callbackFlow { - val listener = ios.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(DocumentSnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() -} - -actual open class Query(open val ios: FIRQuery) { - - actual suspend fun get() = QuerySnapshot(awaitResult { ios.getDocumentsWithCompletion(it) }) +internal actual typealias NativeDocumentReferenceType = FIRDocumentReference - actual fun limit(limit: Number) = Query(ios.queryLimitedTo(limit.toLong())) +public operator fun DocumentReference.Companion.invoke(ios: FIRDocumentReference): DocumentReference = DocumentReference(ios) +public val DocumentReference.ios: FIRDocumentReference get() = native.ios - actual val snapshots get() = callbackFlow { - val listener = ios.addSnapshotListener { snapshot, error -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val listener = ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - error?.let { close(error.toException()) } - } - awaitClose { listener.remove() } - } - - internal actual fun _where(field: String, equalTo: Any?) = Query(ios.queryWhereField(field, isEqualTo = equalTo!!)) - internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(ios.queryWhereFieldPath(path.ios, isEqualTo = equalTo!!)) - - internal actual fun _where(field: String, equalTo: DocumentReference) = Query(ios.queryWhereField(field, isEqualTo = equalTo.ios)) - internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(ios.queryWhereFieldPath(path.ios, isEqualTo = equalTo.ios)) - - internal actual fun _where( - field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = Query( - when { - lessThan != null -> ios.queryWhereField(field, isLessThan = lessThan) - greaterThan != null -> ios.queryWhereField(field, isGreaterThan = greaterThan) - arrayContains != null -> ios.queryWhereField(field, arrayContains = arrayContains) - notEqualTo != null -> ios.queryWhereField(field, isNotEqualTo = notEqualTo) - lessThanOrEqualTo != null -> ios.queryWhereField(field, isLessThanOrEqualTo = lessThanOrEqualTo) - greaterThanOrEqualTo != null -> ios.queryWhereField(field, isGreaterThanOrEqualTo = greaterThanOrEqualTo) - else -> ios - } - ) - - internal actual fun _where( - path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = Query( - when { - lessThan != null -> ios.queryWhereFieldPath(path.ios, isLessThan = lessThan) - greaterThan != null -> ios.queryWhereFieldPath(path.ios, isGreaterThan = greaterThan) - arrayContains != null -> ios.queryWhereFieldPath(path.ios, arrayContains = arrayContains) - notEqualTo != null -> ios.queryWhereFieldPath(path.ios, isNotEqualTo = notEqualTo) - lessThanOrEqualTo != null -> ios.queryWhereFieldPath(path.ios, isLessThanOrEqualTo = lessThanOrEqualTo) - greaterThanOrEqualTo != null -> ios.queryWhereFieldPath(path.ios, isGreaterThanOrEqualTo = greaterThanOrEqualTo) - else -> ios - } - ) - - internal actual fun _where( - field: String, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = Query( - when { - inArray != null -> ios.queryWhereField(field, `in` = inArray) - arrayContainsAny != null -> ios.queryWhereField(field, arrayContainsAny = arrayContainsAny) - notInArray != null -> ios.queryWhereField(field, notIn = notInArray) - else -> ios - } - ) - - internal actual fun _where( - path: FieldPath, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = Query( - when { - inArray != null -> ios.queryWhereFieldPath(path.ios, `in` = inArray) - arrayContainsAny != null -> ios.queryWhereFieldPath(path.ios, arrayContainsAny = arrayContainsAny) - notInArray != null -> ios.queryWhereFieldPath(path.ios, notIn = notInArray) - else -> ios - } - ) +internal actual typealias NativeQuery = FIRQuery - internal actual fun _orderBy(field: String, direction: Direction) = Query(ios.queryOrderedByField(field, direction == Direction.DESCENDING)) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(ios.queryOrderedByFieldPath(field.ios, direction == Direction.DESCENDING)) +public operator fun Query.Companion.invoke(ios: FIRQuery): Query = Query(ios) +public val Query.ios: NativeQuery get() = native - internal actual fun _startAfter(document: DocumentSnapshot) = Query(ios.queryStartingAfterDocument(document.ios)) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(ios.queryStartingAfterValues(fieldValues.asList())) - internal actual fun _startAt(document: DocumentSnapshot) = Query(ios.queryStartingAtDocument(document.ios)) - internal actual fun _startAt(vararg fieldValues: Any) = Query(ios.queryStartingAtValues(fieldValues.asList())) +internal actual typealias NativeCollectionReference = FIRCollectionReference - internal actual fun _endBefore(document: DocumentSnapshot) = Query(ios.queryEndingBeforeDocument(document.ios)) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(ios.queryEndingBeforeValues(fieldValues.asList())) - internal actual fun _endAt(document: DocumentSnapshot) = Query(ios.queryEndingAtDocument(document.ios)) - internal actual fun _endAt(vararg fieldValues: Any) = Query(ios.queryEndingAtValues(fieldValues.asList())) +public operator fun CollectionReference.Companion.invoke(ios: FIRCollectionReference): CollectionReference = CollectionReference(ios) +public val CollectionReference.ios: FIRCollectionReference get() = native -} -@Suppress("UNCHECKED_CAST") -actual class CollectionReference(override val ios: FIRCollectionReference) : Query(ios) { - - actual val path: String - get() = ios.path - - actual val async = Async(ios) - - actual val document get() = DocumentReference(ios.documentWithAutoID()) - - actual val parent get() = ios.parent?.let{DocumentReference(it)} - - actual fun document(documentPath: String) = DocumentReference(ios.documentWithPath(documentPath)) - - actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = - DocumentReference(await { ios.addDocumentWithData(encode(data, encodeSettings) as Map, it) }) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - DocumentReference(await { ios.addDocumentWithData(encode(strategy, data, encodeSettings) as Map, it) }) +public actual class FirebaseFirestoreException(message: String, public val code: FirestoreExceptionCode) : FirebaseException(message) - actual class Async(@PublishedApi internal val ios: FIRCollectionReference) { - actual inline fun add(data: T, encodeSettings: EncodeSettings) = - deferred { ios.addDocumentWithData(encode(data, encodeSettings) as Map, it) } - .convert(::DocumentReference) +public actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code - actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - deferred { ios.addDocumentWithData(encode(strategy, data, encodeSettings) as Map, it) } - .convert(::DocumentReference) - } -} - -actual class FirebaseFirestoreException(message: String, val code: FirestoreExceptionCode) : FirebaseException(message) - -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") -actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code - -actual enum class FirestoreExceptionCode { +public actual enum class FirestoreExceptionCode { OK, CANCELLED, UNKNOWN, @@ -435,22 +148,24 @@ actual enum class FirestoreExceptionCode { INTERNAL, UNAVAILABLE, DATA_LOSS, - UNAUTHENTICATED + UNAUTHENTICATED, } -actual enum class Direction { +public actual enum class Direction { ASCENDING, - DESCENDING + DESCENDING, } -actual enum class ChangeType(internal val ios: FIRDocumentChangeType) { +public val ChangeType.ios: FIRDocumentChangeType get() = ios + +public actual enum class ChangeType(internal val ios: FIRDocumentChangeType) { ADDED(FIRDocumentChangeTypeAdded), MODIFIED(FIRDocumentChangeTypeModified), - REMOVED(FIRDocumentChangeTypeRemoved) + REMOVED(FIRDocumentChangeTypeRemoved), } -fun NSError.toException() = when(domain) { - FIRFirestoreErrorDomain -> when(code) { +public fun NSError.toException(): FirebaseFirestoreException = when (domain) { + FIRFirestoreErrorDomain -> when (code) { FIRFirestoreErrorCodeOK -> FirestoreExceptionCode.OK FIRFirestoreErrorCodeCancelled -> FirestoreExceptionCode.CANCELLED FIRFirestoreErrorCodeUnknown -> FirestoreExceptionCode.UNKNOWN @@ -473,131 +188,78 @@ fun NSError.toException() = when(domain) { else -> FirestoreExceptionCode.UNKNOWN }.let { FirebaseFirestoreException(description!!, it) } -actual class QuerySnapshot(val ios: FIRQuerySnapshot) { - actual val documents - get() = ios.documents.map { DocumentSnapshot(it as FIRDocumentSnapshot) } - actual val documentChanges +public val QuerySnapshot.ios: FIRQuerySnapshot get() = ios + +public actual class QuerySnapshot(internal val ios: FIRQuerySnapshot) { + public actual val documents: List + get() = ios.documents.map { DocumentSnapshot(NativeDocumentSnapshotWrapper(it as FIRDocumentSnapshot)) } + public actual val documentChanges: List get() = ios.documentChanges.map { DocumentChange(it as FIRDocumentChange) } - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(ios.metadata) + public actual val metadata: SnapshotMetadata get() = SnapshotMetadata(ios.metadata) } -actual class DocumentChange(val ios: FIRDocumentChange) { - actual val document: DocumentSnapshot - get() = DocumentSnapshot(ios.document) - actual val newIndex: Int +public val DocumentChange.ios: FIRDocumentChange get() = ios + +public actual class DocumentChange(internal val ios: FIRDocumentChange) { + public actual val document: DocumentSnapshot + get() = DocumentSnapshot(NativeDocumentSnapshotWrapper(ios.document)) + public actual val newIndex: Int get() = ios.newIndex.toInt() - actual val oldIndex: Int + public actual val oldIndex: Int get() = ios.oldIndex.toInt() - actual val type: ChangeType - get() = ChangeType.values().first { it.ios == ios.type } + public actual val type: ChangeType + get() = ChangeType.entries.first { it.ios == ios.type } } -@Suppress("UNCHECKED_CAST") -actual class DocumentSnapshot(val ios: FIRDocumentSnapshot) { - - actual val id get() = ios.documentID - - actual val reference get() = DocumentReference(ios.reference) - - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T { - val data = ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - return decode(value = data?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } }) - } - - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T { - val data = ios.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) - return decode(strategy, data?.mapValues { (_, value) -> value?.takeIf { it !is NSNull } }, decodeSettings) - } - - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T { - val value = ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - return decode(value) - } - - actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T { - val value = ios.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } - return decode(strategy, value, decodeSettings) - } +internal actual typealias NativeDocumentSnapshot = FIRDocumentSnapshot - actual fun contains(field: String) = ios.valueForField(field) != null +public operator fun DocumentSnapshot.Companion.invoke(ios: FIRDocumentSnapshot): DocumentSnapshot = DocumentSnapshot(ios) +public val DocumentSnapshot.ios: FIRDocumentSnapshot get() = native - actual val exists get() = ios.exists +public val SnapshotMetadata.ios: FIRSnapshotMetadata get() = ios - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(ios.metadata) - - fun ServerTimestampBehavior.toIos() : FIRServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate - ServerTimestampBehavior.NONE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorNone - ServerTimestampBehavior.PREVIOUS -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorPrevious - } +public actual class SnapshotMetadata(internal val ios: FIRSnapshotMetadata) { + public actual val hasPendingWrites: Boolean get() = ios.pendingWrites + public actual val isFromCache: Boolean get() = ios.fromCache } -actual class SnapshotMetadata(val ios: FIRSnapshotMetadata) { - actual val hasPendingWrites: Boolean get() = ios.pendingWrites - actual val isFromCache: Boolean get() = ios.fromCache -} +public val FieldPath.ios: FIRFieldPath get() = ios -actual class FieldPath private constructor(val ios: FIRFieldPath) { - actual constructor(vararg fieldNames: String) : this(FIRFieldPath(fieldNames.asList())) - actual val documentId: FieldPath get() = FieldPath(FIRFieldPath.documentID()) - actual val encoded: EncodedFieldPath = ios +public actual class FieldPath private constructor(internal val ios: FIRFieldPath) { + public actual companion object { + public actual val documentId: FieldPath = FieldPath(FIRFieldPath.documentID()) + } + public actual constructor(vararg fieldNames: String) : this(FIRFieldPath(fieldNames.asList())) + public actual val documentId: FieldPath get() = FieldPath.documentId + public actual val encoded: EncodedFieldPath = ios override fun equals(other: Any?): Boolean = other is FieldPath && ios == other.ios override fun hashCode(): Int = ios.hashCode() override fun toString(): String = ios.toString() } -actual typealias EncodedFieldPath = FIRFieldPath +public actual typealias EncodedFieldPath = FIRFieldPath -private fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { - memScoped { - val errorPointer: CPointer> = alloc>().ptr - val result = block(errorPointer) - val error: NSError? = errorPointer.pointed.value - if (error != null) { - throw error.toException() - } - return result - } -} - -suspend inline fun awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T { +internal suspend inline fun awaitResult(function: (callback: (T?, NSError?) -> Unit) -> Unit): T { val job = CompletableDeferred() - val callback = { result: T?, error: NSError? -> - if(error == null) { + function { result, error -> + if (error == null) { job.complete(result) } else { job.completeExceptionally(error.toException()) } } - function(callback) return job.await() as T } -suspend inline fun await(function: (callback: (NSError?) -> Unit) -> T): T { +internal suspend inline fun await(function: (callback: (NSError?) -> Unit) -> T): T { val job = CompletableDeferred() - val callback = { error: NSError? -> - if(error == null) { + val result = function { error -> + if (error == null) { job.complete(Unit) } else { job.completeExceptionally(error.toException()) } } - val result = function(callback) job.await() return result } - -@Suppress("DeferredIsResult") -@PublishedApi -internal inline fun deferred(function: (callback: (NSError?) -> Unit) -> T): Deferred { - val job = CompletableDeferred() - val callback = { error: NSError? -> - if(error == null) { - job.complete(Unit) - } else { - job.completeExceptionally(error.toException()) - } - } - val result = function(callback) - return job.convert { result } -} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..15513316c --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,22 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + actual val path: String + get() = native.path + + actual val document get() = NativeDocumentReference(native.documentWithAutoID()) + + actual val parent get() = native.parent?.let { NativeDocumentReference(it) } + + actual fun document(documentPath: String) = + NativeDocumentReference(native.documentWithPath(documentPath)) + + actual suspend fun addEncoded(data: EncodedObject) = + NativeDocumentReference(await { native.addDocumentWithData(data.ios, it) }) +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..98856556b --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,82 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.toException +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow + +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val listener = + ios.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + snapshot?.let { trySend(snapshot) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + val ios: NativeDocumentReferenceType by ::nativeValue + + actual val id: String + get() = ios.documentID + + actual val path: String + get() = ios.path + + actual val parent: NativeCollectionReferenceWrapper + get() = NativeCollectionReferenceWrapper(ios.parent) + + actual fun collection(collectionPath: String) = ios.collectionWithPath(collectionPath) + + actual suspend fun get(source: Source) = + awaitResult { ios.getDocumentWithSource(source.toIosSource(), it) } + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = await { + when (setOptions) { + is SetOptions.Merge -> ios.setData(encodedData.ios, true, it) + is SetOptions.Overwrite -> ios.setData(encodedData.ios, false, it) + is SetOptions.MergeFields -> ios.setData(encodedData.ios, setOptions.fields, it) + is SetOptions.MergeFieldPaths -> ios.setData( + encodedData.ios, + setOptions.encodedFieldPaths, + it, + ) + } + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) = await { + ios.updateData(encodedData.ios, it) + } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) = + await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) = + await { + ios.updateData(encodedFieldsAndValues.toMap(), it) + } + + actual suspend fun delete() = await { ios.deleteDocumentWithCompletion(it) } + + actual val snapshots get() = callbackFlow { + val listener = ios.addSnapshotListener { snapshot, error -> + snapshot?.let { trySend(snapshot) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + override fun equals(other: Any?): Boolean = + this === other || other is NativeDocumentReference && nativeValue == other.nativeValue + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = nativeValue.toString() +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..36594699d --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,41 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRServerTimestampBehavior +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata +import platform.Foundation.NSNull + +internal actual class NativeDocumentSnapshotWrapper actual constructor(actual val native: NativeDocumentSnapshot) { + + actual val id get() = native.documentID + + actual val reference get() = NativeDocumentReference(native.reference) + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(field, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + + // Despite its name implying otherwise, valueForField accepts both a String representation of a Field and a FIRFieldPath + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.valueForField(fieldPath, serverTimestampBehavior.toIos())?.takeIf { it !is NSNull } + + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = + native.dataWithServerTimestampBehavior(serverTimestampBehavior.toIos()) + ?.mapValues { (_, value) -> + value?.takeIf { it !is NSNull } + } + + actual fun contains(field: String) = native.valueForField(field) != null + actual fun contains(fieldPath: EncodedFieldPath) = native.valueForField(fieldPath) != null + + actual val exists get() = native.exists + + actual val metadata: SnapshotMetadata get() = SnapshotMetadata(native.metadata) + + fun ServerTimestampBehavior.toIos(): FIRServerTimestampBehavior = when (this) { + ServerTimestampBehavior.ESTIMATE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorEstimate + ServerTimestampBehavior.NONE -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorNone + ServerTimestampBehavior.PREVIOUS -> FIRServerTimestampBehavior.FIRServerTimestampBehaviorPrevious + } +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..50e59c0ad --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,55 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFirestore +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.awaitResult +import kotlinx.coroutines.runBlocking + +@Suppress("UNCHECKED_CAST") +internal actual class NativeFirebaseFirestoreWrapper internal actual constructor(actual val native: NativeFirebaseFirestore) { + + actual fun collection(collectionPath: String) = native.collectionWithPath(collectionPath) + + actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId) + + actual fun document(documentPath: String) = + NativeDocumentReference(native.documentWithPath(documentPath)) + + actual fun batch() = native.batch() + + actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit = + FIRFirestore.enableLogging(loggingEnabled) + + actual fun applySettings(settings: FirebaseFirestoreSettings) { + native.settings = settings.ios + } + + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + awaitResult { + native.runTransactionWithBlock( + { transaction, _ -> runBlocking { transaction!!.func() } }, + it, + ) + } as T + + actual suspend fun clearPersistence() = + await { native.clearPersistenceWithCompletion(it) } + + actual fun useEmulator(host: String, port: Int) { + native.useEmulatorWithHost(host, port.toLong()) + native.settings = native.settings.apply { + this.sslEnabled = false + } + } + + actual suspend fun disableNetwork() { + await { native.disableNetworkWithCompletion(it) } + } + + actual suspend fun enableNetwork() { + await { native.enableNetworkWithCompletion(it) } + } +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..30f01b7f1 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,85 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFilter +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import dev.gitlive.firebase.firestore.awaitResult +import dev.gitlive.firebase.firestore.toException +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import platform.Foundation.NSNull + +internal actual open class NativeQueryWrapper internal actual constructor(actual open val native: NativeQuery) { + + actual fun limit(limit: Number) = native.queryLimitedTo(limit.toLong()) + + actual suspend fun get(source: Source) = + QuerySnapshot(awaitResult { native.getDocumentsWithSource(source.toIosSource(), it) }) + + actual val snapshots get() = callbackFlow { + val listener = native.addSnapshotListener { snapshot, error -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val listener = + native.addSnapshotListenerWithIncludeMetadataChanges(includeMetadataChanges) { snapshot, error -> + snapshot?.let { trySend(QuerySnapshot(snapshot)) } + error?.let { close(error.toException()) } + } + awaitClose { listener.remove() } + } + + actual fun where(filter: Filter) = native.queryWhereFilter(filter.toFIRFilter()) + + private fun Filter.toFIRFilter(): FIRFilter = when (this) { + is Filter.And -> FIRFilter.andFilterWithFilters(filters.map { it.toFIRFilter() }) + is Filter.Or -> FIRFilter.orFilterWithFilters(filters.map { it.toFIRFilter() }) + is Filter.Field -> when (constraint) { + is WhereConstraint.EqualTo -> FIRFilter.filterWhereField(field, isEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereField(field, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.LessThan -> FIRFilter.filterWhereField(field, isLessThan = constraint.safeValue) + is WhereConstraint.GreaterThan -> FIRFilter.filterWhereField(field, isGreaterThan = constraint.safeValue) + is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereField(field, isLessThanOrEqualTo = constraint.safeValue) + is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereField(field, isGreaterThanOrEqualTo = constraint.safeValue) + is WhereConstraint.ArrayContains -> FIRFilter.filterWhereField(field, arrayContains = constraint.safeValue) + is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereField(field, arrayContainsAny = constraint.safeValues) + is WhereConstraint.InArray -> FIRFilter.filterWhereField(field, `in` = constraint.safeValues) + is WhereConstraint.NotInArray -> FIRFilter.filterWhereField(field, notIn = constraint.safeValues) + } + is Filter.Path -> when (constraint) { + is WhereConstraint.EqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.NotEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isNotEqualTo = constraint.safeValue ?: NSNull.`null`()) + is WhereConstraint.LessThan -> FIRFilter.filterWhereFieldPath(path.ios, isLessThan = constraint.safeValue) + is WhereConstraint.GreaterThan -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThan = constraint.safeValue) + is WhereConstraint.LessThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isLessThanOrEqualTo = constraint.safeValue) + is WhereConstraint.GreaterThanOrEqualTo -> FIRFilter.filterWhereFieldPath(path.ios, isGreaterThanOrEqualTo = constraint.safeValue) + is WhereConstraint.ArrayContains -> FIRFilter.filterWhereFieldPath(path.ios, arrayContains = constraint.safeValue) + is WhereConstraint.ArrayContainsAny -> FIRFilter.filterWhereFieldPath(path.ios, arrayContainsAny = constraint.safeValues) + is WhereConstraint.InArray -> FIRFilter.filterWhereFieldPath(path.ios, `in` = constraint.safeValues) + is WhereConstraint.NotInArray -> FIRFilter.filterWhereFieldPath(path.ios, notIn = constraint.safeValues) + } + } + + actual fun orderBy(field: String, direction: Direction) = native.queryOrderedByField(field, direction == Direction.DESCENDING) + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = native.queryOrderedByFieldPath(field, direction == Direction.DESCENDING) + + actual fun startAfter(document: NativeDocumentSnapshot) = native.queryStartingAfterDocument(document) + actual fun startAfter(vararg fieldValues: Any) = native.queryStartingAfterValues(fieldValues.asList()) + actual fun startAt(document: NativeDocumentSnapshot) = native.queryStartingAtDocument(document) + actual fun startAt(vararg fieldValues: Any) = native.queryStartingAtValues(fieldValues.asList()) + + actual fun endBefore(document: NativeDocumentSnapshot) = native.queryEndingBeforeDocument(document) + actual fun endBefore(vararg fieldValues: Any) = native.queryEndingBeforeValues(fieldValues.asList()) + actual fun endAt(document: NativeDocumentSnapshot) = native.queryEndingAtDocument(document) + actual fun endAt(vararg fieldValues: Any) = native.queryEndingAtValues(fieldValues.asList()) +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..a9fe0014d --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,46 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRTransaction +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.ios +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +internal actual class NativeTransactionWrapper actual constructor(actual val native: FIRTransaction) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions, + ): NativeTransactionWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeTransactionWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios, + ).let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeTransactionWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios, + ).let { this } + + actual fun delete(documentRef: DocumentReference) = + native.deleteDocument(documentRef.ios).let { this } + + actual suspend fun get(documentRef: DocumentReference) = + throwError { NativeDocumentSnapshotWrapper(native.getDocument(documentRef.ios, it)!!) } +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..f1551cecf --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,46 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.await +import dev.gitlive.firebase.firestore.ios +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.ios + +internal actual class NativeWriteBatchWrapper actual constructor(actual val native: NativeWriteBatch) { + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions, + ): NativeWriteBatchWrapper = when (setOptions) { + is SetOptions.Merge -> native.setData(encodedData.ios, documentRef.ios, true) + is SetOptions.Overwrite -> native.setData(encodedData.ios, documentRef.ios, false) + is SetOptions.MergeFields -> native.setData(encodedData.ios, documentRef.ios, setOptions.fields) + is SetOptions.MergeFieldPaths -> native.setData(encodedData.ios, documentRef.ios, setOptions.encodedFieldPaths) + }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = native.updateData(encodedData.ios, documentRef.ios).let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeWriteBatchWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios, + ).let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeWriteBatchWrapper = native.updateData( + encodedFieldsAndValues.toMap(), + documentRef.ios, + ).let { this } + + actual fun delete(documentRef: DocumentReference) = + native.deleteDocument(documentRef.ios).let { this } + + actual suspend fun commit() = await { native.commitWithCompletion(it) } +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt new file mode 100644 index 000000000..aa9665ac9 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/Source.kt @@ -0,0 +1,10 @@ +package dev.gitlive.firebase.firestore.internal + +import cocoapods.FirebaseFirestoreInternal.FIRFirestoreSource +import dev.gitlive.firebase.firestore.Source + +internal fun Source.toIosSource() = when (this) { + Source.CACHE -> FIRFirestoreSource.FIRFirestoreSourceCache + Source.SERVER -> FIRFirestoreSource.FIRFirestoreSourceServer + Source.DEFAULT -> FIRFirestoreSource.FIRFirestoreSourceDefault +} diff --git a/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt new file mode 100644 index 000000000..2c8e7a388 --- /dev/null +++ b/firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/throwError.kt @@ -0,0 +1,23 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.toException +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.ObjCObjectVar +import kotlinx.cinterop.alloc +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.ptr +import kotlinx.cinterop.value +import platform.Foundation.NSError + +internal fun T.throwError(block: T.(errorPointer: CPointer>) -> R): R { + memScoped { + val errorPointer: CPointer> = alloc>().ptr + val result = block(errorPointer) + val error: NSError? = errorPointer.pointed.value + if (error != null) { + throw error.toException() + } + return result + } +} diff --git a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt index ac4e668eb..e7c6fc04f 100644 --- a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt +++ b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/ContextSwitchTest.kt @@ -1,6 +1,10 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.async @@ -22,8 +26,8 @@ import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals - private val backgroundContext = newSingleThreadContext("background") + /** * This function performs is intended to test object sharing across several threads. * @param create a block for object creation @@ -44,14 +48,13 @@ fun runTestWithContextSwitch(create: suspend CoroutineScope.() -> T, test: s while (testRun.isActive) { NSRunLoop.mainRunLoop.runMode( NSDefaultRunLoopMode, - beforeDate = NSDate.create(timeInterval = 1.0, sinceDate = NSDate()) + beforeDate = NSDate.create(timeInterval = 1.0, sinceDate = NSDate()), ) yield() } testRun.await() } - class ContextSwitchTest { lateinit var firestore: FirebaseFirestore @@ -66,8 +69,8 @@ class ContextSwitchTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) firestore = Firebase.firestore(app).apply { @@ -84,11 +87,11 @@ class ContextSwitchTest { private data class TestFieldValuesOps( val initial: List, - val updates: List + val updates: List, ) { data class Update( val op: Pair, - val expected: List? + val expected: List?, ) } @@ -103,19 +106,19 @@ class ContextSwitchTest { updates = listOf( TestFieldValuesOps.Update( FieldPath(TestData::values.name) to FieldValue.arrayUnion(2), - listOf(1, 2) + listOf(1, 2), ), TestFieldValuesOps.Update( FieldPath(TestData::values.name) to FieldValue.arrayRemove(1), - listOf(2) + listOf(2), ), TestFieldValuesOps.Update( FieldPath(TestData::values.name) to FieldValue.delete, - null - ) - ) + null, + ), + ), ) - } + }, ) { data -> fun getDocument() = firestore.collection("fieldValuesOps") @@ -142,4 +145,4 @@ class ContextSwitchTest { val deletedList = getDocument().get().get(TestData::values.name, ListSerializer(Int.serializer()).nullable) assertEquals(data.updates[2].expected, deletedList) } -} \ No newline at end of file +} diff --git a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt index 416d1a374..e484ad9f6 100644 --- a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt +++ b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -2,5 +2,6 @@ package dev.gitlive.firebase.firestore @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreJs + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -actual annotation class IgnoreForAndroidUnitTest \ No newline at end of file +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt b/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt deleted file mode 100644 index 04218876e..000000000 --- a/firebase-firestore/src/iosTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.gitlive.firebase.firestore - -import platform.darwin.DISPATCH_QUEUE_CONCURRENT -import platform.darwin.dispatch_queue_create - -private val firestoreTestQueue = dispatch_queue_create("FirestoreQueue", DISPATCH_QUEUE_CONCURRENT) - -actual fun createFirestoreTestSettings( - sslEnabled: Boolean?, - host: String?, - cacheSettings: LocalCacheSettings? -) = FirebaseFirestore.Settings( - sslEnabled, host, cacheSettings, firestoreTestQueue -) diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt index 39aba6c99..5407e19d6 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt @@ -8,25 +8,26 @@ import dev.gitlive.firebase.firestore.externals.arrayUnion as jsArrayUnion import dev.gitlive.firebase.firestore.externals.increment as jsIncrement /** Represents a platform specific Firebase FieldValue. */ -typealias NativeFieldValue = dev.gitlive.firebase.firestore.externals.FieldValue +public typealias NativeFieldValue = dev.gitlive.firebase.firestore.externals.FieldValue /** Represents a Firebase FieldValue. */ @Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { +public actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { init { require(nativeValue is NativeFieldValue) } override fun equals(other: Any?): Boolean = - this === other || other is FieldValue && - (nativeValue as NativeFieldValue).isEqual(other.nativeValue as NativeFieldValue) + this === other || + other is FieldValue && + (nativeValue as NativeFieldValue).isEqual(other.nativeValue as NativeFieldValue) override fun hashCode(): Int = nativeValue.hashCode() override fun toString(): String = nativeValue.toString() - actual companion object { - actual val serverTimestamp: FieldValue get() = rethrow { FieldValue(jsServerTimestamp()) } - 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)) } + public actual companion object { + public actual val serverTimestamp: FieldValue get() = rethrow { FieldValue(jsServerTimestamp()) } + public actual val delete: FieldValue get() = rethrow { FieldValue(deleteField()) } + public actual fun increment(value: Int): FieldValue = rethrow { FieldValue(jsIncrement(value)) } + public actual fun arrayUnion(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayUnion(*elements)) } + public actual fun arrayRemove(vararg elements: Any): FieldValue = rethrow { FieldValue(jsArrayRemove(*elements)) } } } 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 2271e446e..0818e403c 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 @@ -3,14 +3,14 @@ package dev.gitlive.firebase.firestore import kotlinx.serialization.Serializable /** A class representing a platform specific Firebase GeoPoint. */ -actual typealias NativeGeoPoint = dev.gitlive.firebase.firestore.externals.GeoPoint +public actual typealias NativeGeoPoint = dev.gitlive.firebase.firestore.externals.GeoPoint /** A class representing a Firebase GeoPoint. */ @Serializable(with = GeoPointSerializer::class) -actual class GeoPoint internal actual constructor(internal actual val nativeValue: NativeGeoPoint) { - actual constructor(latitude: Double, longitude: Double) : this(NativeGeoPoint(latitude, longitude)) - actual val latitude: Double by nativeValue::latitude - actual val longitude: Double by nativeValue::longitude +public actual class GeoPoint internal actual constructor(internal actual val nativeValue: NativeGeoPoint) { + public actual constructor(latitude: Double, longitude: Double) : this(NativeGeoPoint(latitude, longitude)) + public actual val latitude: Double by nativeValue::latitude + public actual val longitude: Double by nativeValue::longitude override fun equals(other: Any?): Boolean = this === other || other is GeoPoint && nativeValue.isEqual(other.nativeValue) 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 dabec0055..bc18327ba 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 @@ -4,31 +4,31 @@ import kotlinx.serialization.Serializable /** A base class that could be used to combine [Timestamp] and [Timestamp.ServerTimestamp] in the same field. */ @Serializable(with = BaseTimestampSerializer::class) -actual sealed class BaseTimestamp +public actual sealed class BaseTimestamp /** A class representing a platform specific Firebase Timestamp. */ -actual typealias NativeTimestamp = dev.gitlive.firebase.firestore.externals.Timestamp +public actual typealias NativeTimestamp = dev.gitlive.firebase.firestore.externals.Timestamp /** A class representing a Firebase Timestamp. */ @Serializable(with = TimestampSerializer::class) -actual class Timestamp internal actual constructor( - internal actual val nativeValue: NativeTimestamp -): BaseTimestamp() { - actual constructor(seconds: Long, nanoseconds: Int) : this(NativeTimestamp(seconds.toDouble(), nanoseconds.toDouble())) +public actual class Timestamp internal actual constructor( + internal actual val nativeValue: NativeTimestamp, +) : BaseTimestamp() { + public actual constructor(seconds: Long, nanoseconds: Int) : this(NativeTimestamp(seconds.toDouble(), nanoseconds.toDouble())) - actual val seconds: Long = nativeValue.seconds.toLong() - actual val nanoseconds: Int = nativeValue.nanoseconds.toInt() + public actual val seconds: Long = nativeValue.seconds.toLong() + public actual val nanoseconds: Int = nativeValue.nanoseconds.toInt() override fun equals(other: Any?): Boolean = this === other || other is Timestamp && nativeValue.isEqual(other.nativeValue) override fun hashCode(): Int = nativeValue.toMillis().hashCode() override fun toString(): String = nativeValue.toString() - actual companion object { - actual fun now(): Timestamp = Timestamp(NativeTimestamp.now()) + public actual companion object { + public actual fun now(): Timestamp = Timestamp(NativeTimestamp.now()) } /** A server time timestamp. */ @Serializable(with = ServerTimestampSerializer::class) - actual object ServerTimestamp: BaseTimestamp() + public actual object ServerTimestamp : BaseTimestamp() } diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt index 485920fe2..a1e313dd6 100644 --- a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt @@ -1,10 +1,11 @@ package dev.gitlive.firebase.firestore @PublishedApi -internal actual fun isSpecialValue(value: Any) = when(value) { +internal actual fun isSpecialValue(value: Any): Boolean = when (value) { is NativeFieldValue, is NativeGeoPoint, is NativeTimestamp, - is NativeDocumentReference -> true + is NativeDocumentReferenceType, + -> true else -> false -} \ No newline at end of file +} 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 index 6fa365353..31c110099 100644 --- 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 @@ -8,278 +8,326 @@ 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 +public external fun documentId(): FieldPath +public external class FieldPath(vararg fieldNames: String) { + public fun isEqual(other: FieldPath): Boolean } -external fun refEqual(left: DocumentReference, right: DocumentReference): Boolean +public external fun refEqual(left: DocumentReference, right: DocumentReference): Boolean -external fun addDoc(reference: CollectionReference, data: Any): Promise +public external fun addDoc(reference: CollectionReference, data: Any): Promise -external fun arrayRemove(vararg elements: Any): FieldValue +public external fun arrayRemove(vararg elements: Any): FieldValue -external fun arrayUnion(vararg elements: Any): FieldValue +public external fun arrayUnion(vararg elements: Any): FieldValue -external fun clearIndexedDbPersistence(firestore: Firestore): Promise +public external fun clearIndexedDbPersistence(firestore: Firestore): Promise -external fun collection(firestore: Firestore, collectionPath: String): CollectionReference +public external fun collection(firestore: Firestore, collectionPath: String): CollectionReference -external fun collection(reference: DocumentReference, collectionPath: String): CollectionReference +public external fun collection(reference: DocumentReference, collectionPath: String): CollectionReference -external fun collectionGroup(firestore: Firestore, collectionId: String): Query +public external fun collectionGroup(firestore: Firestore, collectionId: String): Query -external fun connectFirestoreEmulator( +public external fun connectFirestoreEmulator( firestore: Firestore, host: String, port: Int, - options: Any? = definedExternally + options: Any? = definedExternally, ) -external fun deleteDoc(reference: DocumentReference): Promise +public external fun deleteDoc(reference: DocumentReference): Promise -external fun deleteField(): FieldValue +public external fun deleteField(): FieldValue -external fun disableNetwork(firestore: Firestore): Promise +public external fun disableNetwork(firestore: Firestore): Promise -external fun doc(firestore: Firestore, documentPath: String): DocumentReference +public external fun doc(firestore: Firestore, documentPath: String): DocumentReference -external fun doc(firestore: CollectionReference, documentPath: String? = definedExternally): DocumentReference +public external fun doc(firestore: CollectionReference, documentPath: String? = definedExternally): DocumentReference -external fun enableIndexedDbPersistence( +public external fun enableIndexedDbPersistence( firestore: Firestore, - persistenceSettings: Any? = definedExternally + persistenceSettings: Any? = definedExternally, ): Promise -external fun enableNetwork(firestore: Firestore): Promise +public external fun enableNetwork(firestore: Firestore): Promise + +public external fun endAt(document: DocumentSnapshot): QueryConstraint + +public external fun endAt(vararg fieldValues: Any): QueryConstraint -external fun endAt(document: DocumentSnapshot): QueryConstraint +public external fun endBefore(document: DocumentSnapshot): QueryConstraint -external fun endAt(vararg fieldValues: Any): QueryConstraint +public external fun endBefore(vararg fieldValues: Any): QueryConstraint -external fun endBefore(document: DocumentSnapshot): QueryConstraint +public external fun getDoc( + reference: DocumentReference, + options: Any? = definedExternally, +): Promise -external fun endBefore(vararg fieldValues: Any): QueryConstraint +public external fun getDocFromCache( + reference: DocumentReference, +): Promise -external fun getDoc( +public external fun getDocFromServer( reference: DocumentReference, - options: Any? = definedExternally ): Promise -external fun getDocs(query: Query): Promise +public external fun getDocs(query: Query): Promise + +public external fun getDocsFromCache(query: Query): Promise + +public external fun getDocsFromServer(query: Query): Promise -external fun getFirestore(app: FirebaseApp? = definedExternally): Firestore +public external fun getFirestore(app: FirebaseApp? = definedExternally): Firestore -external fun increment(n: Int): FieldValue +public external fun increment(n: Int): FieldValue -external fun initializeFirestore(app: FirebaseApp, settings: Any): Firestore +public external fun initializeFirestore(app: FirebaseApp, settings: dynamic = definedExternally, databaseId: String? = definedExternally): Firestore -external fun limit(limit: Number): QueryConstraint +public external fun limit(limit: Number): QueryConstraint -external fun onSnapshot( +public external fun onSnapshot( reference: DocumentReference, next: (snapshot: DocumentSnapshot) -> Unit, - error: (error: Throwable) -> Unit + error: (error: Throwable) -> Unit, ): Unsubscribe -external fun onSnapshot( +public external fun onSnapshot( reference: DocumentReference, options: Json, next: (snapshot: DocumentSnapshot) -> Unit, - error: (error: Throwable) -> Unit + error: (error: Throwable) -> Unit, ): Unsubscribe -external fun onSnapshot( +public external fun onSnapshot( reference: Query, next: (snapshot: QuerySnapshot) -> Unit, - error: (error: Throwable) -> Unit + error: (error: Throwable) -> Unit, ): Unsubscribe -external fun onSnapshot( +public external fun onSnapshot( reference: Query, options: Json, next: (snapshot: QuerySnapshot) -> Unit, - error: (error: Throwable) -> Unit + error: (error: Throwable) -> Unit, ): Unsubscribe -external fun orderBy(field: String, direction: Any): QueryConstraint +public external fun orderBy(field: String, direction: Any): QueryConstraint -external fun orderBy(field: FieldPath, direction: Any): QueryConstraint +public external fun orderBy(field: FieldPath, direction: Any): QueryConstraint -external fun query(query: Query, vararg queryConstraints: QueryConstraint): Query +public external fun query(query: Query, vararg queryConstraints: QueryConstraint): Query -external fun runTransaction( +public external fun runTransaction( firestore: Firestore, updateFunction: (transaction: Transaction) -> Promise, - options: Any? = definedExternally + options: Any? = definedExternally, ): Promise -external fun serverTimestamp(): FieldValue +public external fun serverTimestamp(): FieldValue -external fun setDoc( +public external fun setDoc( documentReference: DocumentReference, data: Any, - options: Any? = definedExternally + options: Any? = definedExternally, ): Promise -external fun setLogLevel(logLevel: String) +public external fun setLogLevel(logLevel: String) -external fun startAfter(document: DocumentSnapshot): QueryConstraint +public external fun startAfter(document: DocumentSnapshot): QueryConstraint -external fun startAfter(vararg fieldValues: Any): QueryConstraint +public external fun startAfter(vararg fieldValues: Any): QueryConstraint -external fun startAt(document: DocumentSnapshot): QueryConstraint +public external fun startAt(document: DocumentSnapshot): QueryConstraint -external fun startAt(vararg fieldValues: Any): QueryConstraint +public external fun startAt(vararg fieldValues: Any): QueryConstraint -external fun updateDoc(reference: DocumentReference, data: Any): Promise +public external fun updateDoc(reference: DocumentReference, data: Any): Promise -external fun updateDoc( +public external fun updateDoc( reference: DocumentReference, field: String, value: Any?, - vararg moreFieldsAndValues: Any? + vararg moreFieldsAndValues: Any?, ): Promise -external fun updateDoc( +public external fun updateDoc( reference: DocumentReference, field: FieldPath, value: Any?, - vararg moreFieldsAndValues: Any? + vararg moreFieldsAndValues: Any?, ): Promise -external fun where(field: String, opStr: String, value: Any?): QueryConstraint +public external fun where(field: String, opStr: String, value: Any?): QueryConstraint -external fun where(field: FieldPath, opStr: String, value: Any?): QueryConstraint +public external fun where(field: FieldPath, opStr: String, value: Any?): QueryConstraint -external fun writeBatch(firestore: Firestore): WriteBatch +public external fun and(vararg queryConstraints: QueryConstraint): QueryConstraint -external interface Firestore { - val app: FirebaseApp +public external fun or(vararg queryConstraints: QueryConstraint): QueryConstraint + +public external fun writeBatch(firestore: Firestore): WriteBatch + +public external interface Firestore { + public val app: FirebaseApp } -external class GeoPoint constructor(latitude: Double, longitude: Double) { - val latitude: Double - val longitude: Double - fun isEqual(other: GeoPoint): Boolean +public external class GeoPoint(latitude: Double, longitude: Double) { + public val latitude: Double + public val longitude: Double + public fun isEqual(other: GeoPoint): Boolean } -external interface CollectionReference : Query { - val id: String - val path: String - val parent: DocumentReference? +public external interface CollectionReference : Query { + public val id: String + public val path: String + public val parent: DocumentReference? } -external interface DocumentChange { - val doc: DocumentSnapshot - val newIndex: Int - val oldIndex: Int - val type: String +public external interface DocumentChange { + public val doc: DocumentSnapshot + public val newIndex: Int + public val oldIndex: Int + public val type: String } -external class DocumentReference { - val id: String - val path: String - val parent: CollectionReference +public external class DocumentReference { + public val id: String + public val path: String + public 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? +public external interface DocumentSnapshot { + public val id: String + public val ref: DocumentReference + public val metadata: SnapshotMetadata + public fun data(options: Any? = definedExternally): Any? + public fun exists(): Boolean + public fun get(fieldPath: String, options: Any? = definedExternally): Any? + public fun get(fieldPath: FieldPath, options: Any? = definedExternally): Any? } -external class FieldValue { - fun isEqual(other: FieldValue): Boolean +public external class FieldValue { + public fun isEqual(other: FieldValue): Boolean } -external interface Query +public external interface Query -external interface QueryConstraint +public external interface QueryConstraint -external interface QuerySnapshot { - val docs: Array - val empty: Boolean - val metadata: SnapshotMetadata - fun docChanges(): Array +public external interface QuerySnapshot { + public val docs: Array + public val empty: Boolean + public val metadata: SnapshotMetadata + public fun docChanges(): Array } -external interface SnapshotMetadata { - val hasPendingWrites: Boolean - val fromCache: Boolean +public external interface SnapshotMetadata { + public val hasPendingWrites: Boolean + public val fromCache: Boolean } -external interface Transaction { - fun get(documentReference: DocumentReference): Promise +public external interface Transaction { + public fun get(documentReference: DocumentReference): Promise - fun set( + public fun set( documentReference: DocumentReference, data: Any, - options: Any? = definedExternally + options: Any? = definedExternally, ): Transaction - fun update(documentReference: DocumentReference, data: Any): Transaction + public fun update(documentReference: DocumentReference, data: Any): Transaction - fun update( + public fun update( documentReference: DocumentReference, field: String, value: Any?, - vararg moreFieldsAndValues: Any? + vararg moreFieldsAndValues: Any?, ): Transaction - fun update( + public fun update( documentReference: DocumentReference, field: FieldPath, value: Any?, - vararg moreFieldsAndValues: Any? + vararg moreFieldsAndValues: Any?, ): Transaction - fun delete(documentReference: DocumentReference): Transaction + public fun delete(documentReference: DocumentReference): Transaction } -external interface WriteBatch { - fun commit(): Promise +public external interface WriteBatch { + public fun commit(): Promise - fun delete(documentReference: DocumentReference): WriteBatch + public fun delete(documentReference: DocumentReference): WriteBatch - fun set( + public fun set( documentReference: DocumentReference, data: Any, - options: Any? = definedExternally + options: Any? = definedExternally, ): WriteBatch - fun update(documentReference: DocumentReference, data: Any): WriteBatch + public fun update(documentReference: DocumentReference, data: Any): WriteBatch - fun update( + public fun update( documentReference: DocumentReference, field: String, value: Any?, - vararg moreFieldsAndValues: Any? + vararg moreFieldsAndValues: Any?, ): WriteBatch - fun update( + public fun update( documentReference: DocumentReference, field: FieldPath, value: Any?, - vararg moreFieldsAndValues: Any? + vararg moreFieldsAndValues: Any?, ): WriteBatch } -external class Timestamp(seconds: Double, nanoseconds: Double) { - companion object { - fun now(): Timestamp +public external class Timestamp(seconds: Double, nanoseconds: Double) { + public companion object { + public fun now(): Timestamp } - val seconds: Double - val nanoseconds: Double - fun toMillis(): Double + public val seconds: Double + public val nanoseconds: Double + public fun toMillis(): Double + + public fun isEqual(other: Timestamp): Boolean +} + +public external interface FirestoreLocalCache { + public val kind: String +} + +public external interface MemoryLocalCache : FirestoreLocalCache +public external interface PersistentLocalCache : FirestoreLocalCache + +public external interface MemoryCacheSettings { + public val garbageCollector: MemoryGarbageCollector +} - fun isEqual(other: Timestamp): Boolean +public external interface MemoryGarbageCollector { + public val kind: String } + +public external interface MemoryLruGarbageCollector : MemoryGarbageCollector +public external interface MemoryEagerGarbageCollector : MemoryGarbageCollector + +public external interface PersistentCacheSettings { + public val cacheSizeBytes: Int + public val tabManager: PersistentTabManager +} + +public external interface PersistentTabManager { + public val kind: String +} + +public external fun memoryLocalCache(settings: MemoryCacheSettings): MemoryLocalCache +public external fun memoryEagerGarbageCollector(): MemoryEagerGarbageCollector +public external fun memoryLruGarbageCollector(settings: dynamic = definedExternally): MemoryLruGarbageCollector +public external fun persistentLocalCache(settings: PersistentCacheSettings): PersistentLocalCache +public external fun persistentSingleTabManager(settings: dynamic = definedExternally): PersistentTabManager +public external fun persistentMultipleTabManager(): PersistentTabManager 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 2f9137c1d..752e53a3c 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,34 +4,22 @@ package dev.gitlive.firebase.firestore -import dev.gitlive.firebase.* -import dev.gitlive.firebase.firestore.externals.Firestore -import dev.gitlive.firebase.firestore.externals.addDoc -import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence -import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator -import dev.gitlive.firebase.firestore.externals.deleteDoc -import dev.gitlive.firebase.firestore.externals.doc -import dev.gitlive.firebase.firestore.externals.enableIndexedDbPersistence -import dev.gitlive.firebase.firestore.externals.getDoc -import dev.gitlive.firebase.firestore.externals.getDocs -import dev.gitlive.firebase.firestore.externals.getFirestore -import dev.gitlive.firebase.firestore.externals.initializeFirestore -import dev.gitlive.firebase.firestore.externals.onSnapshot -import dev.gitlive.firebase.firestore.externals.orderBy -import dev.gitlive.firebase.firestore.externals.query -import dev.gitlive.firebase.firestore.externals.refEqual -import dev.gitlive.firebase.firestore.externals.setDoc -import dev.gitlive.firebase.firestore.externals.setLogLevel -import dev.gitlive.firebase.firestore.externals.writeBatch -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.externals.getApp +import dev.gitlive.firebase.firestore.externals.MemoryCacheSettings +import dev.gitlive.firebase.firestore.externals.PersistentCacheSettings +import dev.gitlive.firebase.firestore.externals.memoryEagerGarbageCollector +import dev.gitlive.firebase.firestore.externals.memoryLocalCache +import dev.gitlive.firebase.firestore.externals.memoryLruGarbageCollector +import dev.gitlive.firebase.firestore.externals.persistentLocalCache +import dev.gitlive.firebase.firestore.internal.NativeDocumentSnapshotWrapper +import dev.gitlive.firebase.firestore.internal.NativeFirebaseFirestoreWrapper +import dev.gitlive.firebase.js import kotlin.js.Json import kotlin.js.json +import dev.gitlive.firebase.firestore.externals.Firestore as JsFirestore 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 @@ -42,510 +30,176 @@ 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.collection as jsCollection -import dev.gitlive.firebase.firestore.externals.collectionGroup as jsCollectionGroup -import dev.gitlive.firebase.firestore.externals.disableNetwork as jsDisableNetwork -import dev.gitlive.firebase.firestore.externals.enableNetwork as jsEnableNetwork -import dev.gitlive.firebase.firestore.externals.endAt as jsEndAt -import dev.gitlive.firebase.firestore.externals.endBefore as jsEndBefore -import dev.gitlive.firebase.firestore.externals.limit as jsLimit -import dev.gitlive.firebase.firestore.externals.runTransaction as jsRunTransaction -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 { FirebaseFirestore(getFirestore()) } - -actual fun Firebase.firestore(app: FirebaseApp) = - rethrow { FirebaseFirestore(getFirestore(app.js)) } - -actual data class FirebaseFirestore(val jsFirestore: Firestore) { - - actual data class Settings( - actual val sslEnabled: Boolean? = null, - actual val host: String? = null, - actual val cacheSettings: LocalCacheSettings? = null - ) { - actual companion object { - actual fun create(sslEnabled: Boolean?, host: String?, cacheSettings: LocalCacheSettings?) = Settings(sslEnabled, host, cacheSettings) - } - } - - private var lastSettings = Settings() - - var js: Firestore = jsFirestore - private set - - actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } - - actual fun collection(collectionPath: String) = rethrow { CollectionReference(jsCollection(js, collectionPath)) } - - actual fun collectionGroup(collectionId: String) = rethrow { Query(jsCollectionGroup(js, collectionId)) } +import dev.gitlive.firebase.firestore.externals.documentId as jsDocumentId - actual fun batch() = rethrow { WriteBatch(writeBatch(js)) } +public actual val Firebase.firestore: FirebaseFirestore get() = + rethrow { FirebaseFirestore(NativeFirebaseFirestoreWrapper(getApp())) } - actual fun setLoggingEnabled(loggingEnabled: Boolean) = - rethrow { setLogLevel( if(loggingEnabled) "error" else "silent") } +public actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore = + rethrow { FirebaseFirestore(NativeFirebaseFirestoreWrapper(app.js)) } - actual suspend fun runTransaction(func: suspend Transaction.() -> T) = - rethrow { jsRunTransaction(js, { GlobalScope.promise { Transaction(it).func() } } ).await() } +internal actual data class NativeFirebaseFirestore(val js: JsFirestore) - actual suspend fun clearPersistence() = - rethrow { clearIndexedDbPersistence(js).await() } +public operator fun FirebaseFirestore.Companion.invoke(js: JsFirestore): FirebaseFirestore = FirebaseFirestore( + NativeFirebaseFirestore(js), +) +public val FirebaseFirestore.js: JsFirestore get() = native.js - actual fun useEmulator(host: String, port: Int) = rethrow { connectFirestoreEmulator(js, host, port) } +public actual data class FirebaseFirestoreSettings( + actual val sslEnabled: Boolean, + actual val host: String, + actual val cacheSettings: LocalCacheSettings, +) { - actual fun setSettings(settings: Settings) { - lastSettings = settings - if(settings.cacheSettings is LocalCacheSettings.Persistent) enableIndexedDbPersistence(js) + public actual companion object { + public actual val CACHE_SIZE_UNLIMITED: Long = -1L + internal actual val DEFAULT_HOST: String = "firestore.googleapis.com" + internal actual val MINIMUM_CACHE_BYTES: Long = 1 * 1024 * 1024 - val jsSettings = json().apply { - settings.sslEnabled?.let { set("ssl", it) } - settings.host?.let { set("host", it) } - when (val cacheSettings = settings.cacheSettings) { - is LocalCacheSettings.Persistent -> cacheSettings.sizeBytes - is LocalCacheSettings.Memory -> when (val garbageCollectorSettings = cacheSettings.garbaseCollectorSettings) { - is LocalCacheSettings.Memory.GarbageCollectorSettings.Eager -> null - is LocalCacheSettings.Memory.GarbageCollectorSettings.LRUGC -> garbageCollectorSettings.sizeBytes - } - null -> null - }?.let { set("cacheSizeBytes", it) } - } - js = initializeFirestore(js.app, jsSettings) + // According to documentation, default JS Firestore cache size is 40MB, not 100MB + internal actual val DEFAULT_CACHE_SIZE_BYTES: Long = 40 * 1024 * 1024 } - actual fun updateSettings(settings: Settings) = setSettings( - Settings(settings.sslEnabled ?: lastSettings.sslEnabled, settings.host ?: lastSettings.host, settings.cacheSettings ?: lastSettings.cacheSettings) - ) + public actual class Builder internal constructor( + public actual var sslEnabled: Boolean, + public actual var host: String, + public actual var cacheSettings: LocalCacheSettings, + ) { - actual suspend fun disableNetwork() { - rethrow { jsDisableNetwork(js).await() } - } + public actual constructor() : this( + true, + DEFAULT_HOST, + persistentCacheSettings { }, + ) + public actual constructor(settings: FirebaseFirestoreSettings) : this(settings.sslEnabled, settings.host, settings.cacheSettings) - actual suspend fun enableNetwork() { - rethrow { jsEnableNetwork(js).await() } + public actual fun build(): FirebaseFirestoreSettings = FirebaseFirestoreSettings(sslEnabled, host, cacheSettings) } -} - -val SetOptions.js: Json get() = when (this) { - is SetOptions.Merge -> json("merge" to true) - is SetOptions.Overwrite -> json("merge" to false) - is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) - is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) -} - -actual class WriteBatch(val js: JsWriteBatch) : BaseWriteBatch() { - - actual val async = Async(js) - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseWriteBatch = rethrow { js.set(documentRef.js, encodedData, setOptions.js) }.let { this } @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List>, - merge: Boolean - ): BaseWriteBatch = rethrow { - val serializedItem = encodedData as Json - val serializedFieldAndValues = json(*encodedFieldsAndValues.toTypedArray()) - - val result = serializedItem.add(serializedFieldAndValues) - if (merge) { - js.set(documentRef.js, result, json("merge" to merge)) - } else { - js.set(documentRef.js, result) - } - }.let { this } - - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = rethrow { js.update(documentRef.js, encodedData) } - .let { this } - - @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") - override fun updateEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List> - ): BaseWriteBatch = rethrow { - val serializedItem = encodedData as Json - val serializedFieldAndValues = json(*encodedFieldsAndValues.toTypedArray()) - - val result = serializedItem.add(serializedFieldAndValues) - js.update(documentRef.js, result) - }.let { this } - - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } - .let { this } - - actual suspend fun commit() = rethrow { async.commit().await() } - - @Suppress("DeferredIsResult") - actual class Async(private val js: JsWriteBatch) { - actual fun commit() = rethrow { js.commit().asDeferred() } - } -} - -actual class Transaction(val js: JsTransaction) : BaseTransaction() { - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseTransaction = rethrow { - js.set(documentRef.js, encodedData, setOptions.js) - } - .let { this } - - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = rethrow { js.update(documentRef.js, encodedData) } - .let { this } - - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseTransaction = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseTransaction = rethrow { - encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - js.update(documentRef.js, field, value, *moreFieldsAndValues) - } - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - rethrow { js.delete(documentRef.js) } - .let { this } - - actual suspend fun get(documentRef: DocumentReference) = - rethrow { DocumentSnapshot(js.get(documentRef.js).await()) } -} - -/** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = JsDocumentReference - -@Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - val js: NativeDocumentReference = nativeValue - - actual val id: String - get() = rethrow { js.id } - - actual val path: String - get() = rethrow { js.path } - - actual val parent: CollectionReference - get() = rethrow { CollectionReference(js.parent) } - - override val async = Async(nativeValue) - - actual fun collection(collectionPath: String) = rethrow { CollectionReference(jsCollection(js, collectionPath)) } - - actual suspend fun get() = rethrow { DocumentSnapshot( getDoc(js).await()) } - - actual val snapshots: Flow get() = snapshots() - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val unsubscribe = onSnapshot( - js, - json("includeMetadataChanges" to includeMetadataChanges), - { trySend(DocumentSnapshot(it)) }, - { close(errorToException(it)) } - ) - awaitClose { unsubscribe() } - } - - override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && refEqual(nativeValue, other.nativeValue) - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = "DocumentReference(path=$path)" - - @Suppress("DeferredIsResult") - class Async(@PublishedApi internal val js: NativeDocumentReference) : BaseDocumentReference.Async() { - - override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = rethrow { - setDoc(js, encodedData, setOptions.js).asDeferred() - } - - override fun updateEncoded(encodedData: Any): Deferred = rethrow { jsUpdate(js, encodedData).asDeferred() } - - override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = rethrow { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) - } - ?.asDeferred() ?: CompletableDeferred(Unit) - } - - override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = rethrow { - encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - jsUpdate(js, field, value, *moreFieldsAndValues) + internal val js: Json get() = json().apply { + set("ssl", sslEnabled) + set("host", host) + set( + "localCache", + when (cacheSettings) { + is LocalCacheSettings.Persistent -> persistentLocalCache( + json( + "cacheSizeBytes" to cacheSettings.sizeBytes, + ).asDynamic() as PersistentCacheSettings, + ) + is LocalCacheSettings.Memory -> { + val garbageCollectorSettings = when (val garbageCollectorSettings = cacheSettings.garbaseCollectorSettings) { + is MemoryGarbageCollectorSettings.Eager -> memoryEagerGarbageCollector() + is MemoryGarbageCollectorSettings.LRUGC -> memoryLruGarbageCollector(json("cacheSizeBytes" to garbageCollectorSettings.sizeBytes)) + } + memoryLocalCache(json("garbageCollector" to garbageCollectorSettings).asDynamic() as MemoryCacheSettings) } - ?.asDeferred() ?: CompletableDeferred(Unit) - } - - override fun delete() = rethrow { deleteDoc(js).asDeferred() } - } -} - -actual open class Query(open val js: JsQuery) { - - actual suspend fun get() = rethrow { QuerySnapshot(getDocs(js).await()) } - - actual fun limit(limit: Number) = Query(query(js, jsLimit(limit))) - - 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(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?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = rethrow { - Query( - when { - lessThan != null -> query(js, jsWhere(field, "<", lessThan)) - greaterThan != null -> query(js, jsWhere(field, ">", greaterThan)) - arrayContains != null -> query(js, jsWhere(field, "array-contains", arrayContains)) - notEqualTo != null -> query(js, jsWhere(field, "!=", notEqualTo)) - lessThanOrEqualTo != null -> query(js, jsWhere(field, "<=", lessThanOrEqualTo)) - greaterThanOrEqualTo != null -> query(js, jsWhere(field, ">=", greaterThanOrEqualTo)) - else -> js - } - ) - } - - internal actual fun _where( - path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = rethrow { - Query( - when { - lessThan != null -> query(js, jsWhere(path.js, "<", lessThan)) - greaterThan != null -> query(js, jsWhere(path.js, ">", greaterThan)) - arrayContains != null -> query(js, jsWhere(path.js, "array-contains", arrayContains)) - notEqualTo != null -> query(js, jsWhere(path.js, "!=", notEqualTo)) - lessThanOrEqualTo != null -> query(js, jsWhere(path.js, "<=", lessThanOrEqualTo)) - greaterThanOrEqualTo != null -> query(js, jsWhere(path.js, ">=", greaterThanOrEqualTo)) - else -> js - } - ) - } - - internal actual fun _where( - field: String, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = rethrow { - Query( - when { - inArray != null -> query(js, jsWhere(field, "in", inArray.toTypedArray())) - arrayContainsAny != null -> query(js, jsWhere(field, "array-contains-any", arrayContainsAny.toTypedArray())) - notInArray != null -> query(js, jsWhere(field, "not-in", notInArray.toTypedArray())) - else -> js - } + }, ) } +} - internal actual fun _where( - path: FieldPath, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = rethrow { - Query( - when { - inArray != null -> query(js, jsWhere(path.js, "in", inArray.toTypedArray())) - arrayContainsAny != null -> query(js, jsWhere(path.js, "array-contains-any", arrayContainsAny.toTypedArray())) - notInArray != null -> query(js, jsWhere(path.js, "not-in", notInArray.toTypedArray())) - else -> js - } - ) - } - - internal actual fun _orderBy(field: String, direction: Direction) = rethrow { - Query(query(js, orderBy(field, direction.jsString))) - } - - internal actual fun _orderBy(field: FieldPath, direction: Direction) = rethrow { - Query(query(js, orderBy(field.js, direction.jsString))) +public actual fun firestoreSettings( + settings: FirebaseFirestoreSettings?, + builder: FirebaseFirestoreSettings.Builder.() -> Unit, +): FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().apply { + settings?.let { + sslEnabled = it.sslEnabled + host = it.host + cacheSettings = it.cacheSettings } +}.apply(builder).build() - internal actual fun _startAfter(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAfter(document.js))) } - - internal actual fun _startAfter(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAfter(*fieldValues))) } +internal actual data class NativeWriteBatch(val js: JsWriteBatch) - internal actual fun _startAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsStartAt(document.js))) } +public operator fun WriteBatch.Companion.invoke(js: JsWriteBatch): WriteBatch = WriteBatch(NativeWriteBatch(js)) +public val WriteBatch.js: JsWriteBatch get() = native.js - internal actual fun _startAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsStartAt(*fieldValues))) } +internal actual data class NativeTransaction(val js: JsTransaction) - internal actual fun _endBefore(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndBefore(document.js))) } +public operator fun Transaction.Companion.invoke(js: JsTransaction): Transaction = Transaction(NativeTransaction(js)) +public val Transaction.js: JsTransaction get() = native.js - internal actual fun _endBefore(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndBefore(*fieldValues))) } - - internal actual fun _endAt(document: DocumentSnapshot) = rethrow { Query(query(js, jsEndAt(document.js))) } - - internal actual fun _endAt(vararg fieldValues: Any) = rethrow { Query(query(js, jsEndAt(*fieldValues))) } - - actual val snapshots get() = callbackFlow { - val unsubscribe = rethrow { - onSnapshot( - js, - { trySend(QuerySnapshot(it)) }, - { close(errorToException(it)) } - ) - } - awaitClose { rethrow { unsubscribe() } } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val unsubscribe = rethrow { - onSnapshot( - js, - json("includeMetadataChanges" to includeMetadataChanges), - { trySend(QuerySnapshot(it)) }, - { close(errorToException(it)) } - ) - } - awaitClose { rethrow { unsubscribe() } } - } -} - -actual class CollectionReference(override val js: JsCollectionReference) : Query(js) { +/** A class representing a platform specific Firebase DocumentReference. */ +internal actual typealias NativeDocumentReferenceType = JsDocumentReference - actual val path: String - get() = rethrow { js.path } - actual val async = Async(js) +public operator fun DocumentReference.Companion.invoke(js: JsDocumentReference): DocumentReference = DocumentReference(js) +public val DocumentReference.js: NativeDocumentReferenceType get() = native.js - actual val document get() = rethrow { DocumentReference(doc(js)) } +internal actual open class NativeQuery(open val js: JsQuery) +internal val JsQuery.wrapped get() = NativeQuery(this) - actual val parent get() = rethrow { js.parent?.let{DocumentReference(it)} } +public operator fun Query.Companion.invoke(js: JsQuery): Query = Query(js.wrapped) +public val Query.js: dev.gitlive.firebase.firestore.externals.Query get() = native.js - actual fun document(documentPath: String) = rethrow { DocumentReference(doc(js, documentPath)) } +internal actual data class NativeCollectionReference(override val js: JsCollectionReference) : NativeQuery(js) - actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = - rethrow { DocumentReference(addDoc(js, encode(data, encodeSettings)!!).await()) } - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - rethrow { DocumentReference(addDoc(js, encode(strategy, data, encodeSettings)!!).await()) } +public operator fun CollectionReference.Companion.invoke(js: JsCollectionReference): CollectionReference = CollectionReference(NativeCollectionReference(js)) +public val CollectionReference.js: dev.gitlive.firebase.firestore.externals.CollectionReference get() = native.js - @Suppress("DeferredIsResult") - actual class Async(@PublishedApi internal val js: JsCollectionReference) { - actual inline fun add(data: T, encodeSettings: EncodeSettings) = - rethrow { - addDoc(js, encode(data, encodeSettings)!!).asDeferred() - .convert(::DocumentReference) - } - actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - rethrow { - addDoc(js, encode(strategy, data, encodeSettings)!!).asDeferred() - .convert(::DocumentReference) - } - } -} - -actual class FirebaseFirestoreException(cause: Throwable, val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) +public actual class FirebaseFirestoreException(cause: Throwable, public val code: FirestoreExceptionCode) : FirebaseException(code.toString(), cause) @Suppress("EXTENSION_SHADOWED_BY_MEMBER") -actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code +public actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code + +public val QuerySnapshot.js: JsQuerySnapshot get() = js -actual class QuerySnapshot(val js: JsQuerySnapshot) { - actual val documents - get() = js.docs.map { DocumentSnapshot(it) } - actual val documentChanges +public actual class QuerySnapshot(internal val js: JsQuerySnapshot) { + public actual val documents: List + get() = js.docs.map { DocumentSnapshot(NativeDocumentSnapshotWrapper(it)) } + public actual val documentChanges: List get() = js.docChanges().map { DocumentChange(it) } - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) + public actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) } -actual class DocumentChange(val js: JsDocumentChange) { - actual val document: DocumentSnapshot - get() = DocumentSnapshot(js.doc) - actual val newIndex: Int +public val DocumentChange.js: JsDocumentChange get() = js + +public actual class DocumentChange(internal val js: JsDocumentChange) { + public actual val document: DocumentSnapshot + get() = DocumentSnapshot(NativeDocumentSnapshotWrapper(js.doc)) + public actual val newIndex: Int get() = js.newIndex - actual val oldIndex: Int + public actual val oldIndex: Int get() = js.oldIndex - actual val type: ChangeType - get() = ChangeType.values().first { it.jsString == js.type } + public actual val type: ChangeType + get() = ChangeType.entries.first { it.jsString == js.type } } -actual class DocumentSnapshot(val js: JsDocumentSnapshot) { - - actual val id get() = rethrow { js.id } - actual val reference get() = rethrow { DocumentReference(js.ref) } - - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = - rethrow { decode(value = js.data(getTimestampsOptions(serverTimestampBehavior))) } - - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - rethrow { decode(strategy, js.data(getTimestampsOptions(serverTimestampBehavior)), decodeSettings) } - - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior) = - rethrow { decode(value = js.get(field, getTimestampsOptions(serverTimestampBehavior))) } +internal actual data class NativeDocumentSnapshot(val js: JsDocumentSnapshot) - actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior) = - rethrow { decode(strategy, js.get(field, getTimestampsOptions(serverTimestampBehavior)), decodeSettings) } +public operator fun DocumentSnapshot.Companion.invoke(js: JsDocumentSnapshot): DocumentSnapshot = DocumentSnapshot(NativeDocumentSnapshot(js)) +public val DocumentSnapshot.js: dev.gitlive.firebase.firestore.externals.DocumentSnapshot get() = native.js - actual fun contains(field: String) = rethrow { js.get(field) != undefined } - actual val exists get() = rethrow { js.exists() } - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(js.metadata) +public val SnapshotMetadata.js: dev.gitlive.firebase.firestore.externals.SnapshotMetadata get() = js - fun getTimestampsOptions(serverTimestampBehavior: ServerTimestampBehavior) = - json("serverTimestamps" to serverTimestampBehavior.name.lowercase()) +public actual class SnapshotMetadata(internal val js: JsSnapshotMetadata) { + public actual val hasPendingWrites: Boolean get() = js.hasPendingWrites + public actual val isFromCache: Boolean get() = js.fromCache } -actual class SnapshotMetadata(val js: JsSnapshotMetadata) { - actual val hasPendingWrites: Boolean get() = js.hasPendingWrites - actual val isFromCache: Boolean get() = js.fromCache -} +public val FieldPath.js: dev.gitlive.firebase.firestore.externals.FieldPath get() = js + +public actual class FieldPath private constructor(internal val js: JsFieldPath) { -actual class FieldPath private constructor(val js: JsFieldPath) { - actual constructor(vararg fieldNames: String) : this(dev.gitlive.firebase.firestore.rethrow { - js("Reflect").construct(JsFieldPath, fieldNames).unsafeCast() - }) - actual val documentId: FieldPath get() = FieldPath(JsFieldPath.documentId) - actual val encoded: EncodedFieldPath = js + public actual companion object { + public actual val documentId: FieldPath = FieldPath(jsDocumentId()) + } + public actual constructor(vararg fieldNames: String) : this( + dev.gitlive.firebase.firestore.rethrow { + JsFieldPath(*fieldNames) + }, + ) + public actual val documentId: FieldPath get() = FieldPath.documentId + public actual val encoded: EncodedFieldPath = js override fun equals(other: Any?): Boolean = other is FieldPath && js.isEqual(other.js) override fun hashCode(): Int = js.hashCode() override fun toString(): String = js.toString() } -actual typealias EncodedFieldPath = JsFieldPath +public actual typealias EncodedFieldPath = JsFieldPath -//actual data class FirebaseFirestoreSettings internal constructor( -// val cacheSizeBytes: Number? = undefined, -// val host: String? = undefined, -// val ssl: Boolean? = undefined, -// var timestampsInSnapshots: Boolean? = undefined, -// var enablePersistence: Boolean = false -//) - -actual enum class FirestoreExceptionCode { +public actual enum class FirestoreExceptionCode { OK, CANCELLED, UNKNOWN, @@ -562,66 +216,66 @@ actual enum class FirestoreExceptionCode { INTERNAL, UNAVAILABLE, DATA_LOSS, - UNAUTHENTICATED + UNAUTHENTICATED, } -actual enum class Direction(internal val jsString : String) { +public actual enum class Direction(internal val jsString: String) { ASCENDING("asc"), - DESCENDING("desc"); + DESCENDING("desc"), } -actual enum class ChangeType(internal val jsString : String) { +public actual enum class ChangeType(internal val jsString: String) { ADDED("added"), MODIFIED("modified"), - REMOVED("removed"); + REMOVED("removed"), } -inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.firestore.rethrow { function() } +internal inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.firestore.rethrow { function() } -inline fun rethrow(function: () -> R): R { +internal inline fun rethrow(function: () -> R): R { try { return function() } catch (e: Exception) { throw e - } catch(e: dynamic) { + } catch (e: dynamic) { throw errorToException(e) } } -fun errorToException(e: dynamic) = (e?.code ?: e?.message ?: "") +internal fun errorToException(e: dynamic) = (e?.code ?: e?.message ?: "") .toString() .lowercase() .let { when { - "cancelled" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.CANCELLED) - "invalid-argument" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.INVALID_ARGUMENT) - "deadline-exceeded" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.DEADLINE_EXCEEDED) - "not-found" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.NOT_FOUND) - "already-exists" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.ALREADY_EXISTS) - "permission-denied" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.PERMISSION_DENIED) - "resource-exhausted" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.RESOURCE_EXHAUSTED) - "failed-precondition" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.FAILED_PRECONDITION) - "aborted" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.ABORTED) - "out-of-range" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.OUT_OF_RANGE) - "unimplemented" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNIMPLEMENTED) - "internal" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.INTERNAL) - "unavailable" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNAVAILABLE) - "data-loss" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.DATA_LOSS) - "unauthenticated" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNAUTHENTICATED) - "unknown" in it -> FirebaseFirestoreException(e, FirestoreExceptionCode.UNKNOWN) + "cancelled" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.CANCELLED) + "invalid-argument" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.INVALID_ARGUMENT) + "deadline-exceeded" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.DEADLINE_EXCEEDED) + "not-found" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.NOT_FOUND) + "already-exists" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.ALREADY_EXISTS) + "permission-denied" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.PERMISSION_DENIED) + "resource-exhausted" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.RESOURCE_EXHAUSTED) + "failed-precondition" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.FAILED_PRECONDITION) + "aborted" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.ABORTED) + "out-of-range" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.OUT_OF_RANGE) + "unimplemented" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.UNIMPLEMENTED) + "internal" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.INTERNAL) + "unavailable" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.UNAVAILABLE) + "data-loss" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.DATA_LOSS) + "unauthenticated" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.UNAUTHENTICATED) + "unknown" in it -> FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.UNKNOWN) else -> { println("Unknown error code in ${JSON.stringify(e)}") - FirebaseFirestoreException(e, FirestoreExceptionCode.UNKNOWN) + FirebaseFirestoreException(e.unsafeCast(), FirestoreExceptionCode.UNKNOWN) } } -} + } // from: https://discuss.kotlinlang.org/t/how-to-access-native-js-object-as-a-map-string-any/509/8 -fun entriesOf(jsObject: dynamic): List> = +internal fun entriesOf(jsObject: dynamic): List> = (js("Object.entries") as (dynamic) -> Array>) .invoke(jsObject) .map { entry -> entry[0] as String to entry[1] } // from: https://discuss.kotlinlang.org/t/how-to-access-native-js-object-as-a-map-string-any/509/8 -fun mapOf(jsObject: dynamic): Map = +internal fun mapOf(jsObject: dynamic): Map = entriesOf(jsObject).toMap() diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt new file mode 100644 index 000000000..e91d6089f --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeCollectionReferenceWrapper.kt @@ -0,0 +1,37 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.externals.CollectionReference +import dev.gitlive.firebase.firestore.externals.addDoc +import dev.gitlive.firebase.firestore.externals.doc +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +internal actual class NativeCollectionReferenceWrapper internal actual constructor(actual override val native: NativeCollectionReference) : NativeQueryWrapper(native) { + + constructor(js: CollectionReference) : this(NativeCollectionReference(js)) + + override val js: CollectionReference = native.js + + actual val path: String + get() = rethrow { js.path } + + actual val document get() = rethrow { NativeDocumentReference(doc(js)) } + + actual val parent get() = rethrow { js.parent?.let { NativeDocumentReference(it) } } + + actual fun document(documentPath: String) = rethrow { + NativeDocumentReference( + doc( + js, + documentPath, + ), + ) + } + + actual suspend fun addEncoded(data: EncodedObject) = rethrow { + NativeDocumentReference(addDoc(js, data.js).await()) + } +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt new file mode 100644 index 000000000..5065defa2 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentReference.kt @@ -0,0 +1,113 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeDocumentReferenceType +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.errorToException +import dev.gitlive.firebase.firestore.externals.deleteDoc +import dev.gitlive.firebase.firestore.externals.getDoc +import dev.gitlive.firebase.firestore.externals.getDocFromCache +import dev.gitlive.firebase.firestore.externals.getDocFromServer +import dev.gitlive.firebase.firestore.externals.onSnapshot +import dev.gitlive.firebase.firestore.externals.refEqual +import dev.gitlive.firebase.firestore.externals.setDoc +import dev.gitlive.firebase.firestore.externals.updateDoc +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlin.js.json + +internal actual class NativeDocumentReference actual constructor(actual val nativeValue: NativeDocumentReferenceType) { + val js: NativeDocumentReferenceType = nativeValue + + actual val id: String + get() = rethrow { js.id } + + actual val path: String + get() = rethrow { js.path } + + actual val parent: NativeCollectionReferenceWrapper + get() = rethrow { NativeCollectionReferenceWrapper(js.parent) } + + actual fun collection(collectionPath: String) = rethrow { + NativeCollectionReference( + dev.gitlive.firebase.firestore.externals.collection( + js, + collectionPath, + ), + ) + } + + actual suspend fun get(source: Source) = rethrow { + NativeDocumentSnapshot( + js.get(source).await(), + ) + } + + actual val snapshots: Flow get() = snapshots() + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val unsubscribe = onSnapshot( + js, + json("includeMetadataChanges" to includeMetadataChanges), + { trySend(NativeDocumentSnapshot(it)) }, + { close(errorToException(it)) }, + ) + awaitClose { unsubscribe() } + } + + actual suspend fun setEncoded(encodedData: EncodedObject, setOptions: SetOptions) = rethrow { + setDoc(js, encodedData.js, setOptions.js).await() + } + + actual suspend fun updateEncoded(encodedData: EncodedObject) = rethrow { + updateDoc( + js, + encodedData.js, + ).await() + } + + actual suspend fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>) { + rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + updateDoc(js, field, value, *moreFieldsAndValues) + } + ?.await() + } + } + + actual suspend fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>) { + rethrow { + encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } + ?.performUpdate { field, value, moreFieldsAndValues -> + updateDoc(js, field, value, *moreFieldsAndValues) + }?.await() + } + } + + actual suspend fun delete() = rethrow { deleteDoc(js).await() } + + override fun equals(other: Any?): Boolean = + this === other || + other is NativeDocumentReference && + refEqual( + nativeValue, + other.nativeValue, + ) + override fun hashCode(): Int = nativeValue.hashCode() + override fun toString(): String = "DocumentReference(path=$path)" +} + +private fun NativeDocumentReferenceType.get(source: Source) = when (source) { + Source.DEFAULT -> getDoc(this) + Source.CACHE -> getDocFromCache(this) + Source.SERVER -> getDocFromServer(this) +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt new file mode 100644 index 000000000..bf6e967a6 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeDocumentSnapshotWrapper.kt @@ -0,0 +1,39 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.ServerTimestampBehavior +import dev.gitlive.firebase.firestore.SnapshotMetadata +import dev.gitlive.firebase.firestore.externals.DocumentSnapshot +import dev.gitlive.firebase.firestore.rethrow +import kotlin.js.json + +internal actual class NativeDocumentSnapshotWrapper internal actual constructor(actual val native: NativeDocumentSnapshot) { + + constructor(js: DocumentSnapshot) : this(NativeDocumentSnapshot(js)) + + val js: DocumentSnapshot = native.js + + actual val id get() = rethrow { js.id } + actual val reference get() = rethrow { NativeDocumentReference(js.ref) } + + actual fun getEncoded(field: String, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(field, getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun getEncoded(fieldPath: EncodedFieldPath, serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.get(fieldPath, getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun encodedData(serverTimestampBehavior: ServerTimestampBehavior): Any? = rethrow { + js.data(getTimestampsOptions(serverTimestampBehavior)) + } + + actual fun contains(field: String) = rethrow { js.get(field) != undefined } + actual fun contains(fieldPath: EncodedFieldPath) = rethrow { js.get(fieldPath) != undefined } + 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()) +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt new file mode 100644 index 000000000..c8c1a31ae --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt @@ -0,0 +1,132 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.externals.FirebaseApp +import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings +import dev.gitlive.firebase.firestore.NativeCollectionReference +import dev.gitlive.firebase.firestore.NativeFirebaseFirestore +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence +import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator +import dev.gitlive.firebase.firestore.externals.doc +import dev.gitlive.firebase.firestore.externals.getFirestore +import dev.gitlive.firebase.firestore.externals.initializeFirestore +import dev.gitlive.firebase.firestore.externals.setLogLevel +import dev.gitlive.firebase.firestore.externals.writeBatch +import dev.gitlive.firebase.firestore.firestoreSettings +import dev.gitlive.firebase.firestore.rethrow +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.await +import kotlinx.coroutines.promise + +internal actual class NativeFirebaseFirestoreWrapper internal constructor( + private val createNative: NativeFirebaseFirestoreWrapper.(FirebaseFirestoreSettings?) -> NativeFirebaseFirestore, +) { + + internal actual constructor(native: NativeFirebaseFirestore) : this( + { settings -> + settings?.let { + NativeFirebaseFirestore(initializeFirestore(native.js.app, settings)) + } ?: native + }, + ) + internal constructor(app: FirebaseApp) : this( + { settings -> + NativeFirebaseFirestore( + settings?.let { + initializeFirestore(app, it.js).also { + emulatorSettings?.run { + connectFirestoreEmulator(it, host, port) + } + } + } ?: getFirestore(app), + ) + }, + ) + + private data class EmulatorSettings(val host: String, val port: Int) + + private var settings: FirebaseFirestoreSettings? = null + set(value) { + if (lazyNative.isInitialized()) { + createNative(value) // Call initialize again to ensure native crash occurs if settings have changed + } + field = value + } + private var emulatorSettings: EmulatorSettings? = null + + // initializeFirestore must be called before any call, including before `getFirestore()` + // To allow settings to be updated, we defer creating the wrapper until the first call to `native` + private val lazyNative = lazy { + createNative(settings) + } + actual val native: NativeFirebaseFirestore by lazyNative + private val js get() = native.js + + actual fun collection(collectionPath: String) = rethrow { + NativeCollectionReference( + dev.gitlive.firebase.firestore.externals.collection( + js, + collectionPath, + ), + ) + } + + actual fun collectionGroup(collectionId: String) = rethrow { + NativeQuery( + dev.gitlive.firebase.firestore.externals.collectionGroup( + js, + collectionId, + ), + ) + } + + actual fun document(documentPath: String) = rethrow { + NativeDocumentReference( + doc( + js, + documentPath, + ), + ) + } + + actual fun batch() = rethrow { NativeWriteBatch(writeBatch(js)) } + + actual fun setLoggingEnabled(loggingEnabled: Boolean) = + rethrow { setLogLevel(if (loggingEnabled) "error" else "silent") } + + actual fun applySettings(settings: FirebaseFirestoreSettings) { + this.settings = settings + } + + @OptIn(DelicateCoroutinesApi::class) + actual suspend fun runTransaction(func: suspend NativeTransaction.() -> T) = + rethrow { + dev.gitlive.firebase.firestore.externals.runTransaction( + js, + { GlobalScope.promise { NativeTransaction(it).func() } }, + ).await() + } + + actual suspend fun clearPersistence() = + rethrow { clearIndexedDbPersistence(js).await() } + + actual fun useEmulator(host: String, port: Int) = rethrow { + if (settings != null) { + settings = firestoreSettings(settings) { + this.host = "$host:$port" + } + } + emulatorSettings = EmulatorSettings(host, port) + } + + actual suspend fun disableNetwork() { + rethrow { dev.gitlive.firebase.firestore.externals.disableNetwork(js).await() } + } + + actual suspend fun enableNetwork() { + rethrow { dev.gitlive.firebase.firestore.externals.enableNetwork(js).await() } + } +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt new file mode 100644 index 000000000..bc49b3437 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeQueryWrapper.kt @@ -0,0 +1,169 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.Direction +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.Filter +import dev.gitlive.firebase.firestore.NativeDocumentSnapshot +import dev.gitlive.firebase.firestore.NativeQuery +import dev.gitlive.firebase.firestore.QuerySnapshot +import dev.gitlive.firebase.firestore.Source +import dev.gitlive.firebase.firestore.WhereConstraint +import dev.gitlive.firebase.firestore.errorToException +import dev.gitlive.firebase.firestore.externals.Query +import dev.gitlive.firebase.firestore.externals.QueryConstraint +import dev.gitlive.firebase.firestore.externals.and +import dev.gitlive.firebase.firestore.externals.getDocs +import dev.gitlive.firebase.firestore.externals.getDocsFromCache +import dev.gitlive.firebase.firestore.externals.getDocsFromServer +import dev.gitlive.firebase.firestore.externals.onSnapshot +import dev.gitlive.firebase.firestore.externals.or +import dev.gitlive.firebase.firestore.externals.query +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.firestore.wrapped +import kotlinx.coroutines.await +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlin.js.json + +internal actual open class NativeQueryWrapper internal actual constructor(actual open val native: NativeQuery) { + + constructor(js: Query) : this(NativeQuery(js)) + + open val js: Query get() = native.js + + actual suspend fun get(source: Source) = rethrow { QuerySnapshot(js.get(source).await()) } + + actual fun limit(limit: Number) = query( + js, + dev.gitlive.firebase.firestore.externals.limit(limit), + ).wrapped + + actual fun where(filter: Filter) = query(js, filter.toQueryConstraint()).wrapped + + private fun Filter.toQueryConstraint(): QueryConstraint = when (this) { + is Filter.And -> and(*filters.map { it.toQueryConstraint() }.toTypedArray()) + is Filter.Or -> or(*filters.map { it.toQueryConstraint() }.toTypedArray()) + is Filter.Field -> { + val value = when (constraint) { + is WhereConstraint.ForNullableObject -> constraint.safeValue + is WhereConstraint.ForObject -> constraint.safeValue + is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() + } + dev.gitlive.firebase.firestore.externals.where(field, constraint.filterOp, value) + } + is Filter.Path -> { + val value = when (constraint) { + is WhereConstraint.ForNullableObject -> constraint.safeValue + is WhereConstraint.ForObject -> constraint.safeValue + is WhereConstraint.ForArray -> constraint.safeValues.toTypedArray() + } + dev.gitlive.firebase.firestore.externals.where(path.js, constraint.filterOp, value) + } + } + + private val WhereConstraint.filterOp: String get() = when (this) { + is WhereConstraint.EqualTo -> "==" + is WhereConstraint.NotEqualTo -> "!=" + is WhereConstraint.LessThan -> "<" + is WhereConstraint.LessThanOrEqualTo -> "<=" + is WhereConstraint.GreaterThan -> ">" + is WhereConstraint.GreaterThanOrEqualTo -> ">=" + is WhereConstraint.ArrayContains -> "array-contains" + is WhereConstraint.ArrayContainsAny -> "array-contains-any" + is WhereConstraint.InArray -> "in" + is WhereConstraint.NotInArray -> "not-in" + } + + actual fun orderBy(field: String, direction: Direction) = rethrow { + query(js, dev.gitlive.firebase.firestore.externals.orderBy(field, direction.jsString)).wrapped + } + + actual fun orderBy(field: EncodedFieldPath, direction: Direction) = rethrow { + query(js, dev.gitlive.firebase.firestore.externals.orderBy(field, direction.jsString)).wrapped + } + + actual fun startAfter(document: NativeDocumentSnapshot) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.startAfter(document.js), + ).wrapped + } + + actual fun startAfter(vararg fieldValues: Any) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.startAfter(*fieldValues), + ).wrapped + } + + actual fun startAt(document: NativeDocumentSnapshot) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.startAt(document.js), + ).wrapped + } + + actual fun startAt(vararg fieldValues: Any) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.startAt(*fieldValues), + ).wrapped + } + + actual fun endBefore(document: NativeDocumentSnapshot) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.endBefore(document.js), + ).wrapped + } + + actual fun endBefore(vararg fieldValues: Any) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.endBefore(*fieldValues), + ).wrapped + } + + actual fun endAt(document: NativeDocumentSnapshot) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.endAt(document.js), + ).wrapped + } + + actual fun endAt(vararg fieldValues: Any) = rethrow { + query( + js, + dev.gitlive.firebase.firestore.externals.endAt(*fieldValues), + ).wrapped + } + + actual val snapshots get() = callbackFlow { + val unsubscribe = rethrow { + onSnapshot( + js, + { trySend(QuerySnapshot(it)) }, + { close(errorToException(it)) }, + ) + } + awaitClose { rethrow { unsubscribe() } } + } + + actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { + val unsubscribe = rethrow { + onSnapshot( + js, + json("includeMetadataChanges" to includeMetadataChanges), + { trySend(QuerySnapshot(it)) }, + { close(errorToException(it)) }, + ) + } + awaitClose { rethrow { unsubscribe() } } + } +} + +private fun Query.get(source: Source) = when (source) { + Source.DEFAULT -> getDocs(this) + Source.CACHE -> getDocsFromCache(this) + Source.SERVER -> getDocsFromServer(this) +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt new file mode 100644 index 000000000..59ba9896d --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeTransactionWrapper.kt @@ -0,0 +1,56 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeTransaction +import dev.gitlive.firebase.firestore.externals.Transaction +import dev.gitlive.firebase.firestore.js +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +internal actual class NativeTransactionWrapper internal actual constructor(actual val native: NativeTransaction) { + + constructor(js: Transaction) : this(NativeTransaction(js)) + + val js = native.js + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions, + ): NativeTransactionWrapper = rethrow { + js.set(documentRef.js, encodedData.js, setOptions.js) + } + .let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeTransactionWrapper = rethrow { js.update(documentRef.js, encodedData.js) } + .let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeTransactionWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeTransactionWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + rethrow { js.delete(documentRef.js) } + .let { this } + + actual suspend fun get(documentRef: DocumentReference) = + rethrow { NativeDocumentSnapshotWrapper(js.get(documentRef.js).await()) } +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt new file mode 100644 index 000000000..e97cc31ab --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeWriteBatchWrapper.kt @@ -0,0 +1,52 @@ +package dev.gitlive.firebase.firestore.internal + +import dev.gitlive.firebase.firestore.DocumentReference +import dev.gitlive.firebase.firestore.EncodedFieldPath +import dev.gitlive.firebase.firestore.NativeWriteBatch +import dev.gitlive.firebase.firestore.externals.WriteBatch +import dev.gitlive.firebase.firestore.js +import dev.gitlive.firebase.firestore.performUpdate +import dev.gitlive.firebase.firestore.rethrow +import dev.gitlive.firebase.internal.EncodedObject +import dev.gitlive.firebase.internal.js +import kotlinx.coroutines.await + +internal actual class NativeWriteBatchWrapper internal actual constructor(actual val native: NativeWriteBatch) { + + constructor(js: WriteBatch) : this(NativeWriteBatch(js)) + + val js = native.js + + actual fun setEncoded( + documentRef: DocumentReference, + encodedData: EncodedObject, + setOptions: SetOptions, + ): NativeWriteBatchWrapper = rethrow { js.set(documentRef.js, encodedData.js, setOptions.js) }.let { this } + + actual fun updateEncoded(documentRef: DocumentReference, encodedData: EncodedObject): NativeWriteBatchWrapper = rethrow { js.update(documentRef.js, encodedData.js) } + .let { this } + + actual fun updateEncodedFieldsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeWriteBatchWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun updateEncodedFieldPathsAndValues( + documentRef: DocumentReference, + encodedFieldsAndValues: List>, + ): NativeWriteBatchWrapper = rethrow { + encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> + js.update(documentRef.js, field, value, *moreFieldsAndValues) + } + }.let { this } + + actual fun delete(documentRef: DocumentReference) = + rethrow { js.delete(documentRef.js) } + .let { this } + + actual suspend fun commit() = rethrow { js.commit().await() } +} diff --git a/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt new file mode 100644 index 000000000..24b667d32 --- /dev/null +++ b/firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/SetOptions.kt @@ -0,0 +1,12 @@ +package dev.gitlive.firebase.firestore.internal + +import kotlin.js.Json +import kotlin.js.json + +internal val SetOptions.js: Json + get() = when (this) { + is SetOptions.Merge -> json("merge" to true) + is SetOptions.Overwrite -> json("merge" to false) + is SetOptions.MergeFields -> json("mergeFields" to fields.toTypedArray()) + is SetOptions.MergeFieldPaths -> json("mergeFields" to encodedFieldPaths.toTypedArray()) + } diff --git a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt index a727e13d8..d2119d902 100644 --- a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt +++ b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -3,5 +3,6 @@ package dev.gitlive.firebase.firestore import kotlin.test.Ignore actual typealias IgnoreJs = Ignore + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -actual annotation class IgnoreForAndroidUnitTest \ No newline at end of file +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt b/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt deleted file mode 100644 index dcb2c5452..000000000 --- a/firebase-firestore/src/jsTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.gitlive.firebase.firestore - -actual fun createFirestoreTestSettings( - sslEnabled: Boolean?, - host: String?, - cacheSettings: LocalCacheSettings? -) = FirebaseFirestore.Settings( - sslEnabled, host, cacheSettings -) 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 17006e809..9c6ba0d4c 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 @@ -10,11 +10,8 @@ actual val emulatorHost: String = "localhost" actual val context: Any = Unit -actual fun encodedAsMap(encoded: Any?): Map { - return (js("Object").entries(encoded) as Array>).associate { - it[0] as String to it[1] - } +actual fun encodedAsMap(encoded: Any?): Map = (js("Object").entries(encoded) as Array>).associate { + it[0] as String to it[1] } actual fun Map.asEncoded(): Any = json(*entries.map { (key, value) -> key to value }.toTypedArray()) - diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt deleted file mode 100644 index f5f2cee34..000000000 --- a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/FieldValue.kt +++ /dev/null @@ -1,26 +0,0 @@ -package dev.gitlive.firebase.firestore - -import kotlinx.serialization.Serializable - -/** Represents a platform specific Firebase FieldValue. */ -typealias NativeFieldValue = com.google.firebase.firestore.FieldValue - -/** Represents a Firebase FieldValue. */ -@Serializable(with = FieldValueSerializer::class) -actual class FieldValue internal actual constructor(internal actual val nativeValue: Any) { - init { - require(nativeValue is NativeFieldValue) - } - override fun equals(other: Any?): Boolean = - this === other || other is FieldValue && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - actual companion object { - actual val serverTimestamp: FieldValue get() = FieldValue(NativeFieldValue.serverTimestamp()) - actual val delete: FieldValue get() = FieldValue(NativeFieldValue.delete()) - actual fun increment(value: Int): FieldValue = FieldValue(NativeFieldValue.increment(value.toDouble())) - actual fun arrayUnion(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayUnion(*elements)) - actual fun arrayRemove(vararg elements: Any): FieldValue = FieldValue(NativeFieldValue.arrayRemove(*elements)) - } -} diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/Geopoint.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/Geopoint.kt deleted file mode 100644 index 7523619f5..000000000 --- a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/Geopoint.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.gitlive.firebase.firestore - -import kotlinx.serialization.Serializable - -/** A class representing a platform specific Firebase GeoPoint. */ -actual typealias NativeGeoPoint = com.google.firebase.firestore.GeoPoint - -/** A class representing a Firebase GeoPoint. */ -@Serializable(with = GeoPointSerializer::class) -actual class GeoPoint internal actual constructor(internal actual val nativeValue: NativeGeoPoint) { - actual constructor(latitude: Double, longitude: Double) : this(NativeGeoPoint(latitude, longitude)) - actual val latitude: Double = nativeValue.latitude - actual val longitude: Double = nativeValue.longitude - - override fun equals(other: Any?): Boolean = - this === other || other is GeoPoint && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() -} diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt deleted file mode 100644 index cc9a2ddb9..000000000 --- a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/Timestamp.kt +++ /dev/null @@ -1,35 +0,0 @@ -@file:JvmName("androidTimestamp") -package dev.gitlive.firebase.firestore - -import kotlinx.serialization.Serializable - -/** A class representing a platform specific Firebase Timestamp. */ -actual typealias NativeTimestamp = com.google.firebase.Timestamp - -/** A base class that could be used to combine [Timestamp] and [Timestamp.ServerTimestamp] in the same field. */ -@Serializable(with = BaseTimestampSerializer::class) -actual sealed class BaseTimestamp - -/** A class representing a Firebase Timestamp. */ -@Serializable(with = TimestampSerializer::class) -actual class Timestamp internal actual constructor( - internal actual val nativeValue: NativeTimestamp -): BaseTimestamp() { - actual constructor(seconds: Long, nanoseconds: Int) : this(NativeTimestamp(seconds, nanoseconds)) - - actual val seconds: Long = nativeValue.seconds - actual val nanoseconds: Int = nativeValue.nanoseconds - - override fun equals(other: Any?): Boolean = - this === other || other is Timestamp && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - actual companion object { - actual fun now(): Timestamp = Timestamp(NativeTimestamp.now()) - } - - /** A server time timestamp. */ - @Serializable(with = ServerTimestampSerializer::class) - actual object ServerTimestamp: BaseTimestamp() -} diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt deleted file mode 100644 index d12bda859..000000000 --- a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/_encoders.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.gitlive.firebase.firestore - -@PublishedApi -internal actual fun isSpecialValue(value: Any) = when(value) { - is NativeFieldValue, - is NativeGeoPoint, - is NativeTimestamp, - is NativeDocumentReference -> true - else -> false -} diff --git a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt deleted file mode 100644 index ea28ccf65..000000000 --- a/firebase-firestore/src/jvmMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:JvmName("android") -package dev.gitlive.firebase.firestore - -import com.google.android.gms.tasks.Task -import com.google.firebase.firestore.MetadataChanges -import dev.gitlive.firebase.* -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.tasks.asDeferred -import kotlinx.coroutines.tasks.await -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy - -actual val Firebase.firestore get() = - FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance()) - -actual fun Firebase.firestore(app: FirebaseApp) = - FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance(app.android)) - -@Suppress("DeferredIsResult") -@PublishedApi -internal fun Task.asUnitDeferred(): Deferred = CompletableDeferred() - .apply { - asDeferred().invokeOnCompletion { exception -> - if (exception == null) complete(Unit) else completeExceptionally(exception) - } - } - -actual data class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) { - - actual data class Settings( - actual val sslEnabled: Boolean? = null, - actual val host: String? = null, - actual val cacheSettings: LocalCacheSettings? = null - ) { - actual companion object { - actual fun create(sslEnabled: Boolean?, host: String?, cacheSettings: LocalCacheSettings?) = Settings(sslEnabled, host, cacheSettings) - } - } - - private var lastSettings = Settings() - - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) - - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - - actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId)) - - actual fun batch() = WriteBatch(android.batch()) - - actual fun setLoggingEnabled(loggingEnabled: Boolean) = - com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled) - - actual suspend fun runTransaction(func: suspend Transaction.() -> T): T = - android.runTransaction { runBlocking { Transaction(it).func() } }.await() - - actual suspend fun clearPersistence() = - android.clearPersistence().await().run { } - - actual fun useEmulator(host: String, port: Int) { - android.useEmulator(host, port) - android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder() - .setPersistenceEnabled(false) - .build() - } - - actual fun setSettings(settings: Settings) { - lastSettings = settings - android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder().also { builder -> - if (settings.cacheSettings is LocalCacheSettings.Persistent) { - builder.isPersistenceEnabled = true - } - settings.sslEnabled?.let { builder.isSslEnabled = it } - settings.host?.let { builder.host = it } - when (val cacheSettings = settings.cacheSettings) { - is LocalCacheSettings.Persistent -> cacheSettings.sizeBytes - is LocalCacheSettings.Memory -> when (val garbageCollectorSettings = cacheSettings.garbaseCollectorSettings) { - is LocalCacheSettings.Memory.GarbageCollectorSettings.Eager -> null - is LocalCacheSettings.Memory.GarbageCollectorSettings.LRUGC -> garbageCollectorSettings.sizeBytes - } - null -> null - }?.let { builder.cacheSizeBytes = it } - }.build() - } - - actual fun updateSettings(settings: Settings) = setSettings( - Settings(settings.sslEnabled ?: lastSettings.sslEnabled, settings.host ?: lastSettings.host, settings.cacheSettings ?: lastSettings.cacheSettings) - ) - - actual suspend fun disableNetwork() = - android.disableNetwork().await().run { } - - actual suspend fun enableNetwork() = - android.enableNetwork().await().run { } - -} - -val SetOptions.android: com.google.firebase.firestore.SetOptions? get() = when (this) { - is SetOptions.Merge -> com.google.firebase.firestore.SetOptions.merge() - is SetOptions.Overwrite -> null - is SetOptions.MergeFields -> com.google.firebase.firestore.SetOptions.mergeFields(fields) - is SetOptions.MergeFieldPaths -> com.google.firebase.firestore.SetOptions.mergeFieldPaths(encodedFieldPaths) -} - -actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) : BaseWriteBatch() { - - actual val async = Async(android) - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseWriteBatch = (setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData)).let { - this - } - - @Suppress("UNCHECKED_CAST") - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List>, - merge: Boolean - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - if (merge) { - android.set(documentRef.android, result, com.google.firebase.firestore.SetOptions.merge()) - } else { - android.set(documentRef.android, result) - } - return this - } - - @Suppress("UNCHECKED_CAST") - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseWriteBatch = android.update(documentRef.android, encodedData as Map).let { this } - - @Suppress("UNCHECKED_CAST") - override fun updateEncoded( - documentRef: DocumentReference, - encodedData: Any, - encodedFieldsAndValues: List> - ): BaseWriteBatch { - val serializedItem = encodedData as Map - val serializedFieldAndValues = encodedFieldsAndValues.toMap() - - val result = serializedItem + serializedFieldAndValues - return android.update(documentRef.android, result).let { this } - } - - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseWriteBatch = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } - - actual suspend fun commit() = async.commit().await() - - @Suppress("DeferredIsResult") - actual class Async(private val android: com.google.firebase.firestore.WriteBatch) { - actual fun commit(): Deferred = android.commit().asUnitDeferred() - } -} - -actual class Transaction(val android: com.google.firebase.firestore.Transaction) : BaseTransaction() { - - override fun setEncoded( - documentRef: DocumentReference, - encodedData: Any, - setOptions: SetOptions - ): BaseTransaction { - setOptions.android?.let { - android.set(documentRef.android, encodedData, it) - } ?: android.set(documentRef.android, encodedData) - return this - } - - @Suppress("UNCHECKED_CAST") - override fun updateEncoded(documentRef: DocumentReference, encodedData: Any): BaseTransaction = android.update(documentRef.android, encodedData as Map).let { this } - - override fun updateEncodedFieldsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ): BaseTransaction = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - override fun updateEncodedFieldPathsAndValues( - documentRef: DocumentReference, - encodedFieldsAndValues: List> - ) = encodedFieldsAndValues.performUpdate { field, value, moreFieldsAndValues -> - android.update(documentRef.android, field, value, *moreFieldsAndValues) - }.let { this } - - actual fun delete(documentRef: DocumentReference) = - android.delete(documentRef.android).let { this } - - actual suspend fun get(documentRef: DocumentReference) = - DocumentSnapshot(android.get(documentRef.android)) -} - -/** A class representing a platform specific Firebase DocumentReference. */ -actual typealias NativeDocumentReference = com.google.firebase.firestore.DocumentReference - -@Serializable(with = DocumentReferenceSerializer::class) -actual class DocumentReference actual constructor(internal actual val nativeValue: NativeDocumentReference) : BaseDocumentReference() { - val android: NativeDocumentReference by ::nativeValue - actual val id: String - get() = android.id - - actual val path: String - get() = android.path - - actual val parent: CollectionReference - get() = CollectionReference(android.parent) - - override val async = Async(android) - - actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath)) - - actual suspend fun get() = - DocumentSnapshot(android.get().await()) - - actual val snapshots: Flow get() = snapshots() - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(DocumentSnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - override fun equals(other: Any?): Boolean = - this === other || other is DocumentReference && nativeValue == other.nativeValue - override fun hashCode(): Int = nativeValue.hashCode() - override fun toString(): String = nativeValue.toString() - - @Suppress("DeferredIsResult") - class Async(@PublishedApi internal val android: NativeDocumentReference) : BaseDocumentReference.Async() { - - override fun setEncoded(encodedData: Any, setOptions: SetOptions): Deferred = (setOptions.android?.let { - android.set(encodedData, it) - } ?: android.set(encodedData)).asUnitDeferred() - - @Suppress("UNCHECKED_CAST") - override fun updateEncoded(encodedData: Any): Deferred = android.update(encodedData as Map).asUnitDeferred() - - override fun updateEncodedFieldsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() }?.let { - android.update(encodedFieldsAndValues.toMap()) - }?.asUnitDeferred() ?: CompletableDeferred(Unit) - - override fun updateEncodedFieldPathsAndValues(encodedFieldsAndValues: List>): Deferred = encodedFieldsAndValues.takeUnless { encodedFieldsAndValues.isEmpty() } - ?.performUpdate { field, value, moreFieldsAndValues -> - android.update(field, value, *moreFieldsAndValues) - }?.asUnitDeferred() ?: CompletableDeferred(Unit) - - override fun delete() = - android.delete().asUnitDeferred() - } -} - -actual open class Query(open val android: com.google.firebase.firestore.Query) { - - actual suspend fun get() = QuerySnapshot(android.get().await()) - - actual fun limit(limit: Number) = Query(android.limit(limit.toLong())) - - actual val snapshots get() = callbackFlow { - val listener = android.addSnapshotListener { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow { - val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE - val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception -> - snapshot?.let { trySend(QuerySnapshot(snapshot)) } - exception?.let { close(exception) } - } - awaitClose { listener.remove() } - } - - internal actual fun _where(field: String, equalTo: Any?) = Query(android.whereEqualTo(field, equalTo)) - internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(android.whereEqualTo(path.android, equalTo)) - - internal actual fun _where(field: String, equalTo: DocumentReference) = Query(android.whereEqualTo(field, equalTo.android)) - internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(android.whereEqualTo(path.android, equalTo.android)) - - internal actual fun _where( - field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = Query( - when { - lessThan != null -> android.whereLessThan(field, lessThan) - greaterThan != null -> android.whereGreaterThan(field, greaterThan) - arrayContains != null -> android.whereArrayContains(field, arrayContains) - notEqualTo != null -> android.whereNotEqualTo(field, notEqualTo) - lessThanOrEqualTo != null -> android.whereLessThanOrEqualTo(field, lessThanOrEqualTo) - greaterThanOrEqualTo != null -> android.whereGreaterThanOrEqualTo(field, greaterThanOrEqualTo) - else -> android - } - ) - - internal actual fun _where( - path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?, notEqualTo: Any?, - lessThanOrEqualTo: Any?, greaterThanOrEqualTo: Any? - ) = Query( - when { - lessThan != null -> android.whereLessThan(path.android, lessThan) - greaterThan != null -> android.whereGreaterThan(path.android, greaterThan) - arrayContains != null -> android.whereArrayContains(path.android, arrayContains) - notEqualTo != null -> android.whereNotEqualTo(path.android, notEqualTo) - lessThanOrEqualTo != null -> android.whereLessThanOrEqualTo(path.android, lessThanOrEqualTo) - greaterThanOrEqualTo != null -> android.whereGreaterThanOrEqualTo(path.android, greaterThanOrEqualTo) - else -> android - } - ) - - internal actual fun _where( - field: String, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = Query( - when { - inArray != null -> android.whereIn(field, inArray) - arrayContainsAny != null -> android.whereArrayContainsAny(field, arrayContainsAny) - notInArray != null -> android.whereNotIn(field, notInArray) - else -> android - } - ) - - internal actual fun _where( - path: FieldPath, inArray: List?, arrayContainsAny: List?, notInArray: List? - ) = Query( - when { - inArray != null -> android.whereIn(path.android, inArray) - arrayContainsAny != null -> android.whereArrayContainsAny(path.android, arrayContainsAny) - notInArray != null -> android.whereNotIn(path.android, notInArray) - else -> android - } - ) - - internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction)) - internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(android.orderBy(field.android, direction)) - - internal actual fun _startAfter(document: DocumentSnapshot) = Query(android.startAfter(document.android)) - internal actual fun _startAfter(vararg fieldValues: Any) = Query(android.startAfter(*fieldValues)) - internal actual fun _startAt(document: DocumentSnapshot) = Query(android.startAt(document.android)) - internal actual fun _startAt(vararg fieldValues: Any) = Query(android.startAt(*fieldValues)) - - internal actual fun _endBefore(document: DocumentSnapshot) = Query(android.endBefore(document.android)) - internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues)) - internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android)) - internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues)) -} - -actual typealias Direction = com.google.firebase.firestore.Query.Direction -actual typealias ChangeType = com.google.firebase.firestore.DocumentChange.Type - -actual class CollectionReference(override val android: com.google.firebase.firestore.CollectionReference) : Query(android) { - - actual val path: String - get() = android.path - actual val async = Async(android) - - actual val document: DocumentReference - get() = DocumentReference(android.document()) - - actual val parent: DocumentReference? - get() = android.parent?.let{DocumentReference(it)} - - actual fun document(documentPath: String) = DocumentReference(android.document(documentPath)) - - actual suspend inline fun add(data: T, encodeSettings: EncodeSettings) = - DocumentReference(android.add(encode(data, encodeSettings)!!).await()) - actual suspend fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - DocumentReference(android.add(encode(strategy, data, encodeSettings)!!).await()) - - @Suppress("DeferredIsResult") - actual class Async(@PublishedApi internal val android: com.google.firebase.firestore.CollectionReference) { - actual inline fun add(data: T, encodeSettings: EncodeSettings) = - android.add(encode(data, encodeSettings)!!).asDeferred().convert(::DocumentReference) - actual fun add(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings) = - android.add(encode(strategy, data, encodeSettings)!!).asDeferred().convert(::DocumentReference) - } -} - -actual typealias FirebaseFirestoreException = com.google.firebase.firestore.FirebaseFirestoreException - -actual val FirebaseFirestoreException.code: FirestoreExceptionCode get() = code - -actual typealias FirestoreExceptionCode = com.google.firebase.firestore.FirebaseFirestoreException.Code - -actual class QuerySnapshot(val android: com.google.firebase.firestore.QuerySnapshot) { - actual val documents - get() = android.documents.map { DocumentSnapshot(it) } - actual val documentChanges - get() = android.documentChanges.map { DocumentChange(it) } - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) -} - -actual class DocumentChange(val android: com.google.firebase.firestore.DocumentChange) { - actual val document: DocumentSnapshot - get() = DocumentSnapshot(android.document) - actual val newIndex: Int - get() = android.newIndex - actual val oldIndex: Int - get() = android.oldIndex - actual val type: ChangeType - get() = android.type -} - -@Suppress("UNCHECKED_CAST") -actual class DocumentSnapshot(val android: com.google.firebase.firestore.DocumentSnapshot) { - - actual val id get() = android.id - actual val reference get() = DocumentReference(android.reference) - - actual inline fun data(serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.getData(serverTimestampBehavior.toAndroid())) - - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.getData(serverTimestampBehavior.toAndroid()), decodeSettings) - - actual inline fun get(field: String, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(value = android.get(field, serverTimestampBehavior.toAndroid())) - - actual fun get(field: String, strategy: DeserializationStrategy, decodeSettings: DecodeSettings, serverTimestampBehavior: ServerTimestampBehavior): T = - decode(strategy, android.get(field, serverTimestampBehavior.toAndroid()), decodeSettings) - - actual fun contains(field: String) = android.contains(field) - - actual val exists get() = android.exists() - - actual val metadata: SnapshotMetadata get() = SnapshotMetadata(android.metadata) - - fun ServerTimestampBehavior.toAndroid(): com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior = when (this) { - ServerTimestampBehavior.ESTIMATE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.ESTIMATE - ServerTimestampBehavior.NONE -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.NONE - ServerTimestampBehavior.PREVIOUS -> com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior.PREVIOUS - } -} - -actual class SnapshotMetadata(val android: com.google.firebase.firestore.SnapshotMetadata) { - actual val hasPendingWrites: Boolean get() = android.hasPendingWrites() - actual val isFromCache: Boolean get() = android.isFromCache -} - -actual class FieldPath private constructor(val android: com.google.firebase.firestore.FieldPath) { - actual constructor(vararg fieldNames: String) : this( - com.google.firebase.firestore.FieldPath.of( - *fieldNames - ) - ) - - actual val documentId: FieldPath get() = FieldPath(com.google.firebase.firestore.FieldPath.documentId()) - actual val encoded: EncodedFieldPath = android - override fun equals(other: Any?): Boolean = other is FieldPath && android == other.android - override fun hashCode(): Int = android.hashCode() - override fun toString(): String = android.toString() -} - -actual typealias EncodedFieldPath = com.google.firebase.firestore.FieldPath diff --git a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt index 416d1a374..e484ad9f6 100644 --- a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt +++ b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/Ignore.kt @@ -2,5 +2,6 @@ package dev.gitlive.firebase.firestore @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreJs + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -actual annotation class IgnoreForAndroidUnitTest \ No newline at end of file +actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt deleted file mode 100644 index dcb2c5452..000000000 --- a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/createFirestoreTestSettings.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.gitlive.firebase.firestore - -actual fun createFirestoreTestSettings( - sslEnabled: Boolean?, - host: String?, - cacheSettings: LocalCacheSettings? -) = FirebaseFirestore.Settings( - sslEnabled, host, cacheSettings -) diff --git a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt index 2b3641239..742bd4e9b 100644 --- a/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt +++ b/firebase-firestore/src/jvmTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt @@ -1,17 +1,11 @@ -/* - * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:JvmName("tests") package dev.gitlive.firebase.firestore -import android.content.Context -import com.google.firebase.FirebasePlatform -import dev.gitlive.firebase.MockFirebasePlatform +import dev.gitlive.firebase.testContext -actual val emulatorHost: String = "10.0.2.2" +actual val emulatorHost: String = "localhost" -actual val context: Any get() = Context().also { FirebasePlatform.initializeFirebasePlatform(MockFirebasePlatform()) } +actual val context: Any = testContext +@Suppress("UNCHECKED_CAST") actual fun encodedAsMap(encoded: Any?): Map = encoded as Map actual fun Map.asEncoded(): Any = this diff --git a/firebase-functions/api/android/firebase-functions.api b/firebase-functions/api/android/firebase-functions.api new file mode 100644 index 000000000..5da5d349d --- /dev/null +++ b/firebase-functions/api/android/firebase-functions.api @@ -0,0 +1,51 @@ +public final class dev/gitlive/firebase/functions/AndroidFunctions { + public static final fun functions (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/functions/FirebaseFunctions; + public static final fun functions (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;Ljava/lang/String;)Ldev/gitlive/firebase/functions/FirebaseFunctions; + public static final fun functions (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/functions/FirebaseFunctions; + public static final fun getAndroid (Ldev/gitlive/firebase/functions/FirebaseFunctions;)Lcom/google/firebase/functions/FirebaseFunctions; + public static final fun getAndroid (Ldev/gitlive/firebase/functions/HttpsCallableResult;)Lcom/google/firebase/functions/HttpsCallableResult; + public static final fun getCode (Lcom/google/firebase/functions/FirebaseFunctionsException;)Lcom/google/firebase/functions/FirebaseFunctionsException$Code; + public static final fun getDetails (Lcom/google/firebase/functions/FirebaseFunctionsException;)Ljava/lang/Object; + public static final fun getFunctions (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/functions/FirebaseFunctions; +} + +public final class dev/gitlive/firebase/functions/FirebaseFunctions { + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun httpsCallable-6Au4x4Y (Ljava/lang/String;Lkotlin/time/Duration;)Ldev/gitlive/firebase/functions/HttpsCallableReference; + public static synthetic fun httpsCallable-6Au4x4Y$default (Ldev/gitlive/firebase/functions/FirebaseFunctions;Ljava/lang/String;Lkotlin/time/Duration;ILjava/lang/Object;)Ldev/gitlive/firebase/functions/HttpsCallableReference; + public fun toString ()Ljava/lang/String; + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/functions/FunctionsKt { + public static final fun httpsCallable (Ldev/gitlive/firebase/functions/FirebaseFunctions;Ljava/lang/String;J)Ldev/gitlive/firebase/functions/HttpsCallableReference; +} + +public final class dev/gitlive/firebase/functions/HttpsCallableReference { + public final fun getNative ()Ldev/gitlive/firebase/functions/NativeHttpsCallableReference; + public final fun invoke (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun invoke (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun invoke (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun invoke$default (Ldev/gitlive/firebase/functions/HttpsCallableReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/functions/HttpsCallableResult { + public fun (Lcom/google/firebase/functions/HttpsCallableResult;)V + public final fun data (Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun data$default (Ldev/gitlive/firebase/functions/HttpsCallableResult;Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/functions/NativeHttpsCallableReference { + public fun (Lcom/google/firebase/functions/HttpsCallableReference;)V + public final fun component1 ()Lcom/google/firebase/functions/HttpsCallableReference; + public final fun copy (Lcom/google/firebase/functions/HttpsCallableReference;)Ldev/gitlive/firebase/functions/NativeHttpsCallableReference; + public static synthetic fun copy$default (Ldev/gitlive/firebase/functions/NativeHttpsCallableReference;Lcom/google/firebase/functions/HttpsCallableReference;ILjava/lang/Object;)Ldev/gitlive/firebase/functions/NativeHttpsCallableReference; + public fun equals (Ljava/lang/Object;)Z + public final fun getAndroid ()Lcom/google/firebase/functions/HttpsCallableReference; + public fun hashCode ()I + public final fun invoke (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun invoke (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun toString ()Ljava/lang/String; +} + diff --git a/firebase-functions/api/jvm/firebase-functions.api b/firebase-functions/api/jvm/firebase-functions.api new file mode 100644 index 000000000..5da5d349d --- /dev/null +++ b/firebase-functions/api/jvm/firebase-functions.api @@ -0,0 +1,51 @@ +public final class dev/gitlive/firebase/functions/AndroidFunctions { + public static final fun functions (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/functions/FirebaseFunctions; + public static final fun functions (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;Ljava/lang/String;)Ldev/gitlive/firebase/functions/FirebaseFunctions; + public static final fun functions (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/functions/FirebaseFunctions; + public static final fun getAndroid (Ldev/gitlive/firebase/functions/FirebaseFunctions;)Lcom/google/firebase/functions/FirebaseFunctions; + public static final fun getAndroid (Ldev/gitlive/firebase/functions/HttpsCallableResult;)Lcom/google/firebase/functions/HttpsCallableResult; + public static final fun getCode (Lcom/google/firebase/functions/FirebaseFunctionsException;)Lcom/google/firebase/functions/FirebaseFunctionsException$Code; + public static final fun getDetails (Lcom/google/firebase/functions/FirebaseFunctionsException;)Ljava/lang/Object; + public static final fun getFunctions (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/functions/FirebaseFunctions; +} + +public final class dev/gitlive/firebase/functions/FirebaseFunctions { + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun httpsCallable-6Au4x4Y (Ljava/lang/String;Lkotlin/time/Duration;)Ldev/gitlive/firebase/functions/HttpsCallableReference; + public static synthetic fun httpsCallable-6Au4x4Y$default (Ldev/gitlive/firebase/functions/FirebaseFunctions;Ljava/lang/String;Lkotlin/time/Duration;ILjava/lang/Object;)Ldev/gitlive/firebase/functions/HttpsCallableReference; + public fun toString ()Ljava/lang/String; + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/functions/FunctionsKt { + public static final fun httpsCallable (Ldev/gitlive/firebase/functions/FirebaseFunctions;Ljava/lang/String;J)Ldev/gitlive/firebase/functions/HttpsCallableReference; +} + +public final class dev/gitlive/firebase/functions/HttpsCallableReference { + public final fun getNative ()Ldev/gitlive/firebase/functions/NativeHttpsCallableReference; + public final fun invoke (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun invoke (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun invoke (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun invoke$default (Ldev/gitlive/firebase/functions/HttpsCallableReference;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/functions/HttpsCallableResult { + public fun (Lcom/google/firebase/functions/HttpsCallableResult;)V + public final fun data (Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun data$default (Ldev/gitlive/firebase/functions/HttpsCallableResult;Lkotlinx/serialization/DeserializationStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/functions/NativeHttpsCallableReference { + public fun (Lcom/google/firebase/functions/HttpsCallableReference;)V + public final fun component1 ()Lcom/google/firebase/functions/HttpsCallableReference; + public final fun copy (Lcom/google/firebase/functions/HttpsCallableReference;)Ldev/gitlive/firebase/functions/NativeHttpsCallableReference; + public static synthetic fun copy$default (Ldev/gitlive/firebase/functions/NativeHttpsCallableReference;Lcom/google/firebase/functions/HttpsCallableReference;ILjava/lang/Object;)Ldev/gitlive/firebase/functions/NativeHttpsCallableReference; + public fun equals (Ljava/lang/Object;)Z + public final fun getAndroid ()Lcom/google/firebase/functions/HttpsCallableReference; + public fun hashCode ()I + public final fun invoke (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun invoke (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun toString ()Ljava/lang/String; +} + diff --git a/firebase-functions/build.gradle.kts b/firebase-functions/build.gradle.kts index 3eb2c3571..21a24dd94 100644 --- a/firebase-functions/build.gradle.kts +++ b/firebase-functions/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -10,6 +13,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -25,15 +29,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -47,10 +47,23 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + freeCompilerArgs.add("-Xconsistent-data-class-copy-visibility") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -59,25 +72,22 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } + jvm() + if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseFunctions" } noPodspec() pod("FirebaseFunctions") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() extraOpts += listOf("-compiler-option", "-fmodules") } } @@ -86,29 +96,17 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) - } - } - - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" } } } @@ -116,10 +114,8 @@ kotlin { sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") optIn("kotlinx.serialization.InternalSerializationApi") @@ -132,7 +128,8 @@ kotlin { getByName("commonMain") { dependencies { api(project(":firebase-app")) - implementation(project(":firebase-common")) + api(project(":firebase-common")) + implementation(project(":firebase-common-internal")) } } @@ -144,7 +141,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-functions") + api(libs.google.firebase.functions) } } @@ -160,6 +157,12 @@ if (project.property("firebase-functions.skipIosTests") == "true") { } } +if (project.property("firebase-functions.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-functions.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-functions/documentation.md b/firebase-functions/documentation.md new file mode 100644 index 000000000..5f9c006d4 --- /dev/null +++ b/firebase-functions/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-functions +This module is a direct forward of the Firebase Functions library. It provides the main functionality, like calling Firebase Remote Fuctions. \ No newline at end of file diff --git a/firebase-functions/package.json b/firebase-functions/package.json index c18b774e4..ed25dbf4e 100644 --- a/firebase-functions/package.json +++ b/firebase-functions/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-functions", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt index 5c744950a..00b1a3dab 100644 --- a/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/androidMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -2,57 +2,70 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ +@file:JvmName("AndroidFunctions") + package dev.gitlive.firebase.functions import dev.gitlive.firebase.DecodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.decode -import dev.gitlive.firebase.encode +import dev.gitlive.firebase.android +import dev.gitlive.firebase.functions.android as publicAndroid +import dev.gitlive.firebase.internal.decode import kotlinx.coroutines.tasks.await import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy import java.util.concurrent.TimeUnit +import kotlin.time.Duration + +public val FirebaseFunctions.android: com.google.firebase.functions.FirebaseFunctions get() = com.google.firebase.functions.FirebaseFunctions.getInstance() -actual val Firebase.functions +public actual val Firebase.functions: FirebaseFunctions get() = FirebaseFunctions(com.google.firebase.functions.FirebaseFunctions.getInstance()) -actual fun Firebase.functions(region: String) = +public actual fun Firebase.functions(region: String): FirebaseFunctions = FirebaseFunctions(com.google.firebase.functions.FirebaseFunctions.getInstance(region)) -actual fun Firebase.functions(app: FirebaseApp) = +public actual fun Firebase.functions(app: FirebaseApp): FirebaseFunctions = FirebaseFunctions(com.google.firebase.functions.FirebaseFunctions.getInstance(app.android)) -actual fun Firebase.functions(app: FirebaseApp, region: String) = +public actual fun Firebase.functions(app: FirebaseApp, region: String): FirebaseFunctions = FirebaseFunctions(com.google.firebase.functions.FirebaseFunctions.getInstance(app.android, region)) -actual data class FirebaseFunctions internal constructor(val android: com.google.firebase.functions.FirebaseFunctions) { - actual fun httpsCallable(name: String, timeout: Long?) = - HttpsCallableReference(android.getHttpsCallable(name).apply { timeout?.let { setTimeout(it, TimeUnit.MILLISECONDS) } }) +public actual data class FirebaseFunctions internal constructor(internal val android: com.google.firebase.functions.FirebaseFunctions) { + public actual fun httpsCallable(name: String, timeout: Duration?): HttpsCallableReference = + HttpsCallableReference(android.getHttpsCallable(name).apply { timeout?.let { setTimeout(it.inWholeMilliseconds, TimeUnit.MILLISECONDS) } }.native) - actual fun useEmulator(host: String, port: Int) = android.useEmulator(host, port) + public actual fun useEmulator(host: String, port: Int) { + android.useEmulator(host, port) + } } -actual class HttpsCallableReference internal constructor(val android: com.google.firebase.functions.HttpsCallableReference) : BaseHttpsCallableReference() { - actual suspend operator fun invoke() = HttpsCallableResult(android.call().await()) - - override suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(android.call(encodedData).await()) +@PublishedApi +internal actual data class NativeHttpsCallableReference(val android: com.google.firebase.functions.HttpsCallableReference) { + actual suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(android.call(encodedData).await()) + actual suspend fun invoke(): HttpsCallableResult = HttpsCallableResult(android.call().await()) } -actual class HttpsCallableResult constructor(val android: com.google.firebase.functions.HttpsCallableResult) { +internal val com.google.firebase.functions.HttpsCallableReference.native get() = NativeHttpsCallableReference(this) - actual inline fun data() = - decode(value = android.data) +internal val HttpsCallableReference.android: com.google.firebase.functions.HttpsCallableReference get() = native.android +public val HttpsCallableResult.android: com.google.firebase.functions.HttpsCallableResult get() = android - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, android.data, decodeSettings) -} +public actual class HttpsCallableResult(internal val android: com.google.firebase.functions.HttpsCallableResult) { + + public actual inline fun data(): T = + decode(value = publicAndroid.data) -actual typealias FirebaseFunctionsException = com.google.firebase.functions.FirebaseFunctionsException + public actual inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(strategy, publicAndroid.data, buildSettings) +} -actual val FirebaseFunctionsException.code: FunctionsExceptionCode get() = code +public actual typealias FirebaseFunctionsException = com.google.firebase.functions.FirebaseFunctionsException -actual val FirebaseFunctionsException.details: Any? get() = details +@Suppress("ConflictingExtensionProperty") +public actual val FirebaseFunctionsException.code: FunctionsExceptionCode get() = code -actual typealias FunctionsExceptionCode = com.google.firebase.functions.FirebaseFunctionsException.Code +@Suppress("ConflictingExtensionProperty") +public actual val FirebaseFunctionsException.details: Any? get() = details +public actual typealias FunctionsExceptionCode = com.google.firebase.functions.FirebaseFunctionsException.Code 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 edb57bed3..497283c0f 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 @@ -9,51 +9,125 @@ import dev.gitlive.firebase.EncodeSettings import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import dev.gitlive.firebase.encode +import dev.gitlive.firebase.internal.encode import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds -expect class FirebaseFunctions { - fun httpsCallable(name: String, timeout: Long? = null): HttpsCallableReference - fun useEmulator(host: String, port: Int) +/** FirebaseFunctions lets you call Cloud Functions for Firebase. */ +public expect class FirebaseFunctions { + /** Returns a reference to the callable HTTPS trigger with the given name. */ + public fun httpsCallable(name: String, timeout: Duration? = null): HttpsCallableReference + + /** + * Modifies this FirebaseFunctions instance to communicate with the Cloud Functions emulator. + * + * Note: Call this method before using the instance to do any functions operations. + * + * @param host the emulator host (for example, 10.0.2.2) + * @param port the emulator port (for example, 5001) + */ + public fun useEmulator(host: String, port: Int) } -abstract class BaseHttpsCallableReference { - suspend inline operator fun invoke(data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(data, encodeSettings)!!) - suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeSettings: EncodeSettings = EncodeSettings()): HttpsCallableResult = invoke(encode(strategy, data, encodeSettings)!!) - abstract suspend fun invoke(encodedData: Any): HttpsCallableResult +@Deprecated("Replaced with Kotlin Duration", replaceWith = ReplaceWith("httpsCallable(name, timeout.milliseconds)")) +public fun FirebaseFunctions.httpsCallable(name: String, timeout: Long): HttpsCallableReference = httpsCallable(name, timeout.milliseconds) + +@PublishedApi +internal expect class NativeHttpsCallableReference { + suspend fun invoke(encodedData: Any): HttpsCallableResult + suspend fun invoke(): HttpsCallableResult } -expect class HttpsCallableReference : BaseHttpsCallableReference { - suspend operator fun invoke(): HttpsCallableResult +/** A reference to a particular Callable HTTPS trigger in Cloud Functions. */ +public class HttpsCallableReference internal constructor( + @PublishedApi + internal val native: NativeHttpsCallableReference, +) { + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(data) { this.encodeDefaults = encodeDefaults }")) + public suspend inline operator fun invoke(data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(data) { + this.encodeDefaults = encodeDefaults + } + + /** + * Executes this Callable HTTPS trigger asynchronously. + * + * If the returned task fails, the Exception will be one of the following types: + * - [FirebaseFunctionsException] - if the request connected, but the function returned + * an error. + * + * The request to the Cloud Functions backend made by this method automatically includes a + * Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase + * Auth, an auth token for the user will also be automatically included. + * + * @param data Parameters to pass to the trigger. + * @return A Task that will be completed when the HTTPS request has completed. + * @see FirebaseFunctionsException + */ + public suspend inline operator fun invoke(data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = native.invoke(encodedData = encode(data, buildSettings)!!) + + @Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("invoke(strategy, data) { this.encodeDefaults = encodeDefaults }")) + public suspend operator fun invoke(strategy: SerializationStrategy, data: T, encodeDefaults: Boolean): HttpsCallableResult = invoke(strategy, data) { + this.encodeDefaults = encodeDefaults + } + + public suspend inline operator fun invoke(strategy: SerializationStrategy, data: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}): HttpsCallableResult = invoke(encode(strategy, data, buildSettings)!!) + + /** + * Executes this HTTPS endpoint asynchronously without arguments. + * + * The request to the Cloud Functions backend made by this method automatically includes a + * Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase + * Auth, an auth token for the user will also be automatically included. + * + * @return A [HttpsCallableResult] that will contain the result. + */ + public suspend operator fun invoke(): HttpsCallableResult = native.invoke() } -expect class HttpsCallableResult { - inline fun data(): T - fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings = DecodeSettings()): T +public expect class HttpsCallableResult { + public inline fun data(): T + public inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit = {}): T } /** Returns the [FirebaseFunctions] instance of the default [FirebaseApp]. */ -expect val Firebase.functions: FirebaseFunctions +public expect val Firebase.functions: FirebaseFunctions /** Returns the [FirebaseFunctions] instance of a given [region]. */ -expect fun Firebase.functions(region: String): FirebaseFunctions +public expect fun Firebase.functions(region: String): FirebaseFunctions /** Returns the [FirebaseFunctions] instance of a given [FirebaseApp]. */ -expect fun Firebase.functions(app: FirebaseApp): FirebaseFunctions +public expect fun Firebase.functions(app: FirebaseApp): FirebaseFunctions /** Returns the [FirebaseFunctions] instance of a given [FirebaseApp] and [region]. */ -expect fun Firebase.functions(app: FirebaseApp, region: String): FirebaseFunctions +public expect fun Firebase.functions(app: FirebaseApp, region: String): FirebaseFunctions -expect class FirebaseFunctionsException: FirebaseException +/** + * Exception that gets thrown when an operation on Firebase Functions fails. + */ +public expect class FirebaseFunctionsException : FirebaseException -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") -expect val FirebaseFunctionsException.code: FunctionsExceptionCode +/** + * Returns the error code for this exception. + * + * @return [code] [FunctionsExceptionCode] that caused the exception. + */ +public expect val FirebaseFunctionsException.code: FunctionsExceptionCode -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") -expect val FirebaseFunctionsException.details: Any? +/** + * Returns the message for this exception. + * + * @return [details] message for this exception. + */ +public expect val FirebaseFunctionsException.details: Any? -expect enum class FunctionsExceptionCode { +/** + * The set of error status codes that can be returned from a Callable HTTPS tigger. These are the + * canonical error codes for Google APIs, as documented here: + * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto#L26 + */ +public expect enum class FunctionsExceptionCode { OK, CANCELLED, UNKNOWN, @@ -70,5 +144,5 @@ expect enum class FunctionsExceptionCode { INTERNAL, UNAVAILABLE, DATA_LOSS, - UNAUTHENTICATED + UNAUTHENTICATED, } diff --git a/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt b/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt index 7184e4836..d002f93b4 100644 --- a/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt +++ b/firebase-functions/src/iosMain/kotlin/dev/gitlive/firebase/functions/functions.kt @@ -2,65 +2,81 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ +@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER") + package dev.gitlive.firebase.functions import cocoapods.FirebaseFunctions.FIRFunctions import cocoapods.FirebaseFunctions.FIRHTTPSCallable import cocoapods.FirebaseFunctions.FIRHTTPSCallableResult -import dev.gitlive.firebase.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.functions.ios as publicIos +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.ios import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy import platform.Foundation.NSError +import kotlin.time.Duration +import kotlin.time.DurationUnit + +public val FirebaseFunctions.ios: FIRFunctions get() = FIRFunctions.functions() -actual val Firebase.functions +public actual val Firebase.functions: FirebaseFunctions get() = FirebaseFunctions(FIRFunctions.functions()) -actual fun Firebase.functions(region: String) = +public actual fun Firebase.functions(region: String): FirebaseFunctions = FirebaseFunctions(FIRFunctions.functionsForRegion(region)) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.functions(app: FirebaseApp): FirebaseFunctions = FirebaseFunctions( - FIRFunctions.functionsForApp(app.ios as objcnames.classes.FIRApp) +public actual fun Firebase.functions(app: FirebaseApp): FirebaseFunctions = FirebaseFunctions( + FIRFunctions.functionsForApp(app.ios as objcnames.classes.FIRApp), ) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.functions( +public actual fun Firebase.functions( app: FirebaseApp, region: String, ): FirebaseFunctions = FirebaseFunctions( - FIRFunctions.functionsForApp(app.ios as objcnames.classes.FIRApp, region = region) + FIRFunctions.functionsForApp(app.ios as objcnames.classes.FIRApp, region = region), ) -actual data class FirebaseFunctions internal constructor(val ios: FIRFunctions) { - actual fun httpsCallable(name: String, timeout: Long?) = - HttpsCallableReference(ios.HTTPSCallableWithName(name).apply { timeout?.let { setTimeoutInterval(it/1000.0) } }) +public actual data class FirebaseFunctions internal constructor(internal val ios: FIRFunctions) { + public actual fun httpsCallable(name: String, timeout: Duration?): HttpsCallableReference = + HttpsCallableReference(ios.HTTPSCallableWithName(name).apply { timeout?.let { setTimeoutInterval(it.toDouble(DurationUnit.SECONDS)) } }.native) - actual fun useEmulator(host: String, port: Int) = ios.useEmulatorWithHost(host, port.toLong()) + public actual fun useEmulator(host: String, port: Int) { + ios.useEmulatorWithHost(host, port.toLong()) + } } -actual class HttpsCallableReference internal constructor(val ios: FIRHTTPSCallable) : BaseHttpsCallableReference() { - actual suspend operator fun invoke() = HttpsCallableResult(ios.awaitResult { callWithCompletion(it) }) - - override suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(ios.awaitResult { callWithObject(encodedData, it) }) +@PublishedApi +internal actual data class NativeHttpsCallableReference(val ios: FIRHTTPSCallable) { + actual suspend fun invoke(encodedData: Any): HttpsCallableResult = HttpsCallableResult(ios.awaitResult { callWithObject(encodedData, it) }) + actual suspend fun invoke(): HttpsCallableResult = HttpsCallableResult(ios.awaitResult { callWithCompletion(it) }) } -actual class HttpsCallableResult constructor(val ios: FIRHTTPSCallableResult) { +internal val FIRHTTPSCallable.native get() = NativeHttpsCallableReference(this) - actual inline fun data() = - decode(value = ios.data()) +internal val HttpsCallableReference.ios: FIRHTTPSCallable get() = native.ios +public val HttpsCallableResult.ios: FIRHTTPSCallableResult get() = ios - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - decode(strategy, ios.data(), decodeSettings) +public actual class HttpsCallableResult(internal val ios: FIRHTTPSCallableResult) { + + public actual inline fun data(): T = + decode(value = publicIos.data()) + + public actual inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit): T = + decode(strategy, publicIos.data(), buildSettings) } -actual class FirebaseFunctionsException(message: String, val code: FunctionsExceptionCode, val details: Any?) : FirebaseException(message) +public actual class FirebaseFunctionsException(message: String, public val code: FunctionsExceptionCode, public val details: Any?) : FirebaseException(message) -actual val FirebaseFunctionsException.code: FunctionsExceptionCode get() = code +public actual val FirebaseFunctionsException.code: FunctionsExceptionCode get() = code -actual val FirebaseFunctionsException.details: Any? get() = details +public actual val FirebaseFunctionsException.details: Any? get() = details -actual enum class FunctionsExceptionCode { +public actual enum class FunctionsExceptionCode { OK, CANCELLED, UNKNOWN, @@ -77,10 +93,11 @@ actual enum class FunctionsExceptionCode { INTERNAL, UNAVAILABLE, DATA_LOSS, - UNAUTHENTICATED + UNAUTHENTICATED, } -//todo uncomment once https://github.com/firebase/firebase-ios-sdk/issues/11862 fixed -fun NSError.toException() = when(domain) { + +// todo uncomment once https://github.com/firebase/firebase-ios-sdk/issues/11862 fixed +internal fun NSError.toException() = when (domain) { // FIRFunctionsErrorDomain -> when(code) { // FIRFunctionsErrorCodeOK -> FunctionsExceptionCode.OK // FIRFunctionsErrorCodeCancelled -> FunctionsExceptionCode.CANCELLED @@ -102,30 +119,34 @@ fun NSError.toException() = when(domain) { // else -> FunctionsExceptionCode.UNKNOWN // } else -> FunctionsExceptionCode.UNKNOWN -}.let { FirebaseFunctionsException(description!!, it, null/*userInfo[FIRFunctionsErrorDetails]*/) } +}.let { + FirebaseFunctionsException( + description!!, + it, + null, // userInfo[FIRFunctionsErrorDetails + ) +} -suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { +internal suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { val job = CompletableDeferred() - val callback = { error: NSError? -> - if(error == null) { + function { error -> + if (error == null) { job.complete(Unit) } else { job.completeExceptionally(error.toException()) } } - function(callback) job.await() } -suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { +internal suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { val job = CompletableDeferred() - val callback = { result: R?, error: NSError? -> - if(error == null) { + function { result, error -> + if (error == null) { job.complete(result) } else { job.completeExceptionally(error.toException()) } } - function(callback) return job.await() as R } 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 index ef4d3f6f0..219260d7e 100644 --- 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 @@ -6,5 +6,5 @@ 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 +public operator fun HttpsCallable.invoke(): Promise = asDynamic()() as Promise +public operator fun HttpsCallable.invoke(data: Any?): Promise = 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 index 7cf5388c6..c0898b956 100644 --- 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 @@ -6,19 +6,19 @@ 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) +public external fun connectFunctionsEmulator(functions: Functions, host: String, port: Int) -external fun getFunctions( +public external fun getFunctions( app: FirebaseApp? = definedExternally, - regionOrCustomDomain: String? = definedExternally + regionOrCustomDomain: String? = definedExternally, ): Functions -external fun httpsCallable(functions: Functions, name: String, options: Json?): HttpsCallable +public external fun httpsCallable(functions: Functions, name: String, options: Json?): HttpsCallable -external interface Functions +public external interface Functions -external interface HttpsCallableResult { - val data: Any? +public external interface HttpsCallableResult { + public val data: Any? } -external interface HttpsCallable +public 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 4db0c0284..5797002cb 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 @@ -2,63 +2,84 @@ * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ +@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER") + package dev.gitlive.firebase.functions -import dev.gitlive.firebase.* -import dev.gitlive.firebase.functions.externals.* +import dev.gitlive.firebase.DecodeSettings +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.functions.externals.Functions +import dev.gitlive.firebase.functions.externals.HttpsCallable +import dev.gitlive.firebase.functions.externals.connectFunctionsEmulator +import dev.gitlive.firebase.functions.externals.getFunctions +import dev.gitlive.firebase.functions.externals.httpsCallable +import dev.gitlive.firebase.functions.externals.invoke +import dev.gitlive.firebase.internal.decode +import dev.gitlive.firebase.js import kotlinx.coroutines.await import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy import kotlin.js.json +import kotlin.time.Duration +import kotlin.time.DurationUnit import dev.gitlive.firebase.functions.externals.HttpsCallableResult as JsHttpsCallableResult +import dev.gitlive.firebase.functions.js as publicJs -actual val Firebase.functions: FirebaseFunctions +public actual val Firebase.functions: FirebaseFunctions get() = rethrow { FirebaseFunctions(getFunctions()) } -actual fun Firebase.functions(region: String) = +public actual fun Firebase.functions(region: String): FirebaseFunctions = rethrow { FirebaseFunctions(getFunctions(regionOrCustomDomain = region)) } -actual fun Firebase.functions(app: FirebaseApp) = +public actual fun Firebase.functions(app: FirebaseApp): FirebaseFunctions = rethrow { FirebaseFunctions(getFunctions(app.js)) } -actual fun Firebase.functions(app: FirebaseApp, region: String) = +public actual fun Firebase.functions(app: FirebaseApp, region: String): FirebaseFunctions = rethrow { FirebaseFunctions(getFunctions(app.js, region)) } -actual class FirebaseFunctions internal constructor(val js: Functions) { - actual fun httpsCallable(name: String, timeout: Long?) = - rethrow { HttpsCallableReference(httpsCallable(js, name, timeout?.let { json("timeout" to timeout.toDouble()) })) } - - actual fun useEmulator(host: String, port: Int) = connectFunctionsEmulator(js, host, port) -} +public val FirebaseFunctions.js get() = js -@Suppress("UNCHECKED_CAST") -actual class HttpsCallableReference internal constructor(val js: HttpsCallable) : BaseHttpsCallableReference() { +public actual class FirebaseFunctions internal constructor(internal val js: Functions) { + public actual fun httpsCallable(name: String, timeout: Duration?): HttpsCallableReference = + rethrow { HttpsCallableReference(httpsCallable(js, name, timeout?.let { json("timeout" to timeout.toDouble(DurationUnit.MILLISECONDS)) }).native) } - actual suspend operator fun invoke() = - rethrow { HttpsCallableResult(js().await()) } + public actual fun useEmulator(host: String, port: Int) { + connectFunctionsEmulator(js, host, port) + } +} - override suspend fun invoke(encodedData: Any): HttpsCallableResult = rethrow { +@PublishedApi +internal actual data class NativeHttpsCallableReference(val js: HttpsCallable) { + actual suspend fun invoke(encodedData: Any): HttpsCallableResult = rethrow { HttpsCallableResult(js(encodedData).await()) } + actual suspend fun invoke(): HttpsCallableResult = rethrow { HttpsCallableResult(js().await()) } } -actual class HttpsCallableResult constructor(val js: JsHttpsCallableResult) { +@PublishedApi +internal val HttpsCallable.native: NativeHttpsCallableReference get() = NativeHttpsCallableReference(this) + +public val HttpsCallableReference.js: HttpsCallable get() = native.js + +public val HttpsCallableResult.js: JsHttpsCallableResult get() = js - actual inline fun data() = - rethrow { decode(value = js.data) } +public actual class HttpsCallableResult(internal val js: JsHttpsCallableResult) { - actual fun data(strategy: DeserializationStrategy, decodeSettings: DecodeSettings) = - rethrow { decode(strategy, js.data, decodeSettings) } + public actual inline fun data(): T = + rethrow { decode(value = publicJs.data) } + public actual inline fun data(strategy: DeserializationStrategy, buildSettings: DecodeSettings.Builder.() -> Unit): T = + rethrow { decode(strategy, publicJs.data, buildSettings) } } -actual class FirebaseFunctionsException(cause: Throwable, val code: FunctionsExceptionCode, val details: Any?) : FirebaseException(cause.message, cause) +public actual class FirebaseFunctionsException(cause: Throwable, public val code: FunctionsExceptionCode, public val details: Any?) : FirebaseException(cause.message, cause) -actual val FirebaseFunctionsException.code: FunctionsExceptionCode get() = code +public actual val FirebaseFunctionsException.code: FunctionsExceptionCode get() = code -actual val FirebaseFunctionsException.details: Any? get() = details +public actual val FirebaseFunctionsException.details: Any? get() = details -actual enum class FunctionsExceptionCode { +public actual enum class FunctionsExceptionCode { OK, CANCELLED, UNKNOWN, @@ -75,45 +96,48 @@ actual enum class FunctionsExceptionCode { INTERNAL, UNAVAILABLE, DATA_LOSS, - UNAUTHENTICATED + UNAUTHENTICATED, } -inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.functions.rethrow { function() } +@PublishedApi +internal inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.functions.rethrow { function() } -inline fun rethrow(function: () -> R): R { +@PublishedApi +internal inline fun rethrow(function: () -> R): R { try { return function() } catch (e: Exception) { throw e - } catch(e: dynamic) { + } catch (e: dynamic) { throw errorToException(e) } } -fun errorToException(e: dynamic) = (e?.code ?: e?.message ?: "") +@PublishedApi +internal fun errorToException(e: dynamic): FirebaseFunctionsException = (e?.code ?: e?.message ?: "") .toString() .lowercase() .let { when { - "cancelled" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.CANCELLED, e.details) - "invalid-argument" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.INVALID_ARGUMENT, e.details) - "deadline-exceeded" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.DEADLINE_EXCEEDED, e.details) - "not-found" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.NOT_FOUND, e.details) - "already-exists" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.ALREADY_EXISTS, e.details) - "permission-denied" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.PERMISSION_DENIED, e.details) - "resource-exhausted" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.RESOURCE_EXHAUSTED, e.details) - "failed-precondition" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.FAILED_PRECONDITION, e.details) - "aborted" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.ABORTED, e.details) - "out-of-range" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.OUT_OF_RANGE, e.details) - "unimplemented" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.UNIMPLEMENTED, e.details) - "internal" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.INTERNAL, e.details) - "unavailable" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.UNAVAILABLE, e.details) - "data-loss" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.DATA_LOSS, e.details) - "unauthenticated" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.UNAUTHENTICATED, e.details) - "unknown" in it -> FirebaseFunctionsException(e, FunctionsExceptionCode.UNKNOWN, e.details) + "cancelled" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.CANCELLED, e.details) + "invalid-argument" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.INVALID_ARGUMENT, e.details) + "deadline-exceeded" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.DEADLINE_EXCEEDED, e.details) + "not-found" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.NOT_FOUND, e.details) + "already-exists" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.ALREADY_EXISTS, e.details) + "permission-denied" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.PERMISSION_DENIED, e.details) + "resource-exhausted" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.RESOURCE_EXHAUSTED, e.details) + "failed-precondition" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.FAILED_PRECONDITION, e.details) + "aborted" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.ABORTED, e.details) + "out-of-range" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.OUT_OF_RANGE, e.details) + "unimplemented" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.UNIMPLEMENTED, e.details) + "internal" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.INTERNAL, e.details) + "unavailable" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.UNAVAILABLE, e.details) + "data-loss" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.DATA_LOSS, e.details) + "unauthenticated" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.UNAUTHENTICATED, e.details) + "unknown" in it -> FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.UNKNOWN, e.details) else -> { println("Unknown error code in ${JSON.stringify(e)}") - FirebaseFunctionsException(e, FunctionsExceptionCode.UNKNOWN, e.details) + FirebaseFunctionsException(e.unsafeCast(), FunctionsExceptionCode.UNKNOWN, e.details) } } } diff --git a/firebase-installations/api/android/firebase-installations.api b/firebase-installations/api/android/firebase-installations.api new file mode 100644 index 000000000..f4a4e0187 --- /dev/null +++ b/firebase-installations/api/android/firebase-installations.api @@ -0,0 +1,12 @@ +public final class dev/gitlive/firebase/installations/FirebaseInstallations { + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getId (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getToken (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/installations/InstallationsKt { + public static final fun getAndroid (Ldev/gitlive/firebase/installations/FirebaseInstallations;)Lcom/google/firebase/installations/FirebaseInstallations; + public static final fun getInstallations (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/installations/FirebaseInstallations; + public static final fun installations (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/installations/FirebaseInstallations; +} + diff --git a/firebase-installations/api/jvm/firebase-installations.api b/firebase-installations/api/jvm/firebase-installations.api new file mode 100644 index 000000000..f4a4e0187 --- /dev/null +++ b/firebase-installations/api/jvm/firebase-installations.api @@ -0,0 +1,12 @@ +public final class dev/gitlive/firebase/installations/FirebaseInstallations { + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getId (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getToken (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/gitlive/firebase/installations/InstallationsKt { + public static final fun getAndroid (Ldev/gitlive/firebase/installations/FirebaseInstallations;)Lcom/google/firebase/installations/FirebaseInstallations; + public static final fun getInstallations (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/installations/FirebaseInstallations; + public static final fun installations (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/installations/FirebaseInstallations; +} + diff --git a/firebase-installations/build.gradle.kts b/firebase-installations/build.gradle.kts index 22fde5be0..c98103ac7 100644 --- a/firebase-installations/build.gradle.kts +++ b/firebase-installations/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -10,6 +13,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -25,15 +29,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -47,10 +47,22 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -59,38 +71,23 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseInstallations" } noPodspec() pod("FirebaseInstallations") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") } } } @@ -98,32 +95,26 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true if (name.lowercase().contains("ios")) { optIn("kotlinx.cinterop.ExperimentalForeignApi") @@ -146,7 +137,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-installations-ktx") + api(libs.google.firebase.installations.ktx) } } @@ -162,6 +153,12 @@ if (project.property("firebase-installations.skipIosTests") == "true") { } } +if (project.property("firebase-installations.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-installations.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-installations/documentation.md b/firebase-installations/documentation.md new file mode 100644 index 000000000..73a95b70b --- /dev/null +++ b/firebase-installations/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-installations +This module is a direct forward of the Firebase Installations library. It provides the main functionality, like getting an installation ID. \ No newline at end of file diff --git a/firebase-installations/package.json b/firebase-installations/package.json index af5d99226..bd98df6a8 100644 --- a/firebase-installations/package.json +++ b/firebase-installations/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-installations", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.8.20", "kotlinx-coroutines-core": "1.6.4" diff --git a/firebase-installations/src/androidMain/kotlin/dev/gitlive/firebase/installations/installations.kt b/firebase-installations/src/androidMain/kotlin/dev/gitlive/firebase/installations/installations.kt index f0fef861c..f9c0d3224 100644 --- a/firebase-installations/src/androidMain/kotlin/dev/gitlive/firebase/installations/installations.kt +++ b/firebase-installations/src/androidMain/kotlin/dev/gitlive/firebase/installations/installations.kt @@ -2,22 +2,24 @@ package dev.gitlive.firebase.installations import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android import kotlinx.coroutines.tasks.await -actual val Firebase.installations +public val FirebaseInstallations.android: com.google.firebase.installations.FirebaseInstallations get() = com.google.firebase.installations.FirebaseInstallations.getInstance() + +public actual val Firebase.installations: FirebaseInstallations get() = FirebaseInstallations(com.google.firebase.installations.FirebaseInstallations.getInstance()) -actual fun Firebase.installations(app: FirebaseApp) - = FirebaseInstallations(com.google.firebase.installations.FirebaseInstallations.getInstance(app.android)) +public actual fun Firebase.installations(app: FirebaseApp): FirebaseInstallations = FirebaseInstallations(com.google.firebase.installations.FirebaseInstallations.getInstance(app.android)) -actual class FirebaseInstallations internal constructor(val android: com.google.firebase.installations.FirebaseInstallations) { +public actual class FirebaseInstallations internal constructor(internal val android: com.google.firebase.installations.FirebaseInstallations) { - actual suspend fun delete() = android.delete().await().let { } + public actual suspend fun delete(): Unit = android.delete().await().let { } - actual suspend fun getId(): String = android.id.await() + public actual suspend fun getId(): String = android.id.await() - actual suspend fun getToken(forceRefresh: Boolean): String = + public actual suspend fun getToken(forceRefresh: Boolean): String = android.getToken(forceRefresh).await().token } -actual typealias FirebaseInstallationsException = com.google.firebase.installations.FirebaseInstallationsException +public actual typealias FirebaseInstallationsException = com.google.firebase.installations.FirebaseInstallationsException diff --git a/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt b/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt index 1ab269920..0cbea09ec 100644 --- a/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt +++ b/firebase-installations/src/commonMain/kotlin/dev/gitlive/firebase/installations/installations.kt @@ -3,18 +3,47 @@ package dev.gitlive.firebase.installations import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy -expect val Firebase.installations: FirebaseInstallations +/** Returns the [FirebaseInstallations] instance of the default [FirebaseApp]. */ +public expect val Firebase.installations: FirebaseInstallations -expect fun Firebase.installations(app: FirebaseApp): FirebaseInstallations +/** Returns the [FirebaseInstallations] instance of a given [FirebaseApp]. */ +public expect fun Firebase.installations(app: FirebaseApp): FirebaseInstallations -expect class FirebaseInstallations { - suspend fun delete() - suspend fun getId(): String - suspend fun getToken(forceRefresh: Boolean): String -} +/** + * Entry point for Firebase installations. + * + * The Firebase installations service: + * - provides a unique identifier for a Firebase installation + * - provides an auth token for a Firebase installation + * - provides a API to perform GDPR-compliant deletion of a Firebase installation. + */ +public expect class FirebaseInstallations { + /** + * Call to delete this Firebase app installation from the Firebase backend. This call may cause + * Firebase Cloud Messaging, Firebase Remote Config, Firebase A/B Testing, or Firebase In-App + * Messaging to not function properly. + */ + public suspend fun delete() + + /** + * Returns a globally unique identifier of this Firebase app installation. This is a url-safe + * base64 string of a 128-bit integer. + */ + public suspend fun getId(): String -expect class FirebaseInstallationsException: FirebaseException + /** + * Returns a valid authentication token for the Firebase installation. Generates a new token if + * one doesn't exist, is expired, or is about to expire. + * + * Should only be called if the Firebase installation is registered. + * + * @param forceRefresh Options to get an auth token either by force refreshing or not. + */ + public suspend fun getToken(forceRefresh: Boolean): String +} +/** + * Exception that gets thrown when an operation on Firebase Installations fails. + */ +public expect class FirebaseInstallationsException : FirebaseException diff --git a/firebase-installations/src/iosMain/kotlin/dev/gitlive/firebase/installations/installations.kt b/firebase-installations/src/iosMain/kotlin/dev/gitlive/firebase/installations/installations.kt index f133a7228..00f61cd02 100644 --- a/firebase-installations/src/iosMain/kotlin/dev/gitlive/firebase/installations/installations.kt +++ b/firebase-installations/src/iosMain/kotlin/dev/gitlive/firebase/installations/installations.kt @@ -4,36 +4,38 @@ import cocoapods.FirebaseInstallations.* import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.ios as publicIos import kotlinx.coroutines.CompletableDeferred import platform.Foundation.* -actual val Firebase.installations +public val FirebaseInstallations.ios: FIRInstallations get() = FIRInstallations.installations() + +public actual val Firebase.installations: FirebaseInstallations get() = FirebaseInstallations(FIRInstallations.installations()) -@Suppress("CAST_NEVER_SUCCEEDS") -actual fun Firebase.installations(app: FirebaseApp): FirebaseInstallations = FirebaseInstallations( - FIRInstallations.installationsWithApp(app.ios as objcnames.classes.FIRApp) +public actual fun Firebase.installations(app: FirebaseApp): FirebaseInstallations = FirebaseInstallations( + FIRInstallations.installationsWithApp(app.publicIos as objcnames.classes.FIRApp), ) -actual class FirebaseInstallations internal constructor(val ios: FIRInstallations) { +public actual class FirebaseInstallations internal constructor(internal val ios: FIRInstallations) { - actual suspend fun delete() = ios.await { deleteWithCompletion(completion = it) } + public actual suspend fun delete(): Unit = ios.await { deleteWithCompletion(completion = it) } - actual suspend fun getId(): String = ios.awaitResult { installationIDWithCompletion(completion = it) } + public actual suspend fun getId(): String = ios.awaitResult { installationIDWithCompletion(completion = it) } - actual suspend fun getToken(forceRefresh: Boolean): String { + public actual suspend fun getToken(forceRefresh: Boolean): String { val result: FIRInstallationsAuthTokenResult = ios.awaitResult { authTokenForcingRefresh(forceRefresh = forceRefresh, completion = it) } return result.authToken } } -actual class FirebaseInstallationsException(message: String): FirebaseException(message) +public actual class FirebaseInstallationsException(message: String) : FirebaseException(message) -suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { +internal suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { val job = CompletableDeferred() function { error -> - if(error == null) { + if (error == null) { job.complete(Unit) } else { job.completeExceptionally(FirebaseInstallationsException(error.toString())) @@ -42,10 +44,10 @@ suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Uni job.await() } -suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { +internal suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { val job = CompletableDeferred() function { result, error -> - if(error == null) { + if (error == null) { job.complete(result) } else { job.completeExceptionally(FirebaseInstallationsException(error.toString())) 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 index c2b236f76..c88f343cd 100644 --- 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 @@ -6,12 +6,12 @@ package dev.gitlive.firebase.installations.externals import dev.gitlive.firebase.externals.FirebaseApp import kotlin.js.Promise -external fun delete(installations: Installations): Promise +public external fun delete(installations: Installations): Promise -external fun getId(installations: Installations): Promise +public external fun getId(installations: Installations): Promise -external fun getInstallations(app: FirebaseApp? = definedExternally): Installations +public external fun getInstallations(app: FirebaseApp? = definedExternally): Installations -external fun getToken(installations: Installations, forceRefresh: Boolean): Promise +public external fun getToken(installations: Installations, forceRefresh: Boolean): Promise -external interface Installations +public 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 7329c3626..de34f3637 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,35 +1,40 @@ package dev.gitlive.firebase.installations -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.installations.externals.* +import dev.gitlive.firebase.js import kotlinx.coroutines.await -actual val Firebase.installations +public actual val Firebase.installations: FirebaseInstallations get() = rethrow { FirebaseInstallations(getInstallations()) } -actual fun Firebase.installations(app: FirebaseApp) = +public actual fun Firebase.installations(app: FirebaseApp): FirebaseInstallations = rethrow { FirebaseInstallations(getInstallations(app.js)) } -actual class FirebaseInstallations internal constructor(val js: Installations) { +public val FirebaseInstallations.js get() = js - actual suspend fun delete() = rethrow { delete(js).await() } +public actual class FirebaseInstallations internal constructor(internal val js: Installations) { - actual suspend fun getId(): String = rethrow { getId(js).await() } + public actual suspend fun delete(): Unit = rethrow { delete(js).await() } - actual suspend fun getToken(forceRefresh: Boolean): String = + public actual suspend fun getId(): String = rethrow { getId(js).await() } + + public actual suspend fun getToken(forceRefresh: Boolean): String = rethrow { getToken(js, forceRefresh).await() } } -actual open class FirebaseInstallationsException(code: String?, cause: Throwable): FirebaseException(code, cause) +public actual open class FirebaseInstallationsException(code: String?, cause: Throwable) : FirebaseException(code, cause) -inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.installations.rethrow { function() } +internal inline fun T.rethrow(function: T.() -> R): R = dev.gitlive.firebase.installations.rethrow { function() } -inline fun rethrow(function: () -> R): R { +internal inline fun rethrow(function: () -> R): R { try { return function() } catch (e: Exception) { throw e - } catch(e: dynamic) { - throw FirebaseInstallationsException(e.code as String?, e) + } catch (e: dynamic) { + throw FirebaseInstallationsException(e.code.unsafeCast(), e.unsafeCast()) } } diff --git a/firebase-messaging/api/android/firebase-messaging.api b/firebase-messaging/api/android/firebase-messaging.api new file mode 100644 index 000000000..d9e61245a --- /dev/null +++ b/firebase-messaging/api/android/firebase-messaging.api @@ -0,0 +1,13 @@ +public final class dev/gitlive/firebase/messaging/FirebaseMessaging { + public fun (Lcom/google/firebase/messaging/FirebaseMessaging;)V + public final fun deleteToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun subscribeToTopic (Ljava/lang/String;)V + public final fun unsubscribeFromTopic (Ljava/lang/String;)V +} + +public final class dev/gitlive/firebase/messaging/android { + public static final fun getAndroid (Ldev/gitlive/firebase/messaging/FirebaseMessaging;)Lcom/google/firebase/messaging/FirebaseMessaging; + public static final fun getMessaging (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/messaging/FirebaseMessaging; +} + diff --git a/firebase-messaging/api/jvm/firebase-messaging.api b/firebase-messaging/api/jvm/firebase-messaging.api new file mode 100644 index 000000000..be6a0d6a1 --- /dev/null +++ b/firebase-messaging/api/jvm/firebase-messaging.api @@ -0,0 +1,12 @@ +public final class dev/gitlive/firebase/messaging/FirebaseMessaging { + public fun ()V + public final fun deleteToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun subscribeToTopic (Ljava/lang/String;)V + public final fun unsubscribeFromTopic (Ljava/lang/String;)V +} + +public final class dev/gitlive/firebase/messaging/android { + public static final fun getMessaging (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/messaging/FirebaseMessaging; +} + diff --git a/firebase-messaging/build.gradle.kts b/firebase-messaging/build.gradle.kts new file mode 100644 index 000000000..f5bf9a81b --- /dev/null +++ b/firebase-messaging/build.gradle.kts @@ -0,0 +1,171 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree + +/* + * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. + */ + +version = project.property("firebase-messaging.version") as String + +plugins { + id("com.android.library") + kotlin("multiplatform") + kotlin("native.cocoapods") + id("testOptionsConvention") +} + +android { + val minSdkVersion: Int by project + val compileSdkVersion: Int by project + + compileSdk = compileSdkVersion + namespace = "dev.gitlive.firebase.messaging" + + defaultConfig { + minSdk = minSdkVersion + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + testOptions.configureTestOptions(project) + packaging { + resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") + resources.pickFirsts.add("META-INF/AL2.0") + resources.pickFirsts.add("META-INF/LGPL2.1") + } + lint { + abortOnError = false + } +} + +val supportIosTarget = project.property("skipIosTarget") != "true" + +kotlin { + explicitApi() + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + targets.configureEach { + compilations.configureEach { + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } + } + } + + @Suppress("OPT_IN_USAGE") + androidTarget { + instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + publishAllLibraryVariants() + } + + jvm() + + if (supportIosTarget) { + iosArm64() + iosX64() + iosSimulatorArm64() + cocoapods { + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() + framework { + baseName = "FirebaseMessaging" + } + noPodspec() + pod("FirebaseMessaging") { + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") + } + } + } + + js(IR) { + useCommonJs() + nodejs { + testTask { + useKarma { + useChromeHeadless() + } + } + } + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + } + + sourceSets { + all { + languageSettings.apply { + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() + progressiveMode = true + optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") + if (name.lowercase().contains("ios")) { + optIn("kotlinx.cinterop.ExperimentalForeignApi") + optIn("kotlinx.cinterop.BetaInteropApi") + } + } + } + + getByName("commonMain") { + dependencies { + api(project(":firebase-app")) + implementation(project(":firebase-common")) + } + } + + getByName("commonTest") { + dependencies { + implementation(project(":test-utils")) + } + } + + getByName("androidMain") { + dependencies { + api(libs.google.firebase.messaging) + } + } + } +} + +if (project.property("firebase-messaging.skipIosTests") == "true") { + tasks.forEach { + if (it.name.contains("ios", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-messaging.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +if (project.property("firebase-messaging.skipJsTests") == "true") { + tasks.forEach { + if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) +} diff --git a/firebase-messaging/documentation.md b/firebase-messaging/documentation.md new file mode 100644 index 000000000..969326676 --- /dev/null +++ b/firebase-messaging/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-messaging +This module is a direct forward of the Firebase Messaging library. It provides the main functionality, like subscribing to topics. \ No newline at end of file diff --git a/firebase-messaging/firebase_messaging.podspec b/firebase-messaging/firebase_messaging.podspec new file mode 100644 index 000000000..a6535f70e --- /dev/null +++ b/firebase-messaging/firebase_messaging.podspec @@ -0,0 +1,39 @@ +Pod::Spec.new do |spec| + spec.name = 'firebase_messaging' + spec.version = '1.8.1' + spec.homepage = '' + spec.source = { :http=> ''} + spec.authors = '' + spec.license = '' + spec.summary = '' + spec.vendored_frameworks = 'build/cocoapods/framework/firebase_messaging.framework' + spec.libraries = 'c++' + + + + spec.pod_target_xcconfig = { + 'KOTLIN_PROJECT_PATH' => ':firebase-messaging', + 'PRODUCT_MODULE_NAME' => 'firebase_messaging', + } + + spec.script_phases = [ + { + :name => 'Build firebase_messaging', + :execution_position => :before_compile, + :shell_path => '/bin/sh', + :script => <<-SCRIPT + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" + exit 0 + fi + set -ev + REPO_ROOT="$PODS_TARGET_SRCROOT" + "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ + -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ + -Pkotlin.native.cocoapods.archs="$ARCHS" \ + -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" + SCRIPT + } + ] + +end \ No newline at end of file diff --git a/firebase-messaging/package.json b/firebase-messaging/package.json new file mode 100644 index 000000000..057b92487 --- /dev/null +++ b/firebase-messaging/package.json @@ -0,0 +1,31 @@ +{ + "name": "@gitlive/firebase-messaging", + "version": "2.0.0", + "description": "Wrapper around firebase for usage in Kotlin Multiplatform projects", + "main": "firebase-messaging.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/GitLiveApp/firebase-kotlin-sdk.git" + }, + "keywords": [ + "kotlin", + "multiplatform", + "kotlin-js", + "firebase" + ], + "author": "dev.gitlive", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/GitLiveApp/firebase-kotlin-sdk/issues" + }, + "homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk", + "dependencies": { + "@gitlive/firebase-app": "2.0.0", + "firebase": "9.19.1", + "kotlin": "1.8.20", + "kotlinx-coroutines-core": "1.6.4" + } +} diff --git a/firebase-messaging/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..eef7d337e --- /dev/null +++ b/firebase-messaging/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,28 @@ +package dev.gitlive.firebase.messaging + +import androidx.test.platform.app.InstrumentationRegistry +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import kotlin.test.BeforeTest + +class AndroidInstrumentedFirebaseMessagingTest : FirebaseMessagingTest() { + + private val context = InstrumentationRegistry.getInstrumentation().context + + @BeforeTest + fun initializeFirebase() { + Firebase.apps(context).firstOrNull() ?: Firebase.initialize( + context, + FirebaseOptions( + applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", + apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", + databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", + storageBucket = "fir-kotlin-sdk.appspot.com", + projectId = "fir-kotlin-sdk", + gcmSenderId = "846484016111", + ), + ) + } +} diff --git a/firebase-messaging/src/androidMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/androidMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..a46dfaace --- /dev/null +++ b/firebase-messaging/src/androidMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,27 @@ +@file:JvmName("android") + +package dev.gitlive.firebase.messaging + +import dev.gitlive.firebase.Firebase +import kotlinx.coroutines.tasks.await + +public val FirebaseMessaging.android: com.google.firebase.messaging.FirebaseMessaging get() = com.google.firebase.messaging.FirebaseMessaging.getInstance() + +public actual val Firebase.messaging: FirebaseMessaging + get() = FirebaseMessaging(com.google.firebase.messaging.FirebaseMessaging.getInstance()) + +public actual class FirebaseMessaging(internal val android: com.google.firebase.messaging.FirebaseMessaging) { + public actual fun subscribeToTopic(topic: String) { + android.subscribeToTopic(topic) + } + + public actual fun unsubscribeFromTopic(topic: String) { + android.unsubscribeFromTopic(topic) + } + + public actual suspend fun getToken(): String = android.token.await() + + public actual suspend fun deleteToken() { + android.deleteToken().await() + } +} diff --git a/firebase-messaging/src/commonMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/commonMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..86134852f --- /dev/null +++ b/firebase-messaging/src/commonMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,36 @@ +package dev.gitlive.firebase.messaging + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp + +/** Returns the [FirebaseMessaging] instance of the default [FirebaseApp]. */ +public expect val Firebase.messaging: FirebaseMessaging + +/** + * Top level [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) + * singleton that provides methods for subscribing to topics and sending upstream messages. + */ +public expect class FirebaseMessaging { + /** + * Subscribe to a topic. + * @param topic The topic to subscribe to. + */ + public fun subscribeToTopic(topic: String) + + /** + * Unsubscribe from a topic. + * @param topic The topic to unsubscribe from. + */ + public fun unsubscribeFromTopic(topic: String) + + /** + * Get FCM token for client + * @return [String] FCM token + */ + public suspend fun getToken(): String + + /** + * Delete FCM token for client + */ + public suspend fun deleteToken() +} diff --git a/firebase-messaging/src/commonTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/commonTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..a098cb24f --- /dev/null +++ b/firebase-messaging/src/commonTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,13 @@ +package dev.gitlive.firebase.messaging + +import dev.gitlive.firebase.Firebase +import kotlin.test.Test +import kotlin.test.assertNotNull + +abstract class FirebaseMessagingTest { + + @Test + fun initialization() { + assertNotNull(Firebase.messaging) + } +} diff --git a/firebase-messaging/src/iosMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/iosMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..dd707d5a8 --- /dev/null +++ b/firebase-messaging/src/iosMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,51 @@ +package dev.gitlive.firebase.messaging + +import cocoapods.FirebaseMessaging.FIRMessaging +import dev.gitlive.firebase.Firebase +import kotlinx.coroutines.CompletableDeferred +import platform.Foundation.NSError + +public val FirebaseMessaging.ios: FIRMessaging get() = FIRMessaging.messaging() + +public actual val Firebase.messaging: FirebaseMessaging + get() = FirebaseMessaging(FIRMessaging.messaging()) + +public actual class FirebaseMessaging(internal val ios: FIRMessaging) { + public actual fun subscribeToTopic(topic: String) { + ios.subscribeToTopic(topic) + } + + public actual fun unsubscribeFromTopic(topic: String) { + ios.unsubscribeFromTopic(topic) + } + + public actual suspend fun getToken(): String = awaitResult { ios.tokenWithCompletion(it) } + + public actual suspend fun deleteToken() { + await { ios.deleteTokenWithCompletion(it) } + } +} + +public suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { + val job = CompletableDeferred() + function { error -> + if (error == null) { + job.complete(Unit) + } else { + job.completeExceptionally(Exception(error.toString())) + } + } + job.await() +} + +public suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { + val job = CompletableDeferred() + function { result, error -> + if (error == null) { + job.complete(result) + } else { + job.completeExceptionally(Exception(error.toString())) + } + } + return job.await() as R +} diff --git a/firebase-messaging/src/iosTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/iosTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..1a64584ed --- /dev/null +++ b/firebase-messaging/src/iosTest/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,3 @@ +package dev.gitlive.firebase.messaging + +class IOSFirebaseMessagingTest : FirebaseMessagingTest() diff --git a/firebase-messaging/src/jsMain/kotlin/dev/gitlive/firebase/messaging/externals/messaging.kt b/firebase-messaging/src/jsMain/kotlin/dev/gitlive/firebase/messaging/externals/messaging.kt new file mode 100644 index 000000000..323eac8de --- /dev/null +++ b/firebase-messaging/src/jsMain/kotlin/dev/gitlive/firebase/messaging/externals/messaging.kt @@ -0,0 +1,14 @@ +package dev.gitlive.firebase.messaging.externals + +import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Promise + +public external fun getMessaging( + app: FirebaseApp? = definedExternally, +): Messaging + +public external fun getToken(messaging: Messaging = definedExternally, options: dynamic = definedExternally): Promise + +public external fun deleteToken(messaging: Messaging = definedExternally): Promise + +public external interface Messaging diff --git a/firebase-messaging/src/jsMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/jsMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..6dac0ed74 --- /dev/null +++ b/firebase-messaging/src/jsMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,31 @@ +package dev.gitlive.firebase.messaging + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.messaging.externals.Messaging +import dev.gitlive.firebase.messaging.externals.getMessaging +import kotlinx.coroutines.await + +public actual val Firebase.messaging: FirebaseMessaging + get() = FirebaseMessaging(getMessaging()) + +public val FirebaseMessaging.js: Messaging get() = js + +public actual class FirebaseMessaging(internal val js: Messaging) { + public actual fun subscribeToTopic(topic: String) { + // This is not supported in the JS SDK + // https://firebase.google.com/docs/reference/js/messaging_.md#@firebase/messaging + throw NotImplementedError("Subscribing to topics is not supported in the JS SDK") + } + + public actual fun unsubscribeFromTopic(topic: String) { + // This is not supported in the JS SDK + // https://firebase.google.com/docs/reference/js/messaging_.md#@firebase/messaging + throw NotImplementedError("Unsubscribing from topics is not supported in the JS SDK") + } + + public actual suspend fun getToken(): String = dev.gitlive.firebase.messaging.externals.getToken(js).await() + + public actual suspend fun deleteToken() { + dev.gitlive.firebase.messaging.externals.deleteToken(js).await() + } +} diff --git a/firebase-messaging/src/jvmMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt b/firebase-messaging/src/jvmMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt new file mode 100644 index 000000000..eeef9c26f --- /dev/null +++ b/firebase-messaging/src/jvmMain/kotlin/dev/gitlive/firebase/messaging/messaging.kt @@ -0,0 +1,26 @@ +@file:JvmName("android") + +package dev.gitlive.firebase.messaging + +import dev.gitlive.firebase.Firebase + +public actual val Firebase.messaging: FirebaseMessaging + get() = TODO("Not yet implemented") + +public actual class FirebaseMessaging { + public actual fun subscribeToTopic(topic: String) { + TODO("Not yet implemented") + } + + public actual fun unsubscribeFromTopic(topic: String) { + TODO("Not yet implemented") + } + + public actual suspend fun getToken(): String { + TODO("Not yet implemented") + } + + public actual suspend fun deleteToken() { + TODO("Not yet implemented") + } +} diff --git a/firebase-perf/api/android/firebase-perf.api b/firebase-perf/api/android/firebase-perf.api new file mode 100644 index 000000000..9fe0d96ca --- /dev/null +++ b/firebase-perf/api/android/firebase-perf.api @@ -0,0 +1,41 @@ +public final class dev/gitlive/firebase/perf/FirebasePerformance { + public fun (Lcom/google/firebase/perf/FirebasePerformance;)V + public final fun isPerformanceCollectionEnabled ()Z + public final fun newTrace (Ljava/lang/String;)Ldev/gitlive/firebase/perf/metrics/Trace; + public final fun setPerformanceCollectionEnabled (Z)V +} + +public class dev/gitlive/firebase/perf/FirebasePerformanceException : com/google/firebase/FirebaseException { + public fun (Ljava/lang/String;)V +} + +public final class dev/gitlive/firebase/perf/PerformanceKt { + public static final fun getAndroid (Ldev/gitlive/firebase/perf/FirebasePerformance;)Lcom/google/firebase/perf/FirebasePerformance; + public static final fun getPerformance (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/perf/FirebasePerformance; + public static final fun performance (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/perf/FirebasePerformance; +} + +public final class dev/gitlive/firebase/perf/metrics/Trace { + public final fun getAttribute (Ljava/lang/String;)Ljava/lang/String; + public final fun getAttributes ()Ljava/util/Map; + public final fun getLongMetric (Ljava/lang/String;)J + public final fun incrementMetric (Ljava/lang/String;J)V + public final fun putAttribute (Ljava/lang/String;Ljava/lang/String;)V + public final fun putMetric (Ljava/lang/String;J)V + public final fun removeAttribute (Ljava/lang/String;)V + public final fun start ()V + public final fun stop ()V + public final fun updateSession (Ldev/gitlive/firebase/perf/session/PerfSession;)V +} + +public final class dev/gitlive/firebase/perf/metrics/TraceKt { + public static final fun getAndroid (Ldev/gitlive/firebase/perf/metrics/Trace;)Lcom/google/firebase/perf/metrics/Trace; +} + +public final class dev/gitlive/firebase/perf/session/PerfSession { +} + +public final class dev/gitlive/firebase/perf/session/PerfSessionKt { + public static final fun getAndroid (Ldev/gitlive/firebase/perf/session/PerfSession;)Lcom/google/firebase/perf/session/PerfSession; +} + diff --git a/firebase-perf/api/jvm/firebase-perf.api b/firebase-perf/api/jvm/firebase-perf.api new file mode 100644 index 000000000..e3e8c9056 --- /dev/null +++ b/firebase-perf/api/jvm/firebase-perf.api @@ -0,0 +1,24 @@ +public final class dev/gitlive/firebase/perf/FirebasePerformance { + public fun ()V + public final fun isPerformanceCollectionEnabled ()Z + public final fun newTrace (Ljava/lang/String;)Ldev/gitlive/firebase/perf/metrics/Trace; + public final fun setPerformanceCollectionEnabled (Z)V +} + +public class dev/gitlive/firebase/perf/FirebasePerformanceException : com/google/firebase/FirebaseException { +} + +public final class dev/gitlive/firebase/perf/Performance_jvmKt { + public static final fun getPerformance (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/perf/FirebasePerformance; + public static final fun performance (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/perf/FirebasePerformance; +} + +public final class dev/gitlive/firebase/perf/metrics/Trace { + public fun ()V + public final fun getLongMetric (Ljava/lang/String;)J + public final fun incrementMetric (Ljava/lang/String;J)V + public final fun putMetric (Ljava/lang/String;J)V + public final fun start ()V + public final fun stop ()V +} + diff --git a/firebase-perf/build.gradle.kts b/firebase-perf/build.gradle.kts index 8025e192c..c62c9f5c5 100644 --- a/firebase-perf/build.gradle.kts +++ b/firebase-perf/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -10,6 +13,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") + id("testOptionsConvention") } android { @@ -26,15 +30,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -48,10 +48,22 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -60,38 +72,23 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebasePerformance" } noPodspec() pod("FirebasePerformance") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() + extraOpts += listOf("-compiler-option", "-fmodules") } } } @@ -99,23 +96,19 @@ kotlin { js(IR) { useCommonJs() browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") if (name.lowercase().contains("ios")) { @@ -139,7 +132,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-perf-ktx") + api(libs.google.firebase.perf.ktx) } } @@ -155,6 +148,12 @@ if (project.property("firebase-perf.skipIosTests") == "true") { } } +if (project.property("firebase-perf.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-perf.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-perf/documentation.md b/firebase-perf/documentation.md new file mode 100644 index 000000000..ce067f48a --- /dev/null +++ b/firebase-perf/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-perf +This module is a direct forward of the Firebase Performance library. It provides the main functionality, like tracking performance. \ No newline at end of file diff --git a/firebase-perf/package.json b/firebase-perf/package.json index e53193deb..cd6e77684 100644 --- a/firebase-perf/package.json +++ b/firebase-perf/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-perf", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index 6768f9247..5db4a766f 100644 --- a/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,6 +1,9 @@ package dev.gitlive.firebase.perf.metrics -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize import dev.gitlive.firebase.perf.FirebasePerformance import dev.gitlive.firebase.perf.context import dev.gitlive.firebase.perf.performance @@ -20,15 +23,15 @@ class AndroidTraceTest { @BeforeTest fun initializeFirebase() { val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize( - dev.gitlive.firebase.perf.context, + context, FirebaseOptions( applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) performance = Firebase.performance(app) @@ -84,4 +87,4 @@ class AndroidTraceTest { assertEquals("Test Put Attribute Value", trace.getAttribute("Test_Put_Attribute")) trace.stop() } -} \ No newline at end of file +} diff --git a/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/performance.kt index 045b947fe..3317b9f48 100644 --- a/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -3,12 +3,11 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.perf import androidx.test.platform.app.InstrumentationRegistry -actual val emulatorHost: String = "10.0.2.2" - actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) diff --git a/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index 3375b2cd4..dca1211cc 100644 --- a/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,27 +1,43 @@ package dev.gitlive.firebase.perf.metrics -import com.google.firebase.perf.metrics.Trace +import com.google.firebase.perf.metrics.Trace as AndroidTrace import dev.gitlive.firebase.perf.session.PerfSession -actual class Trace internal constructor(private val android: Trace) { +public val Trace.android: AndroidTrace get() = android - actual fun start() = android.start() +public actual class Trace internal constructor(internal val android: AndroidTrace) { - actual fun stop() = android.stop() + public actual fun start() { + android.start() + } - actual fun getLongMetric(metricName: String): Long = android.getLongMetric(metricName) + public actual fun stop() { + android.stop() + } - actual fun incrementMetric(metricName: String, incrementBy: Long) = android.incrementMetric(metricName, incrementBy) + public actual fun getLongMetric(metricName: String): Long = android.getLongMetric(metricName) - actual fun putMetric(metricName: String, value: Long) = android.putMetric(metricName, value) + public actual fun incrementMetric(metricName: String, incrementBy: Long) { + android.incrementMetric(metricName, incrementBy) + } - fun getAttributes(): Map = android.attributes + public actual fun putMetric(metricName: String, value: Long) { + android.putMetric(metricName, value) + } - fun getAttribute(attribute: String): String? = android.getAttribute(attribute) + public fun getAttributes(): Map = android.attributes - fun putAttribute(attribute: String, value: String) = android.putAttribute(attribute, value) + public fun getAttribute(attribute: String): String? = android.getAttribute(attribute) - fun removeAttribute(attribute: String) = android.removeAttribute(attribute) + public fun putAttribute(attribute: String, value: String) { + android.putAttribute(attribute, value) + } - fun updateSession(session: PerfSession) = android.updateSession(session.android) -} \ No newline at end of file + public fun removeAttribute(attribute: String) { + android.removeAttribute(attribute) + } + + public fun updateSession(session: PerfSession) { + android.updateSession(session.android) + } +} diff --git a/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/performance.kt index ea5beb3fb..a4e4d3d47 100644 --- a/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -1,25 +1,27 @@ package dev.gitlive.firebase.perf -import com.google.firebase.FirebaseException import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android as publicAndroid import dev.gitlive.firebase.perf.metrics.Trace -actual val Firebase.performance get() = +public val FirebasePerformance.android: com.google.firebase.perf.FirebasePerformance get() = com.google.firebase.perf.FirebasePerformance.getInstance() + +public actual val Firebase.performance: FirebasePerformance get() = FirebasePerformance(com.google.firebase.perf.FirebasePerformance.getInstance()) -actual fun Firebase.performance(app: FirebaseApp) = - FirebasePerformance(app.android.get(com.google.firebase.perf.FirebasePerformance::class.java)) +public actual fun Firebase.performance(app: FirebaseApp): FirebasePerformance = + FirebasePerformance(app.publicAndroid.get(com.google.firebase.perf.FirebasePerformance::class.java)) -actual class FirebasePerformance(val android: com.google.firebase.perf.FirebasePerformance){ +public actual class FirebasePerformance(internal val android: com.google.firebase.perf.FirebasePerformance) { - actual fun newTrace(traceName: String): Trace = Trace(android.newTrace(traceName)) + public actual fun newTrace(traceName: String): Trace = Trace(android.newTrace(traceName)) - actual fun isPerformanceCollectionEnabled() = android.isPerformanceCollectionEnabled + public actual fun isPerformanceCollectionEnabled(): Boolean = android.isPerformanceCollectionEnabled - actual fun setPerformanceCollectionEnabled(enable: Boolean) { + public actual fun setPerformanceCollectionEnabled(enable: Boolean) { android.isPerformanceCollectionEnabled = enable } } -actual open class FirebasePerformanceException(message: String) : FirebaseException(message) +public actual open class FirebasePerformanceException(message: String) : com.google.firebase.FirebaseException(message) diff --git a/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/session/PerfSession.kt b/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/session/PerfSession.kt index 80e0a2c0d..a861992a6 100644 --- a/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/session/PerfSession.kt +++ b/firebase-perf/src/androidMain/kotlin/dev/gitlive/firebase/perf/session/PerfSession.kt @@ -1,6 +1,7 @@ package dev.gitlive.firebase.perf.session -import com.google.firebase.perf.session.PerfSession +import com.google.firebase.perf.session.PerfSession as AndroidPerfSession -class PerfSession internal constructor(val android: PerfSession) { -} \ No newline at end of file +public val PerfSession.android: AndroidPerfSession get() = android + +public class PerfSession internal constructor(internal val android: AndroidPerfSession) diff --git a/firebase-perf/src/androidUnitTest/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/androidUnitTest/kotlin/dev/gitlive/firebase/perf/performance.kt index a69661aac..ec9880598 100644 --- a/firebase-perf/src/androidUnitTest/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/androidUnitTest/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -3,12 +3,11 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.perf import org.junit.Ignore -actual val emulatorHost: String = "10.0.2.2" - actual val context: Any = "" actual typealias IgnoreForAndroidUnitTest = Ignore diff --git a/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index eff704ef2..81b4ed7c1 100644 --- a/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,10 +1,43 @@ package dev.gitlive.firebase.perf.metrics -expect class Trace { - - fun start() - fun stop() - fun getLongMetric(metricName: String): Long - fun incrementMetric(metricName: String, incrementBy: Long) - fun putMetric(metricName: String, value: Long) -} \ No newline at end of file +/** Trace allows you to set beginning and end of a certain action in your app. */ +public expect class Trace { + /** Starts this trace. */ + public fun start() + + /** Stops this trace. */ + public fun stop() + + /** + * Gets the value of the metric with the given name in the current trace. If a metric with the + * given name doesn't exist, it is NOT created and a 0 is returned. This method is atomic. + * + * @param metricName Name of the metric to get. Requires no leading or trailing whitespace, no + * leading underscore '_' character, max length is 100 characters. + * @return Value of the metric or 0 if it hasn't yet been set. + */ + public fun getLongMetric(metricName: String): Long + + /** + * Atomically increments the metric with the given name in this trace by the incrementBy value. If + * the metric does not exist, a new one will be created. If the trace has not been started or has + * already been stopped, returns immediately without taking action. + * + * @param metricName Name of the metric to be incremented. Requires no leading or trailing + * whitespace, no leading underscore [_] character, max length of 100 characters. + * @param incrementBy Amount by which the metric has to be incremented. + */ + public fun incrementMetric(metricName: String, incrementBy: Long) + + /** + * Sets the value of the metric with the given name in this trace to the value provided. If a + * metric with the given name doesn't exist, a new one will be created. If the trace has not been + * started or has already been stopped, returns immediately without taking action. This method is + * atomic. + * + * @param metricName Name of the metric to set. Requires no leading or trailing whitespace, no + * leading underscore '_' character, max length is 100 characters. + * @param value The value to which the metric should be set to. + */ + public fun putMetric(metricName: String, value: Long) +} diff --git a/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt index 32c5c81b0..5cf4f45c2 100644 --- a/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/commonMain/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -6,18 +6,66 @@ import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.perf.metrics.Trace /** Returns the [FirebasePerformance] instance of the default [FirebaseApp]. */ -expect val Firebase.performance: FirebasePerformance +public expect val Firebase.performance: FirebasePerformance /** Returns the [FirebasePerformance] instance of a given [FirebaseApp]. */ -expect fun Firebase.performance(app: FirebaseApp): FirebasePerformance +public expect fun Firebase.performance(app: FirebaseApp): FirebasePerformance -expect class FirebasePerformance { +/** + * The Firebase Performance Monitoring API. + * + * It is automatically initialized by FirebaseApp. + * + * This SDK uses FirebaseInstallations to identify the app instance and periodically sends data + * to the Firebase backend. To stop sending performance events, call [setPerformanceCollectionEnabled] with value [false]. + */ +public expect class FirebasePerformance { + /** + * Creates a Trace object with given name. + * + * @param traceName name of the trace, requires no leading or trailing whitespace, no leading + * underscore '_' character. + * @return the new Trace object. + */ + public fun newTrace(traceName: String): Trace - fun newTrace(traceName: String): Trace + /** + * Determines whether performance monitoring is enabled or disabled. This respects the Firebase + * Performance specific values first, and if these aren't set, uses the Firebase wide data + * collection switch. + * + * @return true if performance monitoring is enabled and false if performance monitoring is + * disabled. This is for dynamic enable/disable state. This does not reflect whether + * instrumentation is enabled/disabled in Gradle properties. + */ + public fun isPerformanceCollectionEnabled(): Boolean - fun isPerformanceCollectionEnabled(): Boolean - - fun setPerformanceCollectionEnabled(enable: Boolean) + /** + * Enables or disables performance monitoring. This setting is persisted and applied on future + * invocations of your application. By default, performance monitoring is enabled. If you need to + * change the default (for example, because you want to prompt the user before collecting + * performance stats), add: + * + * `` + * + * to your application’s manifest. Changing the value during runtime will override the manifest + * value. + * + * If you want to permanently disable sending performance metrics, add + * + * `` + * + * to your application's manifest. Changing the value during runtime will not override the + * manifest value. + * + * This is separate from enabling/disabling instrumentation in Gradle properties. + * + * @param enable Should performance monitoring be enabled + */ + public fun setPerformanceCollectionEnabled(enable: Boolean) } -expect open class FirebasePerformanceException : FirebaseException \ No newline at end of file +/** + * Exception that gets thrown when an operation on Firebase Performance fails. + */ +public expect open class FirebasePerformanceException : FirebaseException diff --git a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index 65ccafb65..e3298744d 100644 --- a/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/commonTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,6 +1,9 @@ package dev.gitlive.firebase.perf.metrics -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize import dev.gitlive.firebase.perf.FirebasePerformance import dev.gitlive.firebase.perf.IgnoreForAndroidUnitTest import dev.gitlive.firebase.perf.context @@ -22,15 +25,15 @@ class TraceTest { @BeforeTest fun initializeFirebase() { val app = Firebase.apps(context).firstOrNull() ?: Firebase.initialize( - dev.gitlive.firebase.perf.context, + context, FirebaseOptions( applicationId = "1:846484016111:ios:dd1f6688bad7af768c841a", apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) performance = Firebase.performance(app) @@ -51,7 +54,7 @@ class TraceTest { trace.start() trace.putMetric("Get Long Metric Test", 1L) - assertEquals(1L, trace.getLongMetric("Get Long Metric Test")) + assertEquals(1L, trace.getLongMetric("Get Long Metric Test")) trace.stop() } @@ -63,7 +66,7 @@ class TraceTest { trace.incrementMetric("Get Increment Metric Test", 1L) - assertEquals(2L, trace.getLongMetric("Get Increment Metric Test")) + assertEquals(2L, trace.getLongMetric("Get Increment Metric Test")) trace.stop() } @@ -73,7 +76,7 @@ class TraceTest { trace.start() trace.putMetric("Get Put Metric Test", 1L) - assertEquals(1L, trace.getLongMetric("Get Put Metric Test")) + assertEquals(1L, trace.getLongMetric("Get Put Metric Test")) trace.stop() } -} \ No newline at end of file +} 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 fae090c5c..69d44b621 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 @@ -4,14 +4,16 @@ package dev.gitlive.firebase.perf -import dev.gitlive.firebase.* -import kotlinx.coroutines.CoroutineScope +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest import kotlinx.coroutines.delay -import kotlinx.coroutines.test.TestResult import kotlin.test.* import kotlin.time.Duration.Companion.seconds -expect val emulatorHost: String expect val context: Any expect annotation class IgnoreForAndroidUnitTest() @@ -30,8 +32,8 @@ class FirebasePerformanceTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) performance = Firebase.performance(app) diff --git a/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index 1b6390ebb..8e1e564ec 100644 --- a/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -2,23 +2,25 @@ package dev.gitlive.firebase.perf.metrics import cocoapods.FirebasePerformance.FIRTrace -actual class Trace internal constructor(val ios: FIRTrace?) { +public val Trace.ios: FIRTrace? get() = ios - actual fun start() { +public actual class Trace internal constructor(internal val ios: FIRTrace?) { + + public actual fun start() { ios?.start() } - actual fun stop() { + public actual fun stop() { ios?.stop() } - actual fun getLongMetric(metricName: String): Long = ios?.valueForIntMetric(metricName) ?: 0L + public actual fun getLongMetric(metricName: String): Long = ios?.valueForIntMetric(metricName) ?: 0L - actual fun incrementMetric(metricName: String, incrementBy: Long) { + public actual fun incrementMetric(metricName: String, incrementBy: Long) { ios?.incrementMetric(metricName, incrementBy) } - actual fun putMetric(metricName: String, value: Long) { + public actual fun putMetric(metricName: String, value: Long) { ios?.setIntValue(value, metricName) } -} \ No newline at end of file +} diff --git a/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/performance.kt index 9a008cdb9..527660077 100644 --- a/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/iosMain/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -6,21 +6,23 @@ import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.perf.metrics.Trace -actual val Firebase.performance get() = +public val FirebasePerformance.ios: FIRPerformance get() = FIRPerformance.sharedInstance() + +public actual val Firebase.performance: FirebasePerformance get() = FirebasePerformance(FIRPerformance.sharedInstance()) -actual fun Firebase.performance(app: FirebaseApp) = +public actual fun Firebase.performance(app: FirebaseApp): FirebasePerformance = FirebasePerformance(FIRPerformance.sharedInstance()) -actual class FirebasePerformance(val ios: FIRPerformance) { +public actual class FirebasePerformance(internal val ios: FIRPerformance) { - actual fun newTrace(traceName: String): Trace = Trace(ios.traceWithName(traceName)) + public actual fun newTrace(traceName: String): Trace = Trace(ios.traceWithName(traceName)) - actual fun isPerformanceCollectionEnabled(): Boolean = ios.isDataCollectionEnabled() + public actual fun isPerformanceCollectionEnabled(): Boolean = ios.isDataCollectionEnabled() - actual fun setPerformanceCollectionEnabled(enable: Boolean) { + public actual fun setPerformanceCollectionEnabled(enable: Boolean) { ios.dataCollectionEnabled = enable } } -actual open class FirebasePerformanceException(message: String) : FirebaseException(message) \ No newline at end of file +public actual open class FirebasePerformanceException(message: String) : FirebaseException(message) diff --git a/firebase-perf/src/iosTest/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/iosTest/kotlin/dev/gitlive/firebase/perf/performance.kt index 67cfa78ac..621c85378 100644 --- a/firebase-perf/src/iosTest/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/iosTest/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -4,8 +4,6 @@ package dev.gitlive.firebase.perf -actual val emulatorHost: String = "localhost" - actual val context: Any = Unit @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 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 index dc6a04d0a..c20ca6ebc 100644 --- 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 @@ -5,23 +5,23 @@ package dev.gitlive.firebase.perf.externals import dev.gitlive.firebase.externals.FirebaseApp -external fun getPerformance(app: FirebaseApp? = definedExternally): FirebasePerformance +public external fun getPerformance(app: FirebaseApp? = definedExternally): FirebasePerformance -external fun trace(performance: FirebasePerformance, name: String): PerformanceTrace +public external fun trace(performance: FirebasePerformance, name: String): PerformanceTrace -external interface FirebasePerformance { - var dataCollectionEnabled: Boolean - var instrumentationEnabled: Boolean +public external interface FirebasePerformance { + public var dataCollectionEnabled: Boolean + public 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() +public external interface PerformanceTrace { + public fun getAttribute(attr: String): String? + public fun getAttributes(): Map + public fun getMetric(metricName: String): Int + public fun incrementMetric(metricName: String, num: Int? = definedExternally) + public fun putAttribute(attr: String, value: String) + public fun putMetric(metricName: String, num: Int) + public fun removeAttribute(attr: String) + public fun start() + public 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 2f89a26c7..fd0582711 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 @@ -3,24 +3,16 @@ package dev.gitlive.firebase.perf.metrics import dev.gitlive.firebase.perf.externals.PerformanceTrace import dev.gitlive.firebase.perf.rethrow +public val Trace.js get() = js -actual class Trace internal constructor(private val js: PerformanceTrace) { - - actual fun start() = rethrow { js.start() } - actual fun stop() = rethrow { js.stop() } - actual fun getLongMetric(metricName: String) = rethrow { js.getMetric(metricName).toLong() } - actual fun incrementMetric(metricName: String, incrementBy: Long) = rethrow { js.incrementMetric(metricName, incrementBy.toInt()) } - actual fun putMetric(metricName: String, value: Long) = rethrow { js.putMetric(metricName, value.toInt()) } - fun getAttribute(attribute: String): String? = rethrow { js.getAttribute(attribute) } - fun putAttribute(attribute: String, value: String) = rethrow { js.putAttribute(attribute, value) } - fun removeAttribute(attribute: String) = rethrow { js.removeAttribute(attribute) } - - fun primitiveHashMap(container: dynamic): HashMap { - val m = HashMap().asDynamic() - m.map = container - val keys = js("Object.keys") - m.`$size` = keys(container).length - return m - } +public actual class Trace internal constructor(internal val js: PerformanceTrace) { + public actual fun start(): Unit = rethrow { js.start() } + public actual fun stop(): Unit = rethrow { js.stop() } + public actual fun getLongMetric(metricName: String): Long = rethrow { js.getMetric(metricName).toLong() } + public actual fun incrementMetric(metricName: String, incrementBy: Long): Unit = rethrow { js.incrementMetric(metricName, incrementBy.toInt()) } + public actual fun putMetric(metricName: String, value: Long): Unit = rethrow { js.putMetric(metricName, value.toInt()) } + public fun getAttribute(attribute: String): String? = rethrow { js.getAttribute(attribute) } + public fun putAttribute(attribute: String, value: String): Unit = rethrow { js.putAttribute(attribute, value) } + public fun removeAttribute(attribute: String): Unit = rethrow { js.removeAttribute(attribute) } } 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 ed27b5001..be5144a10 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,41 +3,43 @@ package dev.gitlive.firebase.perf import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.js 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 +public actual val Firebase.performance: FirebasePerformance get() = rethrow { FirebasePerformance(getPerformance()) } -actual fun Firebase.performance(app: FirebaseApp): FirebasePerformance = rethrow { +public actual fun Firebase.performance(app: FirebaseApp): FirebasePerformance = rethrow { FirebasePerformance(getPerformance(app.js)) } -actual class FirebasePerformance internal constructor(val js: JsFirebasePerformance) { +public val FirebasePerformance.js get() = js - actual fun newTrace(traceName: String): Trace = rethrow { +public actual class FirebasePerformance internal constructor(internal val js: JsFirebasePerformance) { + + public actual fun newTrace(traceName: String): Trace = rethrow { Trace(trace(js, traceName)) } - actual fun isPerformanceCollectionEnabled(): Boolean = js.dataCollectionEnabled + public actual fun isPerformanceCollectionEnabled(): Boolean = js.dataCollectionEnabled - actual fun setPerformanceCollectionEnabled(enable: Boolean) { + public actual fun setPerformanceCollectionEnabled(enable: Boolean) { js.dataCollectionEnabled = enable } - fun isInstrumentationEnabled(): Boolean = js.instrumentationEnabled + public fun isInstrumentationEnabled(): Boolean = js.instrumentationEnabled - fun setInstrumentationEnabled(enable: Boolean) { + public fun setInstrumentationEnabled(enable: Boolean) { js.instrumentationEnabled = enable } } -actual open class FirebasePerformanceException(code: String, cause: Throwable) : - FirebaseException(code, cause) +public actual open class FirebasePerformanceException(code: String, cause: Throwable) : FirebaseException(code, cause) internal inline fun rethrow(function: () -> R): R { try { @@ -56,7 +58,7 @@ internal fun errorToException(error: dynamic) = (error?.code ?: error?.message ? when { else -> { println("Unknown error code in ${JSON.stringify(error)}") - FirebasePerformanceException(code, error) + FirebasePerformanceException(code, error as Throwable) } } } diff --git a/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt b/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt index c732a4a82..686878e68 100644 --- a/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt +++ b/firebase-perf/src/jsTest/kotlin/dev/gitlive/firebase/perf/metrics/Trace.kt @@ -1,10 +1,14 @@ package dev.gitlive.firebase.perf.metrics -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize import dev.gitlive.firebase.perf.FirebasePerformance import dev.gitlive.firebase.perf.context import dev.gitlive.firebase.perf.performance import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -24,8 +28,8 @@ class JsTraceTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) performance = Firebase.performance(app) @@ -57,4 +61,4 @@ class JsTraceTest { assertEquals("Test Put Attribute Value", trace.getAttribute("Test_Put_Attribute")) trace.stop() } -} \ No newline at end of file +} 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 58931677a..821b92f54 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 @@ -10,9 +10,6 @@ import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize import dev.gitlive.firebase.runTest import kotlin.test.* -import kotlin.time.Duration.Companion.minutes - -actual val emulatorHost: String = "localhost" actual val context: Any = Unit @@ -33,8 +30,8 @@ class JsPerformanceTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) performance = Firebase.performance(app) diff --git a/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.jvm.kt b/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.jvm.kt index 43cfaec2a..03144fc7c 100644 --- a/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.jvm.kt +++ b/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/metrics/Trace.jvm.kt @@ -1,20 +1,19 @@ package dev.gitlive.firebase.perf.metrics -actual class Trace { - actual fun start() { +public actual class Trace { + public actual fun start() { } - actual fun stop() { + public actual fun stop() { } - actual fun getLongMetric(metricName: String): Long { + public actual fun getLongMetric(metricName: String): Long { TODO("Not yet implemented") } - actual fun incrementMetric(metricName: String, incrementBy: Long) { + public actual fun incrementMetric(metricName: String, incrementBy: Long) { } - actual fun putMetric(metricName: String, value: Long) { + public actual fun putMetric(metricName: String, value: Long) { } - -} \ No newline at end of file +} diff --git a/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/performance.jvm.kt b/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/performance.jvm.kt index 941a3bd8a..486e96c44 100644 --- a/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/performance.jvm.kt +++ b/firebase-perf/src/jvmMain/kotlin/dev/gitlive/firebase/perf/performance.jvm.kt @@ -6,26 +6,25 @@ import dev.gitlive.firebase.FirebaseException import dev.gitlive.firebase.perf.metrics.Trace /** Returns the [FirebasePerformance] instance of the default [FirebaseApp]. */ -actual val Firebase.performance: FirebasePerformance +public actual val Firebase.performance: FirebasePerformance get() = TODO("Not yet implemented") /** Returns the [FirebasePerformance] instance of a given [FirebaseApp]. */ -actual fun Firebase.performance(app: FirebaseApp): FirebasePerformance { +public actual fun Firebase.performance(app: FirebaseApp): FirebasePerformance { TODO("Not yet implemented") } -actual class FirebasePerformance { - actual fun newTrace(traceName: String): Trace { +public actual class FirebasePerformance { + public actual fun newTrace(traceName: String): Trace { TODO("Not yet implemented") } - actual fun isPerformanceCollectionEnabled(): Boolean { + public actual fun isPerformanceCollectionEnabled(): Boolean { TODO("Not yet implemented") } - actual fun setPerformanceCollectionEnabled(enable: Boolean) { + public actual fun setPerformanceCollectionEnabled(enable: Boolean) { } - } -actual open class FirebasePerformanceException internal constructor(message: String) : FirebaseException(message) \ No newline at end of file +public actual open class FirebasePerformanceException internal constructor(message: String) : FirebaseException(message) diff --git a/firebase-perf/src/jvmTest/kotlin/dev/gitlive/firebase/perf/performance.kt b/firebase-perf/src/jvmTest/kotlin/dev/gitlive/firebase/perf/performance.kt index 9bf145644..958b1936c 100644 --- a/firebase-perf/src/jvmTest/kotlin/dev/gitlive/firebase/perf/performance.kt +++ b/firebase-perf/src/jvmTest/kotlin/dev/gitlive/firebase/perf/performance.kt @@ -3,15 +3,12 @@ */ @file:JvmName("tests") -package dev.gitlive.firebase.perf -import android.content.Context -import com.google.firebase.FirebasePlatform -import dev.gitlive.firebase.MockFirebasePlatform +package dev.gitlive.firebase.perf -actual val emulatorHost: String = "10.0.2.2" +import dev.gitlive.firebase.testContext -actual val context: Any get() = Context().also { FirebasePlatform.initializeFirebasePlatform(MockFirebasePlatform()) } +actual val context: Any = testContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest diff --git a/firebase-storage/api/android/firebase-storage.api b/firebase-storage/api/android/firebase-storage.api new file mode 100644 index 000000000..4715f8dcf --- /dev/null +++ b/firebase-storage/api/android/firebase-storage.api @@ -0,0 +1,118 @@ +public final class dev/gitlive/firebase/storage/Data { + public fun ([B)V + public final fun getData ()[B +} + +public final class dev/gitlive/firebase/storage/File { + public fun (Landroid/net/Uri;)V + public final fun getUri ()Landroid/net/Uri; +} + +public final class dev/gitlive/firebase/storage/FirebaseStorage { + public fun (Lcom/google/firebase/storage/FirebaseStorage;)V + public final fun getMaxOperationRetryTime-UwyO8pc ()J + public final fun getMaxUploadRetryTime-UwyO8pc ()J + public final fun getReference ()Ldev/gitlive/firebase/storage/StorageReference; + public final fun reference (Ljava/lang/String;)Ldev/gitlive/firebase/storage/StorageReference; + public final fun setMaxOperationRetryTime-LRDsOJo (J)V + public final fun setMaxUploadRetryTime-LRDsOJo (J)V + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/storage/FirebaseStorageMetadata { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Ldev/gitlive/firebase/storage/FirebaseStorageMetadata; + public static synthetic fun copy$default (Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Ldev/gitlive/firebase/storage/FirebaseStorageMetadata; + public fun equals (Ljava/lang/Object;)Z + public final fun getCacheControl ()Ljava/lang/String; + public final fun getContentDisposition ()Ljava/lang/String; + public final fun getContentEncoding ()Ljava/lang/String; + public final fun getContentLanguage ()Ljava/lang/String; + public final fun getContentType ()Ljava/lang/String; + public final fun getCustomMetadata ()Ljava/util/Map; + public final fun getMd5Hash ()Ljava/lang/String; + public fun hashCode ()I + public final fun setCacheControl (Ljava/lang/String;)V + public final fun setContentDisposition (Ljava/lang/String;)V + public final fun setContentEncoding (Ljava/lang/String;)V + public final fun setContentLanguage (Ljava/lang/String;)V + public final fun setContentType (Ljava/lang/String;)V + public final fun setCustomMetadata (Ljava/lang/String;Ljava/lang/String;)V + public final fun setCustomMetadata (Ljava/util/Map;)V + public final fun setMd5Hash (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/storage/ListResult { + public fun (Lcom/google/firebase/storage/ListResult;)V + public final fun getItems ()Ljava/util/List; + public final fun getPageToken ()Ljava/lang/String; + public final fun getPrefixes ()Ljava/util/List; +} + +public abstract class dev/gitlive/firebase/storage/Progress { + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getBytesTransferred ()Ljava/lang/Number; + public final fun getTotalByteCount ()Ljava/lang/Number; +} + +public final class dev/gitlive/firebase/storage/Progress$Paused : dev/gitlive/firebase/storage/Progress { +} + +public final class dev/gitlive/firebase/storage/Progress$Running : dev/gitlive/firebase/storage/Progress { +} + +public abstract interface class dev/gitlive/firebase/storage/ProgressFlow : kotlinx/coroutines/flow/Flow { + public abstract fun cancel ()V + public abstract fun pause ()V + public abstract fun resume ()V +} + +public final class dev/gitlive/firebase/storage/StorageKt { + public static final fun getMaxOperationRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;)J + public static final fun getMaxUploadRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;)J + public static final fun setMaxOperationRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;J)V + public static final fun setMaxUploadRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;J)V + public static final fun storageMetadata (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/storage/FirebaseStorageMetadata; +} + +public final class dev/gitlive/firebase/storage/StorageReference { + public fun (Lcom/google/firebase/storage/StorageReference;)V + public final fun child (Ljava/lang/String;)Ldev/gitlive/firebase/storage/StorageReference; + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getBucket ()Ljava/lang/String; + public final fun getDownloadUrl (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getMetadata (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getName ()Ljava/lang/String; + public final fun getParent ()Ldev/gitlive/firebase/storage/StorageReference; + public final fun getPath ()Ljava/lang/String; + public final fun getRoot ()Ldev/gitlive/firebase/storage/StorageReference; + public final fun getStorage ()Ldev/gitlive/firebase/storage/FirebaseStorage; + public final fun listAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun putData (Ldev/gitlive/firebase/storage/Data;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun putData$default (Ldev/gitlive/firebase/storage/StorageReference;Ldev/gitlive/firebase/storage/Data;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun putFile (Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun putFile$default (Ldev/gitlive/firebase/storage/StorageReference;Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun putFileResumable (Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;)Ldev/gitlive/firebase/storage/ProgressFlow; + public static synthetic fun putFileResumable$default (Ldev/gitlive/firebase/storage/StorageReference;Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;ILjava/lang/Object;)Ldev/gitlive/firebase/storage/ProgressFlow; +} + +public final class dev/gitlive/firebase/storage/android { + public static final fun getAndroid (Ldev/gitlive/firebase/storage/FirebaseStorage;)Lcom/google/firebase/storage/FirebaseStorage; + public static final fun getAndroid (Ldev/gitlive/firebase/storage/ListResult;)Lcom/google/firebase/storage/ListResult; + public static final fun getAndroid (Ldev/gitlive/firebase/storage/StorageReference;)Lcom/google/firebase/storage/StorageReference; + public static final fun getStorage (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/storage/FirebaseStorage; + public static final fun storage (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/storage/FirebaseStorage; + public static final fun storage (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;Ljava/lang/String;)Ldev/gitlive/firebase/storage/FirebaseStorage; + public static final fun storage (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/storage/FirebaseStorage; +} + diff --git a/firebase-storage/api/jvm/firebase-storage.api b/firebase-storage/api/jvm/firebase-storage.api new file mode 100644 index 000000000..e94fb4ab9 --- /dev/null +++ b/firebase-storage/api/jvm/firebase-storage.api @@ -0,0 +1,116 @@ +public final class dev/gitlive/firebase/storage/Data { + public fun ()V +} + +public final class dev/gitlive/firebase/storage/File { + public fun ()V +} + +public final class dev/gitlive/firebase/storage/FirebaseStorage { + public fun ()V + public final fun getMaxOperationRetryTime-UwyO8pc ()J + public final fun getMaxUploadRetryTime-UwyO8pc ()J + public final fun getReference ()Ldev/gitlive/firebase/storage/StorageReference; + public final fun reference (Ljava/lang/String;)Ldev/gitlive/firebase/storage/StorageReference; + public final fun setMaxOperationRetryTime-LRDsOJo (J)V + public final fun setMaxUploadRetryTime-LRDsOJo (J)V + public final fun useEmulator (Ljava/lang/String;I)V +} + +public final class dev/gitlive/firebase/storage/FirebaseStorageException : com/google/firebase/FirebaseException { +} + +public final class dev/gitlive/firebase/storage/FirebaseStorageMetadata { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Ldev/gitlive/firebase/storage/FirebaseStorageMetadata; + public static synthetic fun copy$default (Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Ldev/gitlive/firebase/storage/FirebaseStorageMetadata; + public fun equals (Ljava/lang/Object;)Z + public final fun getCacheControl ()Ljava/lang/String; + public final fun getContentDisposition ()Ljava/lang/String; + public final fun getContentEncoding ()Ljava/lang/String; + public final fun getContentLanguage ()Ljava/lang/String; + public final fun getContentType ()Ljava/lang/String; + public final fun getCustomMetadata ()Ljava/util/Map; + public final fun getMd5Hash ()Ljava/lang/String; + public fun hashCode ()I + public final fun setCacheControl (Ljava/lang/String;)V + public final fun setContentDisposition (Ljava/lang/String;)V + public final fun setContentEncoding (Ljava/lang/String;)V + public final fun setContentLanguage (Ljava/lang/String;)V + public final fun setContentType (Ljava/lang/String;)V + public final fun setCustomMetadata (Ljava/lang/String;Ljava/lang/String;)V + public final fun setCustomMetadata (Ljava/util/Map;)V + public final fun setMd5Hash (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final class dev/gitlive/firebase/storage/ListResult { + public fun ()V + public final fun getItems ()Ljava/util/List; + public final fun getPageToken ()Ljava/lang/String; + public final fun getPrefixes ()Ljava/util/List; +} + +public abstract class dev/gitlive/firebase/storage/Progress { + public synthetic fun (Ljava/lang/Number;Ljava/lang/Number;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getBytesTransferred ()Ljava/lang/Number; + public final fun getTotalByteCount ()Ljava/lang/Number; +} + +public final class dev/gitlive/firebase/storage/Progress$Paused : dev/gitlive/firebase/storage/Progress { +} + +public final class dev/gitlive/firebase/storage/Progress$Running : dev/gitlive/firebase/storage/Progress { +} + +public abstract interface class dev/gitlive/firebase/storage/ProgressFlow : kotlinx/coroutines/flow/Flow { + public abstract fun cancel ()V + public abstract fun pause ()V + public abstract fun resume ()V +} + +public final class dev/gitlive/firebase/storage/StorageKt { + public static final fun getMaxOperationRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;)J + public static final fun getMaxUploadRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;)J + public static final fun setMaxOperationRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;J)V + public static final fun setMaxUploadRetryTimeMillis (Ldev/gitlive/firebase/storage/FirebaseStorage;J)V + public static final fun storageMetadata (Lkotlin/jvm/functions/Function1;)Ldev/gitlive/firebase/storage/FirebaseStorageMetadata; +} + +public final class dev/gitlive/firebase/storage/StorageReference { + public fun ()V + public final fun child (Ljava/lang/String;)Ldev/gitlive/firebase/storage/StorageReference; + public final fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getBucket ()Ljava/lang/String; + public final fun getDownloadUrl (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getMetadata (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getName ()Ljava/lang/String; + public final fun getParent ()Ldev/gitlive/firebase/storage/StorageReference; + public final fun getPath ()Ljava/lang/String; + public final fun getRoot ()Ldev/gitlive/firebase/storage/StorageReference; + public final fun getStorage ()Ldev/gitlive/firebase/storage/FirebaseStorage; + public final fun listAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun putData (Ldev/gitlive/firebase/storage/Data;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun putData$default (Ldev/gitlive/firebase/storage/StorageReference;Ldev/gitlive/firebase/storage/Data;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun putFile (Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun putFile$default (Ldev/gitlive/firebase/storage/StorageReference;Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun putFileResumable (Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;)Ldev/gitlive/firebase/storage/ProgressFlow; + public static synthetic fun putFileResumable$default (Ldev/gitlive/firebase/storage/StorageReference;Ldev/gitlive/firebase/storage/File;Ldev/gitlive/firebase/storage/FirebaseStorageMetadata;ILjava/lang/Object;)Ldev/gitlive/firebase/storage/ProgressFlow; +} + +public final class dev/gitlive/firebase/storage/Storage_jvmKt { + public static final fun getStorage (Ldev/gitlive/firebase/Firebase;)Ldev/gitlive/firebase/storage/FirebaseStorage; + public static final fun storage (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;)Ldev/gitlive/firebase/storage/FirebaseStorage; + public static final fun storage (Ldev/gitlive/firebase/Firebase;Ldev/gitlive/firebase/FirebaseApp;Ljava/lang/String;)Ldev/gitlive/firebase/storage/FirebaseStorage; + public static final fun storage (Ldev/gitlive/firebase/Firebase;Ljava/lang/String;)Ldev/gitlive/firebase/storage/FirebaseStorage; +} + diff --git a/firebase-storage/build.gradle.kts b/firebase-storage/build.gradle.kts index 1d4c5d65e..dacfd1143 100644 --- a/firebase-storage/build.gradle.kts +++ b/firebase-storage/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree /* @@ -10,6 +13,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -25,15 +29,11 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions(project) packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -47,10 +47,22 @@ android { val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { + explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @@ -59,38 +71,22 @@ kotlin { instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() if (supportIosTarget) { iosArm64() iosX64() iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = libs.versions.ios.deploymentTarget.get() framework { baseName = "FirebaseStorage" } noPodspec() pod("FirebaseStorage") { - version = "10.25.0" + version = libs.versions.firebase.cocoapods.get() extraOpts += listOf("-compiler-option", "-fmodules") } } @@ -99,32 +95,26 @@ kotlin { js(IR) { useCommonJs() nodejs { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } browser { - testTask( - Action { - useKarma { - useChromeHeadless() - } + testTask { + useKarma { + useChromeHeadless() } - ) + } } } sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true if (name.lowercase().contains("ios")) { optIn("kotlinx.cinterop.ExperimentalForeignApi") @@ -147,7 +137,7 @@ kotlin { getByName("androidMain") { dependencies { - api("com.google.firebase:firebase-storage") + api(libs.google.firebase.storage) } } } @@ -159,6 +149,12 @@ if (project.property("firebase-storage.skipIosTests") == "true") { } } +if (project.property("firebase-storage.skipJvmTests") == "true") { + tasks.forEach { + if (it.name.contains("jvm", true) && it.name.contains("test", true)) { it.enabled = false } + } +} + if (project.property("firebase-storage.skipJsTests") == "true") { tasks.forEach { if (it.name.contains("js", true) && it.name.contains("test", true)) { it.enabled = false } diff --git a/firebase-storage/documentation.md b/firebase-storage/documentation.md new file mode 100644 index 000000000..889808555 --- /dev/null +++ b/firebase-storage/documentation.md @@ -0,0 +1,2 @@ +# Module firebase-storage +This module is a direct forward of the Firebase Storage library. It provides the main functionality, like storing files. \ No newline at end of file diff --git a/firebase-storage/package.json b/firebase-storage/package.json index 92ff99ae6..07e46118b 100644 --- a/firebase-storage/package.json +++ b/firebase-storage/package.json @@ -1,6 +1,6 @@ { "name": "@gitlive/firebase-storage", - "version": "1.10.4", + "version": "2.0.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.10.4", + "@gitlive/firebase-app": "2.0.0", "firebase": "9.19.1", "kotlin": "1.6.10", "kotlinx-coroutines-core": "1.6.1-native-mt" diff --git a/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt b/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt new file mode 100644 index 000000000..513d4847a --- /dev/null +++ b/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt @@ -0,0 +1,3 @@ +package dev.gitlive.firebase.storage + +actual fun createTestData(): Data = Data("test".toByteArray()) diff --git a/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.kt index 67a1affe0..c6965d33c 100644 --- a/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/androidInstrumentedTest/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -3,6 +3,7 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.storage import androidx.test.platform.app.InstrumentationRegistry 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 868d380a5..8d435afed 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 @@ -3,6 +3,7 @@ */ @file:JvmName("android") + package dev.gitlive.firebase.storage import android.net.Uri @@ -10,9 +11,11 @@ import com.google.android.gms.tasks.OnCanceledListener import com.google.android.gms.tasks.OnCompleteListener import com.google.firebase.storage.OnPausedListener import com.google.firebase.storage.OnProgressListener +import com.google.firebase.storage.StorageMetadata import com.google.firebase.storage.UploadTask import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.android as publicAndroid import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking @@ -20,55 +23,84 @@ import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.tasks.await +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +public val FirebaseStorage.android: com.google.firebase.storage.FirebaseStorage get() = com.google.firebase.storage.FirebaseStorage.getInstance() + +public actual val Firebase.storage: FirebaseStorage get() = FirebaseStorage(com.google.firebase.storage.FirebaseStorage.getInstance()) -actual val Firebase.storage get() = - FirebaseStorage(com.google.firebase.storage.FirebaseStorage.getInstance()) +public actual fun Firebase.storage(url: String): FirebaseStorage = FirebaseStorage(com.google.firebase.storage.FirebaseStorage.getInstance(url)) -actual fun Firebase.storage(app: FirebaseApp) = - FirebaseStorage(com.google.firebase.storage.FirebaseStorage.getInstance(app.android)) +public actual fun Firebase.storage(app: FirebaseApp): FirebaseStorage = FirebaseStorage(com.google.firebase.storage.FirebaseStorage.getInstance(app.publicAndroid)) -actual class FirebaseStorage(val android: com.google.firebase.storage.FirebaseStorage) { - actual val maxOperationRetryTimeMillis = android.maxOperationRetryTimeMillis - actual val maxUploadRetryTimeMillis = android.maxUploadRetryTimeMillis +public actual fun Firebase.storage(app: FirebaseApp, url: String): FirebaseStorage = FirebaseStorage(com.google.firebase.storage.FirebaseStorage.getInstance(app.publicAndroid, url)) - actual fun setMaxOperationRetryTimeMillis(maxOperationRetryTimeMillis: Long) { - android.maxOperationRetryTimeMillis = maxOperationRetryTimeMillis +public actual class FirebaseStorage(internal val android: com.google.firebase.storage.FirebaseStorage) { + public actual val maxOperationRetryTime: Duration = android.maxOperationRetryTimeMillis.milliseconds + public actual val maxUploadRetryTime: Duration = android.maxUploadRetryTimeMillis.milliseconds + + public actual fun setMaxOperationRetryTime(maxOperationRetryTime: Duration) { + android.maxOperationRetryTimeMillis = maxOperationRetryTime.inWholeMilliseconds } - actual fun setMaxUploadRetryTimeMillis(maxUploadRetryTimeMillis: Long) { - android.maxUploadRetryTimeMillis = maxUploadRetryTimeMillis + public actual fun setMaxUploadRetryTime(maxUploadRetryTime: Duration) { + android.maxUploadRetryTimeMillis = maxUploadRetryTime.inWholeMilliseconds } - actual fun useEmulator(host: String, port: Int) { + public actual fun useEmulator(host: String, port: Int) { android.useEmulator(host, port) } - actual val reference get() = StorageReference(android.reference) - - actual fun reference(location: String )= StorageReference(android.getReference(location)) + public actual val reference: StorageReference get() = StorageReference(android.reference) + public actual fun reference(location: String): StorageReference = StorageReference(android.getReference(location)) } -actual class StorageReference(val android: com.google.firebase.storage.StorageReference) { - actual val name: String get() = android.name - actual val path: String get() = android.path - actual val bucket: String get() = android.bucket - actual val parent: StorageReference? get() = android.parent?.let { StorageReference(it) } - actual val root: StorageReference get() = StorageReference(android.root) - actual val storage: FirebaseStorage get() = FirebaseStorage(android.storage) +public val StorageReference.android: com.google.firebase.storage.StorageReference get() = android - actual fun child(path: String): StorageReference = StorageReference(android.child(path)) +public actual class StorageReference(internal val android: com.google.firebase.storage.StorageReference) { + public actual val name: String get() = android.name + public actual val path: String get() = android.path + public actual val bucket: String get() = android.bucket + public actual val parent: StorageReference? get() = android.parent?.let { StorageReference(it) } + public actual val root: StorageReference get() = StorageReference(android.root) + public actual val storage: FirebaseStorage get() = FirebaseStorage(android.storage) - actual suspend fun delete() = android.delete().await().run { Unit } + public actual suspend fun getMetadata(): FirebaseStorageMetadata? = android.metadata.await().toFirebaseStorageMetadata() - actual suspend fun getDownloadUrl(): String = android.downloadUrl.await().toString() + public actual fun child(path: String): StorageReference = StorageReference(android.child(path)) - actual suspend fun listAll(): ListResult = ListResult(android.listAll().await()) + public actual suspend fun delete() { + android.delete().await() + } - actual suspend fun putFile(file: File) = android.putFile(file.uri).await().run {} + public actual suspend fun getDownloadUrl(): String = android.downloadUrl.await().toString() - actual fun putFileResumable(file: File): ProgressFlow { - val android = android.putFile(file.uri) + public actual suspend fun listAll(): ListResult = ListResult(android.listAll().await()) + + public actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) { + if (metadata != null) { + android.putFile(file.uri, metadata.toStorageMetadata()).await().run {} + } else { + android.putFile(file.uri).await().run {} + } + } + + public actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?) { + if (metadata != null) { + android.putBytes(data.data, metadata.toStorageMetadata()).await().run {} + } else { + android.putBytes(data.data).await().run {} + } + } + + public actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { + val android = if (metadata != null) { + android.putFile(file.uri, metadata.toStorageMetadata()) + } else { + android.putFile(file.uri) + } val flow = callbackFlow { val onCanceledListener = OnCanceledListener { cancel() } @@ -96,12 +128,43 @@ actual class StorageReference(val android: com.google.firebase.storage.StorageRe } } -actual class ListResult(android: com.google.firebase.storage.ListResult) { - actual val prefixes: List = android.prefixes.map { StorageReference(it) } - actual val items: List = android.items.map { StorageReference(it) } - actual val pageToken: String? = android.pageToken +public val ListResult.android: com.google.firebase.storage.ListResult get() = android + +public actual class ListResult(internal val android: com.google.firebase.storage.ListResult) { + public actual val prefixes: List = android.prefixes.map { StorageReference(it) } + public actual val items: List = android.items.map { StorageReference(it) } + public actual val pageToken: String? = android.pageToken } -actual class File(val uri: Uri) +public actual class File(public val uri: Uri) + +public actual class Data(public val data: ByteArray) -actual typealias FirebaseStorageException = com.google.firebase.storage.StorageException +public actual typealias FirebaseStorageException = com.google.firebase.storage.StorageException + +internal fun FirebaseStorageMetadata.toStorageMetadata(): StorageMetadata = StorageMetadata.Builder() + .setCacheControl(this.cacheControl) + .setContentDisposition(this.contentDisposition) + .setContentEncoding(this.contentEncoding) + .setContentLanguage(this.contentLanguage) + .setContentType(this.contentType) + .apply { + customMetadata.entries.forEach { (key, value) -> + setCustomMetadata(key, value) + } + }.build() + +internal fun StorageMetadata.toFirebaseStorageMetadata(): FirebaseStorageMetadata { + val sdkMetadata = this + return storageMetadata { + md5Hash = sdkMetadata.md5Hash + cacheControl = sdkMetadata.cacheControl + contentDisposition = sdkMetadata.contentDisposition + contentEncoding = sdkMetadata.contentEncoding + contentLanguage = sdkMetadata.contentLanguage + contentType = sdkMetadata.contentType + sdkMetadata.customMetadataKeys.forEach { + setCustomMetadata(it, sdkMetadata.getCustomMetadata(it)) + } + } +} diff --git a/firebase-storage/src/androidUnitTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt b/firebase-storage/src/androidUnitTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt new file mode 100644 index 000000000..513d4847a --- /dev/null +++ b/firebase-storage/src/androidUnitTest/kotlin/dev/gitlive/firebase/storage/storage.android.kt @@ -0,0 +1,3 @@ +package dev.gitlive.firebase.storage + +actual fun createTestData(): Data = Data("test".toByteArray()) 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 ada31266b..8485adc51 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 @@ -4,65 +4,356 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException import kotlinx.coroutines.flow.Flow +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds /** Returns the [FirebaseStorage] instance of the default [FirebaseApp]. */ -expect val Firebase.storage: FirebaseStorage +public expect val Firebase.storage: FirebaseStorage + +/** Returns the [FirebaseStorage] instance of the default [FirebaseApp]. */ +public expect fun Firebase.storage(url: String): FirebaseStorage + +/** Returns the [FirebaseStorage] instance of a given [FirebaseApp]. */ +public expect fun Firebase.storage(app: FirebaseApp): FirebaseStorage /** Returns the [FirebaseStorage] instance of a given [FirebaseApp]. */ -expect fun Firebase.storage(app: FirebaseApp): FirebaseStorage +public expect fun Firebase.storage(app: FirebaseApp, url: String): FirebaseStorage + +/** + * FirebaseStorage is a service that supports uploading and downloading large objects to Google + * Cloud Storage. Pass a custom instance of [FirebaseApp] to [Firebase.storage] + * which will initialize it with a storage location. + * + * Otherwise, if you call [Firebase.storage] without a [FirebaseApp], the + * [FirebaseStorage] instance will initialize with the default [FirebaseApp] obtainable from + * [Firebase.storage]. The storage location in this case will come the JSON + * configuration file downloaded from the web. + */ +public expect class FirebaseStorage { + /** + * Returns the maximum time to retry operations other than upload and download if a failure + * occurs. + * + * @return the maximum time. Defaults to 2 minutes (120,000 milliseconds). + */ + public val maxOperationRetryTime: Duration + + /** + * Returns the maximum time to retry an upload if a failure occurs. + * + * @return the maximum time. Defaults to 10 minutes (600,000 milliseconds). + */ + public val maxUploadRetryTime: Duration + + /** + * Sets the maximum time to retry operations other than upload and download if a failure occurs. + * + * @param maxOperationRetryTime the maximum time. Defaults to 2 minutes (120,000 + * milliseconds). + */ + public fun setMaxOperationRetryTime(maxOperationRetryTime: Duration) + + /** + * Sets the maximum time to retry an upload if a failure occurs. + * + * @param maxUploadRetryTime the maximum time in milliseconds. Defaults to 10 minutes (600,000 + * milliseconds). + */ + public fun setMaxUploadRetryTime(maxUploadRetryTime: Duration) -expect class FirebaseStorage { - val maxOperationRetryTimeMillis: Long - val maxUploadRetryTimeMillis: Long + /** + * Modifies this FirebaseStorage instance to communicate with the Storage emulator. + * + * Note: Call this method before using the instance to do any storage operations. + * + * @param host the emulator host (for example, 10.0.2.2) + * @param port the emulator port (for example, 9000) + */ + public fun useEmulator(host: String, port: Int) - fun setMaxOperationRetryTimeMillis(maxOperationRetryTimeMillis: Long) - fun setMaxUploadRetryTimeMillis(maxUploadRetryTimeMillis: Long) - fun useEmulator(host: String, port: Int) + /** + * Creates a new [StorageReference] initialized at the root Firebase Storage location. + * + * @return An instance of [StorageReference]. + */ + public val reference: StorageReference - val reference: StorageReference - fun reference(location: String): StorageReference + /** + * Creates a new [StorageReference] initialized with a child Firebase Storage location. + * + * @param location A relative path from the root to initialize the reference with, for instance + * "path/to/object" + * @return An instance of [StorageReference] at the given child path. + */ + public fun reference(location: String): StorageReference +} + +@Deprecated("Deprecated to use Kotlin Duration", replaceWith = ReplaceWith("maxOperationRetryTime")) +public val FirebaseStorage.maxOperationRetryTimeMillis: Long get() = maxOperationRetryTime.inWholeMilliseconds + +@Deprecated("Deprecated to use Kotlin Duration", replaceWith = ReplaceWith("maxUploadRetryTime")) +public val FirebaseStorage.maxUploadRetryTimeMillis: Long get() = maxUploadRetryTime.inWholeMilliseconds + +@Deprecated("Deprecated to use Kotlin Duration", replaceWith = ReplaceWith("setMaxOperationRetryTime(maxOperationRetryTimeMillis.milliseconds)")) +public fun FirebaseStorage.setMaxOperationRetryTimeMillis(maxOperationRetryTimeMillis: Long) { + setMaxOperationRetryTime(maxOperationRetryTimeMillis.milliseconds) +} +@Deprecated("Deprecated to use Kotlin Duration", replaceWith = ReplaceWith("setMaxUploadRetryTime(maxUploadRetryTimeMillis.milliseconds)")) +public fun FirebaseStorage.setMaxUploadRetryTimeMillis(maxUploadRetryTimeMillis: Long) { + setMaxUploadRetryTime(maxUploadRetryTimeMillis.milliseconds) } -expect class StorageReference { - val name: String - val path: String - val bucket: String - val parent: StorageReference? - val root: StorageReference - val storage: FirebaseStorage +/** + * Represents a reference to a Google Cloud Storage object. Developers can upload and download + * objects, get/set object metadata, and delete an object at a specified path. + */ +public expect class StorageReference { + /** + * Returns the short name of this object. + * + * @return the name. + */ + public val name: String + + /** + * Returns the full path to this object, not including the Google Cloud Storage bucket. + * + * @return the path. + */ + public val path: String + + /** + * Return the Google Cloud Storage bucket that holds this object. + * + * @return the bucket. + */ + public val bucket: String + + /** + * Returns a new instance of [StorageReference] pointing to the parent location or null if + * this instance references the root location. For example: + * + * ``` + * path = foo/bar/baz parent = foo/bar + * path = foo parent = (root) + * path = (root) parent = (null) + * ``` + * + * @return the parent [StorageReference]. + */ + public val parent: StorageReference? - fun child(path: String): StorageReference + /** + * Returns a new instance of {@link StorageReference} pointing to the root location. + * + * @return the root {@link StorageReference}. + */ + public val root: StorageReference - suspend fun delete() + /** + * Returns the [FirebaseStorage] service which created this reference. + * + * @return The [FirebaseStorage] service. + */ + public val storage: FirebaseStorage - suspend fun getDownloadUrl(): String + /** + * Retrieves metadata associated with an object at this [StorageReference]. + * + * @return the metadata. + */ + public suspend fun getMetadata(): FirebaseStorageMetadata? - suspend fun listAll(): ListResult + /** + * Returns a new instance of [StorageReference] pointing to a child location of the current + * reference. All leading and trailing slashes will be removed, and consecutive slashes will be + * compressed to single slashes. For example: + * + * ``` + * child = /foo/bar path = foo/bar + * child = foo/bar/ path = foo/bar + * child = foo///bar path = foo/bar + * ``` + * + * @param path The relative path from this reference. + * @return the child [StorageReference]. + */ + public fun child(path: String): StorageReference - suspend fun putFile(file: File) + /** + * Deletes the object at this {@link StorageReference}. + * + * @return A {@link Task} that indicates whether the operation succeeded or failed. + */ + public suspend fun delete() - fun putFileResumable(file: File): ProgressFlow + /** + * Asynchronously retrieves a long lived download URL with a revokable token. This can be used to + * share the file with others, but can be revoked by a developer in the Firebase Console if + * desired. + * + * @return The String representing the download URL. + */ + public suspend fun getDownloadUrl(): String + + /** + * List all items (files) and prefixes (folders) under this StorageReference. + * + * This is a helper method for calling list() repeatedly until there are no more + * results. Consistency of the result is not guaranteed if objects are inserted or removed while + * this operation is executing. + * + * [listAll] is only available for projects using Firebase Rules Version 2. + * + * @return A [ListResult] that returns all items and prefixes under the current StorageReference. + */ + public suspend fun listAll(): ListResult + + /** + * Asynchronously uploads from a content URI to this [StorageReference]. + * + * @param file The source of the upload. This is a [File]. A content + * resolver will be used to load the data. + * @param metadata [FirebaseStorageMetadata] containing additional information (MIME type, etc.) + * about the object being uploaded. + */ + public suspend fun putFile(file: File, metadata: FirebaseStorageMetadata? = null) + + /** + * Asynchronously uploads byte data to this [StorageReference]. This is not recommended for + * large files. Instead upload a file via [putFile]. + * + * @param data The [Data] to upload. + * @param metadata [FirebaseStorageMetadata] containing additional information (MIME type, etc.) + * about the object being uploaded. + */ + public suspend fun putData(data: Data, metadata: FirebaseStorageMetadata? = null) + + /** + * Asynchronously uploads from a content URI to this [StorageReference]. + * + * @param file The source of the upload. This is a [File]. A content + * resolver will be used to load the data. + * @param metadata [FirebaseStorageMetadata] containing additional information (MIME type, etc.) + * about the object being uploaded. + * @return A [ProgressFlow] that can be used to monitor and manage the upload. + */ + public fun putFileResumable(file: File, metadata: FirebaseStorageMetadata? = null): ProgressFlow } -expect class ListResult { - val prefixes: List - val items: List - val pageToken: String? +public expect class ListResult { + public val prefixes: List + public val items: List + public val pageToken: String? } -expect class File +/** + * Represents a reference to a local file for all platforms. Every platform has its own constructor. + */ +public expect class File -sealed class Progress(val bytesTransferred: Number, val totalByteCount: Number) { - class Running internal constructor(bytesTransferred: Number, totalByteCount: Number): Progress(bytesTransferred, totalByteCount) - class Paused internal constructor(bytesTransferred: Number, totalByteCount: Number): Progress(bytesTransferred, totalByteCount) +/** + * Represents a reference to data for all platforms. Every platform has its own constructor. + */ +public expect class Data + +/** + * Represents the progress of an operation. + */ +public sealed class Progress(public val bytesTransferred: Number, public val totalByteCount: Number) { + /** Represents the progress of an operation that is still running. */ + public class Running internal constructor(bytesTransferred: Number, totalByteCount: Number) : Progress(bytesTransferred, totalByteCount) + + /** Represents the progress of an operation that is paused. */ + public class Paused internal constructor(bytesTransferred: Number, totalByteCount: Number) : Progress(bytesTransferred, totalByteCount) } -interface ProgressFlow : Flow { - fun pause() - fun resume() - fun cancel() +/** + * A flow that emits [Progress] objects containing the state of an upload. + */ +public interface ProgressFlow : Flow { + public fun pause() + public fun resume() + public fun cancel() } +/** + * Exception that gets thrown when an operation on Firebase Storage fails. + */ +public expect class FirebaseStorageException : FirebaseException + +/** + * Metadata for a [StorageReference]. Metadata stores default attributes such as size and + * content type. You may also store custom metadata key value pairs. Metadata values may be used to + * authorize operations using declarative validation rules. + */ +public data class FirebaseStorageMetadata( + /** + * Returns the path of the [StorageReference] object. + * + * @return the MD5Hash of the [StorageReference] object + */ + var md5Hash: String? = null, + + /** + * Returns the size of the [StorageReference] object in bytes. + * + * @return the Cache Control header for the [StorageReference] + */ + var cacheControl: String? = null, -expect class FirebaseStorageException : FirebaseException + /** + * Returns the content disposition of the [StorageReference] + * + * @return the content disposition of the [StorageReference] + */ + var contentDisposition: String? = null, + + /** + * Returns the content encoding for the [StorageReference] + * + * @return the content encoding for the [StorageReference] + */ + var contentEncoding: String? = null, + + /** + * Returns the content language for the [StorageReference] + * + * @return the content language for the [StorageReference] + */ + var contentLanguage: String? = null, + + /** + * Returns the Content Type of this associated [StorageReference] + * + * @return the Content Type of this associated [StorageReference] + */ + var contentType: String? = null, + + /** + * Returns custom metadata for a StorageReference + * + * @return the metadata stored in the object. + */ + var customMetadata: MutableMap = mutableMapOf(), +) { + /** + * Sets custom metadata + * + * @param key the key of the new value + * @param value the value to set. + */ + public fun setCustomMetadata(key: String, value: String?) { + value?.let { + customMetadata[key] = it + } + } +} + +/** Returns a [FirebaseStorageMetadata] object initialized using the [init] function. */ +public fun storageMetadata(init: FirebaseStorageMetadata.() -> Unit): FirebaseStorageMetadata { + val metadata = FirebaseStorageMetadata() + metadata.init() + return metadata +} diff --git a/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt index 0b96512af..9ae117f2c 100644 --- a/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/commonTest/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -8,7 +8,14 @@ import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.runBlockingTest +import dev.gitlive.firebase.runTest +import kotlin.test.AfterTest import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.time.Duration.Companion.seconds expect val emulatorHost: String expect val context: Any @@ -29,12 +36,68 @@ class FirebaseStorageTest { databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", projectId = "fir-kotlin-sdk", - gcmSenderId = "846484016111" - ) + gcmSenderId = "846484016111", + ), ) storage = Firebase.storage(app).apply { useEmulator(emulatorHost, 9199) + setMaxOperationRetryTime(10.seconds) + setMaxUploadRetryTime(10.seconds) } } -} \ No newline at end of file + + @AfterTest + fun deinitializeFirebase() = runBlockingTest { + Firebase.apps(context).forEach { + it.delete() + } + } + + @Test + fun testStorageNotNull() = runTest { + assertNotNull(storage) + } + + @Test + fun testUploadShouldNotCrash() = runTest { + val data = createTestData() + val ref = storage.reference("test").child("testUploadShouldNotCrash.txt") + + ref.putData(data) + } + + @Test + fun testUploadMetadata() = runTest { + val data = createTestData() + val ref = storage.reference("test").child("testUploadMetadata.txt") + val metadata = storageMetadata { + contentType = "text/plain" + } + ref.putData(data, metadata) + + val metadataResult = ref.getMetadata() + + assertNotNull(metadataResult) + assertNotNull(metadataResult.contentType) + assertEquals(metadata.contentType, metadataResult.contentType) + } + + @Test + fun testUploadCustomMetadata() = runTest { + val data = createTestData() + val ref = storage.reference("test").child("testUploadCustomMetadata.txt") + val metadata = storageMetadata { + contentType = "text/plain" + setCustomMetadata("key", "value") + } + ref.putData(data, metadata) + + val metadataResult = ref.getMetadata() + + assertNotNull(metadataResult) + assertEquals(metadata.customMetadata["key"], metadataResult.customMetadata["key"]) + } +} + +expect fun createTestData(): Data 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 3a988926e..0a0c1afab 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 @@ -6,6 +6,7 @@ package dev.gitlive.firebase.storage import cocoapods.FirebaseStorage.FIRStorage import cocoapods.FirebaseStorage.FIRStorageListResult +import cocoapods.FirebaseStorage.FIRStorageMetadata import cocoapods.FirebaseStorage.FIRStorageReference import cocoapods.FirebaseStorage.FIRStorageTaskStatusFailure import cocoapods.FirebaseStorage.FIRStorageTaskStatusPause @@ -15,6 +16,7 @@ import cocoapods.FirebaseStorage.FIRStorageTaskStatusSuccess import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import dev.gitlive.firebase.ios import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.awaitClose @@ -22,64 +24,95 @@ import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emitAll +import platform.Foundation.NSData import platform.Foundation.NSError import platform.Foundation.NSURL +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit +public val FirebaseStorage.ios: FIRStorage get() = FIRStorage.storage() -actual val Firebase.storage get() = +public actual val Firebase.storage: FirebaseStorage get() = FirebaseStorage(FIRStorage.storage()) -actual fun Firebase.storage(app: FirebaseApp): FirebaseStorage = FirebaseStorage( - FIRStorage.storageForApp(app.ios as objcnames.classes.FIRApp) +public actual fun Firebase.storage(url: String): FirebaseStorage = FirebaseStorage( + FIRStorage.storageWithURL(url), ) -actual class FirebaseStorage(val ios: FIRStorage) { - actual val maxOperationRetryTimeMillis = ios.maxOperationRetryTime().toLong() - actual val maxUploadRetryTimeMillis = ios.maxUploadRetryTime().toLong() +public actual fun Firebase.storage(app: FirebaseApp): FirebaseStorage = FirebaseStorage( + FIRStorage.storageForApp(app.ios as objcnames.classes.FIRApp), +) + +public actual fun Firebase.storage(app: FirebaseApp, url: String): FirebaseStorage = FirebaseStorage( + FIRStorage.storageForApp(app.ios as objcnames.classes.FIRApp, url), +) - actual fun setMaxOperationRetryTimeMillis(maxOperationRetryTimeMillis: Long) { - ios.setMaxOperationRetryTime(maxOperationRetryTimeMillis.toDouble()) +public actual class FirebaseStorage(internal val ios: FIRStorage) { + public actual val maxOperationRetryTime: Duration = ios.maxOperationRetryTime().seconds + public actual val maxUploadRetryTime: Duration = ios.maxUploadRetryTime().seconds + + public actual fun setMaxOperationRetryTime(maxOperationRetryTime: Duration) { + ios.setMaxOperationRetryTime(maxOperationRetryTime.toDouble(DurationUnit.SECONDS)) } - actual fun setMaxUploadRetryTimeMillis(maxUploadRetryTimeMillis: Long) { - ios.setMaxUploadRetryTime(maxUploadRetryTimeMillis.toDouble()) + public actual fun setMaxUploadRetryTime(maxUploadRetryTime: Duration) { + ios.setMaxUploadRetryTime(maxUploadRetryTime.toDouble(DurationUnit.SECONDS)) } - actual fun useEmulator(host: String, port: Int) { + public actual fun useEmulator(host: String, port: Int) { ios.useEmulatorWithHost(host, port.toLong()) } - actual val reference get() = StorageReference(ios.reference()) + public actual val reference: StorageReference get() = StorageReference(ios.reference()) - actual fun reference(location: String) = StorageReference(ios.referenceWithPath(location)) + public actual fun reference(location: String): StorageReference = StorageReference(ios.referenceWithPath(location)) } -actual class StorageReference(val ios: FIRStorageReference) { - actual val name: String get() = ios.name() - actual val path: String get() = ios.fullPath() - actual val bucket: String get() = ios.bucket() - actual val parent: StorageReference? get() = ios.parent()?.let { StorageReference(it) } - actual val root: StorageReference get() = StorageReference(ios.root()) - actual val storage: FirebaseStorage get() = FirebaseStorage(ios.storage()) +public val StorageReference.ios: FIRStorageReference get() = ios + +public actual class StorageReference(internal val ios: FIRStorageReference) { + public actual val name: String get() = ios.name() + public actual val path: String get() = ios.fullPath() + public actual val bucket: String get() = ios.bucket() + public actual val parent: StorageReference? get() = ios.parent()?.let { StorageReference(it) } + public actual val root: StorageReference get() = StorageReference(ios.root()) + public actual val storage: FirebaseStorage get() = FirebaseStorage(ios.storage()) - actual fun child(path: String): StorageReference = StorageReference(ios.child(path)) + public actual fun child(path: String): StorageReference = StorageReference(ios.child(path)) - actual suspend fun delete() = await { ios.deleteWithCompletion(it) } + public actual suspend fun getMetadata(): FirebaseStorageMetadata? = ios.awaitResult { + metadataWithCompletion { metadata, error -> + if (error == null) { + it.invoke(metadata?.toFirebaseStorageMetadata(), null) + } else { + it.invoke(null, error) + } + } + } - actual suspend fun getDownloadUrl(): String = ios.awaitResult { + public actual suspend fun delete(): Unit = await { ios.deleteWithCompletion(it) } + + public actual suspend fun getDownloadUrl(): String = ios.awaitResult { downloadURLWithCompletion(completion = it) }.absoluteString()!! - actual suspend fun listAll(): ListResult = awaitResult { + public actual suspend fun listAll(): ListResult = awaitResult { ios.listAllWithCompletion { firStorageListResult, nsError -> it.invoke(firStorageListResult?.let { ListResult(it) }, nsError) } } - actual suspend fun putFile(file: File) = ios.awaitResult { putFile(file.url, null, completion = it) }.run {} + public actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?): Unit = ios.awaitResult { callback -> + putFile(file.url, metadata?.toFIRMetadata(), callback) + }.run {} + + public actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?): Unit = ios.awaitResult { callback -> + putData(data.data, metadata?.toFIRMetadata(), callback) + }.run {} - actual fun putFileResumable(file: File): ProgressFlow { - val ios = ios.putFile(file.url) + public actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { + val ios = ios.putFile(file.url, metadata?.toFIRMetadata()) val flow = callbackFlow { ios.observeStatus(FIRStorageTaskStatusProgress) { @@ -96,8 +129,9 @@ actual class StorageReference(val ios: FIRStorageReference) { } ios.observeStatus(FIRStorageTaskStatusSuccess) { close(FirebaseStorageException(it!!.error().toString())) } ios.observeStatus(FIRStorageTaskStatusFailure) { - when(it!!.error()!!.code) { - /*FIRStorageErrorCodeCancelled = */ -13040L -> cancel(it.error()!!.localizedDescription) + when (it!!.error()!!.code) { + /*FIRStorageErrorCodeCancelled = */ + -13040L -> cancel(it.error()!!.localizedDescription) else -> close(FirebaseStorageException(it.error().toString())) } } @@ -111,23 +145,26 @@ actual class StorageReference(val ios: FIRStorageReference) { override fun cancel() = ios.cancel() } } - } -actual class ListResult(ios: FIRStorageListResult) { - actual val prefixes: List = ios.prefixes().map { StorageReference(it as FIRStorageReference) } - actual val items: List = ios.items().map { StorageReference(it as FIRStorageReference) } - actual val pageToken: String? = ios.pageToken() +public val ListResult.ios: FIRStorageListResult get() = ios + +public actual class ListResult(internal val ios: FIRStorageListResult) { + public actual val prefixes: List = ios.prefixes().map { StorageReference(it as FIRStorageReference) } + public actual val items: List = ios.items().map { StorageReference(it as FIRStorageReference) } + public actual val pageToken: String? = ios.pageToken() } -actual class File(val url: NSURL) +public actual class File(public val url: NSURL) + +public actual class Data(public val data: NSData) -actual class FirebaseStorageException(message: String): FirebaseException(message) +public actual class FirebaseStorageException(message: String) : FirebaseException(message) -suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { +internal suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Unit) { val job = CompletableDeferred() function { error -> - if(error == null) { + if (error == null) { job.complete(Unit) } else { job.completeExceptionally(FirebaseStorageException(error.toString())) @@ -136,10 +173,10 @@ suspend inline fun T.await(function: T.(callback: (NSError?) -> Unit) -> Uni job.await() } -suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { +internal suspend inline fun T.awaitResult(function: T.(callback: (R?, NSError?) -> Unit) -> Unit): R { val job = CompletableDeferred() function { result, error -> - if(error == null) { + if (error == null) { job.complete(result) } else { job.completeExceptionally(FirebaseStorageException(error.toString())) @@ -147,3 +184,32 @@ suspend inline fun T.awaitResult(function: T.(callback: (R?, NSEr } return job.await() as R } + +internal fun FirebaseStorageMetadata.toFIRMetadata(): FIRStorageMetadata { + val metadata = FIRStorageMetadata() + val mappedMetadata: Map = this.customMetadata.map { + it.key to it.value + }.toMap() + metadata.setCustomMetadata(mappedMetadata) + metadata.setCacheControl(this.cacheControl) + metadata.setContentDisposition(this.contentDisposition) + metadata.setContentEncoding(this.contentEncoding) + metadata.setContentLanguage(this.contentLanguage) + metadata.setContentType(this.contentType) + return metadata +} + +internal fun FIRStorageMetadata.toFirebaseStorageMetadata(): FirebaseStorageMetadata { + val sdkMetadata = this + return storageMetadata { + md5Hash = sdkMetadata.md5Hash() + cacheControl = sdkMetadata.cacheControl() + contentDisposition = sdkMetadata.contentDisposition() + contentEncoding = sdkMetadata.contentEncoding() + contentLanguage = sdkMetadata.contentLanguage() + contentType = sdkMetadata.contentType() + sdkMetadata.customMetadata()?.forEach { + setCustomMetadata(it.key.toString(), it.value.toString()) + } + } +} diff --git a/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.ios.kt b/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.ios.kt new file mode 100644 index 000000000..f7f974883 --- /dev/null +++ b/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.ios.kt @@ -0,0 +1,13 @@ +package dev.gitlive.firebase.storage + +import kotlinx.cinterop.BetaInteropApi +import platform.Foundation.NSString +import platform.Foundation.NSUTF8StringEncoding +import platform.Foundation.create +import platform.Foundation.dataUsingEncoding + +@OptIn(BetaInteropApi::class) +actual fun createTestData(): Data { + val value = NSString.create(string = "test") + return Data(value.dataUsingEncoding(NSUTF8StringEncoding, false)!!) +} diff --git a/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.kt index 7b3541194..51eb0b48a 100644 --- a/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/iosTest/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -4,7 +4,7 @@ package dev.gitlive.firebase.storage -actual val emulatorHost: String = "127.0.0.1" +actual val emulatorHost: String = "localhost" actual val context: Any = Unit 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 index dbd7768d4..2cabf05ba 100644 --- 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 @@ -4,64 +4,107 @@ package dev.gitlive.firebase.storage.externals import dev.gitlive.firebase.externals.FirebaseApp +import kotlin.js.Json import kotlin.js.Promise -external fun getStorage(app: FirebaseApp? = definedExternally): FirebaseStorage +public external fun getStorage(app: FirebaseApp? = definedExternally, bucketUrl: String): FirebaseStorage -external fun ref(storage: FirebaseStorage, url: String? = definedExternally): StorageReference -external fun ref(ref: StorageReference, url: String? = definedExternally): StorageReference +public external fun getStorage(app: FirebaseApp? = definedExternally): FirebaseStorage -external fun getDownloadURL(ref: StorageReference): Promise +public external fun ref(storage: FirebaseStorage, url: String? = definedExternally): StorageReference +public external fun ref(ref: StorageReference, path: String? = definedExternally): StorageReference -external fun uploadBytes(ref: StorageReference, file: dynamic): Promise +public external fun getDownloadURL(ref: StorageReference): Promise -external fun uploadBytesResumable(ref: StorageReference, data: dynamic): UploadTask +public external fun getMetadata(ref: StorageReference): Promise +public external fun updateMetadata(ref: StorageReference, metadata: SettableMetadata): Promise -external fun deleteObject(ref: StorageReference): Promise; +public external fun uploadBytes(ref: StorageReference, file: dynamic, metadata: Json?): Promise +public external fun uploadBytesResumable(ref: StorageReference, data: dynamic, metadata: Json?): UploadTask -external fun listAll(ref: StorageReference): Promise; +public external fun deleteObject(ref: StorageReference): Promise -external fun connectFirestoreEmulator( +public external fun list(ref: StorageReference, options: ListOptions?): Promise +public external fun listAll(ref: StorageReference): Promise + +public external fun connectStorageEmulator( storage: FirebaseStorage, host: String, port: Double, - options: Any? = definedExternally + options: Any? = definedExternally, ) -external interface FirebaseStorage { - var maxOperationRetryTime: Double - var maxUploadRetryTime: Double +public external interface FirebaseStorage { + public var maxOperationRetryTime: Double + public var maxUploadRetryTime: Double +} + +public external interface StorageReference { + public val bucket: String + public val fullPath: String + public val name: String + public val parent: StorageReference? + public val root: StorageReference + public val storage: FirebaseStorage } -external interface StorageReference { - val bucket: String - val fullPath: String - val name: String - val parent: StorageReference? - val root: StorageReference - val storage: FirebaseStorage +public external interface ListOptions { + public val maxResults: Double? + public val pageToken: String? } -external open class ListResult { - val items: Array - val nextPageToken: String - val prefixes: Array +public external interface ListResult { + public val items: Array + public val nextPageToken: String + public val prefixes: Array +} + +public external interface StorageError + +public external interface SettableMetadata { + public val cacheControl: String? + public val contentDisposition: String? + public val contentEncoding: String? + public val contentLanguage: String? + public val contentType: String? + public val customMetadata: Json? } -external interface StorageError +public external interface UploadMetadata : SettableMetadata { + public val md5Hash: String? +} + +public external interface FullMetadata : UploadMetadata { + public val bucket: String + public val downloadTokens: Array? + public val fullPath: String + public val generation: String + public val metageneration: String + public val name: String + public val ref: StorageReference? + public val size: Double + public val timeCreated: String + public val updated: String +} + +public external interface UploadResult { + public val metadata: FullMetadata + public val ref: StorageReference +} -external interface UploadTaskSnapshot { - val bytesTransferred: Number - val ref: StorageReference - val state: String - val task: UploadTask - val totalBytes: Number +public external interface UploadTask { + public fun cancel(): Boolean + public fun on(event: String, next: (snapshot: UploadTaskSnapshot) -> Unit, error: (a: StorageError) -> Unit, complete: () -> Unit): () -> Unit + public fun pause(): Boolean + public fun resume(): Boolean + public fun then(onFulfilled: ((UploadTaskSnapshot) -> Unit)?, onRejected: ((StorageError) -> Unit)?): Promise + public val snapshot: UploadTaskSnapshot } -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 +public external interface UploadTaskSnapshot { + public val bytesTransferred: Double + public val ref: StorageReference + public val state: String + public val task: UploadTask + public val totalBytes: Double } 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 9ba0380d5..14a7897b3 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,6 +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.js import dev.gitlive.firebase.storage.externals.* import kotlinx.coroutines.await import kotlinx.coroutines.cancel @@ -14,61 +15,76 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emitAll +import kotlin.js.Json +import kotlin.js.json +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit -actual val Firebase.storage +public actual val Firebase.storage: FirebaseStorage get() = FirebaseStorage(getStorage()) -actual fun Firebase.storage(app: FirebaseApp) = - FirebaseStorage(getStorage(app.js)) +public actual fun Firebase.storage(url: String): FirebaseStorage = FirebaseStorage(getStorage(null, url)) -actual class FirebaseStorage(val js: dev.gitlive.firebase.storage.externals.FirebaseStorage) { - actual val maxOperationRetryTimeMillis = js.maxOperationRetryTime.toLong() - actual val maxUploadRetryTimeMillis = js.maxUploadRetryTime.toLong() +public actual fun Firebase.storage(app: FirebaseApp): FirebaseStorage = FirebaseStorage(getStorage(app.js)) - actual fun setMaxOperationRetryTimeMillis(maxOperationRetryTimeMillis: Long) { - js.maxOperationRetryTime = maxOperationRetryTimeMillis.toDouble() - } +public actual fun Firebase.storage(app: FirebaseApp, url: String): FirebaseStorage = FirebaseStorage(getStorage(app.js, url)) + +public val FirebaseStorage.js get() = js - actual fun setMaxUploadRetryTimeMillis(maxUploadRetryTimeMillis: Long) { - js.maxUploadRetryTime = maxUploadRetryTimeMillis.toDouble() +public actual class FirebaseStorage(internal val js: dev.gitlive.firebase.storage.externals.FirebaseStorage) { + public actual val maxOperationRetryTime: Duration = js.maxOperationRetryTime.milliseconds + public actual val maxUploadRetryTime: Duration = js.maxUploadRetryTime.milliseconds + + public actual fun setMaxOperationRetryTime(maxOperationRetryTime: Duration) { + js.maxOperationRetryTime = maxOperationRetryTime.toDouble(DurationUnit.MILLISECONDS) } - actual fun useEmulator(host: String, port: Int) { - connectFirestoreEmulator(js, host, port.toDouble()) + public actual fun setMaxUploadRetryTime(maxUploadRetryTime: Duration) { + js.maxUploadRetryTime = maxUploadRetryTime.toDouble(DurationUnit.MILLISECONDS) } - actual val reference: StorageReference get() = StorageReference(ref(js)) + public actual fun useEmulator(host: String, port: Int) { + connectStorageEmulator(js, host, port.toDouble()) + } - actual fun reference(location: String) = rethrow { StorageReference(ref(js, location)) } + public actual val reference: StorageReference get() = StorageReference(ref(js)) + public actual fun reference(location: String): StorageReference = rethrow { StorageReference(ref(js, location)) } } -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 - actual val parent: StorageReference? get() = js.parent?.let { StorageReference(it) } - actual val root: StorageReference get() = StorageReference(js.root) - actual val storage: FirebaseStorage get() = FirebaseStorage(js.storage) +public val StorageReference.js get() = js + +public actual class StorageReference(internal val js: dev.gitlive.firebase.storage.externals.StorageReference) { + public actual val path: String get() = js.fullPath + public actual val name: String get() = js.name + public actual val bucket: String get() = js.bucket + public actual val parent: StorageReference? get() = js.parent?.let { StorageReference(it) } + public actual val root: StorageReference get() = StorageReference(js.root) + public actual val storage: FirebaseStorage get() = FirebaseStorage(js.storage) + + public actual suspend fun getMetadata(): FirebaseStorageMetadata? = rethrow { getMetadata(js).await().toFirebaseStorageMetadata() } + + public actual fun child(path: String): StorageReference = StorageReference(ref(js, path)) - actual fun child(path: String): StorageReference = StorageReference(ref(js, path)) + public actual suspend fun delete(): Unit = rethrow { deleteObject(js).await() } - actual suspend fun delete() = rethrow { deleteObject(js).await() } + public actual suspend fun getDownloadUrl(): String = rethrow { getDownloadURL(js).await().toString() } - actual suspend fun getDownloadUrl(): String = rethrow { getDownloadURL(js).await().toString() } + public actual suspend fun listAll(): ListResult = rethrow { ListResult(listAll(js).await()) } - actual suspend fun listAll(): ListResult = rethrow { ListResult(listAll(js).await()) } + public actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?): Unit = rethrow { uploadBytes(js, file, metadata?.toStorageMetadata()).await() } - actual suspend fun putFile(file: File): Unit = rethrow { uploadBytes(js, file).await() } + public actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?): Unit = rethrow { uploadBytes(js, data.data, metadata?.toStorageMetadata()).await() } - actual fun putFileResumable(file: File): ProgressFlow = rethrow { - val uploadTask = uploadBytesResumable(js, file) + public actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow = rethrow { + val uploadTask = uploadBytesResumable(js, file, metadata?.toStorageMetadata()) val flow = callbackFlow { val unsubscribe = uploadTask.on( "state_changed", { - when(it.state) { + when (it.state) { "paused" -> trySend(Progress.Paused(it.bytesTransferred, it.totalBytes)) "running" -> trySend(Progress.Running(it.bytesTransferred, it.totalBytes)) "canceled" -> cancel() @@ -77,7 +93,7 @@ actual class StorageReference(val js: dev.gitlive.firebase.storage.externals.Sto } }, { close(errorToException(it)) }, - { close() } + { close() }, ) awaitClose { unsubscribe() } } @@ -89,19 +105,18 @@ actual class StorageReference(val js: dev.gitlive.firebase.storage.externals.Sto override fun cancel() = uploadTask.cancel().run {} } } - } -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 +public actual class ListResult(js: dev.gitlive.firebase.storage.externals.ListResult) { + public actual val prefixes: List = js.prefixes.map { StorageReference(it) } + public actual val items: List = js.items.map { StorageReference(it) } + public actual val pageToken: String? = js.nextPageToken } -actual typealias File = org.w3c.files.File +public actual typealias File = org.w3c.files.File +public actual class Data(public val data: org.khronos.webgl.Uint8Array) -actual open class FirebaseStorageException(code: String, cause: Throwable) : - FirebaseException(code, cause) +public actual open class FirebaseStorageException(code: String, cause: Throwable) : FirebaseException(code, cause) internal inline fun rethrow(function: () -> R): R { try { @@ -120,7 +135,35 @@ internal fun errorToException(error: dynamic) = (error?.code ?: error?.message ? when { else -> { println("Unknown error code in ${JSON.stringify(error)}") - FirebaseStorageException(code, error) + FirebaseStorageException(code, error as Throwable) } } - } \ No newline at end of file + } + +internal fun UploadMetadata.toFirebaseStorageMetadata(): FirebaseStorageMetadata { + val sdkMetadata = this + return storageMetadata { + md5Hash = sdkMetadata.md5Hash + cacheControl = sdkMetadata.cacheControl + contentDisposition = sdkMetadata.contentDisposition + contentEncoding = sdkMetadata.contentEncoding + contentLanguage = sdkMetadata.contentLanguage + contentType = sdkMetadata.contentType + customMetadata = sdkMetadata.customMetadata?.let { metadata -> + val objectKeys = js("Object.keys") + objectKeys(metadata).unsafeCast>().associateWith { key -> + metadata[key]?.toString().orEmpty() + } + }.orEmpty().toMutableMap() + } +} + +internal fun FirebaseStorageMetadata.toStorageMetadata(): Json = json( + "cacheControl" to cacheControl, + "contentDisposition" to contentDisposition, + "contentEncoding" to contentEncoding, + "contentLanguage" to contentLanguage, + "contentType" to contentType, + "customMetadata" to json(*customMetadata.toList().toTypedArray()), + "md5Hash" to md5Hash, +) diff --git a/firebase-storage/src/jsTest/kotlin/dev/gitlive/firebase/storage/storage.js.kt b/firebase-storage/src/jsTest/kotlin/dev/gitlive/firebase/storage/storage.js.kt new file mode 100644 index 000000000..dbbe5fd82 --- /dev/null +++ b/firebase-storage/src/jsTest/kotlin/dev/gitlive/firebase/storage/storage.js.kt @@ -0,0 +1,5 @@ +package dev.gitlive.firebase.storage + +import org.khronos.webgl.Uint8Array + +actual fun createTestData(): Data = Data(Uint8Array("test".encodeToByteArray().toTypedArray())) 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 cc61e378b..9d4f16ab8 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 @@ -3,86 +3,95 @@ package dev.gitlive.firebase.storage import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseApp import dev.gitlive.firebase.FirebaseException +import kotlin.time.Duration /** Returns the [FirebaseStorage] instance of the default [FirebaseApp]. */ -actual val Firebase.storage: FirebaseStorage +public actual val Firebase.storage: FirebaseStorage get() = TODO("Not yet implemented") +public actual fun Firebase.storage(url: String): FirebaseStorage = TODO("Not yet implemented") + /** Returns the [FirebaseStorage] instance of a given [FirebaseApp]. */ -actual fun Firebase.storage(app: FirebaseApp): FirebaseStorage { - TODO("Not yet implemented") -} +public actual fun Firebase.storage(app: FirebaseApp): FirebaseStorage = TODO("Not yet implemented") -actual class FirebaseStorage { - actual val maxOperationRetryTimeMillis: Long +public actual fun Firebase.storage(app: FirebaseApp, url: String): FirebaseStorage = TODO("Not yet implemented") + +public actual class FirebaseStorage { + public actual val maxOperationRetryTime: Duration get() = TODO("Not yet implemented") - actual val maxUploadRetryTimeMillis: Long + public actual val maxUploadRetryTime: Duration get() = TODO("Not yet implemented") - actual fun setMaxOperationRetryTimeMillis(maxOperationRetryTimeMillis: Long) { + public actual fun setMaxOperationRetryTime(maxOperationRetryTime: Duration) { } - actual fun setMaxUploadRetryTimeMillis(maxUploadRetryTimeMillis: Long) { + public actual fun setMaxUploadRetryTime(maxUploadRetryTime: Duration) { } - actual fun useEmulator(host: String, port: Int) { + public actual fun useEmulator(host: String, port: Int) { } - actual val reference: StorageReference + public actual val reference: StorageReference get() = TODO("Not yet implemented") - actual fun reference(location: String): StorageReference { + public actual fun reference(location: String): StorageReference { TODO("Not yet implemented") } - } -actual class StorageReference { - actual val name: String +public actual class StorageReference { + public actual val name: String get() = TODO("Not yet implemented") - actual val path: String + public actual val path: String get() = TODO("Not yet implemented") - actual val bucket: String + public actual val bucket: String get() = TODO("Not yet implemented") - actual val parent: StorageReference? + public actual val parent: StorageReference? get() = TODO("Not yet implemented") - actual val root: StorageReference + public actual val root: StorageReference get() = TODO("Not yet implemented") - actual val storage: FirebaseStorage + public actual val storage: FirebaseStorage get() = TODO("Not yet implemented") - actual fun child(path: String): StorageReference { + public actual suspend fun getMetadata(): FirebaseStorageMetadata? { TODO("Not yet implemented") } - actual suspend fun delete() { + public actual fun child(path: String): StorageReference { + TODO("Not yet implemented") + } + + public actual suspend fun delete() { } - actual suspend fun getDownloadUrl(): String { + public actual suspend fun getDownloadUrl(): String { TODO("Not yet implemented") } - actual suspend fun listAll(): ListResult { + public actual suspend fun listAll(): ListResult { TODO("Not yet implemented") } - actual fun putFileResumable(file: File): ProgressFlow { + public actual fun putFileResumable(file: File, metadata: FirebaseStorageMetadata?): ProgressFlow { TODO("Not yet implemented") } - actual suspend fun putFile(file: File) { + public actual suspend fun putFile(file: File, metadata: FirebaseStorageMetadata?) { } + public actual suspend fun putData(data: Data, metadata: FirebaseStorageMetadata?) { + } } -actual class ListResult { - actual val prefixes: List +public actual class ListResult { + public actual val prefixes: List get() = TODO("Not yet implemented") - actual val items: List + public actual val items: List get() = TODO("Not yet implemented") - actual val pageToken: String? + public actual val pageToken: String? get() = TODO("Not yet implemented") } -actual class File -actual class FirebaseStorageException internal constructor(message: String) : FirebaseException(message) \ No newline at end of file +public actual class File +public actual class FirebaseStorageException internal constructor(message: String) : FirebaseException(message) +public actual class Data diff --git a/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt b/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt new file mode 100644 index 000000000..605cfaddb --- /dev/null +++ b/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.jvm.kt @@ -0,0 +1,5 @@ +package dev.gitlive.firebase.storage + +actual fun createTestData(): Data { + TODO("Not yet implemented") +} diff --git a/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.kt b/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.kt index 0417a89aa..b7d065854 100644 --- a/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.kt +++ b/firebase-storage/src/jvmTest/kotlin/dev/gitlive/firebase/storage/storage.kt @@ -3,15 +3,14 @@ */ @file:JvmName("tests") + package dev.gitlive.firebase.storage -import android.content.Context -import com.google.firebase.FirebasePlatform -import dev.gitlive.firebase.MockFirebasePlatform +import dev.gitlive.firebase.testContext -actual val emulatorHost: String = "10.0.2.2" +actual val emulatorHost: String = "localhost" -actual val context: Any get() = Context().also { FirebasePlatform.initializeFirebasePlatform(MockFirebasePlatform()) } +actual val context: Any = testContext @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreForAndroidUnitTest diff --git a/gradle.properties b/gradle.properties index b70fda4a6..01a9fd1fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ kotlin.mpp.stability.nowarn=true #kotlin.native.enableDependencyPropagation=false kotlin.native.enableParallelExecutionCheck=false kotlin.setJvmTargetFromAndroidCompileOptions=true -org.gradle.jvmargs=-Xmx8G -Dfile.encoding\=UTF-8 -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options\=-Xmx8G,-XX:MaxMetaspaceSize=512m +org.gradle.jvmargs=-Xmx8G -Dfile.encoding\=UTF-8 -XX:MaxMetaspaceSize=1024m -Dkotlin.daemon.jvm.options\=-Xmx8G,-XX:MaxMetaspaceSize=1024m org.gradle.parallel=true systemProp.org.gradle.internal.publish.checksums.insecure=true testOptions.unitTests.isIncludeAndroidResources=true @@ -20,50 +20,67 @@ kotlin.native.cacheKind=none # Set to true to skip tests and even compilation of the iOS target. skipIosTarget=false + # Skip iOS Tests +firebase-analytics.skipIosTests=true firebase-app.skipIosTests=false -# We are skipping auth ios tests due to an issue with keychain and simulator. -firebase-auth.skipIosTests=true +firebase-auth.skipIosTests=false +firebase-common-internal.skipIosTests=false firebase-common.skipIosTests=false firebase-config.skipIosTests=false +firebase-crashlytics.skipIosTests=false firebase-database.skipIosTests=false firebase-firestore.skipIosTests=false firebase-functions.skipIosTests=false firebase-installations.skipIosTests=false +firebase-messaging.skipIosTests=false firebase-perf.skipIosTests=false -firebase-crashlytics.skipIosTests=false firebase-storage.skipIosTests=false +# We can have the functionality to skip jvm tests, due to compatibility issues. +firebase-analytics.skipJvmTests=true +firebase-app.skipJvmTests=false +firebase-auth.skipJvmTests=true +firebase-common-internal.skipJvmTests=false +firebase-common.skipJvmTests=false +firebase-config.skipJvmTests=true +firebase-crashlytics.skipJvmTests=true +firebase-database.skipJvmTests=false +firebase-firestore.skipJvmTests=false +firebase-functions.skipJvmTests=false +firebase-installations.skipJvmTests=false +firebase-messaging.skipJvmTests=false +firebase-perf.skipJvmTests=true +firebase-storage.skipJvmTests=true + # We can have the functionality to skip js tests, due to compatibility issues. +firebase-analytics.skipJsTests=false firebase-app.skipJsTests=false firebase-auth.skipJsTests=false +firebase-common-internal.skipJsTests=false firebase-common.skipJsTests=false firebase-config.skipJsTests=false +firebase-crashlytics.skipJsTests=true firebase-database.skipJsTests=false firebase-firestore.skipJsTests=false firebase-functions.skipJsTests=false firebase-installations.skipJsTests=false +firebase-messaging.skipJsTests=false firebase-perf.skipJsTests=false firebase-storage.skipJsTests=false # Versions: -firebase-app.version=1.10.4 -firebase-auth.version=1.10.4 -firebase-common.version=1.10.4 -firebase-config.version=1.10.4 -firebase-database.version=1.10.4 -firebase-firestore.version=1.10.4 -firebase-functions.version=1.10.4 -firebase-installations.version=1.10.4 -firebase-perf.version=1.10.4 -firebase-crashlytics.version=1.10.4 -firebase-storage.version=1.10.4 - -# Dependencies Versions: -gradlePluginVersion=8.2.0 -kotlinVersion=1.9.23 -coroutinesVersion=1.7.3 -serializationVersion=1.6.0 -firebaseBoMVersion=33.0.0 -apiVersion=1.8 -languageVersion=1.9 +firebase-analytics.version=2.0.0 +firebase-app.version=2.0.0 +firebase-auth.version=2.0.0 +firebase-common-internal.version=2.0.0 +firebase-common.version=2.0.0 +firebase-config.version=2.0.0 +firebase-crashlytics.version=2.0.0 +firebase-database.version=2.0.0 +firebase-firestore.version=2.0.0 +firebase-functions.version=2.0.0 +firebase-installations.version=2.0.0 +firebase-messaging.version=2.0.0 +firebase-perf.version=2.0.0 +firebase-storage.version=2.0.0 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..9561276bc --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,64 @@ +[versions] +agp = "8.5.2" +androidx-test-core = "1.6.1" +androidx-test-junit = "1.2.1" +androidx-test-runner = "1.6.2" +ben-manes-versions = "0.51.0" +firebase-bom = "33.2.0" +gitlive-firebase-java-sdk = "0.4.5" +gson = "2.11.0" +junit = "4.13.2" +kotlin = "2.0.20" +kotlinx-coroutines = "1.9.0-RC.2" +kotlinx-serialization = "1.7.1" +kotlinx-binarycompatibilityvalidator = "0.16.3" +kotlinx-datetime = "0.6.1" +kotlinter = "4.4.1" +settings-api = "2.0" +settings-language = "2.0" +firebase-cocoapods = "10.28.0" +ios-deploymentTarget = "13.0" +test-logger-plugin = "4.0.0" +dokka = "1.9.20" +desugar-libs = "2.1.0" + +[libraries] +android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } +androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" } +androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-junit" } +androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } +gitlive-firebase-java-sdk = { module = "dev.gitlive:firebase-java-sdk", version.ref = "gitlive-firebase-java-sdk" } +google-firebase-analytics = { module = "com.google.firebase:firebase-analytics"} +google-firebase-auth-ktx = { module = "com.google.firebase:firebase-auth-ktx"} +google-firebase-common-ktx = { module = "com.google.firebase:firebase-common-ktx"} +google-firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx"} +google-firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx"} +google-firebase-database-ktx = { module = "com.google.firebase:firebase-database-ktx"} +google-firebase-firestore = { module = "com.google.firebase:firebase-firestore"} +google-firebase-functions = { module = "com.google.firebase:firebase-functions"} +google-firebase-installations-ktx = { module = "com.google.firebase:firebase-installations-ktx"} +google-firebase-messaging = { module = "com.google.firebase:firebase-messaging"} +google-firebase-perf-ktx = { module = "com.google.firebase:firebase-perf-ktx"} +google-firebase-storage = { module = "com.google.firebase:firebase-storage"} +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +junit = { module = "junit:junit", version.ref = "junit" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" } +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } +dokka-base = { module = "org.jetbrains.dokka:dokka-base", version.ref = "dokka" } +android-desugarjdk = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar-libs" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +ben-manes-versions = { id = "com.github.ben-manes.versions", version.ref = "ben-manes-versions" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlinx-binarycompatibilityvalidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "kotlinx-binarycompatibilityvalidator" } +multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +native-cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } +test-logger-plugin = { id = "com.adarshr.test-logger", version.ref = "test-logger-plugin" } +dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } +kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinter" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 17655d0ef..9355b4155 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c78733..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,12 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +217,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts index dd80bfd3a..1c2600981 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,24 +1,35 @@ include( + "firebase-analytics", "firebase-app", "firebase-auth", "firebase-common", + "firebase-common-internal", "firebase-config", + "firebase-crashlytics", "firebase-database", "firebase-firestore", "firebase-functions", "firebase-installations", + "firebase-messaging", "firebase-perf", - "firebase-crashlytics", "firebase-storage", "test-utils" ) pluginManagement { - val kotlinVersion: String by settings + includeBuild("convention-plugin-test-option") + repositories { + google() + mavenCentral() + gradlePluginPortal() + maven { url = uri("https://plugins.gradle.org/m2/") } + } +} - plugins { - kotlin("multiplatform") version kotlinVersion - kotlin("native.cocoapods") version kotlinVersion - kotlin("plugin.serialization") version kotlinVersion +dependencyResolutionManagement { + repositories { + mavenLocal() + google() + mavenCentral() } } diff --git a/test-utils/api/android/test-utils.api b/test-utils/api/android/test-utils.api new file mode 100644 index 000000000..470377a53 --- /dev/null +++ b/test-utils/api/android/test-utils.api @@ -0,0 +1,8 @@ +public final class dev/gitlive/firebase/TestUtilsAndroid { + public static final fun nativeAssertEquals (Ljava/lang/Object;Ljava/lang/Object;)V + public static final fun nativeListOf ([Ljava/lang/Object;)Ljava/lang/Object; + public static final fun nativeMapOf ([Lkotlin/Pair;)Ljava/lang/Object; + public static final fun runBlockingTest (Lkotlin/jvm/functions/Function2;)V + public static final fun runTest (Lkotlin/jvm/functions/Function2;)V +} + diff --git a/test-utils/api/jvm/test-utils.api b/test-utils/api/jvm/test-utils.api new file mode 100644 index 000000000..cdab425c2 --- /dev/null +++ b/test-utils/api/jvm/test-utils.api @@ -0,0 +1,9 @@ +public final class dev/gitlive/firebase/TestUtilsJvm { + public static final fun getTestContext ()Landroid/app/Application; + public static final fun nativeAssertEquals (Ljava/lang/Object;Ljava/lang/Object;)V + public static final fun nativeListOf ([Ljava/lang/Object;)Ljava/lang/Object; + public static final fun nativeMapOf ([Lkotlin/Pair;)Ljava/lang/Object; + public static final fun runBlockingTest (Lkotlin/jvm/functions/Function2;)V + public static final fun runTest (Lkotlin/jvm/functions/Function2;)V +} + diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index 4e6780bd6..aca6eb343 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -1,3 +1,8 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree + /* * Copyright (c) 2023 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ @@ -25,8 +30,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } packaging { @@ -40,35 +45,31 @@ android { } kotlin { - + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } targets.configureEach { compilations.configureEach { - kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes" + compileTaskProvider.configure { + compilerOptions { + if (this is KotlinJvmCompilerOptions) { + jvmTarget = JvmTarget.JVM_17 + } + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } } @Suppress("OPT_IN_USAGE") androidTarget { + instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) + unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) publishAllLibraryVariants() - compilations.configureEach { - kotlinOptions { - jvmTarget = "11" - } - } } - jvm { - compilations.getByName("main") { - kotlinOptions { - jvmTarget = "17" - } - } - compilations.getByName("test") { - kotlinOptions { - jvmTarget = "17" - } - } - } + jvm() val supportIosTarget = project.property("skipIosTarget") != "true" @@ -87,10 +88,8 @@ kotlin { sourceSets { all { languageSettings.apply { - val apiVersion: String by project - val languageVersion: String by project - this.apiVersion = apiVersion - this.languageVersion = languageVersion + this.apiVersion = libs.versions.settings.api.get() + this.languageVersion = libs.versions.settings.language.get() progressiveMode = true if (name.lowercase().contains("ios")) { optIn("kotlinx.cinterop.ExperimentalForeignApi") @@ -101,10 +100,9 @@ kotlin { getByName("commonMain") { dependencies { - val coroutinesVersion: String by project api(kotlin("test")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") - api("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") + api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.coroutines.test) } } @@ -113,5 +111,11 @@ kotlin { implementation(kotlin("test-js")) } } + + getByName("jvmMain") { + dependencies { + api(libs.kotlinx.coroutines.swing) + } + } } } diff --git a/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt index 1a4635f85..df47b7bba 100644 --- a/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/androidMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -13,7 +13,7 @@ actual fun runTest(test: suspend CoroutineScope.() -> Unit) = kotlinx.coroutines actual fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) = runBlocking(block = action) actual fun nativeMapOf(vararg pairs: Pair): Any = mapOf(*pairs) -actual fun nativeListOf(vararg elements: Any): Any = listOf(*elements) +actual fun nativeListOf(vararg elements: Any?): Any = listOf(*elements) actual fun nativeAssertEquals(expected: Any?, actual: Any?) { kotlin.test.assertEquals(expected, actual) } diff --git a/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt index bf9fcdc19..99daabbf2 100644 --- a/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/commonMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -1,4 +1,3 @@ -@file:JvmName("TestUtilsJVM") /* * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ @@ -7,11 +6,10 @@ package dev.gitlive.firebase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestResult -import kotlin.jvm.JvmName expect fun runTest(test: suspend CoroutineScope.() -> Unit): TestResult expect fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) expect fun nativeMapOf(vararg pairs: Pair): Any -expect fun nativeListOf(vararg elements: Any): Any +expect fun nativeListOf(vararg elements: Any?): Any expect fun nativeAssertEquals(expected: Any?, actual: Any?) diff --git a/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt index 6ba4248a4..1bd12ad92 100644 --- a/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/iosMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -20,7 +20,7 @@ actual fun runTest(test: suspend CoroutineScope.() -> Unit) = runBlocking { while (testRun.isActive) { NSRunLoop.mainRunLoop.runMode( NSDefaultRunLoopMode, - beforeDate = NSDate.create(timeInterval = 1.0, sinceDate = NSDate()) + beforeDate = NSDate.create(timeInterval = 1.0, sinceDate = NSDate()), ) yield() } @@ -28,7 +28,7 @@ actual fun runTest(test: suspend CoroutineScope.() -> Unit) = runBlocking { } actual fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) = runBlocking(block = action) actual fun nativeMapOf(vararg pairs: Pair): Any = mapOf(*pairs) -actual fun nativeListOf(vararg elements: Any): Any = listOf(*elements) +actual fun nativeListOf(vararg elements: Any?): Any = listOf(*elements) actual fun nativeAssertEquals(expected: Any?, actual: Any?) { kotlin.test.assertEquals(expected, actual) } diff --git a/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt index eac94efcf..5f87d1686 100644 --- a/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/jsMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -15,7 +15,7 @@ actual fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) { } actual fun nativeMapOf(vararg pairs: Pair): Any = json(*pairs.map { (key, value) -> ((key as? String) ?: JSON.stringify(key)) to value }.toTypedArray()) -actual fun nativeListOf(vararg elements: Any): Any = elements +actual fun nativeListOf(vararg elements: Any?): Any = elements actual fun nativeAssertEquals(expected: Any?, actual: Any?) { kotlin.test.assertEquals(JSON.stringify(expected), JSON.stringify(actual)) } diff --git a/test-utils/src/jvmMain/kotlin/dev/gitlive/firebase/MockFirebasePlatform.kt b/test-utils/src/jvmMain/kotlin/dev/gitlive/firebase/MockFirebasePlatform.kt deleted file mode 100644 index 0f48eebcf..000000000 --- a/test-utils/src/jvmMain/kotlin/dev/gitlive/firebase/MockFirebasePlatform.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.gitlive.firebase - -import com.google.firebase.FirebasePlatform - -class MockFirebasePlatform : FirebasePlatform() { - val storage = mutableMapOf() - override fun store(key: String, value: String) = storage.set(key, value) - override fun retrieve(key: String) = storage[key] - override fun clear(key: String) { storage.remove(key) } - override fun log(msg: String) = println(msg) -} diff --git a/test-utils/src/jvmMain/kotlin/dev/gitlive/firebase/TestUtils.kt b/test-utils/src/jvmMain/kotlin/dev/gitlive/firebase/TestUtils.kt index 2eccb55ef..2658e6336 100644 --- a/test-utils/src/jvmMain/kotlin/dev/gitlive/firebase/TestUtils.kt +++ b/test-utils/src/jvmMain/kotlin/dev/gitlive/firebase/TestUtils.kt @@ -1,20 +1,33 @@ -@file:JvmName("TestUtilsJVM") +@file:JvmName("TestUtilsJvm") /* * Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license. */ package dev.gitlive.firebase +import android.app.Application +import com.google.firebase.FirebasePlatform import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking import kotlin.time.Duration.Companion.minutes +val testContext = Application().apply { + FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() { + val storage = mutableMapOf() + override fun store(key: String, value: String) = storage.set(key, value) + override fun retrieve(key: String) = storage[key] + override fun clear(key: String) { + storage.remove(key) + } + override fun log(msg: String) = println(msg) + }) +} + actual fun runTest(test: suspend CoroutineScope.() -> Unit) = kotlinx.coroutines.test.runTest(timeout = 5.minutes) { test() } actual fun runBlockingTest(action: suspend CoroutineScope.() -> Unit) = runBlocking(block = action) actual fun nativeMapOf(vararg pairs: Pair): Any = mapOf(*pairs) -actual fun nativeListOf(vararg elements: Any): Any = listOf(*elements) +actual fun nativeListOf(vararg elements: Any?): Any = listOf(*elements) actual fun nativeAssertEquals(expected: Any?, actual: Any?) { kotlin.test.assertEquals(expected, actual) } - diff --git a/test/database.rules.json b/test/database.rules.json index b104e9c24..9e0cf90a1 100644 --- a/test/database.rules.json +++ b/test/database.rules.json @@ -1,6 +1,16 @@ { "rules": { ".read": true, - ".write": true + ".write": true, + "FirebaseRealtimeDatabaseTest": { + "lastActivity": { + ".validate": "!newData.exists() || newData.isNumber()" + }, + "nested": { + "lastActivity": { + ".validate": "!newData.exists() || newData.isNumber()" + } + } + } } } \ No newline at end of file