├── .gitignore
├── .idea
├── .gitignore
├── .name
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
└── jsMain
└── kotlin
└── dev
└── bitspittle
└── firebase
├── analytics
└── Bindings.kt
├── app
└── Bindings.kt
├── auth
├── Bindings.ActionCode.kt
├── Bindings.Errors.kt
├── Bindings.Providers.kt
└── Bindings.kt
├── database
└── Bindings.kt
├── externals
├── analytics
│ └── Externals.kt
├── app
│ └── Externals.kt
├── auth
│ └── Externals.kt
├── database
│ └── Externals.kt
└── util
│ └── Externals.kt
└── util
├── FirebaseError.kt
├── JsonSupport.kt
└── StringExtensions.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | # General ignores
2 | .DS_Store
3 | build
4 | out
5 | kotlin-js-store
6 |
7 | # IntelliJ ignores
8 | *.iml
9 | /*.ipr
10 |
11 | /.idea/caches
12 | /.idea/libraries
13 | /.idea/modules.xml
14 | /.idea/workspace.xml
15 | /.idea/gradle.xml
16 | /.idea/navEditor.xml
17 | /.idea/assetWizardSettings.xml
18 | /.idea/artifacts
19 | /.idea/compiler.xml
20 | /.idea/jarRepositories.xml
21 | /.idea/*.iml
22 | /.idea/modules
23 | /.idea/libraries-with-intellij-classes.xml
24 |
25 | # Gradle ignores
26 | .gradle
27 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | firebase-kotlin-bindings
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project is a collection of bindings for working with various Firebase services in Kotlin/JS.
2 |
3 | *THIS PROJECT IS STILL VERY EXPERIMENTAL AND NOT READY FOR PUBLIC USE.*
4 |
5 | The goal of these bindings are to provide a clean, Kotlin-idiomatic view of Firebase web services:
6 | * JavaScript methods that return `Promise`s are converted to `suspend fun`s in Kotlin
7 | * Class design is updated for an API that is more object-oriented.
8 | * Some methods which take in / return JSON-objects in JavaScript are changed to typed objects in Kotlin.
9 | * Constructor (factory) methods are added when possible to make constructing JavaScript interfaces with optional
10 | parameters easier.
11 |
12 | ## Gradle
13 |
14 | At the moment, this library is not published anywhere. To use it, clone this project, and then run
15 |
16 | ```bash
17 | $ ./gradlew publishToMavenLocal
18 | ```
19 |
20 | Then, in your own project:
21 |
22 | ```kotlin
23 | // build.gradle.kts
24 |
25 | repositories {
26 | /* ... other repositories ... */
27 | mavenLocal()
28 | }
29 |
30 | kotlin {
31 | js(IR) { /* ... */ }
32 | sourceSets {
33 | val jsMain by getting {
34 | dependencies {
35 | // TODO: Replace with a real version when this library gets more mature
36 | implementation("dev.bitspittle:firebase-kotlin-bindings:+")
37 | }
38 | }
39 | }
40 | }
41 | ```
42 |
43 | ## Background
44 |
45 | This project contains bindings I need for using various Firebase services, wrapping API calls from
46 | [the web API](https://firebase.google.com/docs/reference/js) and porting them to Kotlin.
47 |
48 | Honestly, I probably should have used an existing solution (for example,
49 | [GitLiveApp/firebase-kotlin-sdk](https://github.com/GitLiveApp/firebase-kotlin-sdk)), but I wanted to see what it's like
50 | to wrap a JS API with a Kotlin one. This is, at the moment, a learning project.
51 |
52 | ## Usage
53 |
54 | This library provides a handcrafted Kotlin layer backed by external JS APIs that it delegates to.
55 |
56 | These custom classes are provided instead of the underlying JS APIs as those are designed somewhat inconsistently and
57 | occasionally using features that don't map cleanly to Kotlin concepts.
58 |
59 | To get started, initialize a `FirebaseApp` class, and use that to access the remaining APIs.
60 |
61 | So for example, this JavaScript code (taken from
62 | [the tutorials](https://firebase.google.com/docs/database/web/read-and-write#basic_write)):
63 |
64 | ```javascript
65 | import { initializeApp } from "firebase/app";
66 | import { getDatabase, ref, set } from "firebase/database";
67 |
68 | const firebaseOptions = { /* ... */ };
69 | const app = initializeApp(firebaseOptions)
70 |
71 | function writeUserData(userId, name, email, imageUrl) {
72 | const db = getDatabase(app);
73 | set(ref(db, 'users/' + userId), {
74 | username: name,
75 | email: email,
76 | profile_picture : imageUrl
77 | });
78 | }
79 | ```
80 |
81 | translates to the following Kotlin code:
82 |
83 | ```kotlin
84 | val app = FirebaseApp.initialize(FirebaseOptions(/*...*/))
85 |
86 | fun writeUserData(userId: String, name: String, email: String, imageUrl: String) {
87 | val db = app.getDatabase()
88 | db.ref("users/$userId").set(json(
89 | "username" to name,
90 | "email" to email,
91 | "profile_picture" to imageUrl
92 | ))
93 | }
94 | ```
95 |
96 | ## API support
97 |
98 | - @firebase/analytics (low)
99 | - @firebase/app (low)
100 | - @firebase/app-check (none)
101 | - @firebase/auth (low)
102 | - @firebase/database (low)
103 | - @firebase/firestore (none)
104 | - @firebase/functions (none)
105 | - @firebase/installations (none)
106 | - @firebase/messaging (none)
107 | - @firebase/performance (none)
108 | - @firebase/remote-config (none)
109 | - @firebase/storage (none)
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.kotlin.multiplatform)
3 | `maven-publish`
4 | }
5 |
6 | repositories {
7 | mavenCentral()
8 | google()
9 | }
10 |
11 |
12 | group = "dev.bitspittle"
13 | version = libs.versions.firebase.bindings.get()
14 |
15 | kotlin {
16 | js(IR) {
17 | browser()
18 | }
19 |
20 | @Suppress("UNUSED_VARIABLE") // Suppress spurious warnings about sourceset variables not being used
21 | sourceSets {
22 | val commonMain by getting {
23 | dependencies {
24 | }
25 | }
26 |
27 | val jsMain by getting {
28 | dependencies {
29 | implementation(npm("firebase", libs.versions.firebase.web.get()))
30 | implementation(libs.kotlinx.coroutines)
31 | }
32 | }
33 | }
34 | }
35 |
36 | publishing {
37 | repositories {
38 | maven {
39 | group = project.group
40 | version = project.version.toString()
41 | }
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | kotlin.mpp.stability.nowarn=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | firebase-web = "9.15.0" # from https://www.npmjs.com/package/firebase
3 | firebase-bindings = "0.1-SNAPSHOT"
4 | kotlin = "1.8.20"
5 | kotlinx-coroutines = "1.6.0"
6 |
7 | [libraries]
8 | kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
9 |
10 | [plugins]
11 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
12 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitspittle/firebase-kotlin-bindings/7a38a16b1c6be612e09ffa8d81f0ce7530119dc7/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
134 |
135 | Please set the JAVA_HOME variable in your environment to match the
136 | location of your Java installation."
137 | fi
138 |
139 | # Increase the maximum file descriptors if we can.
140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
141 | case $MAX_FD in #(
142 | max*)
143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
144 | # shellcheck disable=SC3045
145 | MAX_FD=$( ulimit -H -n ) ||
146 | warn "Could not query maximum file descriptor limit"
147 | esac
148 | case $MAX_FD in #(
149 | '' | soft) :;; #(
150 | *)
151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
152 | # shellcheck disable=SC3045
153 | ulimit -n "$MAX_FD" ||
154 | warn "Could not set maximum file descriptor limit to $MAX_FD"
155 | esac
156 | fi
157 |
158 | # Collect all arguments for the java command, stacking in reverse order:
159 | # * args from the command line
160 | # * the main class name
161 | # * -classpath
162 | # * -D...appname settings
163 | # * --module-path (only if needed)
164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
165 |
166 | # For Cygwin or MSYS, switch paths to Windows format before running java
167 | if "$cygwin" || "$msys" ; then
168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
170 |
171 | JAVACMD=$( cygpath --unix "$JAVACMD" )
172 |
173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
174 | for arg do
175 | if
176 | case $arg in #(
177 | -*) false ;; # don't mess with options #(
178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
179 | [ -e "$t" ] ;; #(
180 | *) false ;;
181 | esac
182 | then
183 | arg=$( cygpath --path --ignore --mixed "$arg" )
184 | fi
185 | # Roll the args list around exactly as many times as the number of
186 | # args, so each arg winds up back in the position where it started, but
187 | # possibly modified.
188 | #
189 | # NB: a `for` loop captures its iteration list before it begins, so
190 | # changing the positional parameters here affects neither the number of
191 | # iterations, nor the values presented in `arg`.
192 | shift # remove old arg
193 | set -- "$@" "$arg" # push replacement arg
194 | done
195 | fi
196 |
197 |
198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
200 |
201 | # Collect all arguments for the java command;
202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
203 | # shell script including quotes and variable substitutions, so put them in
204 | # double quotes to make sure that they get re-expanded; and
205 | # * put everything else in single quotes, so that it's not re-expanded.
206 |
207 | set -- \
208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
209 | -classpath "$CLASSPATH" \
210 | org.gradle.wrapper.GradleWrapperMain \
211 | "$@"
212 |
213 | # Stop when "xargs" is not available.
214 | if ! command -v xargs >/dev/null 2>&1
215 | then
216 | die "xargs is not available"
217 | fi
218 |
219 | # Use "xargs" to parse quoted args.
220 | #
221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
222 | #
223 | # In Bash we could simply go:
224 | #
225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
226 | # set -- "${ARGS[@]}" "$@"
227 | #
228 | # but POSIX shell has neither arrays nor command substitution, so instead we
229 | # post-process each arg (as a line of input to sed) to backslash-escape any
230 | # character that might be a shell metacharacter, then use eval to reverse
231 | # that process (while maintaining the separation between arguments), and wrap
232 | # the whole thing up as a single "set" statement.
233 | #
234 | # This will of course break if any of these variables contains a newline or
235 | # an unmatched quote.
236 | #
237 |
238 | eval "set -- $(
239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
240 | xargs -n1 |
241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
242 | tr '\n' ' '
243 | )" '"$@"'
244 |
245 | exec "$JAVACMD" "$@"
246 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | }
5 | }
6 |
7 | rootProject.name = "firebase-kotlin-bindings"
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/analytics/Bindings.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.analytics
2 |
3 | import dev.bitspittle.firebase.util.jsonWithoutNullValues
4 | import kotlin.js.Json
5 |
6 | class Analytics internal constructor(private val wrapped: dev.bitspittle.firebase.externals.analytics.Analytics) {
7 | sealed class Event(val name: String) {
8 | internal abstract fun toParams(): Json
9 |
10 | /** Represents a screen within an app; one page might host multiple screens */
11 | // https://firebase.google.com/docs/reference/js/analytics#logevent_3
12 | class PageView(private val title: String? = null, private val location: String? = null, private val path: String? = null) : Event("page_view") {
13 | override fun toParams() = jsonWithoutNullValues(
14 | "title" to title,
15 | "location" to location,
16 | "path" to path,
17 | )
18 | }
19 |
20 | // region games
21 |
22 | // https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#level_start
23 | class LevelStart(private val levelName: String? = null) : Event("level_start") {
24 | override fun toParams() = jsonWithoutNullValues(
25 | "level_name" to levelName,
26 | )
27 | }
28 |
29 | // https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#level_start
30 | class LevelEnd(private val levelName: String? = null, private val success: Boolean? = null) : Event("level_end") {
31 | override fun toParams() = jsonWithoutNullValues(
32 | "level_name" to levelName,
33 | "success" to success,
34 | )
35 | }
36 |
37 | // endregion
38 | }
39 |
40 | fun log(event: Event, options: AnalyticsCallOptions? = null) {
41 | dev.bitspittle.firebase.externals.analytics.logEvent(
42 | wrapped,
43 | event.name,
44 | event.toParams(),
45 | @Suppress("NAME_SHADOWING")
46 | options?.let { options ->
47 | object : dev.bitspittle.firebase.externals.analytics.AnalyticsCallOptions {
48 | override val global get() = options.global
49 | }
50 | })
51 | }
52 |
53 | fun setAnalyticsCollectionEnabled(enabled: Boolean) {
54 | dev.bitspittle.firebase.externals.analytics.setAnalyticsCollectionEnabled(wrapped, enabled)
55 | }
56 | }
57 |
58 | data class AnalyticsCallOptions(val global: Boolean)
59 |
60 | // TODO: Support passing in EventParams as well.
61 | class AnalyticsSettings(internal val gtagParams: GtagConfigParams = GtagConfigParams())
62 |
63 | // https://firebase.google.com/docs/reference/js/analytics.gtagconfigparams
64 | data class GtagConfigParams(
65 | val allowAdPersonalizationSignals: Boolean? = null,
66 | val allowGoogleSignals: Boolean? = null,
67 | val cookieDomain: String? = null,
68 | val cookieExpires: Number? = null,
69 | val cookieFlags: String? = null,
70 | val cookiePrefix: String? = null,
71 | val cookieUpdate: Boolean? = null,
72 | val pageLocation: String? = null,
73 | val pageTitle: String? = null,
74 | val sendPageView: Boolean? = null,
75 | ) {
76 | internal fun toJson() = jsonWithoutNullValues(
77 | "allow_ad_personalization_signals" to allowAdPersonalizationSignals,
78 | "allow_google_signals" to allowGoogleSignals,
79 | "cookie_domain" to cookieDomain,
80 | "cookie_expires" to cookieExpires,
81 | "cookie_flags" to cookieFlags,
82 | "cookie_prefix" to cookiePrefix,
83 | "cookie_update" to cookieUpdate,
84 | "page_location" to pageLocation,
85 | "page_title" to pageTitle,
86 | "send_page_view" to sendPageView,
87 | )
88 | }
89 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/app/Bindings.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.app
2 |
3 | import dev.bitspittle.firebase.analytics.Analytics
4 | import dev.bitspittle.firebase.analytics.AnalyticsSettings
5 | import dev.bitspittle.firebase.auth.Auth
6 | import dev.bitspittle.firebase.database.Database
7 | import kotlin.js.json
8 |
9 | data class FirebaseOptions(
10 | val apiKey: String,
11 | val authDomain: String,
12 | val databaseURL: String,
13 | val projectId: String,
14 | val storageBucket: String,
15 | val messagingSenderId: String,
16 | val appId: String,
17 | val measurementId: String? = null,
18 | )
19 |
20 | class FirebaseApp internal constructor(private val wrapped: dev.bitspittle.firebase.externals.app.FirebaseApp) {
21 | companion object {
22 | fun initialize(options: FirebaseOptions, name: String? = null) = FirebaseApp(
23 | dev.bitspittle.firebase.externals.app.initializeApp(json(
24 | "apiKey" to options.apiKey,
25 | "authDomain" to options.authDomain,
26 | "databaseURL" to options.databaseURL,
27 | "projectId" to options.projectId,
28 | "storageBucket" to options.storageBucket,
29 | "messagingSenderId" to options.messagingSenderId,
30 | "appId" to options.appId,
31 | "measurementId" to options.measurementId,
32 | ),
33 | name
34 | )
35 | )
36 | }
37 |
38 | val name get() = wrapped.name
39 | val options get() = FirebaseOptions(
40 | apiKey = wrapped.options.apiKey,
41 | authDomain = wrapped.options.authDomain,
42 | databaseURL = wrapped.options.databaseURL,
43 | projectId = wrapped.options.projectId,
44 | storageBucket = wrapped.options.storageBucket,
45 | messagingSenderId = wrapped.options.messagingSenderId,
46 | appId = wrapped.options.appId,
47 | measurementId = wrapped.options.measurementId,
48 | )
49 |
50 | fun getAnalytics() = Analytics(dev.bitspittle.firebase.externals.analytics.getAnalytics(wrapped))
51 | fun initializeAnalytics(settings: AnalyticsSettings) =
52 | Analytics(dev.bitspittle.firebase.externals.analytics.initializeAnalytics(
53 | wrapped,
54 | object : dev.bitspittle.firebase.externals.analytics.AnalyticsSettings {
55 | override val config = settings.gtagParams.toJson()
56 | }
57 | ))
58 |
59 | fun getDatabase(url: String? = null) = Database(dev.bitspittle.firebase.externals.database.getDatabase(wrapped, url))
60 | fun getAuth() = Auth(dev.bitspittle.firebase.externals.auth.getAuth(wrapped))
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/auth/Bindings.ActionCode.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.auth
2 |
3 | class ActionCodeSettings(
4 | val url: String,
5 | val handleCodeInApp: Boolean,
6 | val android: AndroidConfig? = null,
7 | val iOs: IosConfig? = null,
8 | val dynamicLinkDomain: String? = null,
9 | ) {
10 | class AndroidConfig(
11 | val packageName: String,
12 | val installApp: Boolean? = null,
13 | val minimumVersion: String? = null,
14 | )
15 | class IosConfig(val bundleId: String)
16 | }
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/auth/Bindings.Errors.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.auth
2 |
3 | import dev.bitspittle.firebase.util.FirebaseError
4 |
5 | class AuthError internal constructor(
6 | private val wrapped: dev.bitspittle.firebase.externals.auth.AuthError
7 | ) : Exception(), FirebaseError {
8 | // From https://firebase.google.com/docs/reference/js/auth
9 | enum class Code(override val text: String) : FirebaseError.Code {
10 | AdminOnlyOperation("auth/admin-restricted-operation"),
11 | AlreadyInitialized("auth/already-initialized"),
12 | ArgumentError("auth/argument-error"),
13 | AppNotAuthorized("auth/app-not-authorized"),
14 | AppNotInstalled("auth/app-not-installed"),
15 | CaptchaCheckFailed("auth/captcha-check-failed"),
16 | CodeExpired("auth/code-expired"),
17 | CordovaNotReady("auth/cordova-not-ready"),
18 | CorsUnsupported("auth/cors-unsupported"),
19 | CredentialAlreadyInUse("auth/credential-already-in-use"),
20 | CredentialMismatch("auth/custom-token-mismatch"),
21 | CredentialTooOldLoginAgain("auth/requires-recent-login"),
22 | DependentSdkInitBeforeAuth("auth/dependent-sdk-initialized-before-auth"),
23 | DynamicLinkNotActivated("auth/dynamic-link-not-activated"),
24 | EmailChangeNeedsVerification("auth/email-change-needs-verification"),
25 | EmailExists("auth/email-already-in-use"),
26 | EmulatorConfigFailed("auth/emulator-config-failed"),
27 | ExpiredOobCode("auth/expired-action-code"),
28 | ExpiredPopupRequest("auth/cancelled-popup-request"),
29 | InternalError("auth/internal-error"),
30 | InvalidApiKey("auth/invalid-api-key"),
31 | InvalidAppCredential("auth/invalid-app-credential"),
32 | InvalidAppId("auth/invalid-app-id"),
33 | InvalidAuth("auth/invalid-user-token"),
34 | InvalidAuthEvent("auth/invalid-auth-event"),
35 | InvalidCertHash("auth/invalid-cert-hash"),
36 | InvalidCode("auth/invalid-verification-code"),
37 | InvalidContinueUri("auth/invalid-continue-uri"),
38 | InvalidCordovaConfiguration("auth/invalid-cordova-configuration"),
39 | InvalidCustomToken("auth/invalid-custom-token"),
40 | InvalidDynamicLinkDomain("auth/invalid-dynamic-link-domain"),
41 | InvalidEmail("auth/invalid-email"),
42 | InvalidEmulatorScheme("auth/invalid-emulator-scheme"),
43 | InvalidIdpResponse("auth/invalid-credential"),
44 | InvalidMessagePayload("auth/invalid-message-payload"),
45 | InvalidMfaSession("auth/invalid-multi-factor-session"),
46 | InvalidOauthClientId("auth/invalid-oauth-client-id"),
47 | InvalidOauthProvider("auth/invalid-oauth-provider"),
48 | InvalidOobCode("auth/invalid-action-code"),
49 | InvalidOrigin("auth/unauthorized-domain"),
50 | InvalidPassword("auth/wrong-password"),
51 | InvalidPersistence("auth/invalid-persistence-type"),
52 | InvalidPhoneNumber("auth/invalid-phone-number"),
53 | InvalidProviderId("auth/invalid-provider-id"),
54 | InvalidRecipientEmail("auth/invalid-recipient-email"),
55 | InvalidSender("auth/invalid-sender"),
56 | InvalidSessionInfo("auth/invalid-verification-id"),
57 | InvalidTenantId("auth/invalid-tenant-id"),
58 | MfaInfoNotFound("auth/multi-factor-info-not-found"),
59 | MfaRequired("auth/multi-factor-auth-required"),
60 | MissingAndroidPackageName("auth/missing-android-pkg-name"),
61 | MissingAppCredential("auth/missing-app-credential"),
62 | MissingAuthDomain("auth/auth-domain-config-required"),
63 | MissingCode("auth/missing-verification-code"),
64 | MissingContinueUri("auth/missing-continue-uri"),
65 | MissingIframeStart("auth/missing-iframe-start"),
66 | MissingIosBundleId("auth/missing-ios-bundle-id"),
67 | MissingOrInvalidNonce("auth/missing-or-invalid-nonce"),
68 | MissingMfaInfo("auth/missing-multi-factor-info"),
69 | MissingMfaSession("auth/missing-multi-factor-session"),
70 | MissingPhoneNumber("auth/missing-phone-number"),
71 | MissingSessionInfo("auth/missing-verification-id"),
72 | ModuleDestroyed("auth/app-deleted"),
73 | NeedConfirmation("auth/account-exists-with-different-credential"),
74 | NetworkRequestFailed("auth/network-request-failed"),
75 | NullUser("auth/null-user"),
76 | NoAuthEvent("auth/no-auth-event"),
77 | NoSuchProvider("auth/no-such-provider"),
78 | OperationNotAllowed("auth/operation-not-allowed"),
79 | OperationNotSupported("auth/operation-not-supported-in-this-environment"),
80 | PopupBlocked("auth/popup-blocked"),
81 | PopupClosedByUser("auth/popup-closed-by-user"),
82 | ProviderAlreadyLinked("auth/provider-already-linked"),
83 | QuotaExceeded("auth/quota-exceeded"),
84 | RedirectCancelledByUser("auth/redirect-cancelled-by-user"),
85 | RedirectOperationPending("auth/redirect-operation-pending"),
86 | RejectedCredential("auth/rejected-credential"),
87 | SecondFactorAlreadyEnrolled("auth/second-factor-already-in-use"),
88 | SecondFactorLimitExceeded("auth/maximum-second-factor-count-exceeded"),
89 | TenantIdMismatch("auth/tenant-id-mismatch"),
90 | Timeout("auth/timeout"),
91 | TokenExpired("auth/user-token-expired"),
92 | TooManyAttemptsTryLater("auth/too-many-requests"),
93 | UnauthorizedDomain("auth/unauthorized-continue-uri"),
94 | UnsupportedFirstFactor("auth/unsupported-first-factor"),
95 | UnsupportedPersistence("auth/unsupported-persistence-type"),
96 | UnsupportedTenantOperation("auth/unsupported-tenant-operation"),
97 | UnverifiedEmail("auth/unverified-email"),
98 | UserCancelled("auth/user-cancelled"),
99 | UserDeleted("auth/user-not-found"),
100 | UserDisabled("auth/user-disabled"),
101 | UserMismatch("auth/user-mismatch"),
102 | UserSignedOut("auth/user-signed-out"),
103 | WeakPassword("auth/weak-password"),
104 | WebStorageUnsupported("auth/web-storage-unsupported");
105 |
106 | companion object {
107 | fun from(code: String): Code {
108 | return values().first { it.text == code }
109 | }
110 | }
111 | }
112 |
113 |
114 | val customData get() = wrapped.customData
115 | override val code get() = Code.from(wrapped.code)
116 | override val message get() = wrapped.message
117 | }
118 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/auth/Bindings.Providers.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.auth
2 |
3 | import dev.bitspittle.firebase.util.jsonWithoutNullValues
4 |
5 | open class AuthProvider internal constructor(
6 | internal open val wrapped: dev.bitspittle.firebase.externals.auth.AuthProvider
7 | ) {
8 | val providerId get() = wrapped.providerId
9 | }
10 |
11 | // See: https://developers.google.com/identity/openid-connect/openid-connect#authenticationuriparameters
12 | class OAuthCustomParameters(
13 | val accessType: AccessType? = null,
14 | val hd: String? = null,
15 | val includeGrantedScopes: Boolean? = null,
16 | val loginHint: String? = null,
17 | val prompt: Prompt? = null,
18 | val state: String? = null,
19 | ) {
20 | enum class AccessType {
21 | Offline,
22 | Online,
23 | }
24 | enum class Prompt {
25 | None,
26 | Consent,
27 | SelectAccount,
28 | }
29 | }
30 |
31 | open class FederatedAuthProvider internal constructor(
32 | override val wrapped: dev.bitspittle.firebase.externals.auth.FederatedAuthProvider
33 | ) : AuthProvider(wrapped) {
34 |
35 | fun setCustomParameters(params: OAuthCustomParameters): AuthProvider {
36 | return AuthProvider(
37 | wrapped.setCustomParameters(
38 | jsonWithoutNullValues(
39 | "accessType" to params.accessType?.name?.lowercase(),
40 | "hd" to params.hd,
41 | "includeGrantedScopes" to params.includeGrantedScopes,
42 | "loginHint" to params.loginHint,
43 | "prompt" to params.prompt?.name?.lowercase(),
44 | "state" to params.state,
45 | )
46 | )
47 | )
48 | }
49 | }
50 |
51 | sealed class Scope(internal val key: String) {
52 | sealed class Google(key: String): Scope("https://www.googleapis.com/auth/$key") {
53 | object Email : Google("userinfo.email")
54 | object Profile : Google("userinfo.profile")
55 | }
56 | }
57 |
58 | open class BaseOAuthProvider internal constructor(
59 | override val wrapped: dev.bitspittle.firebase.externals.auth.BaseOAuthProvider
60 | ) : FederatedAuthProvider(wrapped) {
61 | fun addScope(scope: S): AuthProvider = AuthProvider(wrapped.addScope(scope.key))
62 | }
63 |
64 | class GoogleAuthProvider internal constructor(
65 | override val wrapped: dev.bitspittle.firebase.externals.auth.GoogleAuthProvider
66 | ) : BaseOAuthProvider(wrapped) {
67 | constructor() : this(dev.bitspittle.firebase.externals.auth.GoogleAuthProvider())
68 | }
69 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/auth/Bindings.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.auth
2 |
3 | import dev.bitspittle.firebase.util.jsonWithoutNullValues
4 | import dev.bitspittle.firebase.util.snakeCaseToTitleCamelCase
5 | import kotlinx.coroutines.await
6 |
7 | @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
8 | private inline fun runUnsafe(block: () -> T): T {
9 | try {
10 | return block()
11 | } catch (e: Throwable) {
12 | (e as? dev.bitspittle.firebase.externals.auth.AuthError)?.let { throw AuthError(it) }
13 | throw e
14 | }
15 | }
16 |
17 | class Auth internal constructor(private val wrapped: dev.bitspittle.firebase.externals.auth.Auth) {
18 | val config get() = Config(wrapped.config)
19 | val currentUser get() = wrapped.currentUser?.let { User(it) }
20 | val languageCode get() = wrapped.languageCode
21 | val name get() = wrapped.name
22 | val settings get() = AuthSettings(wrapped.settings)
23 |
24 | suspend fun setPersistence(persistence: Persistence) =
25 | wrapped.setPersistence(persistence.wrapped).await()
26 |
27 | suspend fun updateCurrentUser(user: User?) =
28 | wrapped.updateCurrentUser(user?.wrapped).await()
29 |
30 | fun useDeviceLanguage() = wrapped.useDeviceLanguage()
31 |
32 | fun onAuthStateChanged(handleStateChanged: (User?) -> Unit) {
33 | dev.bitspittle.firebase.externals.auth.onAuthStateChanged(wrapped) { user ->
34 | handleStateChanged(user?.let { User(it) })
35 | }
36 | }
37 |
38 | suspend fun createUserWithEmailAndPassword(email: String, password: String) = runUnsafe {
39 | UserCredential(
40 | dev.bitspittle.firebase.externals.auth.createUserWithEmailAndPassword(wrapped, email, password).await()
41 | )
42 | }
43 |
44 | fun isSignInLink(emailLink: String) = runUnsafe {
45 | dev.bitspittle.firebase.externals.auth.isSignInWithEmailLink(wrapped, emailLink)
46 | }
47 |
48 | suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = runUnsafe {
49 | dev.bitspittle.firebase.externals.auth.sendSignInLinkToEmail(
50 | auth = wrapped,
51 | email = email,
52 | actionCodeSettings = object : dev.bitspittle.firebase.externals.auth.ActionCodeSettings {
53 | override val android: dynamic = actionCodeSettings.android?.let { android ->
54 | jsonWithoutNullValues(
55 | "installApp" to android.installApp,
56 | "minimumVersion" to android.minimumVersion,
57 | "packageName" to android.packageName,
58 | )
59 | } ?: undefined
60 | override val dynamicLinkDomain: String? = actionCodeSettings.dynamicLinkDomain ?: undefined
61 | override val handleCodeInApp: Boolean = actionCodeSettings.handleCodeInApp
62 | override val iOS: dynamic = actionCodeSettings.iOs?.let { ios ->
63 | jsonWithoutNullValues("bundleId" to ios.bundleId)
64 | } ?: undefined
65 | override val url: String = actionCodeSettings.url
66 | }
67 | ).await()
68 | }
69 |
70 | suspend fun signInWithEmailAndPassword(email: String, password: String) = runUnsafe {
71 | UserCredential(
72 | dev.bitspittle.firebase.externals.auth.signInWithEmailAndPassword(wrapped, email, password).await()
73 | )
74 | }
75 |
76 | suspend fun signInWithEmailLink(email: String, emailLink: String?) = runUnsafe {
77 | UserCredential(
78 | dev.bitspittle.firebase.externals.auth.signInWithEmailLink(wrapped, email, emailLink).await()
79 | )
80 | }
81 |
82 | suspend fun signInWithPopup(provider: AuthProvider) = runUnsafe {
83 | UserCredential(
84 | dev.bitspittle.firebase.externals.auth.signInWithPopup(wrapped, provider.wrapped).await()
85 | )
86 | }
87 |
88 | suspend fun signOut() = dev.bitspittle.firebase.externals.auth.signOut(wrapped).await()
89 | }
90 |
91 | class AuthSettings internal constructor(
92 | internal val wrapped: dev.bitspittle.firebase.externals.auth.AuthSettings
93 | ) {
94 | val appVerificationDisabledForTesting get() = wrapped.appVerificationDisabledForTesting
95 | }
96 |
97 | // https://firebase.google.com/docs/reference/js/auth.config
98 | class Config internal constructor(
99 | internal val wrapped: dev.bitspittle.firebase.externals.auth.Config
100 | ) {
101 | val apiHost get() = wrapped.apiHost
102 | val apiKey get() = wrapped.apiKey
103 | val apiScheme get() = wrapped.apiScheme
104 | val authDomain get() = wrapped.authDomain
105 | val sdkClientVersion get() = wrapped.sdkClientVersion
106 | val tokenApiHost get() = wrapped.tokenApiHost
107 | }
108 |
109 | enum class OperationType {
110 | Link,
111 | Reauthenticate,
112 | SignIn;
113 |
114 | companion object {
115 | internal fun from(type: dynamic) = run {
116 | val name = type.toString().snakeCaseToTitleCamelCase()
117 | OperationType.values().first { it.name == name }
118 | }
119 | }
120 | }
121 |
122 | class Persistence internal constructor(internal val wrapped: dev.bitspittle.firebase.externals.auth.Persistence) {
123 | val type get() = PersistenceType.from(wrapped.type)
124 | }
125 |
126 | enum class PersistenceType {
127 | None,
128 | Local,
129 | Session;
130 |
131 | companion object {
132 | internal fun from(type: dynamic) = run {
133 | val name = type.toString().snakeCaseToTitleCamelCase()
134 | PersistenceType.values().first { it.name == name }
135 | }
136 | }
137 | }
138 |
139 | class User internal constructor(
140 | override val wrapped: dev.bitspittle.firebase.externals.auth.User
141 | ) : UserInfo(wrapped) {
142 | val emailVerified get() = wrapped.emailVerified
143 | val isAnonymous get() = wrapped.isAnonymous
144 | val metadata get() = UserMetadata(wrapped.metadata)
145 | val providerData get() = wrapped.providerData.map { UserInfo(it) }.toTypedArray()
146 | val refreshToken get() = wrapped.refreshToken
147 |
148 | suspend fun delete() =
149 | dev.bitspittle.firebase.externals.auth.deleteUser(wrapped).await()
150 |
151 | /**
152 | * Returns a JSON Web Token (JWT) used to identify the user to a Firebase service.
153 | * Returns the current token if it has not expired. Otherwise, this will refresh the token and return a new one.
154 | */
155 | suspend fun getIdToken(forceRefresh: Boolean? = null) = wrapped.getIdToken(forceRefresh).await()
156 |
157 | suspend fun sendEmailVerification() =
158 | dev.bitspittle.firebase.externals.auth.sendEmailVerification(wrapped).await()
159 | }
160 |
161 | class UserCredential internal constructor(
162 | internal val wrapped: dev.bitspittle.firebase.externals.auth.UserCredential
163 | ) {
164 | val operationType get() = wrapped.operationType
165 | val providerId get() = wrapped.providerId
166 | val user get() = User(wrapped.user)
167 | }
168 |
169 | open class UserInfo internal constructor(
170 | internal open val wrapped: dev.bitspittle.firebase.externals.auth.UserInfo
171 | ) {
172 | val displayName get() = wrapped.displayName
173 | val email get() = wrapped.email
174 | val phoneNumber get() = wrapped.phoneNumber
175 | val photoURL get() = wrapped.photoURL
176 | val providerId get() = wrapped.providerId
177 | val uid get() = wrapped.uid
178 | }
179 |
180 | class UserMetadata internal constructor(
181 | internal val wrapped: dev.bitspittle.firebase.externals.auth.UserMetadata
182 | ) {
183 | val creationTime get() = wrapped.creationTime
184 | val lastSignInTime get() = wrapped.lastSignInTime
185 | }
186 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/database/Bindings.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.database
2 |
3 | import dev.bitspittle.firebase.util.snakeCaseToTitleCamelCase
4 | import dev.bitspittle.firebase.util.titleCamelCaseToSnakeCase
5 | import kotlinx.coroutines.await
6 | import kotlin.js.Json
7 | import kotlin.js.json
8 |
9 | typealias Unsubscribe = () -> Unit
10 |
11 | // See: https://firebase.google.com/docs/database/web/structure-data#how_data_is_structured_its_a_json_tree
12 | private val INVALID_KEY_CHARS = setOf(
13 | '.',
14 | '$',
15 | '#',
16 | '[',
17 | ']',
18 | '/',
19 | )
20 |
21 | /**
22 | * A helper method which encodes a key, ensuring a firebase database can accept it.
23 | *
24 | * Note that slashes get encoded! So if you are specifying a full path, e.g. `a/b/c/$key_with_slashes_maybe`, be sure
25 | * to encode just the trailing part!
26 | *
27 | * Later, you can use [decodeKey] to return a key to its original value.
28 | *
29 | * See also the note about valid keys here:
30 | * https://firebase.google.com/docs/database/web/structure-data#how_data_is_structured_its_a_json_tree
31 | */
32 | fun String.encodeKey(): String {
33 | var encoded = this
34 | INVALID_KEY_CHARS.forEach { c ->
35 | encoded = encoded.replace(c.toString(), "%${c.code.toString(16).uppercase()}")
36 | }
37 | return encoded
38 | }
39 |
40 | private val HEX_STR_REGEX = Regex("""%([a-fA-F0-9]{2})""")
41 |
42 | /**
43 | * A helper method which decodes a value encoded by [encodeKey].
44 | */
45 | fun String.decodeKey(): String {
46 | return this.replace(HEX_STR_REGEX) { result ->
47 | Char(result.groupValues[1].toInt(16)).toString()
48 | }
49 | }
50 |
51 | class Database internal constructor(private val wrapped: dev.bitspittle.firebase.externals.database.Database) {
52 | fun ref(path: String? = null) =
53 | DatabaseReference(dev.bitspittle.firebase.externals.database.ref(wrapped, path))
54 | }
55 |
56 | class ListenOptions(val onlyOnce: Boolean = false)
57 |
58 | open class Query internal constructor(
59 | private val wrapped: dev.bitspittle.firebase.externals.database.Query
60 | ) {
61 | // Note: Must be get() or else you end up with infinite recursion, since initializing a DatabaseReference
62 | // also initialized a Query, which initialized a DatabaseReference....
63 | val ref get() = DatabaseReference(wrapped.ref)
64 |
65 | suspend fun get() = DataSnapshot(
66 | dev.bitspittle.firebase.externals.database.get(wrapped).await()
67 | )
68 |
69 | fun onValue(listenOptions: ListenOptions? = null, callback: (snapshot: DataSnapshot) -> Unit): Unsubscribe {
70 | return dev.bitspittle.firebase.externals.database.onValue(
71 | wrapped,
72 | { callback.invoke(DataSnapshot(it)) },
73 | listenOptions?.let { object : dev.bitspittle.firebase.externals.database.ListenOptions {
74 | override val onlyOnce = it.onlyOnce
75 | }}
76 | ) as Unsubscribe
77 | }
78 |
79 | fun onChildAdded(listenOptions: ListenOptions? = null, callback: (snapshot: DataSnapshot, previousChildName: String?) -> Unit): Unsubscribe {
80 | return dev.bitspittle.firebase.externals.database.onChildAdded(
81 | wrapped,
82 | { _snapshot, _previousChildName -> callback.invoke(DataSnapshot(_snapshot), _previousChildName) },
83 | listenOptions?.let { object : dev.bitspittle.firebase.externals.database.ListenOptions {
84 | override val onlyOnce = it.onlyOnce
85 | }}
86 | ) as Unsubscribe
87 | }
88 |
89 | fun off(eventType: EventType? = null, listenOptions: ListenOptions? = null, callback: (snapshot: DataSnapshot, previousChildName: String?) -> Unit) {
90 | dev.bitspittle.firebase.externals.database.off(
91 | wrapped,
92 | eventType?.toTypeStr(),
93 | { _snapshot, _previousChildName -> callback.invoke(DataSnapshot(_snapshot), _previousChildName) },
94 | listenOptions?.let { object : dev.bitspittle.firebase.externals.database.ListenOptions {
95 | override val onlyOnce = it.onlyOnce
96 | }}
97 | )
98 | }
99 |
100 | fun query(vararg constraints: QueryConstraint): Query {
101 | return Query(
102 | dev.bitspittle.firebase.externals.database.query(
103 | wrapped,
104 | constraints.map { it.wrapped }.toTypedArray()
105 | )
106 | )
107 | }
108 | }
109 |
110 | class DatabaseReference internal constructor(
111 | private val wrapped: dev.bitspittle.firebase.externals.database.DatabaseReference
112 | ) : Query(wrapped) {
113 | val key get() = wrapped.key
114 | val parent get() = wrapped.parent?.let { DatabaseReference(it) }
115 | val root get() = DatabaseReference(wrapped.root)
116 |
117 | fun child(path: String) = DatabaseReference(dev.bitspittle.firebase.externals.database.child(wrapped, path))
118 | fun push() = DatabaseReference(dev.bitspittle.firebase.externals.database.push(wrapped))
119 | suspend fun remove() = dev.bitspittle.firebase.externals.database.remove(wrapped).await()
120 | suspend fun set(value: Any) =
121 | dev.bitspittle.firebase.externals.database.set(wrapped, value).await()
122 | suspend fun update(values: Json) =
123 | dev.bitspittle.firebase.externals.database.update(wrapped, values).await()
124 |
125 | suspend fun runTransaction(
126 | options: TransactionOptions? = null,
127 | transactionUpdate: (currentData: Any?) -> Any?
128 | ) = TransactionResult(
129 | dev.bitspittle.firebase.externals.database.runTransaction(
130 | wrapped,
131 | transactionUpdate = { transactionUpdate(it) },
132 | options?.wrapped
133 | ).await()
134 | )
135 | }
136 |
137 | suspend fun DatabaseReference.update(vararg values: Pair) = update(json(*values))
138 | suspend fun DatabaseReference.update(values: Collection>) = update(*values.toTypedArray())
139 |
140 | class DataSnapshot internal constructor(
141 | private val wrapped: dev.bitspittle.firebase.externals.database.DataSnapshot
142 | ) {
143 | val key get() = wrapped.key
144 | val priority get() = Priority.from(wrapped.priority)
145 | val ref get() = DatabaseReference(wrapped.ref)
146 | val size get() = wrapped.size as Int
147 |
148 | fun child(path: String) = DataSnapshot(wrapped.child(path))
149 | fun exists() = wrapped.exists()
150 | fun forEach(action: (DataSnapshot) -> Unit) { forEachAbortable { action(it); false } }
151 | // return true to abort (meaning, you found your item), false otherwise
152 | fun forEachAbortable(action: (DataSnapshot) -> Boolean): Boolean = wrapped.forEach { action(DataSnapshot(it)) }
153 | fun hasChild(path: String) = wrapped.hasChild(path)
154 | fun hasChildren() = wrapped.hasChildren()
155 | fun `val`() = wrapped.`val`()
156 | }
157 |
158 | /**
159 | * A convenience method that you can call instead of [DataSnapshot.val].
160 | *
161 | * Since `val` is a reserved keyword in Kotlin, making its syntax a little strange.
162 | */
163 | fun DataSnapshot.value() = `val`()
164 |
165 | // https://firebase.google.com/docs/reference/js/database.md#eventtype
166 | enum class EventType {
167 | Value,
168 | ChildAdded,
169 | ChildChanged,
170 | ChildMoved,
171 | ChildRemoved;
172 |
173 | companion object {
174 | internal fun from(typeStr: String) = run {
175 | val name = typeStr.snakeCaseToTitleCamelCase()
176 | EventType.values().first { it.name == name }
177 | }
178 | }
179 |
180 | fun toTypeStr() = name.titleCamelCaseToSnakeCase()
181 | }
182 |
183 | sealed interface Priority {
184 | companion object {
185 | internal fun from(priority: dynamic): Priority? {
186 | return when (priority) {
187 | null -> null
188 | is Number -> OfNumber(priority)
189 | is String -> OfString(priority)
190 | else -> error("Unexpected priority value: $priority")
191 | }
192 | }
193 | }
194 |
195 | val value: Any
196 |
197 | class OfNumber(override val value: Number) : Priority
198 | class OfString(override val value: String) : Priority
199 | }
200 |
201 | // Note: Can't be a class because it's used as a vararg parameter
202 | class QueryConstraint internal constructor(
203 | internal val wrapped: dev.bitspittle.firebase.externals.database.QueryConstraint
204 | ) {
205 | companion object {
206 | fun limitToFirst(limit: Number) = QueryConstraint(dev.bitspittle.firebase.externals.database.limitToFirst(limit))
207 | fun limitToLast(limit: Number) = QueryConstraint(dev.bitspittle.firebase.externals.database.limitToLast(limit))
208 | fun orderByChild(path: String) = QueryConstraint(dev.bitspittle.firebase.externals.database.orderByChild(path))
209 | }
210 |
211 | val type get() = QueryConstraintType.from(wrapped.type)
212 | }
213 |
214 | enum class QueryConstraintType {
215 | EndAt,
216 | EndBefore,
217 | StartAt,
218 | StartAfter,
219 | LimitToFirst,
220 | LimitToLast,
221 | OrderByChild,
222 | OrderByKey,
223 | OrderByPriority,
224 | OrderByValue,
225 | EqualTo;
226 |
227 | companion object {
228 | internal fun from(typeStr: String) = run {
229 | val name = typeStr.capitalize()
230 | QueryConstraintType.values().first { it.name == name }
231 | }
232 | }
233 | }
234 |
235 | class TransactionOptions internal constructor(
236 | internal val wrapped: dev.bitspittle.firebase.externals.database.TransactionOptions
237 | ) {
238 | val applyLocally get() = wrapped.applyLocally
239 | }
240 |
241 | class TransactionResult internal constructor(
242 | internal val wrapped: dev.bitspittle.firebase.externals.database.TransactionResult
243 | ) {
244 | val committed get() = wrapped.committed
245 | val snapshot get() = DataSnapshot(wrapped.snapshot)
246 | }
247 |
248 | object ServerValue {
249 | fun increment(delta: Number = 1) = dev.bitspittle.firebase.externals.database.increment(delta)
250 | fun timestamp() = dev.bitspittle.firebase.externals.database.serverTimestamp()
251 | }
252 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/externals/analytics/Externals.kt:
--------------------------------------------------------------------------------
1 | @file:JsModule("firebase/analytics")
2 | @file:JsNonModule
3 | package dev.bitspittle.firebase.externals.analytics
4 |
5 | import dev.bitspittle.firebase.externals.app.FirebaseApp
6 | import kotlin.js.Json
7 |
8 | // https://firebase.google.com/docs/reference/js/analytics.analytics
9 | internal external interface Analytics {
10 | val app: FirebaseApp
11 | }
12 |
13 | // https://firebase.google.com/docs/reference/js/analytics.analyticscalloptions
14 | internal external interface AnalyticsCallOptions {
15 | val global: Boolean
16 | }
17 |
18 | // https://firebase.google.com/docs/reference/js/analytics.analyticssettings
19 | internal external interface AnalyticsSettings {
20 | val config: dynamic // EventParams | GtagConfigParams
21 | }
22 |
23 | // https://firebase.google.com/docs/reference/js/analytics.eventparams
24 | internal external interface EventParams {
25 | val affiliation: String?
26 | val checkoutOption: String?
27 | val checkoutStep: Number?
28 | val contentType: String?
29 | val coupon: String?
30 | val currency: String?
31 | val description: String?
32 | val eventCategory: String?
33 | val eventLabel: String?
34 | val fatal: Boolean?
35 | val firebaseScreenClass: String?
36 | val firebaseScreen: String?
37 | val itemId: String?
38 | val itemListId: String?
39 | val itemListName: String?
40 | val items: Array- ?
41 | val method: String?
42 | val number: String?
43 | val pageLocation: String?
44 | val pagePath: String?
45 | val pageTitle: String?
46 | val paymentType: String?
47 | val promotionId: String?
48 | val promotionName: String?
49 | val promotions: Array?
50 | val screenName: String?
51 | val searchTerm: String?
52 | val shippingTier: String?
53 | val shipping: dynamic // Currency?
54 | val tax: dynamic // Currency?
55 | val transactionId: String?
56 | val value: Number?
57 | }
58 |
59 | // https://firebase.google.com/docs/reference/js/analytics.gtagconfigparams
60 | internal external interface GtagConfigParams {
61 | val allowAdPersonalizationSignals: Boolean?
62 | val allowGoogleSignals: Boolean?
63 | val cookieDomain: String?
64 | val cookieExpires: Number?
65 | val cookieFlags: String?
66 | val cookiePrefix: String?
67 | val cookieUpdate: Boolean?
68 | val pageLocation: String?
69 | val pageTitle: String?
70 | val sendPageView: Boolean?
71 | }
72 |
73 | // https://firebase.google.com/docs/reference/js/analytics.item
74 | internal external interface Item {
75 | val affiliation: String?
76 | val coupon: String?
77 | val creativeName: String?
78 | val creativeSlot: String?
79 | val discount: dynamic // Currency?
80 | val index: Number?
81 | val itemBrand: String?
82 | val itemCategory: String?
83 | val itemCategory2: String?
84 | val itemCategory3: String?
85 | val itemCategory4: String?
86 | val itemCategory5: String?
87 | val itemId: String?
88 | val itemListId: String?
89 | val itemListName: String?
90 | val itemName: String?
91 | val itemVariant: String?
92 | val locationId: String?
93 | val price: dynamic // Currency?
94 | val promotionId: String?
95 | val promotionName: String?
96 | val quantity: Number?
97 | }
98 |
99 | // https://firebase.google.com/docs/reference/js/analytics.promotion
100 | internal external interface Promotion {
101 | val creativeName: String?
102 | val creativeSlot: String?
103 | val id: String?
104 | val name: String?
105 | }
106 |
107 | // https://firebase.google.com/docs/reference/js/analytics#getanalytics
108 | internal external fun getAnalytics(app: FirebaseApp): Analytics
109 |
110 | // https://firebase.google.com/docs/reference/js/analytics#initializeanalytics
111 | internal external fun initializeAnalytics(app: FirebaseApp, options: AnalyticsSettings?): Analytics
112 |
113 | // https://firebase.google.com/docs/reference/js/analytics#logevent
114 | // Note: There are a bunch of logevent varieties, but we just use a single one here to catch all.
115 | // We'll leave it up to the bindings layer to provide more interesting APIs.
116 | internal external fun logEvent(analytics: Analytics, name: String, params: Json?, options: AnalyticsCallOptions?)
117 |
118 | // https://firebase.google.com/docs/reference/js/analytics#setanalyticscollectionenabled
119 | internal external fun setAnalyticsCollectionEnabled(analytics: Analytics, enabled: Boolean)
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/externals/app/Externals.kt:
--------------------------------------------------------------------------------
1 | @file:JsModule("firebase/app")
2 | @file:JsNonModule
3 | package dev.bitspittle.firebase.externals.app
4 |
5 | import kotlin.js.Json
6 |
7 | // https://firebase.google.com/docs/reference/js/app.firebaseapp.md
8 | internal external interface FirebaseApp {
9 | val name: String
10 | val options: FirebaseOptions
11 | }
12 |
13 | // https://firebase.google.com/docs/reference/js/app.firebaseoptions
14 | internal external interface FirebaseOptions {
15 | val apiKey: String
16 | val authDomain: String
17 | val databaseURL: String
18 | val projectId: String
19 | val storageBucket: String
20 | val messagingSenderId: String
21 | val appId: String
22 | val measurementId: String?
23 | }
24 |
25 | // https://firebase.google.com/docs/reference/js/app.md#initializeapp_2
26 | internal external fun initializeApp(options: Json, name: String?): FirebaseApp
27 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/externals/auth/Externals.kt:
--------------------------------------------------------------------------------
1 | @file:JsModule("firebase/auth")
2 | @file:JsNonModule
3 | package dev.bitspittle.firebase.externals.auth
4 |
5 | import dev.bitspittle.firebase.externals.app.FirebaseApp
6 | import dev.bitspittle.firebase.externals.util.FirebaseError
7 | import kotlin.js.Json
8 | import kotlin.js.Promise
9 |
10 | // https://firebase.google.com/docs/reference/js/auth.actioncodesettings.md#actioncodesettings_interface
11 | internal external interface ActionCodeSettings {
12 | val android: dynamic // { installApp?: boolean; minimumVersion?: string; packageName: string; }
13 | val dynamicLinkDomain: String?
14 | val handleCodeInApp: Boolean
15 | val iOS: dynamic // { bundleId: string; }
16 | val url: String
17 | }
18 |
19 | // https://firebase.google.com/docs/reference/js/auth.auth
20 | internal external interface Auth {
21 | val app: FirebaseApp
22 | val config: Config
23 | val currentUser: User?
24 | val languageCode: String?
25 | val name: String
26 | val settings: AuthSettings
27 |
28 | fun setPersistence(persistence: Persistence): Promise
29 | fun updateCurrentUser(user: User?): Promise
30 | fun useDeviceLanguage()
31 | }
32 |
33 | // https://firebase.google.com/docs/reference/js/auth.autherror
34 | internal external interface AuthError : FirebaseError {
35 | val customData: Json
36 | }
37 |
38 | // https://firebase.google.com/docs/reference/js/auth.authprovider
39 | internal external interface AuthProvider {
40 | val providerId: String
41 | }
42 |
43 | // https://firebase.google.com/docs/reference/js/auth.authsettings
44 | internal external interface AuthSettings {
45 | val appVerificationDisabledForTesting: Boolean
46 | }
47 |
48 | // https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/core/providers/oauth.ts
49 | internal abstract external class BaseOAuthProvider : FederatedAuthProvider {
50 | fun addScope(scope: String): AuthProvider
51 | fun getScopes(): Array
52 | }
53 |
54 | // https://firebase.google.com/docs/reference/js/auth.config
55 | internal external interface Config {
56 | val apiHost: String
57 | val apiKey: String
58 | val apiScheme: String
59 | val authDomain: String
60 | val sdkClientVersion: String
61 | val tokenApiHost: String
62 | }
63 |
64 | // https://firebase.google.com/docs/reference/js/auth.googleauthprovider
65 | internal external class GoogleAuthProvider : BaseOAuthProvider {
66 | override val providerId: String
67 | }
68 |
69 | // https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/core/providers/federated.ts
70 | internal abstract external class FederatedAuthProvider : AuthProvider {
71 | fun setCustomParameters(params: Json): AuthProvider
72 | }
73 |
74 | // https://firebase.google.com/docs/reference/js/auth.persistence
75 | internal external interface Persistence {
76 | val type: dynamic // 'SESSION' | 'LOCAL' | 'NONE'
77 | }
78 |
79 | // https://firebase.google.com/docs/reference/js/auth.user
80 | internal external interface User : UserInfo {
81 | val emailVerified: Boolean
82 | val isAnonymous: Boolean
83 | val metadata: UserMetadata
84 | val providerData: Array
85 | val refreshToken: String
86 |
87 | // https://firebase.google.com/docs/reference/js/auth.user#usergetidtoken
88 | fun getIdToken(forceRefresh: Boolean?): Promise
89 | }
90 |
91 | // https://firebase.google.com/docs/reference/js/auth.usercredential
92 | internal external interface UserCredential {
93 | val operationType: dynamic // 'LINK' | 'REAUTHENTICATE' | 'SIGN_IN'
94 | val providerId: String?
95 | val user: User
96 | }
97 |
98 | // https://firebase.google.com/docs/reference/js/auth.userinfo
99 | internal external interface UserInfo {
100 | val displayName: String?
101 | val email: String?
102 | val phoneNumber: String?
103 | val photoURL: String?
104 | val providerId: String
105 | val uid: String
106 | }
107 |
108 | // https://firebase.google.com/docs/reference/js/auth.usermetadata
109 | internal external interface UserMetadata {
110 | val creationTime: String
111 | val lastSignInTime: String
112 | }
113 |
114 | // https://firebase.google.com/docs/reference/js/auth#getauth
115 | internal external fun getAuth(app: FirebaseApp): Auth
116 |
117 |
118 | // https://firebase.google.com/docs/reference/js/auth#createuserwithemailandpassword
119 | internal external fun createUserWithEmailAndPassword(auth: Auth, email: String, password: String): Promise
120 |
121 | // https://firebase.google.com/docs/reference/js/auth#deleteuser
122 | internal external fun deleteUser(user: User): Promise
123 |
124 | // https://firebase.google.com/docs/reference/js/auth#issigninwithemaillink
125 | internal external fun isSignInWithEmailLink(auth: Auth, emailLink: String): Boolean
126 |
127 | // https://firebase.google.com/docs/reference/js/auth#onauthstatechanged
128 | internal external fun onAuthStateChanged(auth: Auth, handleStateChanged: (User?) -> Unit)
129 |
130 | // https://firebase.google.com/docs/reference/js/auth#sendemailverification
131 | internal external fun sendEmailVerification(user: User): Promise
132 |
133 | // https://firebase.google.com/docs/reference/js/auth#sendsigninlinktoemail
134 | internal external fun sendSignInLinkToEmail(auth: Auth, email: String, actionCodeSettings: ActionCodeSettings): Promise
135 |
136 | // https://firebase.google.com/docs/reference/js/auth#signinwithemailandpassword
137 | internal external fun signInWithEmailAndPassword(auth: Auth, email: String, password: String): Promise
138 |
139 | // https://firebase.google.com/docs/reference/js/auth#signinwithemaillink
140 | internal external fun signInWithEmailLink(auth: Auth, email: String, emailLink: String?): Promise
141 |
142 | // https://firebase.google.com/docs/reference/js/auth#signinwithpopup
143 | internal external fun signInWithPopup(auth: Auth, provider: AuthProvider): Promise
144 |
145 | // https://firebase.google.com/docs/reference/js/auth#signout
146 | internal external fun signOut(auth: Auth): Promise
147 |
148 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/externals/database/Externals.kt:
--------------------------------------------------------------------------------
1 | @file:JsModule("firebase/database")
2 | @file:JsNonModule
3 | package dev.bitspittle.firebase.externals.database
4 |
5 | import dev.bitspittle.firebase.externals.app.FirebaseApp
6 | import kotlin.js.Json
7 | import kotlin.js.Promise
8 |
9 | // https://firebase.google.com/docs/reference/js/database.database
10 | internal external class Database {
11 | val app: FirebaseApp
12 | }
13 |
14 | // https://firebase.google.com/docs/reference/js/database.databasereference
15 | internal external interface DatabaseReference : Query {
16 | val key: String?
17 | val parent: DatabaseReference?
18 | val root: DatabaseReference
19 | }
20 |
21 | // https://firebase.google.com/docs/reference/js/database.datasnapshot
22 | internal external class DataSnapshot {
23 | val key: String?
24 | val priority: dynamic // String | Number | null
25 | val ref: DatabaseReference
26 | val size: Number
27 |
28 | fun child(path: String): DataSnapshot
29 | fun exists(): Boolean
30 | fun forEach(action: (DataSnapshot) -> Boolean): Boolean
31 | fun hasChild(path: String): Boolean
32 | fun hasChildren(): Boolean
33 | fun `val`(): Any?
34 | }
35 |
36 | // https://firebase.google.com/docs/reference/js/database.listenoptions
37 | internal external interface ListenOptions {
38 | val onlyOnce: Boolean
39 | }
40 |
41 | // https://firebase.google.com/docs/reference/js/database.query
42 | internal external interface Query {
43 | val ref: DatabaseReference
44 | }
45 |
46 | // https://firebase.google.com/docs/reference/js/database.queryconstraint
47 | internal abstract external class QueryConstraint {
48 | val type: String // https://firebase.google.com/docs/reference/js/database#queryconstrainttype
49 | }
50 |
51 | // https://firebase.google.com/docs/reference/js/database.transactionoptions
52 | internal external interface TransactionOptions {
53 | val applyLocally: Boolean
54 | }
55 |
56 | // https://firebase.google.com/docs/reference/js/database.transactionresult
57 | internal external class TransactionResult {
58 | val committed: Boolean
59 | val snapshot: DataSnapshot
60 | }
61 |
62 | // https://firebase.google.com/docs/reference/js/database#getdatabase
63 | internal external fun getDatabase(app: FirebaseApp, url: String?): Database
64 |
65 |
66 | // https://firebase.google.com/docs/reference/js/database#child
67 | internal external fun child(parent: DatabaseReference, path: String): DatabaseReference
68 |
69 | // https://firebase.google.com/docs/reference/js/database#get
70 | internal external fun get(query: Query): Promise
71 |
72 | // https://firebase.google.com/docs/reference/js/database#increment
73 | internal external fun increment(delta: Number): Json
74 |
75 | // https://firebase.google.com/docs/reference/js/database#limittofirst
76 | internal external fun limitToFirst(limit: Number): QueryConstraint
77 |
78 | // https://firebase.google.com/docs/reference/js/database#limittolast
79 | internal external fun limitToLast(limit: Number): QueryConstraint
80 |
81 | // https://firebase.google.com/docs/reference/js/database#onvalue
82 | internal external fun onValue(query: Query, callback: (DataSnapshot) -> Unit, listenOptions: ListenOptions?): dynamic
83 |
84 | // https://firebase.google.com/docs/reference/js/database#onchildadded
85 | internal external fun onChildAdded(query: Query, callback: (DataSnapshot, String?) -> Unit, listenOptions: ListenOptions?): dynamic
86 |
87 | // https://firebase.google.com/docs/reference/js/database#off
88 | internal external fun off(query: Query, eventType: dynamic, callback: (DataSnapshot, String?) -> Unit, listenOptions: ListenOptions?)
89 |
90 | // https://firebase.google.com/docs/reference/js/database#orderbychild
91 | internal external fun orderByChild(path: String): QueryConstraint
92 |
93 | // https://firebase.google.com/docs/reference/js/database#push
94 | internal external fun push(ref: DatabaseReference): DatabaseReference
95 |
96 | // https://firebase.google.com/docs/reference/js/database#query
97 | internal external fun query(query: Query, vararg queryConstraints: dynamic): Query
98 |
99 | // https://firebase.google.com/docs/reference/js/database#ref
100 | internal external fun ref(db: Database, path: String?): DatabaseReference
101 |
102 | // https://firebase.google.com/docs/reference/js/database#remove
103 | internal external fun remove(ref: DatabaseReference): Promise
104 |
105 | // https://firebase.google.com/docs/reference/js/database#runtransaction
106 | internal external fun runTransaction(
107 | ref: DatabaseReference,
108 | transactionUpdate: (currentData: dynamic) -> dynamic,
109 | options: TransactionOptions?
110 | ): Promise
111 |
112 | // https://firebase.google.com/docs/reference/js/database#set
113 | internal external fun set(ref: DatabaseReference, value: dynamic): Promise
114 |
115 | // https://firebase.google.com/docs/reference/js/database#servertimestamp
116 | internal external fun serverTimestamp(): Json
117 |
118 | // https://firebase.google.com/docs/reference/js/database#update
119 | internal external fun update(ref: DatabaseReference, values: Json): Promise
120 |
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/externals/util/Externals.kt:
--------------------------------------------------------------------------------
1 | @file:JsModule("firebase/util")
2 | @file:JsNonModule
3 | package dev.bitspittle.firebase.externals.util
4 |
5 | internal external interface FirebaseError {
6 | val code: String
7 | val message: String
8 | }
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/util/FirebaseError.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.util
2 |
3 | interface FirebaseError {
4 | interface Code {
5 | val text: String
6 | }
7 | val code: C
8 | val message: String
9 | }
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/util/JsonSupport.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.util
2 |
3 | import kotlin.js.json
4 |
5 | internal fun Iterable>.filterIfValueIsNull(): List> {
6 | @Suppress("UNCHECKED_CAST")
7 | return this.filter { it.second != null } as List>
8 | }
9 |
10 | internal fun List>.toJson() = run {
11 | json(*this.toTypedArray())
12 | }
13 |
14 | /**
15 | * Create a json object from the incoming pairs after filtering out entries with a null value.
16 | *
17 | * There's no guarantee that a Firebase API treats "null" as the same thing as simply not set in the first place. So
18 | * when we convert from our Kotlin binding classes, which often use null to indicate "not set", we strip those results
19 | * from the final json passed to Firebase.
20 | */
21 | internal fun jsonWithoutNullValues(vararg pairs: Pair) = pairs.toList().filterIfValueIsNull().toJson()
--------------------------------------------------------------------------------
/src/jsMain/kotlin/dev/bitspittle/firebase/util/StringExtensions.kt:
--------------------------------------------------------------------------------
1 | package dev.bitspittle.firebase.util
2 |
3 | // e.g. "ExampleText" to "example_text"
4 | internal fun String.titleCamelCaseToSnakeCase(): String {
5 | require(this.isNotBlank())
6 |
7 | val currentWord = StringBuilder()
8 | val words = mutableListOf()
9 |
10 | this.forEach { c ->
11 | if (c.isUpperCase()) {
12 | if (currentWord.isNotEmpty()) {
13 | words.add(currentWord.toString())
14 | currentWord.clear()
15 | }
16 | }
17 | currentWord.append(c)
18 | }
19 | words.add(currentWord.toString())
20 |
21 | return words.joinToString("_") { it.decapitalize() }
22 | }
23 |
24 | // e.g. "example_text" to "ExampleText"
25 | internal fun String.snakeCaseToTitleCamelCase(): String {
26 | require(this.isNotBlank())
27 |
28 | return this.split('_')
29 | .joinToString("") { it.capitalize() }
30 | }
--------------------------------------------------------------------------------