Merge pull request #920 from invertase/bridge-detox
Merge new testing infra - bridge
This commit is contained in:
commit
ce0f8f19d6
4
.gitignore
vendored
4
.gitignore
vendored
@ -74,6 +74,10 @@ tests/build
|
||||
tests/android/app/build
|
||||
tests/ios/Pods
|
||||
tests/firebase
|
||||
tests-new/build
|
||||
tests-new/android/app/build
|
||||
tests-new/ios/Pods
|
||||
tests-new/firebase
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
|
@ -74,6 +74,7 @@ docs
|
||||
coverage
|
||||
yarn.lock
|
||||
tests
|
||||
bridge/
|
||||
lib/.watchmanconfig
|
||||
buddybuild_postclone.sh
|
||||
bin/test.js
|
||||
|
@ -3,7 +3,6 @@ package io.invertase.firebase;
|
||||
import android.content.Context;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
|
@ -3,7 +3,6 @@ package io.invertase.firebase.storage;
|
||||
import android.support.annotation.RequiresPermission;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
|
18
bridge/.babelrc
Normal file
18
bridge/.babelrc
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"presets": [
|
||||
"react-native"
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": [
|
||||
["istanbul", {
|
||||
"useInlineSourceMaps": true,
|
||||
"instrument": true,
|
||||
"include": [
|
||||
"firebase"
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
6
bridge/.buckconfig
Executable file
6
bridge/.buckconfig
Executable file
@ -0,0 +1,6 @@
|
||||
|
||||
[android]
|
||||
target = Google Inc.:Google APIs:23
|
||||
|
||||
[maven_repositories]
|
||||
central = https://repo1.maven.org/maven2
|
10
bridge/.editorconfig
Normal file
10
bridge/.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
39
bridge/.eslintrc
Normal file
39
bridge/.eslintrc
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"prettier",
|
||||
"prettier/flowtype",
|
||||
"prettier/react"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"flowtype",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"jasmine": true
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": ["error", {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
}],
|
||||
|
||||
"react/forbid-prop-types": "warn",
|
||||
"react/jsx-filename-extension": [
|
||||
"off", { "extensions": [".js", ".jsx"] }
|
||||
],
|
||||
|
||||
"class-methods-use-this": 0,
|
||||
"no-console": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-undef": 0,
|
||||
"no-underscore-dangle": "off",
|
||||
"no-use-before-define": 0
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": true,
|
||||
"window": true
|
||||
}
|
||||
}
|
97
bridge/.flowconfig
Executable file
97
bridge/.flowconfig
Executable file
@ -0,0 +1,97 @@
|
||||
[ignore]
|
||||
|
||||
# We fork some components by platform.
|
||||
.*/*.web.js
|
||||
.*/*.android.js
|
||||
|
||||
# Some modules have their own node_modules with overlap
|
||||
.*/node_modules/node-haste/.*
|
||||
|
||||
# Ugh
|
||||
.*/node_modules/babel.*
|
||||
.*/node_modules/babylon.*
|
||||
.*/node_modules/invariant.*
|
||||
|
||||
# Ignore react and fbjs where there are overlaps, but don't ignore
|
||||
# anything that react-native relies on
|
||||
.*/node_modules/fbjs/lib/Map.js
|
||||
.*/node_modules/fbjs/lib/ErrorUtils.js
|
||||
|
||||
# Flow has a built-in definition for the 'react' module which we prefer to use
|
||||
# over the currently-untyped source
|
||||
.*/node_modules/react/react.js
|
||||
.*/node_modules/react/lib/React.js
|
||||
.*/node_modules/react/lib/ReactDOM.js
|
||||
|
||||
.*/__mocks__/.*
|
||||
.*/__tests__/.*
|
||||
|
||||
.*/commoner/test/source/widget/share.js
|
||||
|
||||
# Ignore commoner tests
|
||||
.*/node_modules/commoner/test/.*
|
||||
|
||||
# See https://github.com/facebook/flow/issues/442
|
||||
.*/react-tools/node_modules/commoner/lib/reader.js
|
||||
|
||||
# Ignore jest
|
||||
.*/node_modules/jest-cli/.*
|
||||
|
||||
# Ignore Website
|
||||
.*/website/.*
|
||||
|
||||
# Ignore generators
|
||||
.*/local-cli/generator.*
|
||||
|
||||
# Ignore BUCK generated folders
|
||||
.*\.buckd/
|
||||
|
||||
# Ignore RNPM
|
||||
.*/local-cli/rnpm/.*
|
||||
|
||||
.*/node_modules/is-my-json-valid/test/.*\.json
|
||||
.*/node_modules/iconv-lite/encodings/tables/.*\.json
|
||||
.*/node_modules/y18n/test/.*\.json
|
||||
.*/node_modules/spdx-license-ids/spdx-license-ids.json
|
||||
.*/node_modules/spdx-exceptions/index.json
|
||||
.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json
|
||||
.*/node_modules/resolve/lib/core.json
|
||||
.*/node_modules/jsonparse/samplejson/.*\.json
|
||||
.*/node_modules/json5/test/.*\.json
|
||||
.*/node_modules/ua-parser-js/test/.*\.json
|
||||
.*/node_modules/builtin-modules/builtin-modules.json
|
||||
.*/node_modules/binary-extensions/binary-extensions.json
|
||||
.*/node_modules/url-regex/tlds.json
|
||||
.*/node_modules/joi/.*\.json
|
||||
.*/node_modules/isemail/.*\.json
|
||||
.*/node_modules/tr46/.*\.json
|
||||
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow
|
||||
flow/
|
||||
|
||||
[options]
|
||||
module.system=haste
|
||||
|
||||
esproposal.class_static_fields=enable
|
||||
esproposal.class_instance_fields=enable
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
|
||||
[version]
|
||||
^0.25.0
|
40
bridge/.gitignore
vendored
Executable file
40
bridge/.gitignore
vendored
Executable file
@ -0,0 +1,40 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Android/IJ
|
||||
#
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
android/app/libs
|
||||
android/keystores/debug.keystore
|
1
bridge/.watchmanconfig
Executable file
1
bridge/.watchmanconfig
Executable file
@ -0,0 +1 @@
|
||||
{}
|
99
bridge/README.md
Executable file
99
bridge/README.md
Executable file
@ -0,0 +1,99 @@
|
||||
# React Native Firebase - Testing Project
|
||||
|
||||
## Requirements
|
||||
|
||||
* Make sure you have Xcode installed (tested with Xcode 8.1-8.2).
|
||||
* make sure you have node installed (`brew install node`, node 7.6.0 and up is required.
|
||||
* Make sure you have react-native dependencies installed:
|
||||
* react-native-cli is installed (`npm install -g react-native-cli`)
|
||||
* watchman is installed (`brew install watchman`)
|
||||
* [appleSimUtils](https://github.com/wix/AppleSimulatorUtils)
|
||||
* detox-cli `npm install -g detox-cli`
|
||||
|
||||
### Step 1: Npm install
|
||||
|
||||
* Run `npm install`.
|
||||
|
||||
## To test Release build of your app
|
||||
|
||||
### Step 2: Build
|
||||
|
||||
* Build the demo project
|
||||
|
||||
```sh
|
||||
detox build --configuration ios.sim.release
|
||||
```
|
||||
|
||||
### Step 3: Test
|
||||
|
||||
* Run tests on the demo project
|
||||
|
||||
```sh
|
||||
detox test --configuration ios.sim.release
|
||||
```
|
||||
|
||||
This action will open a new simulator and run the tests on it.
|
||||
|
||||
## To test Debug build of your app
|
||||
|
||||
### Step 2: Build
|
||||
|
||||
* Build the demo project
|
||||
|
||||
```sh
|
||||
detox build --configuration ios.sim.debug
|
||||
```
|
||||
|
||||
### Step 3: Test
|
||||
|
||||
* start react-native packager
|
||||
|
||||
```sh
|
||||
npm run start
|
||||
```
|
||||
|
||||
* Run tests on the demo project
|
||||
|
||||
```sh
|
||||
detox test --configuration ios.sim.debug
|
||||
```
|
||||
|
||||
This action will open a new simulator and run the tests on it.
|
||||
|
||||
### TODO - Troubleshooting
|
||||
|
||||
Gradle issues... https://stackoverflow.com/questions/46917365/error-could-not-initialize-class-com-android-sdklib-repository-androidsdkhandle?rq=1
|
||||
|
||||
mac: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"`
|
||||
windows `"C://Program Files/Java/jdk_1.x_"`
|
||||
|
||||
android sdk root `export ANDROID_SDK_ROOT="/Users/mike/Library/Android/sdk"`
|
||||
|
||||
Add platform-tools to your path
|
||||
|
||||
echo 'export ANDROID_HOME=/Users/$USER/Library/Android/sdk' >> ~/.bash_profile
|
||||
echo 'export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools' >> ~/.bash_profile
|
||||
|
||||
Name: TestingAVD
|
||||
|
||||
CPU/ABI: null (null)
|
||||
|
||||
Path: /Users/mike/.android/avd/Actually_THIS_one.avd
|
||||
|
||||
Error: Failed to parse properties from /Users/mike/.android/avd/Actually_THIS_one.avd/config.ini
|
||||
|
||||
#### Running specific tests
|
||||
|
||||
Add a `--grep` to e2e/mocha.opts file, e.g. `--grep auth` for all tests that have auth in the file path or tests descriptions.
|
||||
|
||||
#### Running Node debugger
|
||||
|
||||
Add `--inspect` to e2e/mocha.opts file
|
||||
|
||||
To open node debugger tools on chrome navigate to chrome://inspect/#devices and click the `Open dedicated DevTools for Node` link.
|
||||
|
||||
Add the default connection of `localhost:9229` if you haven't already - then the debugger will automatically connect everytime you start tests with inspect flag.
|
||||
|
||||
#### Mocha options
|
||||
|
||||
See https://mochajs.org/#usage
|
10
bridge/android/.editorconfig
Normal file
10
bridge/android/.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
66
bridge/android/app/BUCK
Executable file
66
bridge/android/app/BUCK
Executable file
@ -0,0 +1,66 @@
|
||||
import re
|
||||
|
||||
# To learn about Buck see [Docs](https://buckbuild.com/).
|
||||
# To run your application with Buck:
|
||||
# - install Buck
|
||||
# - `npm start` - to start the packager
|
||||
# - `cd android`
|
||||
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US`
|
||||
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
|
||||
# - `buck install -r android/app` - compile, install and run application
|
||||
#
|
||||
|
||||
lib_deps = []
|
||||
for jarfile in glob(['libs/*.jar']):
|
||||
name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile)
|
||||
lib_deps.append(':' + name)
|
||||
prebuilt_jar(
|
||||
name = name,
|
||||
binary_jar = jarfile,
|
||||
)
|
||||
|
||||
for aarfile in glob(['libs/*.aar']):
|
||||
name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile)
|
||||
lib_deps.append(':' + name)
|
||||
android_prebuilt_aar(
|
||||
name = name,
|
||||
aar = aarfile,
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = 'all-libs',
|
||||
exported_deps = lib_deps
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = 'app-code',
|
||||
srcs = glob([
|
||||
'src/main/java/**/*.java',
|
||||
]),
|
||||
deps = [
|
||||
':all-libs',
|
||||
':build_config',
|
||||
':res',
|
||||
],
|
||||
)
|
||||
|
||||
android_build_config(
|
||||
name = 'build_config',
|
||||
package = 'com.example',
|
||||
)
|
||||
|
||||
android_resource(
|
||||
name = 'res',
|
||||
res = 'src/main/res',
|
||||
package = 'com.example',
|
||||
)
|
||||
|
||||
android_binary(
|
||||
name = 'app',
|
||||
package_type = 'debug',
|
||||
manifest = 'src/main/AndroidManifest.xml',
|
||||
keystore = '//android/keystores:debug',
|
||||
deps = [
|
||||
':app-code',
|
||||
],
|
||||
)
|
124
bridge/android/app/build.gradle
Executable file
124
bridge/android/app/build.gradle
Executable file
@ -0,0 +1,124 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "com.google.firebase.firebase-perf"
|
||||
apply plugin: 'io.fabric'
|
||||
|
||||
import com.android.build.OutputFile
|
||||
|
||||
project.ext.react = [
|
||||
entryFile: "index.js"
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '27.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.testing"
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
missingDimensionStrategy "minReactNative", "minReactNative46"
|
||||
multiDexEnabled true
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
reset()
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk false // If true, also generate a universal APK
|
||||
include "armeabi-v7a", "x86"
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file("keystore.jks")
|
||||
storePassword "12345678"
|
||||
keyAlias "key0"
|
||||
keyPassword "12345678"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
// For each separate APK per architecture, set a unique version code as described here:
|
||||
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
|
||||
def versionCodes = ["armeabi-v7a": 1, "x86": 2]
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||
output.versionCodeOverride =
|
||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/NOTICE.txt'
|
||||
}
|
||||
}
|
||||
|
||||
project.ext.firebaseVersion = '12.0.0'
|
||||
|
||||
dependencies {
|
||||
compile project(':bridge')
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation "com.facebook.react:react-native:+"
|
||||
implementation(project(':react-native-firebase')) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
implementation project(':bridge')
|
||||
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "com.google.android.gms:play-services-base:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-ads:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-auth:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-config:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-core:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-crash:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-database:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-messaging:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-perf:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-storage:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-firestore:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-invites:$firebaseVersion"
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.9.1@aar') {
|
||||
transitive = true
|
||||
}
|
||||
compile "com.android.support:appcompat-v7:27.1.0"
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
androidTestImplementation(project(path: ":detox"))
|
||||
androidTestImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.1'
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.1'
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
// puts all compile dependencies into folder libs for BUCK to use
|
||||
task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.compile
|
||||
into 'libs'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
152
bridge/android/app/google-services.json
Normal file
152
bridge/android/app/google-services.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "305229645282",
|
||||
"firebase_url": "https://rnfirebase-b9ad4.firebaseio.com",
|
||||
"project_id": "rnfirebase-b9ad4",
|
||||
"storage_bucket": "rnfirebase-b9ad4.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:305229645282:android:efe37851d57e1d05",
|
||||
"android_client_info": {
|
||||
"package_name": "com.reactnativefirebasedemo"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "305229645282-5fgq5kq024eqpvji5o0i7jq7q7bnnpl9.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.reactnativefirebasedemo",
|
||||
"certificate_hash": "1f92c8aab0a091a3aaccfa144bf402bb97273494"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "305229645282-cvp6v0iogjjuuvi5g2dcb3lrr9n884a3.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.reactnativefirebasedemo",
|
||||
"certificate_hash": "859f2afac694e21d26ca67e750c9875107c2e755"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 2,
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "305229645282-t29pn6o2t7se1f7rvrfsll4r0pvd6fb6.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.invertase.RNFirebaseTests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:305229645282:android:c9de0f8cb930daf5",
|
||||
"android_client_info": {
|
||||
"package_name": "com.reactnativefirebaseexamples"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "305229645282-hu7tr12kgn5lfhq82l51b1sh66aaue5f.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.reactnativefirebaseexamples",
|
||||
"certificate_hash": "1f92c8aab0a091a3aaccfa144bf402bb97273494"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 2,
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "305229645282-t29pn6o2t7se1f7rvrfsll4r0pvd6fb6.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.invertase.RNFirebaseTests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:305229645282:android:af36d4d29a83e04c",
|
||||
"android_client_info": {
|
||||
"package_name": "com.testing"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
BIN
bridge/android/app/keystore.jks
Executable file
BIN
bridge/android/app/keystore.jks
Executable file
Binary file not shown.
63
bridge/android/app/proguard-rules.pro
vendored
Executable file
63
bridge/android/app/proguard-rules.pro
vendored
Executable file
@ -0,0 +1,63 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Disabling obfuscation is useful if you collect stack traces from production crashes
|
||||
# (unless you are using a system that supports de-obfuscate the stack traces).
|
||||
-dontobfuscate
|
||||
|
||||
# React Native
|
||||
|
||||
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||
# See http://sourceforge.net/p/proguard/bugs/466/
|
||||
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
|
||||
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
|
||||
|
||||
# Do not strip any method/class that is annotated with @DoNotStrip
|
||||
-keep @com.facebook.proguard.annotations.DoNotStrip class *
|
||||
-keepclassmembers class * {
|
||||
@com.facebook.proguard.annotations.DoNotStrip *;
|
||||
}
|
||||
|
||||
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
|
||||
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
|
||||
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
|
||||
|
||||
-dontwarn com.facebook.react.**
|
||||
|
||||
# okhttp
|
||||
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-dontwarn okhttp3.**
|
||||
|
||||
# okio
|
||||
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
24
bridge/android/app/src/androidTest/java/com/testing/DetoxTest.java
Executable file
24
bridge/android/app/src/androidTest/java/com/testing/DetoxTest.java
Executable file
@ -0,0 +1,24 @@
|
||||
package com.testing;
|
||||
|
||||
import android.support.test.filters.LargeTest;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.wix.detox.Detox;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class DetoxTest {
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Test
|
||||
public void runDetoxTests() throws InterruptedException {
|
||||
Detox.runTests(mActivityRule);
|
||||
}
|
||||
}
|
71
bridge/android/app/src/main/AndroidManifest.xml
Executable file
71
bridge/android/app/src/main/AndroidManifest.xml
Executable file
@ -0,0 +1,71 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.testing">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
|
||||
<application
|
||||
android:name="com.testing.MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppTheme">
|
||||
<service
|
||||
android:name="io.invertase.firebase.messaging.RNFirebaseMessagingService"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="io.invertase.firebase.messaging.RNFirebaseInstanceIdService" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />
|
||||
|
||||
<receiver android:name="io.invertase.firebase.notifications.RNFirebaseNotificationReceiver"/>
|
||||
<receiver android:enabled="true" android:exported="true" android:name="io.invertase.firebase.notifications.RNFirebaseNotificationsRebootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
|
||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- App Links -->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:host="je786.app.goo.gl" android:scheme="http"/>
|
||||
<data android:host="je786.app.goo.gl" android:scheme="https"/>
|
||||
</intent-filter>
|
||||
|
||||
<activity
|
||||
android:name="com.testing.MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
80
bridge/android/app/src/main/java/com/testing/MainActivity.java
Executable file
80
bridge/android/app/src/main/java/com/testing/MainActivity.java
Executable file
@ -0,0 +1,80 @@
|
||||
package com.testing;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
public static final int PERMISSION_REQ_CODE = 1234;
|
||||
public static final int OVERLAY_PERMISSION_REQ_CODE = 1235;
|
||||
|
||||
String[] perms = {
|
||||
"android.permission.READ_EXTERNAL_STORAGE",
|
||||
"android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "testing";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
checkWindowPerms();
|
||||
}
|
||||
|
||||
public void checkWindowPerms() {
|
||||
// Checking if device version > 22 and we need to use new permission model
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
// Checking if we can draw window overlay
|
||||
if (!Settings.canDrawOverlays(this)) {
|
||||
// Requesting permission for window overlay(needed for all react-native apps)
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + getPackageName()));
|
||||
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
|
||||
}
|
||||
for (String perm : perms) {
|
||||
// Checking each permission and if denied then requesting permissions
|
||||
if (checkSelfPermission(perm) == PackageManager.PERMISSION_DENIED) {
|
||||
requestPermissions(perms, PERMISSION_REQ_CODE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Window overlay permission intent result
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
|
||||
checkWindowPerms();
|
||||
}
|
||||
}
|
||||
|
||||
// Permission results
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) {
|
||||
switch (permsRequestCode) {
|
||||
case PERMISSION_REQ_CODE:
|
||||
// example how to get result of permissions requests (there can be more then one permission dialog)
|
||||
// boolean readAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED;
|
||||
// boolean writeAccepted = grantResults[1]==PackageManager.PERMISSION_GRANTED;
|
||||
// checking permissions to prevent situation when user denied some permission
|
||||
checkWindowPerms();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
78
bridge/android/app/src/main/java/com/testing/MainApplication.java
Executable file
78
bridge/android/app/src/main/java/com/testing/MainApplication.java
Executable file
@ -0,0 +1,78 @@
|
||||
package com.testing;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import io.invertase.bridge.RNBridgePackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import io.invertase.bridge.RNBridgePackage;
|
||||
import io.invertase.firebase.RNFirebasePackage;
|
||||
import io.invertase.firebase.admob.RNFirebaseAdMobPackage;
|
||||
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
|
||||
import io.invertase.firebase.auth.RNFirebaseAuthPackage;
|
||||
import io.invertase.firebase.config.RNFirebaseRemoteConfigPackage;
|
||||
import io.invertase.firebase.crash.RNFirebaseCrashPackage;
|
||||
import io.invertase.firebase.database.RNFirebaseDatabasePackage;
|
||||
import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage;
|
||||
import io.invertase.firebase.firestore.RNFirebaseFirestorePackage;
|
||||
import io.invertase.firebase.instanceid.RNFirebaseInstanceIdPackage;
|
||||
import io.invertase.firebase.invites.RNFirebaseInvitesPackage;
|
||||
import io.invertase.firebase.links.RNFirebaseLinksPackage;
|
||||
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage;
|
||||
import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage;
|
||||
import io.invertase.firebase.perf.RNFirebasePerformancePackage;
|
||||
import io.invertase.firebase.storage.RNFirebaseStoragePackage;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new RNBridgePackage(),
|
||||
new RNBridgePackage(),
|
||||
new RNFirebasePackage(),
|
||||
new RNFirebaseAdMobPackage(),
|
||||
new RNFirebaseAnalyticsPackage(),
|
||||
new RNFirebaseAuthPackage(),
|
||||
new RNFirebaseRemoteConfigPackage(),
|
||||
new RNFirebaseCrashPackage(),
|
||||
new RNFirebaseCrashlyticsPackage(),
|
||||
new RNFirebaseDatabasePackage(),
|
||||
new RNFirebaseFirestorePackage(),
|
||||
new RNFirebaseInstanceIdPackage(),
|
||||
new RNFirebaseInvitesPackage(),
|
||||
new RNFirebaseLinksPackage(),
|
||||
new RNFirebaseMessagingPackage(),
|
||||
new RNFirebaseNotificationsPackage(),
|
||||
new RNFirebasePerformancePackage(),
|
||||
new RNFirebaseStoragePackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
}
|
||||
|
||||
}
|
BIN
bridge/android/app/src/main/res/drawable-hdpi/ic_launcher.png
Executable file
BIN
bridge/android/app/src/main/res/drawable-hdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
BIN
bridge/android/app/src/main/res/drawable-mdpi/ic_launcher.png
Executable file
BIN
bridge/android/app/src/main/res/drawable-mdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
bridge/android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Executable file
BIN
bridge/android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
BIN
bridge/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Executable file
BIN
bridge/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
bridge/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
Executable file
BIN
bridge/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
3
bridge/android/app/src/main/res/values/strings.xml
Executable file
3
bridge/android/app/src/main/res/values/strings.xml
Executable file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">RNF Test</string>
|
||||
</resources>
|
8
bridge/android/app/src/main/res/values/styles.xml
Executable file
8
bridge/android/app/src/main/res/values/styles.xml
Executable file
@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
50
bridge/android/build.gradle
Executable file
50
bridge/android/build.gradle
Executable file
@ -0,0 +1,50 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
maven {
|
||||
url 'https://maven.fabric.io/public'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.google.gms:google-services:3.1.2'
|
||||
classpath 'com.google.firebase:firebase-plugins:1.1.1'
|
||||
classpath 'io.fabric.tools:gradle:1.25.1'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
google()
|
||||
maven {
|
||||
url "$rootDir/../node_modules/react-native/android"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
ext {
|
||||
compileSdk = 27
|
||||
buildTools = "27.0.2"
|
||||
minSdk = 18
|
||||
targetSdk = 26
|
||||
}
|
||||
|
||||
afterEvaluate { project ->
|
||||
if (!project.name.equalsIgnoreCase("app")
|
||||
&& project.hasProperty("android")) {
|
||||
android {
|
||||
compileSdkVersion compileSdk
|
||||
buildToolsVersion buildTools
|
||||
defaultConfig {
|
||||
minSdkVersion minSdk
|
||||
targetSdkVersion targetSdk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
bridge/android/gradle.properties
Executable file
24
bridge/android/gradle.properties
Executable file
@ -0,0 +1,24 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
||||
android.enableAapt2=false
|
BIN
bridge/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
bridge/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
bridge/android/gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
6
bridge/android/gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
#Sat Sep 09 20:32:40 IDT 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
|
164
bridge/android/gradlew
vendored
Executable file
164
bridge/android/gradlew
vendored
Executable file
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
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.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
bridge/android/gradlew.bat
vendored
Executable file
90
bridge/android/gradlew.bat
vendored
Executable file
@ -0,0 +1,90 @@
|
||||
@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
|
||||
|
||||
@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=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@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 init
|
||||
|
||||
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 init
|
||||
|
||||
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
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
: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 %CMD_LINE_ARGS%
|
||||
|
||||
: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
|
8
bridge/android/keystores/BUCK
Executable file
8
bridge/android/keystores/BUCK
Executable file
@ -0,0 +1,8 @@
|
||||
keystore(
|
||||
name = 'debug',
|
||||
store = 'debug.keystore',
|
||||
properties = 'debug.keystore.properties',
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
4
bridge/android/keystores/debug.keystore.properties
Executable file
4
bridge/android/keystores/debug.keystore.properties
Executable file
@ -0,0 +1,4 @@
|
||||
key.store=debug.keystore
|
||||
key.alias=androiddebugkey
|
||||
key.store.password=android
|
||||
key.alias.password=android
|
14
bridge/android/settings.gradle
Executable file
14
bridge/android/settings.gradle
Executable file
@ -0,0 +1,14 @@
|
||||
rootProject.name = 'RNFTests'
|
||||
include ':bridge'
|
||||
project(':bridge').projectDir = new File(rootProject.projectDir, '../node_modules/bridge/android')
|
||||
|
||||
include ':react-native-firebase'
|
||||
project(':react-native-firebase').projectDir = new File(rootProject.projectDir, './../../android')
|
||||
|
||||
include ':bridge'
|
||||
project(':bridge').projectDir = new File(rootProject.projectDir, '../node_modules/bridge/android')
|
||||
|
||||
include ':app'
|
||||
|
||||
include ':detox'
|
||||
project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')
|
39
bridge/app.js
Executable file
39
bridge/app.js
Executable file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Sample React Native App
|
||||
* https://github.com/facebook/react-native
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// must import before all else
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { AppRegistry, Text, View } from 'react-native';
|
||||
|
||||
import bridge from 'bridge/platform/react-native';
|
||||
import firebase from './firebase';
|
||||
|
||||
require('sinon');
|
||||
require('should-sinon');
|
||||
require('should');
|
||||
|
||||
class Root extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
message: '',
|
||||
};
|
||||
|
||||
bridge.setBridgeProperty('root', this);
|
||||
bridge.setBridgeProperty('module', firebase);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text testID="messageText">{this.state.message}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AppRegistry.registerComponent('testing', () => Root);
|
224
bridge/e2e/auth/module.e2e.js
Normal file
224
bridge/e2e/auth/module.e2e.js
Normal file
@ -0,0 +1,224 @@
|
||||
describe('.auth()', () => {
|
||||
beforeEach(async () => {
|
||||
await device.reloadReactNative();
|
||||
// bridge.root.setState({ message: this.currentTest.title });
|
||||
});
|
||||
|
||||
describe('.signInAnonymously()', () => {
|
||||
it('it should sign in anonymously', () => {
|
||||
const successCb = currentUser => {
|
||||
currentUser.should.be.an.Object();
|
||||
currentUser.uid.should.be.a.String();
|
||||
currentUser.toJSON().should.be.an.Object();
|
||||
should.equal(currentUser.toJSON().email, null);
|
||||
currentUser.isAnonymous.should.equal(true);
|
||||
currentUser.providerId.should.equal('firebase');
|
||||
|
||||
currentUser.should.equal(firebase.auth().currentUser);
|
||||
|
||||
return firebase.auth().signOut();
|
||||
};
|
||||
|
||||
return firebase
|
||||
.auth()
|
||||
.signInAnonymously()
|
||||
.then(successCb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.signInAnonymouslyAndRetrieveData()', () => {
|
||||
it('it should sign in anonymously', () => {
|
||||
const successCb = currentUserCredential => {
|
||||
const currentUser = currentUserCredential.user;
|
||||
currentUser.should.be.an.Object();
|
||||
currentUser.uid.should.be.a.String();
|
||||
currentUser.toJSON().should.be.an.Object();
|
||||
should.equal(currentUser.toJSON().email, null);
|
||||
currentUser.isAnonymous.should.equal(true);
|
||||
currentUser.providerId.should.equal('firebase');
|
||||
currentUser.should.equal(firebase.auth().currentUser);
|
||||
|
||||
const { additionalUserInfo } = currentUserCredential;
|
||||
additionalUserInfo.should.be.an.Object();
|
||||
|
||||
return firebase.auth().signOut();
|
||||
};
|
||||
|
||||
return firebase
|
||||
.auth()
|
||||
.signInAnonymouslyAndRetrieveData()
|
||||
.then(successCb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.signInWithEmailAndPassword()', () => {
|
||||
it('it should login with email and password', () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
const successCb = currentUser => {
|
||||
currentUser.should.be.an.Object();
|
||||
currentUser.uid.should.be.a.String();
|
||||
currentUser.toJSON().should.be.an.Object();
|
||||
currentUser.toJSON().email.should.eql('test@test.com');
|
||||
currentUser.isAnonymous.should.equal(false);
|
||||
currentUser.providerId.should.equal('firebase');
|
||||
currentUser.should.equal(firebase.auth().currentUser);
|
||||
|
||||
return firebase.auth().signOut();
|
||||
};
|
||||
|
||||
return firebase
|
||||
.auth()
|
||||
.signInWithEmailAndPassword(email, pass)
|
||||
.then(successCb);
|
||||
});
|
||||
|
||||
it('it should error on login if user is disabled', () => {
|
||||
const email = 'disabled@account.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
const successCb = () => Promise.reject(new Error('Did not error.'));
|
||||
|
||||
const failureCb = error => {
|
||||
error.code.should.equal('auth/user-disabled');
|
||||
error.message.should.equal(
|
||||
'The user account has been disabled by an administrator.'
|
||||
);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase
|
||||
.auth()
|
||||
.signInWithEmailAndPassword(email, pass)
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
});
|
||||
|
||||
it('it should error on login if password incorrect', () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test1234666';
|
||||
|
||||
const successCb = () => Promise.reject(new Error('Did not error.'));
|
||||
|
||||
const failureCb = error => {
|
||||
error.code.should.equal('auth/wrong-password');
|
||||
error.message.should.equal(
|
||||
'The password is invalid or the user does not have a password.'
|
||||
);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase
|
||||
.auth()
|
||||
.signInWithEmailAndPassword(email, pass)
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
});
|
||||
|
||||
it('it should error on login if user not found', () => {
|
||||
const email = 'randomSomeone@fourOhFour.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
const successCb = () => Promise.reject(new Error('Did not error.'));
|
||||
|
||||
const failureCb = error => {
|
||||
error.code.should.equal('auth/user-not-found');
|
||||
error.message.should.equal(
|
||||
'There is no user record corresponding to this identifier. The user may have been deleted.'
|
||||
);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return firebase
|
||||
.auth()
|
||||
.signInWithEmailAndPassword(email, pass)
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.onAuthStateChanged()', () => {
|
||||
it('calls callback with the current user and when auth state changes', async () => {
|
||||
await firebase.auth().signInAnonymouslyAndRetrieveData();
|
||||
|
||||
// Test
|
||||
const callback = sinon.spy();
|
||||
|
||||
let unsubscribe;
|
||||
await new Promise(resolve => {
|
||||
unsubscribe = firebase.auth().onAuthStateChanged(user => {
|
||||
callback(user);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(firebase.auth().currentUser);
|
||||
callback.should.be.calledOnce();
|
||||
|
||||
// Sign out
|
||||
|
||||
await firebase.auth().signOut();
|
||||
|
||||
await new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 100);
|
||||
});
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledWith(null);
|
||||
callback.should.be.calledTwice();
|
||||
|
||||
// Tear down
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it('stops listening when unsubscribe called', async () => {
|
||||
await firebase.auth().signInAnonymouslyAndRetrieveData();
|
||||
|
||||
// Test
|
||||
const callback = sinon.spy();
|
||||
|
||||
let unsubscribe;
|
||||
await new Promise(resolve => {
|
||||
unsubscribe = firebase.auth().onAuthStateChanged(user => {
|
||||
callback(user);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith(firebase.auth().currentUser);
|
||||
callback.should.be.calledOnce();
|
||||
|
||||
// Sign out
|
||||
|
||||
await firebase.auth().signOut();
|
||||
|
||||
await new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 100);
|
||||
});
|
||||
|
||||
// Assertions
|
||||
|
||||
// callback.should.be.calledWith(null);
|
||||
callback.should.be.calledTwice();
|
||||
|
||||
// Unsubscribe
|
||||
|
||||
unsubscribe();
|
||||
|
||||
// Sign back in
|
||||
|
||||
await firebase.auth().signInAnonymouslyAndRetrieveData();
|
||||
|
||||
// Assertions
|
||||
|
||||
callback.should.be.calledTwice();
|
||||
|
||||
// Tear down
|
||||
|
||||
await firebase.auth().signOut();
|
||||
});
|
||||
});
|
||||
});
|
80
bridge/e2e/bridge.spec.js
Executable file
80
bridge/e2e/bridge.spec.js
Executable file
@ -0,0 +1,80 @@
|
||||
const should = require('should');
|
||||
|
||||
describe('bridge', () => {
|
||||
beforeEach(async function beforeEach() {
|
||||
await device.reloadReactNative();
|
||||
bridge.root.setState({ message: this.currentTest.title });
|
||||
});
|
||||
|
||||
it('should provide -> global.bridge', () => {
|
||||
should(bridge).not.be.undefined();
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// main react-native module you're testing on
|
||||
// in our case react-native-firebase
|
||||
it('should provide -> bridge.module', () => {
|
||||
should(bridge.module).not.be.undefined();
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// react-native module access
|
||||
it('should provide -> bridge.rn', () => {
|
||||
should(bridge.rn).not.be.undefined();
|
||||
should(bridge.rn.Platform.OS).be.a.String();
|
||||
should(bridge.rn.Platform.OS).equal(device.getPlatform());
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// 'global' context of the app's JS environment
|
||||
it('should provide -> bridge.context', () => {
|
||||
should(bridge.context).not.be.undefined();
|
||||
should(bridge.context.setTimeout).be.a.Function();
|
||||
should(bridge.context.window).be.a.Object();
|
||||
// etc ... e.g. __coverage__ is here also if covering
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// the apps root component
|
||||
// allows you to read and set state if required
|
||||
it('should provide -> bridge.root', async () => {
|
||||
should(bridge.root).not.be.undefined();
|
||||
should(bridge.root.setState).be.a.Function();
|
||||
should(bridge.root.state).be.a.Object();
|
||||
|
||||
// test setting state
|
||||
await new Promise(resolve =>
|
||||
bridge.root.setState({ message: 'hello world' }, resolve)
|
||||
);
|
||||
should(bridge.root.state.message).equal('hello world');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// we shim our own reloadReactNative functionality as the detox reloadReactNative built-in
|
||||
// hangs often and seems unpredictable - todo: investigate & PR if solution found
|
||||
// reloadReactNative is replaced on init with bridge.root automatically
|
||||
it('should allow reloadReactNative usage without breaking remote debug', async () => {
|
||||
should(bridge.reload).be.a.Function();
|
||||
// and check it works without breaking anything
|
||||
await device.reloadReactNative();
|
||||
should(bridge.reload).be.a.Function();
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('should allow launchApp usage without breaking remote debug', async () => {
|
||||
should(bridge.module).not.be.undefined();
|
||||
should(bridge.reload).be.a.Function();
|
||||
should(bridge.rn).not.be.undefined();
|
||||
should(bridge.rn.Platform.OS).be.a.String();
|
||||
should(bridge.rn.Platform.OS).equal(device.getPlatform());
|
||||
|
||||
await device.launchApp({ newInstance: true });
|
||||
|
||||
should(bridge.module).not.be.undefined();
|
||||
should(bridge.reload).be.a.Function();
|
||||
should(bridge.rn).not.be.undefined();
|
||||
should(bridge.rn.Platform.OS).be.a.String();
|
||||
should(bridge.rn.Platform.OS).equal(device.getPlatform());
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
77
bridge/e2e/firestore/transactions.e2e.js
Normal file
77
bridge/e2e/firestore/transactions.e2e.js
Normal file
@ -0,0 +1,77 @@
|
||||
const should = require('should');
|
||||
|
||||
describe('firestore.runTransaction', () => {
|
||||
beforeEach(async () => {
|
||||
await device.reloadReactNative();
|
||||
});
|
||||
|
||||
it('should set, update and delete transactionally and allow a return value', async () => {
|
||||
let deleteMe = false;
|
||||
const firestore = firebase.firestore();
|
||||
|
||||
const docRef = firestore
|
||||
.collection('transactions')
|
||||
.doc(Date.now().toString());
|
||||
|
||||
const updateFunction = async transaction => {
|
||||
const doc = await transaction.get(docRef);
|
||||
if (doc.exists && deleteMe) {
|
||||
transaction.delete(docRef);
|
||||
return 'bye';
|
||||
}
|
||||
|
||||
if (!doc.exists) {
|
||||
transaction.set(docRef, { value: 1 });
|
||||
return 1;
|
||||
}
|
||||
|
||||
const newValue = doc.data().value + 1;
|
||||
|
||||
if (newValue > 2) {
|
||||
return Promise.reject(new Error('Value should not be greater than 2!'));
|
||||
}
|
||||
|
||||
transaction.update(docRef, {
|
||||
value: newValue,
|
||||
somethingElse: 'update',
|
||||
});
|
||||
|
||||
return newValue;
|
||||
};
|
||||
|
||||
// set tests
|
||||
const val1 = await firestore.runTransaction(updateFunction);
|
||||
should.equal(val1, 1);
|
||||
const doc1 = await docRef.get();
|
||||
doc1.data().value.should.equal(1);
|
||||
should.equal(doc1.data().somethingElse, undefined);
|
||||
|
||||
// update
|
||||
const val2 = await firestore.runTransaction(updateFunction);
|
||||
should.equal(val2, 2);
|
||||
const doc2 = await docRef.get();
|
||||
doc2.data().value.should.equal(2);
|
||||
doc2.data().somethingElse.should.equal('update');
|
||||
|
||||
// rejecting / cancelling transaction
|
||||
let didReject = false;
|
||||
try {
|
||||
await firestore.runTransaction(updateFunction);
|
||||
} catch (e) {
|
||||
didReject = true;
|
||||
}
|
||||
should.equal(didReject, true);
|
||||
const doc3 = await docRef.get();
|
||||
doc3.data().value.should.equal(2);
|
||||
doc3.data().somethingElse.should.equal('update');
|
||||
|
||||
// delete
|
||||
deleteMe = true;
|
||||
const val4 = await firestore.runTransaction(updateFunction);
|
||||
should.equal(val4, 'bye');
|
||||
const doc4 = await docRef.get();
|
||||
should.equal(doc4.exists, false);
|
||||
|
||||
return Promise.resolve('Test Completed');
|
||||
});
|
||||
});
|
20
bridge/e2e/init.js
Executable file
20
bridge/e2e/init.js
Executable file
@ -0,0 +1,20 @@
|
||||
const detox = require('detox');
|
||||
const config = require('../package.json').detox;
|
||||
|
||||
global.sinon = require('sinon');
|
||||
require('should-sinon');
|
||||
global.should = require('should');
|
||||
|
||||
before(async () => {
|
||||
await detox.init(config);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await detox.cleanup();
|
||||
});
|
||||
|
||||
Object.defineProperty(global, 'firebase', {
|
||||
get() {
|
||||
return bridge.module;
|
||||
},
|
||||
});
|
6
bridge/e2e/mocha.opts
Executable file
6
bridge/e2e/mocha.opts
Executable file
@ -0,0 +1,6 @@
|
||||
--recursive
|
||||
--timeout 120000
|
||||
--slow 2200
|
||||
--bail
|
||||
--exit
|
||||
--require bridge/platform/node
|
175
bridge/firebase/firebase-app.js
Normal file
175
bridge/firebase/firebase-app.js
Normal file
@ -0,0 +1,175 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import INTERNALS from './internals';
|
||||
import { isObject, isAndroid } from './utils';
|
||||
|
||||
import AdMob, { statics as AdMobStatics } from './modules/admob';
|
||||
import Auth, { statics as AuthStatics } from './modules/auth';
|
||||
import Analytics from './modules/analytics';
|
||||
import Crash from './modules/crash';
|
||||
import Performance from './modules/perf';
|
||||
import RemoteConfig from './modules/config';
|
||||
import Storage, { statics as StorageStatics } from './modules/storage';
|
||||
import Database, { statics as DatabaseStatics } from './modules/database';
|
||||
import Messaging, { statics as MessagingStatics } from './modules/messaging';
|
||||
import Firestore, { statics as FirestoreStatics } from './modules/firestore';
|
||||
import Links, { statics as LinksStatics } from './modules/links';
|
||||
import Utils, { statics as UtilsStatics } from './modules/utils';
|
||||
|
||||
const FirebaseCoreModule = NativeModules.RNFirebase;
|
||||
|
||||
export default class FirebaseApp {
|
||||
constructor(name: string, options: Object = {}) {
|
||||
this._name = name;
|
||||
this._namespaces = {};
|
||||
this._options = Object.assign({}, options);
|
||||
|
||||
// native ios/android to confirm initialized
|
||||
this._initialized = false;
|
||||
this._nativeInitialized = false;
|
||||
|
||||
// modules
|
||||
this.admob = this._staticsOrModuleInstance(AdMobStatics, AdMob);
|
||||
this.auth = this._staticsOrModuleInstance(AuthStatics, Auth);
|
||||
this.analytics = this._staticsOrModuleInstance({}, Analytics);
|
||||
this.config = this._staticsOrModuleInstance({}, RemoteConfig);
|
||||
this.crash = this._staticsOrModuleInstance({}, Crash);
|
||||
this.database = this._staticsOrModuleInstance(DatabaseStatics, Database);
|
||||
this.firestore = this._staticsOrModuleInstance(FirestoreStatics, Firestore);
|
||||
this.links = this._staticsOrModuleInstance(LinksStatics, Links);
|
||||
this.messaging = this._staticsOrModuleInstance(MessagingStatics, Messaging);
|
||||
this.perf = this._staticsOrModuleInstance({}, Performance);
|
||||
this.storage = this._staticsOrModuleInstance(StorageStatics, Storage);
|
||||
this.utils = this._staticsOrModuleInstance(UtilsStatics, Utils);
|
||||
this._extendedProps = {};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param native
|
||||
* @private
|
||||
*/
|
||||
_initializeApp(native = false) {
|
||||
if (native) {
|
||||
// for apps already initialized natively that
|
||||
// we have info from RN constants
|
||||
this._initialized = true;
|
||||
this._nativeInitialized = true;
|
||||
} else {
|
||||
FirebaseCoreModule.initializeApp(this._name, this._options, (error, result) => {
|
||||
this._initialized = true;
|
||||
INTERNALS.SharedEventEmitter.emit(`AppReady:${this._name}`, { error, result });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
get name() {
|
||||
if (this._name === INTERNALS.STRINGS.DEFAULT_APP_NAME) {
|
||||
// ios and android firebase sdk's return different
|
||||
// app names - so we just return what the web sdk
|
||||
// would if it was default.
|
||||
return '[DEFAULT]';
|
||||
}
|
||||
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
get options() {
|
||||
return Object.assign({}, this._options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented firebase web sdk method that allows adding additional properties onto
|
||||
* a firebase app instance.
|
||||
*
|
||||
* See: https://github.com/firebase/firebase-js-sdk/blob/master/tests/app/firebase_app.test.ts#L328
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
extendApp(props: Object) {
|
||||
if (!isObject(props)) throw new Error(INTERNALS.ERROR_MISSING_ARG('Object', 'extendApp'));
|
||||
const keys = Object.keys(props);
|
||||
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
const key = keys[i];
|
||||
|
||||
if (!this._extendedProps[key] && Object.hasOwnProperty.call(this, key)) {
|
||||
throw new Error(INTERNALS.ERROR_PROTECTED_PROP(key));
|
||||
}
|
||||
|
||||
this[key] = props[key];
|
||||
this._extendedProps[key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
delete() {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('app', 'delete'));
|
||||
// TODO only the ios sdk currently supports delete, add back in when android also supports it
|
||||
// if (this._name === INTERNALS.STRINGS.DEFAULT_APP_NAME && this._nativeInitialized) {
|
||||
// return Promise.reject(
|
||||
// new Error('Unable to delete the default native firebase app instance.'),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// return FirebaseCoreModule.deleteApp(this._name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
onReady(): Promise {
|
||||
if (this._initialized) return Promise.resolve(this);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
INTERNALS.SharedEventEmitter.once(`AppReady:${this._name}`, ({ error }) => {
|
||||
if (error) return reject(new Error(error)); // error is a string as it's from native
|
||||
return resolve(this); // return app
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param statics
|
||||
* @param InstanceClass
|
||||
* @return {function()}
|
||||
* @private
|
||||
*/
|
||||
_staticsOrModuleInstance(statics = {}, InstanceClass): Function {
|
||||
const getInstance = () => {
|
||||
const _name = `_${InstanceClass._NAMESPACE}`;
|
||||
|
||||
if (isAndroid && InstanceClass._NAMESPACE !== Utils._NAMESPACE && !INTERNALS.FLAGS.checkedPlayServices) {
|
||||
INTERNALS.FLAGS.checkedPlayServices = true;
|
||||
this.utils().checkPlayServicesAvailability();
|
||||
}
|
||||
|
||||
if (!this._namespaces[_name]) {
|
||||
this._namespaces[_name] = new InstanceClass(this, this._options);
|
||||
}
|
||||
|
||||
return this._namespaces[_name];
|
||||
};
|
||||
|
||||
Object.assign(getInstance, statics, {
|
||||
nativeModuleExists: !!NativeModules[InstanceClass._NATIVE_MODULE],
|
||||
});
|
||||
|
||||
return getInstance;
|
||||
}
|
||||
}
|
228
bridge/firebase/firebase.js
Normal file
228
bridge/firebase/firebase.js
Normal file
@ -0,0 +1,228 @@
|
||||
/**
|
||||
* @providesModule Firebase
|
||||
* @flow
|
||||
*/
|
||||
import { NativeModules, NativeEventEmitter } from 'react-native';
|
||||
|
||||
import INTERNALS from './internals';
|
||||
import FirebaseApp from './firebase-app';
|
||||
import { isObject, isString, isAndroid } from './utils';
|
||||
|
||||
// module imports
|
||||
import AdMob, { statics as AdMobStatics } from './modules/admob';
|
||||
import Auth, { statics as AuthStatics } from './modules/auth';
|
||||
import Analytics from './modules/analytics';
|
||||
import Crash from './modules/crash';
|
||||
import Performance from './modules/perf';
|
||||
import Links, { statics as LinksStatics } from './modules/links';
|
||||
import RemoteConfig from './modules/config';
|
||||
import Storage, { statics as StorageStatics } from './modules/storage';
|
||||
import Database, { statics as DatabaseStatics } from './modules/database';
|
||||
import Messaging, { statics as MessagingStatics } from './modules/messaging';
|
||||
import Firestore, { statics as FirestoreStatics } from './modules/firestore';
|
||||
import Utils, { statics as UtilsStatics } from './modules/utils';
|
||||
|
||||
const FirebaseCoreModule = NativeModules.RNFirebase;
|
||||
|
||||
class FirebaseCore {
|
||||
constructor() {
|
||||
this._nativeEmitters = {};
|
||||
this._nativeSubscriptions = {};
|
||||
|
||||
if (!FirebaseCoreModule) {
|
||||
throw (new Error(INTERNALS.STRINGS.ERROR_MISSING_CORE));
|
||||
}
|
||||
|
||||
this._initializeNativeApps();
|
||||
|
||||
// modules
|
||||
this.admob = this._appNamespaceOrStatics(AdMobStatics, AdMob);
|
||||
this.auth = this._appNamespaceOrStatics(AuthStatics, Auth);
|
||||
this.analytics = this._appNamespaceOrStatics({}, Analytics);
|
||||
this.config = this._appNamespaceOrStatics({}, RemoteConfig);
|
||||
this.crash = this._appNamespaceOrStatics({}, Crash);
|
||||
this.database = this._appNamespaceOrStatics(DatabaseStatics, Database);
|
||||
this.firestore = this._appNamespaceOrStatics(FirestoreStatics, Firestore);
|
||||
this.links = this._appNamespaceOrStatics(LinksStatics, Links);
|
||||
this.messaging = this._appNamespaceOrStatics(MessagingStatics, Messaging);
|
||||
this.perf = this._appNamespaceOrStatics(DatabaseStatics, Performance);
|
||||
this.storage = this._appNamespaceOrStatics(StorageStatics, Storage);
|
||||
this.utils = this._appNamespaceOrStatics(UtilsStatics, Utils);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps all native app instances that were discovered on boot
|
||||
* @private
|
||||
*/
|
||||
_initializeNativeApps() {
|
||||
for (let i = 0, len = FirebaseCoreModule.apps.length; i < len; i++) {
|
||||
const app = FirebaseCoreModule.apps[i];
|
||||
const options = Object.assign({}, app);
|
||||
delete options.name;
|
||||
INTERNALS.APPS[app.name] = new FirebaseApp(app.name, options);
|
||||
INTERNALS.APPS[app.name]._initializeApp(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Web SDK initializeApp
|
||||
*
|
||||
* @param options
|
||||
* @param name
|
||||
* @return {*}
|
||||
*/
|
||||
initializeApp(options: Object = {}, name: string): FirebaseApp {
|
||||
if (name && !isString(name)) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_INIT_STRING_NAME);
|
||||
}
|
||||
|
||||
const _name = (name || INTERNALS.STRINGS.DEFAULT_APP_NAME).toUpperCase();
|
||||
|
||||
// return an existing app if found
|
||||
// todo in v4 remove deprecation and throw an error
|
||||
if (INTERNALS.APPS[_name]) {
|
||||
console.warn(INTERNALS.STRINGS.WARN_INITIALIZE_DEPRECATION);
|
||||
return INTERNALS.APPS[_name];
|
||||
}
|
||||
|
||||
// only validate if app doesn't already exist
|
||||
// to allow apps already initialized natively
|
||||
// to still go through init without erroring (backwards compatibility)
|
||||
if (!isObject(options)) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_INIT_OBJECT);
|
||||
}
|
||||
|
||||
if (!options.apiKey) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('apiKey'));
|
||||
}
|
||||
|
||||
if (!options.appId) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('appId'));
|
||||
}
|
||||
|
||||
if (!options.databaseURL) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('databaseURL'));
|
||||
}
|
||||
|
||||
if (!options.messagingSenderId) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('messagingSenderId'));
|
||||
}
|
||||
|
||||
if (!options.projectId) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('projectId'));
|
||||
}
|
||||
|
||||
if (!options.storageBucket) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_OPT('storageBucket'));
|
||||
}
|
||||
|
||||
INTERNALS.APPS[_name] = new FirebaseApp(_name, options);
|
||||
// only initialize if certain props are available
|
||||
if (options.databaseURL && options.apiKey) {
|
||||
INTERNALS.APPS[_name]._initializeApp();
|
||||
}
|
||||
|
||||
return INTERNALS.APPS[_name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Firebase app instance.
|
||||
*
|
||||
* When called with no arguments, the default app is returned.
|
||||
* When an app name is provided, the app corresponding to that name is returned.
|
||||
*
|
||||
* @param name
|
||||
* @return {*}
|
||||
*/
|
||||
app(name?: string): FirebaseApp {
|
||||
const _name = name ? name.toUpperCase() : INTERNALS.STRINGS.DEFAULT_APP_NAME;
|
||||
const app = INTERNALS.APPS[_name];
|
||||
if (!app) throw new Error(INTERNALS.STRINGS.ERROR_APP_NOT_INIT(_name));
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* A (read-only) array of all initialized apps.
|
||||
* @return {Array}
|
||||
*/
|
||||
get apps(): Array<Object> {
|
||||
return Object.values(INTERNALS.APPS);
|
||||
}
|
||||
|
||||
/*
|
||||
* INTERNALS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Subscribe to a native event for js side distribution by appName
|
||||
* React Native events are hard set at compile - cant do dynamic event names
|
||||
* so we use a single event send it to js and js then internally can prefix it
|
||||
* and distribute dynamically.
|
||||
*
|
||||
* @param eventName
|
||||
* @param nativeEmitter
|
||||
* @private
|
||||
*/
|
||||
_subscribeForDistribution(eventName, nativeEmitter) {
|
||||
if (!this._nativeSubscriptions[eventName]) {
|
||||
nativeEmitter.addListener(eventName, (event) => {
|
||||
if (event.appName) {
|
||||
// native event has an appName property - auto prefix and internally emit
|
||||
INTERNALS.SharedEventEmitter.emit(`${event.appName}-${eventName}`, event);
|
||||
} else {
|
||||
// standard event - no need to prefix
|
||||
INTERNALS.SharedEventEmitter.emit(eventName, event);
|
||||
}
|
||||
});
|
||||
|
||||
this._nativeSubscriptions[eventName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param statics
|
||||
* @param InstanceClass
|
||||
* @return {function(FirebaseApp=)}
|
||||
* @private
|
||||
*/
|
||||
_appNamespaceOrStatics(statics = {}, InstanceClass): Function {
|
||||
const namespace = InstanceClass._NAMESPACE;
|
||||
|
||||
const getNamespace = (app?: FirebaseApp) => {
|
||||
let _app = app;
|
||||
|
||||
// throw an error if it's not a valid app instance
|
||||
if (_app && !(_app instanceof FirebaseApp)) throw new Error(INTERNALS.STRINGS.ERROR_NOT_APP(namespace));
|
||||
|
||||
// default to the 'DEFAULT' app if no arg provided - will throw an error
|
||||
// if default app not initialized
|
||||
else if (!_app) _app = this.app(INTERNALS.STRINGS.DEFAULT_APP_NAME);
|
||||
return INTERNALS.APPS[_app._name][namespace](_app);
|
||||
};
|
||||
|
||||
Object.assign(getNamespace, statics, {
|
||||
nativeModuleExists: !!NativeModules[InstanceClass._NATIVE_MODULE],
|
||||
});
|
||||
|
||||
return getNamespace;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param nativeModule
|
||||
* @return {*}
|
||||
* @private
|
||||
*/
|
||||
_getOrSetNativeEmitter(name, nativeModule) {
|
||||
if (this._nativeEmitters[name]) {
|
||||
return this._nativeEmitters[name];
|
||||
}
|
||||
|
||||
return this._nativeEmitters[name] = new NativeEventEmitter(nativeModule);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new FirebaseCore();
|
50
bridge/firebase/flow.js
Normal file
50
bridge/firebase/flow.js
Normal file
@ -0,0 +1,50 @@
|
||||
/* eslint-disable */
|
||||
// declare module 'react-native' {
|
||||
// // noinspection ES6ConvertVarToLetConst
|
||||
// declare var exports: any;
|
||||
// }
|
||||
|
||||
declare type AuthResultType = {
|
||||
authenticated: boolean,
|
||||
user: Object|null
|
||||
} | null;
|
||||
|
||||
declare type CredentialType = {
|
||||
providerId: string,
|
||||
token: string,
|
||||
secret: string
|
||||
};
|
||||
|
||||
declare type DatabaseListener = {
|
||||
listenerId: number;
|
||||
eventName: string;
|
||||
successCallback: Function;
|
||||
failureCallback?: Function;
|
||||
};
|
||||
|
||||
declare type DatabaseModifier = {
|
||||
type: 'orderBy' | 'limit' | 'filter';
|
||||
name?: string;
|
||||
key?: string;
|
||||
limit?: number;
|
||||
value?: any;
|
||||
valueType?: string;
|
||||
};
|
||||
|
||||
declare type GoogleApiAvailabilityType = {
|
||||
status: number,
|
||||
isAvailable: boolean,
|
||||
isUserResolvableError?: boolean,
|
||||
hasResolution?: boolean,
|
||||
error?: string
|
||||
};
|
||||
|
||||
declare class FirebaseError {
|
||||
message: string,
|
||||
name: string,
|
||||
code: string,
|
||||
stack: string,
|
||||
path: string,
|
||||
details: string,
|
||||
modifiers: string
|
||||
};
|
1368
bridge/firebase/index.d.ts
vendored
Normal file
1368
bridge/firebase/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
80
bridge/firebase/index.js
Normal file
80
bridge/firebase/index.js
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
import firebase from './modules/core/firebase';
|
||||
|
||||
export default firebase;
|
||||
|
||||
/*
|
||||
* Export App types
|
||||
*/
|
||||
export type { default as App } from './modules/core/app';
|
||||
|
||||
/*
|
||||
* Export Auth types
|
||||
*/
|
||||
export type {
|
||||
ActionCodeInfo,
|
||||
ActionCodeSettings,
|
||||
AdditionalUserInfo,
|
||||
AuthCredential,
|
||||
UserCredential,
|
||||
UserInfo,
|
||||
UserMetadata,
|
||||
} from './modules/auth/types';
|
||||
export type {
|
||||
default as ConfirmationResult,
|
||||
} from './modules/auth/phone/ConfirmationResult';
|
||||
export type { default as User } from './modules/auth/User';
|
||||
|
||||
/*
|
||||
* Export Database types
|
||||
*/
|
||||
export type { default as DataSnapshot } from './modules/database/DataSnapshot';
|
||||
export type { default as OnDisconnect } from './modules/database/OnDisconnect';
|
||||
export type { default as Reference } from './modules/database/Reference';
|
||||
export type { default as DataQuery } from './modules/database/Query';
|
||||
|
||||
/*
|
||||
* Export Firestore types
|
||||
*/
|
||||
export type {
|
||||
DocumentListenOptions,
|
||||
QueryListenOptions,
|
||||
SetOptions,
|
||||
SnapshotMetadata,
|
||||
} from './modules/firestore/types';
|
||||
export type {
|
||||
default as CollectionReference,
|
||||
} from './modules/firestore/CollectionReference';
|
||||
export type {
|
||||
default as DocumentChange,
|
||||
} from './modules/firestore/DocumentChange';
|
||||
export type {
|
||||
default as DocumentReference,
|
||||
} from './modules/firestore/DocumentReference';
|
||||
export type {
|
||||
default as DocumentSnapshot,
|
||||
} from './modules/firestore/DocumentSnapshot';
|
||||
export type { default as FieldPath } from './modules/firestore/FieldPath';
|
||||
export type { default as FieldValue } from './modules/firestore/FieldValue';
|
||||
export type { default as GeoPoint } from './modules/firestore/GeoPoint';
|
||||
export type { default as Query } from './modules/firestore/Query';
|
||||
export type {
|
||||
default as QuerySnapshot,
|
||||
} from './modules/firestore/QuerySnapshot';
|
||||
export type { default as WriteBatch } from './modules/firestore/WriteBatch';
|
||||
|
||||
/*
|
||||
* Export Messaging types
|
||||
*/
|
||||
export type {
|
||||
default as RemoteMessage,
|
||||
} from './modules/messaging/RemoteMessage';
|
||||
|
||||
/*
|
||||
* Export Notifications types
|
||||
*/
|
||||
export type {
|
||||
default as Notification,
|
||||
} from './modules/notifications/Notification';
|
239
bridge/firebase/internals.js
Normal file
239
bridge/firebase/internals.js
Normal file
@ -0,0 +1,239 @@
|
||||
import { Platform, NativeModules } from 'react-native';
|
||||
|
||||
import EventEmitter from './utils/emitter/EventEmitter';
|
||||
import SyncTree from './utils/SyncTree';
|
||||
|
||||
const DEFAULT_APP_NAME = Platform.OS === 'ios' ? '__FIRAPP_DEFAULT' : '[DEFAULT]';
|
||||
|
||||
const NAMESPACE_PODS = {
|
||||
admob: 'Firebase/AdMob',
|
||||
analytics: 'Firebase/Analytics',
|
||||
auth: 'Firebase/Auth',
|
||||
config: 'Firebase/RemoteConfig',
|
||||
crash: 'Firebase/Crash',
|
||||
database: 'Firebase/Database',
|
||||
links: 'Firebase/DynamicLinks',
|
||||
messaging: 'Firebase/Messaging',
|
||||
perf: 'Firebase/Performance',
|
||||
storage: 'Firebase/Storage',
|
||||
};
|
||||
|
||||
const GRADLE_DEPS = {
|
||||
admob: 'ads',
|
||||
};
|
||||
|
||||
const PLAY_SERVICES_CODES = {
|
||||
1: {
|
||||
code: 'SERVICE_MISSING',
|
||||
message: 'Google Play services is missing on this device.',
|
||||
},
|
||||
2: {
|
||||
code: 'SERVICE_VERSION_UPDATE_REQUIRED',
|
||||
message: 'The installed version of Google Play services on this device is out of date.',
|
||||
},
|
||||
3: {
|
||||
code: 'SERVICE_DISABLED',
|
||||
message: 'The installed version of Google Play services has been disabled on this device.',
|
||||
},
|
||||
9: {
|
||||
code: 'SERVICE_INVALID',
|
||||
message: 'The version of the Google Play services installed on this device is not authentic.',
|
||||
},
|
||||
18: {
|
||||
code: 'SERVICE_UPDATING',
|
||||
message: 'Google Play services is currently being updated on this device.',
|
||||
},
|
||||
19: {
|
||||
code: 'SERVICE_MISSING_PERMISSION',
|
||||
message: 'Google Play service doesn\'t have one or more required permissions.',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
// default options
|
||||
OPTIONS: {
|
||||
logLevel: 'warn',
|
||||
errorOnMissingPlayServices: true,
|
||||
promptOnMissingPlayServices: true,
|
||||
},
|
||||
|
||||
FLAGS: {
|
||||
checkedPlayServices: false,
|
||||
},
|
||||
|
||||
// track all initialized firebase apps
|
||||
APPS: {
|
||||
[DEFAULT_APP_NAME]: null,
|
||||
},
|
||||
|
||||
STRINGS: {
|
||||
WARN_INITIALIZE_DEPRECATION: 'Deprecation: Calling \'initializeApp()\' for apps that are already initialised natively ' +
|
||||
'is unnecessary, use \'firebase.app()\' instead to access the already initialized default app instance.',
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
get ERROR_MISSING_CORE() {
|
||||
if (Platform.OS === 'ios') {
|
||||
return 'RNFirebase core module was not found natively on iOS, ensure you have ' +
|
||||
'correctly included the RNFirebase pod in your projects `Podfile` and have run `pod install`.' +
|
||||
'\r\n\r\n See http://invertase.link/ios for the ios setup guide.';
|
||||
}
|
||||
|
||||
return 'RNFirebase core module was not found natively on Android, ensure you have ' +
|
||||
'correctly added the RNFirebase and Firebase gradle dependencies to your `android/app/build.gradle` file.' +
|
||||
'\r\n\r\n See http://invertase.link/android for the android setup guide.';
|
||||
},
|
||||
|
||||
|
||||
ERROR_INIT_OBJECT: 'Firebase.initializeApp(options <-- requires a valid configuration object.',
|
||||
ERROR_INIT_STRING_NAME: 'Firebase.initializeApp(options, name <-- requires a valid string value.',
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_MISSING_CB(method) {
|
||||
return `Missing required callback for method ${method}().`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_MISSING_ARG(type, method) {
|
||||
return `Missing required argument of type '${type}' for method '${method}()'.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_MISSING_ARG_NAMED(name, type, method) {
|
||||
return `Missing required argument '${name}' of type '${type}' for method '${method}()'.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_ARG_INVALID_VALUE(name, expected, got) {
|
||||
return `Invalid value for argument '${name}' expected value '${expected}' but got '${got}'.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_PROTECTED_PROP(name) {
|
||||
return `Property '${name}' is protected and can not be overridden by extendApp.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @param namespace
|
||||
* @param nativeModule
|
||||
*/
|
||||
ERROR_MISSING_MODULE(namespace, nativeModule) {
|
||||
const snippet = `firebase.${namespace}()`;
|
||||
if (Platform.OS === 'ios') {
|
||||
return `You attempted to use a firebase module that's not installed natively on your iOS project by calling ${snippet}.` +
|
||||
'\r\n\r\nEnsure you have the required Firebase iOS SDK pod for this module included in your Podfile, in this instance ' +
|
||||
`confirm you've added "pod '${NAMESPACE_PODS[namespace]}'" to your Podfile` +
|
||||
'\r\n\r\nSee http://invertase.link/ios for full setup instructions.';
|
||||
}
|
||||
|
||||
const fbSDKDep = `'com.google.firebase:firebase-${GRADLE_DEPS[namespace] || namespace}'`;
|
||||
const rnFirebasePackage = `'io.invertase.firebase.${namespace}.${nativeModule}Package'`;
|
||||
const newInstance = `'new ${nativeModule}Package()'`;
|
||||
return `You attempted to use a firebase module that's not installed on your Android project by calling ${snippet}.` +
|
||||
`\r\n\r\nEnsure you have:\r\n\r\n1) Installed the required Firebase Android SDK dependency ${fbSDKDep} in your 'android/app/build.gradle' ` +
|
||||
`file.\r\n\r\n2) Imported the ${rnFirebasePackage} module in your 'MainApplication.java' file.\r\n\r\n3) Added the ` +
|
||||
`${newInstance} line inside of the RN 'getPackages()' method list.` +
|
||||
'\r\n\r\nSee http://invertase.link/android for full setup instructions.';
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_APP_NOT_INIT(appName) {
|
||||
return `The [${appName}] firebase app has not been initialized!`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param optName
|
||||
* @return {string}
|
||||
* @constructor
|
||||
*/
|
||||
ERROR_MISSING_OPT(optName) {
|
||||
return `Failed to initialize app. FirebaseOptions missing or invalid '${optName}' property.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_NOT_APP(namespace) {
|
||||
return `Invalid FirebaseApp instance passed to firebase.${namespace}(app <--).`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_UNSUPPORTED_CLASS_METHOD(className, method) {
|
||||
return `${className}.${method}() is unsupported by the native Firebase SDKs.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_UNSUPPORTED_CLASS_PROPERTY(className, property) {
|
||||
return `${className}.${property} is unsupported by the native Firebase SDKs.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_UNSUPPORTED_MODULE_METHOD(module, method) {
|
||||
return `firebase.${module._NAMESPACE}().${method}() is unsupported by the native Firebase SDKs.`;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
ERROR_PLAY_SERVICES(statusCode) {
|
||||
const knownError = PLAY_SERVICES_CODES[statusCode];
|
||||
let start = 'Google Play Services is required to run firebase services on android but a valid installation was not found on this device.';
|
||||
|
||||
if (statusCode === 2) {
|
||||
start = 'Google Play Services is out of date and may cause some firebase services like authentication to hang when used. It is recommended that you update it.';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-template
|
||||
return `${start}\r\n\r\n` +
|
||||
'-------------------------\r\n' +
|
||||
(knownError ?
|
||||
`${knownError.code}: ${knownError.message} (code ${statusCode})` :
|
||||
`A specific play store availability reason reason was not available (unknown code: ${statusCode || null})`
|
||||
) +
|
||||
'\r\n-------------------------' +
|
||||
'\r\n\r\n' +
|
||||
'For more information on how to resolve this issue, configure Play Services checks or for guides on how to validate Play Services on your users devices see the link below:' +
|
||||
'\r\n\r\nhttp://invertase.link/play-services';
|
||||
},
|
||||
|
||||
|
||||
DEFAULT_APP_NAME,
|
||||
},
|
||||
|
||||
|
||||
SharedEventEmitter: new EventEmitter(),
|
||||
SyncTree: NativeModules.RNFirebaseDatabase ? new SyncTree(NativeModules.RNFirebaseDatabase) : null,
|
||||
|
||||
// internal utils
|
||||
deleteApp(name: String) {
|
||||
const app = this.APPS[name];
|
||||
if (!app) return Promise.resolve();
|
||||
|
||||
// https://firebase.google.com/docs/reference/js/firebase.app.App#delete
|
||||
return app.delete().then(() => {
|
||||
delete this.APPS[name];
|
||||
return true;
|
||||
});
|
||||
},
|
||||
};
|
100
bridge/firebase/modules/admob/AdMobComponent.js
Normal file
100
bridge/firebase/modules/admob/AdMobComponent.js
Normal file
@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { ViewPropTypes, requireNativeComponent } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import EventTypes, { NativeExpressEventTypes } from './EventTypes';
|
||||
import { nativeToJSError } from '../../utils';
|
||||
|
||||
import AdRequest from './AdRequest';
|
||||
import VideoOptions from './VideoOptions';
|
||||
|
||||
const adMobPropTypes = {
|
||||
...ViewPropTypes,
|
||||
size: PropTypes.string.isRequired,
|
||||
unitId: PropTypes.string.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
request: PropTypes.object,
|
||||
video: PropTypes.object,
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
};
|
||||
Object.keys(EventTypes).forEach(eventType => {
|
||||
adMobPropTypes[eventType] = PropTypes.func;
|
||||
});
|
||||
Object.keys(NativeExpressEventTypes).forEach(eventType => {
|
||||
adMobPropTypes[eventType] = PropTypes.func;
|
||||
});
|
||||
|
||||
const nativeComponents = {};
|
||||
|
||||
function getNativeComponent(name) {
|
||||
if (nativeComponents[name]) return nativeComponents[name];
|
||||
const component = requireNativeComponent(name, AdMobComponent, {
|
||||
nativeOnly: {
|
||||
onBannerEvent: true,
|
||||
},
|
||||
});
|
||||
nativeComponents[name] = component;
|
||||
return component;
|
||||
}
|
||||
|
||||
class AdMobComponent extends React.Component {
|
||||
static propTypes = adMobPropTypes;
|
||||
|
||||
static defaultProps = {
|
||||
request: new AdRequest().addTestDevice().build(),
|
||||
video: new VideoOptions().build(),
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
this.nativeView = getNativeComponent(props.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a single banner event and pass to
|
||||
* any props watching it
|
||||
* @param nativeEvent
|
||||
*/
|
||||
onBannerEvent = ({ nativeEvent }) => {
|
||||
if (this.props[nativeEvent.type]) {
|
||||
if (nativeEvent.type === 'onAdFailedToLoad') {
|
||||
const { code, message } = nativeEvent.payload;
|
||||
this.props[nativeEvent.type](nativeToJSError(code, message));
|
||||
} else {
|
||||
this.props[nativeEvent.type](nativeEvent.payload || {});
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeEvent.type === 'onSizeChange')
|
||||
this.updateSize(nativeEvent.payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the JS size of the loaded banner
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
updateSize = ({ width, height }) => {
|
||||
this.setState({ width, height });
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the native component
|
||||
* @returns {XML}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<this.nativeView
|
||||
{...this.props}
|
||||
style={[this.props.style, { ...this.state }]}
|
||||
onBannerEvent={this.onBannerEvent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AdMobComponent;
|
58
bridge/firebase/modules/admob/AdRequest.js
Normal file
58
bridge/firebase/modules/admob/AdRequest.js
Normal file
@ -0,0 +1,58 @@
|
||||
export default class AdRequest {
|
||||
constructor() {
|
||||
this._props = {
|
||||
keywords: [],
|
||||
testDevices: [],
|
||||
};
|
||||
}
|
||||
|
||||
build() {
|
||||
return this._props;
|
||||
}
|
||||
|
||||
addTestDevice(deviceId?: string) {
|
||||
this._props.testDevices.push(deviceId || 'DEVICE_ID_EMULATOR');
|
||||
return this;
|
||||
}
|
||||
|
||||
addKeyword(keyword: string) {
|
||||
this._props.keywords.push(keyword);
|
||||
return this;
|
||||
}
|
||||
|
||||
setBirthday() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
setContentUrl(url: string) {
|
||||
this._props.contentUrl = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
setGender(gender: 'male | female | unknown') {
|
||||
const genders = ['male', 'female', 'unknown'];
|
||||
if (genders.includes(gender)) {
|
||||
this._props.gender = gender;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
setLocation() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
setRequestAgent(requestAgent: string) {
|
||||
this._props.requestAgent = requestAgent;
|
||||
return this;
|
||||
}
|
||||
|
||||
setIsDesignedForFamilies(isDesignedForFamilies: boolean) {
|
||||
this._props.isDesignedForFamilies = isDesignedForFamilies;
|
||||
return this;
|
||||
}
|
||||
|
||||
tagForChildDirectedTreatment(tagForChildDirectedTreatment: boolean) {
|
||||
this._props.tagForChildDirectedTreatment = tagForChildDirectedTreatment;
|
||||
return this;
|
||||
}
|
||||
}
|
14
bridge/firebase/modules/admob/Banner.js
Normal file
14
bridge/firebase/modules/admob/Banner.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import AdMobComponent from './AdMobComponent';
|
||||
|
||||
function Banner({ ...props }) {
|
||||
return <AdMobComponent {...props} class="RNFirebaseAdMobBanner" />;
|
||||
}
|
||||
|
||||
Banner.propTypes = AdMobComponent.propTypes;
|
||||
|
||||
Banner.defaultProps = {
|
||||
size: 'SMART_BANNER',
|
||||
};
|
||||
|
||||
export default Banner;
|
23
bridge/firebase/modules/admob/EventTypes.js
Normal file
23
bridge/firebase/modules/admob/EventTypes.js
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
export default {
|
||||
onAdLoaded: 'onAdLoaded',
|
||||
onAdOpened: 'onAdOpened',
|
||||
onAdLeftApplication: 'onAdLeftApplication',
|
||||
onAdClosed: 'onAdClosed',
|
||||
onAdFailedToLoad: 'onAdFailedToLoad',
|
||||
};
|
||||
|
||||
export const NativeExpressEventTypes = {
|
||||
onVideoEnd: 'onVideoEnd',
|
||||
onVideoMute: 'onVideoMute',
|
||||
onVideoPause: 'onVideoPause',
|
||||
onVideoPlay: 'onVideoPlay',
|
||||
onVideoStart: 'onVideoStart',
|
||||
};
|
||||
|
||||
export const RewardedVideoEventTypes = {
|
||||
onRewarded: 'onRewarded',
|
||||
onRewardedVideoStarted: 'onRewardedVideoStarted',
|
||||
};
|
119
bridge/firebase/modules/admob/Interstitial.js
Normal file
119
bridge/firebase/modules/admob/Interstitial.js
Normal file
@ -0,0 +1,119 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { statics } from './';
|
||||
import AdRequest from './AdRequest';
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import { nativeToJSError } from '../../utils';
|
||||
import type AdMob from './';
|
||||
|
||||
let subscriptions = [];
|
||||
|
||||
export default class Interstitial {
|
||||
_admob: AdMob;
|
||||
|
||||
constructor(admob: AdMob, adUnit: string) {
|
||||
// Interstitials on iOS require a new instance each time
|
||||
if (Platform.OS === 'ios') {
|
||||
getNativeModule(admob).clearInterstitial(adUnit);
|
||||
}
|
||||
|
||||
for (let i = 0, len = subscriptions.length; i < len; i++) {
|
||||
subscriptions[i].remove();
|
||||
}
|
||||
subscriptions = [];
|
||||
|
||||
this._admob = admob;
|
||||
this.adUnit = adUnit;
|
||||
this.loaded = false;
|
||||
SharedEventEmitter.removeAllListeners(`interstitial_${adUnit}`);
|
||||
SharedEventEmitter.addListener(
|
||||
`interstitial_${adUnit}`,
|
||||
this._onInterstitialEvent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a JS emit event
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onInterstitialEvent = event => {
|
||||
const eventType = `interstitial:${this.adUnit}:${event.type}`;
|
||||
|
||||
let emitData = Object.assign({}, event);
|
||||
|
||||
switch (event.type) {
|
||||
case 'onAdLoaded':
|
||||
this.loaded = true;
|
||||
break;
|
||||
case 'onAdFailedToLoad':
|
||||
emitData = nativeToJSError(event.payload.code, event.payload.message);
|
||||
emitData.type = event.type;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
SharedEventEmitter.emit(eventType, emitData);
|
||||
SharedEventEmitter.emit(`interstitial:${this.adUnit}:*`, emitData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load an ad with an instance of AdRequest
|
||||
* @param request
|
||||
* @returns {*}
|
||||
*/
|
||||
loadAd(request?: AdRequest) {
|
||||
let adRequest = request;
|
||||
|
||||
if (!adRequest || !Object.keys(adRequest)) {
|
||||
adRequest = new AdRequest().addTestDevice().build();
|
||||
}
|
||||
|
||||
return getNativeModule(this._admob).interstitialLoadAd(
|
||||
this.adUnit,
|
||||
adRequest
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a local instance of isLoaded
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the advert - will only show if loaded
|
||||
* @returns {*}
|
||||
*/
|
||||
show() {
|
||||
if (this.loaded) {
|
||||
getNativeModule(this._admob).interstitialShowAd(this.adUnit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an Ad event
|
||||
* @param eventType
|
||||
* @param listenerCb
|
||||
* @returns {null}
|
||||
*/
|
||||
on(eventType, listenerCb) {
|
||||
if (!statics.EventTypes[eventType]) {
|
||||
console.warn(
|
||||
`Invalid event type provided, must be one of: ${Object.keys(
|
||||
statics.EventTypes
|
||||
).join(', ')}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const sub = SharedEventEmitter.addListener(
|
||||
`interstitial:${this.adUnit}:${eventType}`,
|
||||
listenerCb
|
||||
);
|
||||
subscriptions.push(sub);
|
||||
return sub;
|
||||
}
|
||||
}
|
14
bridge/firebase/modules/admob/NativeExpress.js
Normal file
14
bridge/firebase/modules/admob/NativeExpress.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import AdMobComponent from './AdMobComponent';
|
||||
|
||||
function NativeExpress({ ...props }) {
|
||||
return <AdMobComponent {...props} class="RNFirebaseAdMobNativeExpress" />;
|
||||
}
|
||||
|
||||
NativeExpress.propTypes = AdMobComponent.propTypes;
|
||||
|
||||
NativeExpress.defaultProps = {
|
||||
size: 'SMART_BANNER',
|
||||
};
|
||||
|
||||
export default NativeExpress;
|
118
bridge/firebase/modules/admob/RewardedVideo.js
Normal file
118
bridge/firebase/modules/admob/RewardedVideo.js
Normal file
@ -0,0 +1,118 @@
|
||||
import { statics } from './';
|
||||
import AdRequest from './AdRequest';
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import { nativeToJSError } from '../../utils';
|
||||
import type AdMob from './';
|
||||
|
||||
let subscriptions = [];
|
||||
|
||||
export default class RewardedVideo {
|
||||
_admob: AdMob;
|
||||
|
||||
constructor(admob: AdMob, adUnit: string) {
|
||||
for (let i = 0, len = subscriptions.length; i < len; i++) {
|
||||
subscriptions[i].remove();
|
||||
}
|
||||
subscriptions = [];
|
||||
|
||||
this._admob = admob;
|
||||
this.adUnit = adUnit;
|
||||
this.loaded = false;
|
||||
SharedEventEmitter.removeAllListeners(`rewarded_video_${adUnit}`);
|
||||
SharedEventEmitter.addListener(
|
||||
`rewarded_video_${adUnit}`,
|
||||
this._onRewardedVideoEvent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a JS emit event
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onRewardedVideoEvent = event => {
|
||||
const eventType = `rewarded_video:${this.adUnit}:${event.type}`;
|
||||
|
||||
let emitData = Object.assign({}, event);
|
||||
|
||||
switch (event.type) {
|
||||
case 'onAdLoaded':
|
||||
this.loaded = true;
|
||||
break;
|
||||
case 'onAdFailedToLoad':
|
||||
emitData = nativeToJSError(event.payload.code, event.payload.message);
|
||||
emitData.type = event.type;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
SharedEventEmitter.emit(eventType, emitData);
|
||||
SharedEventEmitter.emit(`rewarded_video:${this.adUnit}:*`, emitData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load an ad with an instance of AdRequest
|
||||
* @param request
|
||||
* @returns {*}
|
||||
*/
|
||||
loadAd(request?: AdRequest) {
|
||||
let adRequest = request;
|
||||
|
||||
if (!adRequest || !Object.keys(adRequest)) {
|
||||
adRequest = new AdRequest().addTestDevice().build();
|
||||
}
|
||||
|
||||
return getNativeModule(this._admob).rewardedVideoLoadAd(
|
||||
this.adUnit,
|
||||
adRequest
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a local instance of isLoaded
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the advert - will only show if loaded
|
||||
* @returns {*}
|
||||
*/
|
||||
show() {
|
||||
if (this.loaded) {
|
||||
getNativeModule(this._admob).rewardedVideoShowAd(this.adUnit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an Ad event
|
||||
* @param eventType
|
||||
* @param listenerCb
|
||||
* @returns {null}
|
||||
*/
|
||||
on(eventType, listenerCb) {
|
||||
const types = {
|
||||
...statics.EventTypes,
|
||||
...statics.RewardedVideoEventTypes,
|
||||
};
|
||||
|
||||
if (!types[eventType]) {
|
||||
console.warn(
|
||||
`Invalid event type provided, must be one of: ${Object.keys(types).join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const sub = SharedEventEmitter.addListener(
|
||||
`rewarded_video:${this.adUnit}:${eventType}`,
|
||||
listenerCb
|
||||
);
|
||||
subscriptions.push(sub);
|
||||
return sub;
|
||||
}
|
||||
}
|
16
bridge/firebase/modules/admob/VideoOptions.js
Normal file
16
bridge/firebase/modules/admob/VideoOptions.js
Normal file
@ -0,0 +1,16 @@
|
||||
export default class VideoOptions {
|
||||
constructor() {
|
||||
this._props = {
|
||||
startMuted: true,
|
||||
};
|
||||
}
|
||||
|
||||
build() {
|
||||
return this._props;
|
||||
}
|
||||
|
||||
setStartMuted(muted: boolean = true) {
|
||||
this._props.startMuted = muted;
|
||||
return this;
|
||||
}
|
||||
}
|
121
bridge/firebase/modules/admob/index.js
Normal file
121
bridge/firebase/modules/admob/index.js
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @flow
|
||||
* AdMob representation wrapper
|
||||
*/
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
|
||||
import Interstitial from './Interstitial';
|
||||
import RewardedVideo from './RewardedVideo';
|
||||
import AdRequest from './AdRequest';
|
||||
import VideoOptions from './VideoOptions';
|
||||
import Banner from './Banner';
|
||||
import NativeExpress from './NativeExpress';
|
||||
|
||||
import EventTypes, {
|
||||
NativeExpressEventTypes,
|
||||
RewardedVideoEventTypes,
|
||||
} from './EventTypes';
|
||||
|
||||
import type App from '../core/app';
|
||||
|
||||
type NativeEvent = {
|
||||
adUnit: string,
|
||||
payload: Object,
|
||||
type: string,
|
||||
};
|
||||
|
||||
const NATIVE_EVENTS = ['interstitial_event', 'rewarded_video_event'];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseAdMob';
|
||||
export const NAMESPACE = 'admob';
|
||||
|
||||
export default class AdMob extends ModuleBase {
|
||||
_appId: ?string;
|
||||
_initialized: boolean;
|
||||
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
events: NATIVE_EVENTS,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
|
||||
this._initialized = false;
|
||||
this._appId = null;
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
'interstitial_event',
|
||||
this._onInterstitialEvent.bind(this)
|
||||
);
|
||||
SharedEventEmitter.addListener(
|
||||
'rewarded_video_event',
|
||||
this._onRewardedVideoEvent.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
_onInterstitialEvent(event: NativeEvent): void {
|
||||
const { adUnit } = event;
|
||||
const jsEventType = `interstitial_${adUnit}`;
|
||||
|
||||
if (SharedEventEmitter.listeners(jsEventType).length === 0) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
SharedEventEmitter.emit(jsEventType, event);
|
||||
}
|
||||
|
||||
_onRewardedVideoEvent(event: NativeEvent): void {
|
||||
const { adUnit } = event;
|
||||
const jsEventType = `rewarded_video_${adUnit}`;
|
||||
|
||||
if (SharedEventEmitter.listeners(jsEventType).length === 0) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
SharedEventEmitter.emit(jsEventType, event);
|
||||
}
|
||||
|
||||
initialize(appId: string): void {
|
||||
if (this._initialized) {
|
||||
getLogger(this).warn('AdMob has already been initialized!');
|
||||
} else {
|
||||
this._initialized = true;
|
||||
this._appId = appId;
|
||||
getNativeModule(this).initialize(appId);
|
||||
}
|
||||
}
|
||||
|
||||
openDebugMenu(): void {
|
||||
if (!this._initialized) {
|
||||
getLogger(this).warn(
|
||||
'AdMob needs to be initialized before opening the dev menu!'
|
||||
);
|
||||
} else {
|
||||
getLogger(this).info('Opening debug menu');
|
||||
getNativeModule(this).openDebugMenu(this._appId);
|
||||
}
|
||||
}
|
||||
|
||||
interstitial(adUnit: string): Interstitial {
|
||||
return new Interstitial(this, adUnit);
|
||||
}
|
||||
|
||||
rewarded(adUnit: string): RewardedVideo {
|
||||
return new RewardedVideo(this, adUnit);
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
Banner,
|
||||
NativeExpress,
|
||||
AdRequest,
|
||||
VideoOptions,
|
||||
EventTypes,
|
||||
RewardedVideoEventTypes,
|
||||
NativeExpressEventTypes,
|
||||
};
|
167
bridge/firebase/modules/analytics/index.js
Normal file
167
bridge/firebase/modules/analytics/index.js
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @flow
|
||||
* Analytics representation wrapper
|
||||
*/
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import { isString, isObject } from '../../utils';
|
||||
|
||||
import type App from '../core/app';
|
||||
|
||||
const AlphaNumericUnderscore = /^[a-zA-Z0-9_]+$/;
|
||||
|
||||
const ReservedEventNames = [
|
||||
'app_clear_data',
|
||||
'app_uninstall',
|
||||
'app_update',
|
||||
'error',
|
||||
'first_open',
|
||||
'in_app_purchase',
|
||||
'notification_dismiss',
|
||||
'notification_foreground',
|
||||
'notification_open',
|
||||
'notification_receive',
|
||||
'os_update',
|
||||
'session_start',
|
||||
'user_engagement',
|
||||
];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseAnalytics';
|
||||
export const NAMESPACE = 'analytics';
|
||||
|
||||
export default class Analytics extends ModuleBase {
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an app event.
|
||||
* @param {string} name
|
||||
* @param params
|
||||
* @return {Promise}
|
||||
*/
|
||||
logEvent(name: string, params: Object = {}): void {
|
||||
if (!isString(name)) {
|
||||
throw new Error(
|
||||
`analytics.logEvent(): First argument 'name' is required and must be a string value.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof params !== 'undefined' && !isObject(params)) {
|
||||
throw new Error(
|
||||
`analytics.logEvent(): Second optional argument 'params' must be an object if provided.`
|
||||
);
|
||||
}
|
||||
|
||||
// check name is not a reserved event name
|
||||
if (ReservedEventNames.includes(name)) {
|
||||
throw new Error(
|
||||
`analytics.logEvent(): event name '${name}' is a reserved event name and can not be used.`
|
||||
);
|
||||
}
|
||||
|
||||
// name format validation
|
||||
if (!AlphaNumericUnderscore.test(name)) {
|
||||
throw new Error(
|
||||
`analytics.logEvent(): Event name '${name}' is invalid. Names should contain 1 to 32 alphanumeric characters or underscores.`
|
||||
);
|
||||
}
|
||||
|
||||
// maximum number of allowed params check
|
||||
if (params && Object.keys(params).length > 25)
|
||||
throw new Error(
|
||||
'analytics.logEvent(): Maximum number of parameters exceeded (25).'
|
||||
);
|
||||
|
||||
// Parameter names can be up to 24 characters long and must start with an alphabetic character
|
||||
// and contain only alphanumeric characters and underscores. Only String, long and double param
|
||||
// types are supported. String parameter values can be up to 36 characters long. The "firebase_"
|
||||
// prefix is reserved and should not be used for parameter names.
|
||||
|
||||
getNativeModule(this).logEvent(name, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether analytics collection is enabled for this app on this device.
|
||||
* @param enabled
|
||||
*/
|
||||
setAnalyticsCollectionEnabled(enabled: boolean): void {
|
||||
getNativeModule(this).setAnalyticsCollectionEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current screen name, which specifies the current visual context in your app.
|
||||
* @param screenName
|
||||
* @param screenClassOverride
|
||||
*/
|
||||
setCurrentScreen(screenName: string, screenClassOverride: string): void {
|
||||
getNativeModule(this).setCurrentScreen(screenName, screenClassOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum engagement time required before starting a session. The default value is 10000 (10 seconds).
|
||||
* @param milliseconds
|
||||
*/
|
||||
setMinimumSessionDuration(milliseconds: number = 10000): void {
|
||||
getNativeModule(this).setMinimumSessionDuration(milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration of inactivity that terminates the current session. The default value is 1800000 (30 minutes).
|
||||
* @param milliseconds
|
||||
*/
|
||||
setSessionTimeoutDuration(milliseconds: number = 1800000): void {
|
||||
getNativeModule(this).setSessionTimeoutDuration(milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user ID property.
|
||||
* @param id
|
||||
*/
|
||||
setUserId(id: string | null): void {
|
||||
if (id !== null && !isString(id)) {
|
||||
throw new Error(
|
||||
'analytics.setUserId(): The supplied userId must be a string value or null.'
|
||||
);
|
||||
}
|
||||
getNativeModule(this).setUserId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a user property to a given value.
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
setUserProperty(name: string, value: string | null): void {
|
||||
if (value !== null && !isString(value)) {
|
||||
throw new Error(
|
||||
'analytics.setUserProperty(): The supplied property must be a string value or null.'
|
||||
);
|
||||
}
|
||||
getNativeModule(this).setUserProperty(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple user properties to the supplied values.
|
||||
* @RNFirebaseSpecific
|
||||
* @param object
|
||||
*/
|
||||
setUserProperties(object: Object): void {
|
||||
Object.keys(object).forEach(property => {
|
||||
const value = object[property];
|
||||
if (value !== null && !isString(value)) {
|
||||
throw new Error(
|
||||
`analytics.setUserProperties(): The property with name '${property}' must be a string value or null.`
|
||||
);
|
||||
}
|
||||
getNativeModule(this).setUserProperty(property, object[property]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {};
|
37
bridge/firebase/modules/auth/ConfirmationResult.js
Normal file
37
bridge/firebase/modules/auth/ConfirmationResult.js
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @flow
|
||||
* ConfirmationResult representation wrapper
|
||||
*/
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import type Auth from './';
|
||||
import type User from './User';
|
||||
|
||||
export default class ConfirmationResult {
|
||||
_auth: Auth;
|
||||
_verificationId: string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param auth
|
||||
* @param verificationId The phone number authentication operation's verification ID.
|
||||
*/
|
||||
constructor(auth: Auth, verificationId: string) {
|
||||
this._auth = auth;
|
||||
this._verificationId = verificationId;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param verificationCode
|
||||
* @return {*}
|
||||
*/
|
||||
confirm(verificationCode: string): Promise<User> {
|
||||
return getNativeModule(this._auth)
|
||||
._confirmVerificationCode(verificationCode)
|
||||
.then(user => this._auth._setUser(user));
|
||||
}
|
||||
|
||||
get verificationId(): string | null {
|
||||
return this._verificationId;
|
||||
}
|
||||
}
|
347
bridge/firebase/modules/auth/PhoneAuthListener.js
Normal file
347
bridge/firebase/modules/auth/PhoneAuthListener.js
Normal file
@ -0,0 +1,347 @@
|
||||
// @flow
|
||||
import INTERNALS from '../../utils/internals';
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import {
|
||||
generatePushID,
|
||||
isFunction,
|
||||
isAndroid,
|
||||
isIOS,
|
||||
isString,
|
||||
nativeToJSError,
|
||||
} from '../../utils';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type Auth from './';
|
||||
|
||||
type PhoneAuthSnapshot = {
|
||||
state: 'sent' | 'timeout' | 'verified' | 'error',
|
||||
verificationId: string,
|
||||
code: string | null,
|
||||
error: Error | null,
|
||||
};
|
||||
|
||||
type PhoneAuthError = {
|
||||
code: string | null,
|
||||
verificationId: string,
|
||||
message: string | null,
|
||||
stack: string | null,
|
||||
};
|
||||
|
||||
export default class PhoneAuthListener {
|
||||
_auth: Auth;
|
||||
_timeout: number;
|
||||
_publicEvents: Object;
|
||||
_internalEvents: Object;
|
||||
_reject: Function | null;
|
||||
_resolve: Function | null;
|
||||
_credential: Object | null;
|
||||
_promise: Promise<*> | null;
|
||||
_phoneAuthRequestKey: string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param auth
|
||||
* @param phoneNumber
|
||||
* @param timeout
|
||||
*/
|
||||
constructor(auth: Auth, phoneNumber: string, timeout?: number) {
|
||||
this._auth = auth;
|
||||
this._reject = null;
|
||||
this._resolve = null;
|
||||
this._promise = null;
|
||||
this._credential = null;
|
||||
|
||||
this._timeout = timeout || 20; // 20 secs
|
||||
this._phoneAuthRequestKey = generatePushID();
|
||||
|
||||
// internal events
|
||||
this._internalEvents = {
|
||||
codeSent: `phone:auth:${this._phoneAuthRequestKey}:onCodeSent`,
|
||||
verificationFailed: `phone:auth:${
|
||||
this._phoneAuthRequestKey
|
||||
}:onVerificationFailed`,
|
||||
verificationComplete: `phone:auth:${
|
||||
this._phoneAuthRequestKey
|
||||
}:onVerificationComplete`,
|
||||
codeAutoRetrievalTimeout: `phone:auth:${
|
||||
this._phoneAuthRequestKey
|
||||
}:onCodeAutoRetrievalTimeout`,
|
||||
};
|
||||
|
||||
// user observer events
|
||||
this._publicEvents = {
|
||||
// error cb
|
||||
error: `phone:auth:${this._phoneAuthRequestKey}:error`,
|
||||
// observer
|
||||
event: `phone:auth:${this._phoneAuthRequestKey}:event`,
|
||||
// success cb
|
||||
success: `phone:auth:${this._phoneAuthRequestKey}:success`,
|
||||
};
|
||||
|
||||
// setup internal event listeners
|
||||
this._subscribeToEvents();
|
||||
|
||||
// start verification flow natively
|
||||
if (isAndroid) {
|
||||
getNativeModule(this._auth).verifyPhoneNumber(
|
||||
phoneNumber,
|
||||
this._phoneAuthRequestKey,
|
||||
this._timeout
|
||||
);
|
||||
}
|
||||
|
||||
if (isIOS) {
|
||||
getNativeModule(this._auth).verifyPhoneNumber(
|
||||
phoneNumber,
|
||||
this._phoneAuthRequestKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to all EE events on this._internalEvents
|
||||
* @private
|
||||
*/
|
||||
_subscribeToEvents() {
|
||||
const events = Object.keys(this._internalEvents);
|
||||
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
const type = events[i];
|
||||
SharedEventEmitter.once(
|
||||
this._internalEvents[type],
|
||||
// $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
this[`_${type}Handler`].bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe a users listener cb to the snapshot events.
|
||||
* @param observer
|
||||
* @private
|
||||
*/
|
||||
_addUserObserver(observer) {
|
||||
SharedEventEmitter.addListener(this._publicEvents.event, observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a snapshot event to users event observer.
|
||||
* @param snapshot PhoneAuthSnapshot
|
||||
* @private
|
||||
*/
|
||||
_emitToObservers(snapshot: PhoneAuthSnapshot) {
|
||||
SharedEventEmitter.emit(this._publicEvents.event, snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a error snapshot event to any subscribed errorCb's
|
||||
* @param snapshot
|
||||
* @private
|
||||
*/
|
||||
_emitToErrorCb(snapshot) {
|
||||
const { error } = snapshot;
|
||||
if (this._reject) this._reject(error);
|
||||
SharedEventEmitter.emit(this._publicEvents.error, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a success snapshot event to any subscribed completeCb's
|
||||
* @param snapshot
|
||||
* @private
|
||||
*/
|
||||
_emitToSuccessCb(snapshot) {
|
||||
if (this._resolve) this._resolve(snapshot);
|
||||
SharedEventEmitter.emit(this._publicEvents.success, snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all listeners for this phone auth instance
|
||||
* @private
|
||||
*/
|
||||
_removeAllListeners() {
|
||||
setTimeout(() => {
|
||||
// move to next event loop - not sure if needed
|
||||
// internal listeners
|
||||
Object.values(this._internalEvents).forEach(event => {
|
||||
SharedEventEmitter.removeAllListeners(event);
|
||||
});
|
||||
|
||||
// user observer listeners
|
||||
Object.values(this._publicEvents).forEach(publicEvent => {
|
||||
SharedEventEmitter.removeAllListeners(publicEvent);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new internal deferred promise, if not already created
|
||||
* @private
|
||||
*/
|
||||
_promiseDeferred() {
|
||||
if (!this._promise) {
|
||||
this._promise = new Promise((resolve, reject) => {
|
||||
this._resolve = result => {
|
||||
this._resolve = null;
|
||||
return resolve(result);
|
||||
};
|
||||
|
||||
this._reject = possibleError => {
|
||||
this._reject = null;
|
||||
return reject(possibleError);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------
|
||||
--- INTERNAL EVENT HANDLERS
|
||||
---------------------------- */
|
||||
|
||||
/**
|
||||
* Internal code sent event handler
|
||||
* @private
|
||||
* @param credential
|
||||
*/
|
||||
_codeSentHandler(credential) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: credential.verificationId,
|
||||
code: null,
|
||||
error: null,
|
||||
state: 'sent',
|
||||
};
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
|
||||
if (isIOS) {
|
||||
this._emitToSuccessCb(snapshot);
|
||||
}
|
||||
|
||||
if (isAndroid) {
|
||||
// android can auto retrieve so we don't emit to successCb immediately,
|
||||
// if auto retrieve times out then that will emit to successCb
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal code auto retrieve timeout event handler
|
||||
* @private
|
||||
* @param credential
|
||||
*/
|
||||
_codeAutoRetrievalTimeoutHandler(credential) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: credential.verificationId,
|
||||
code: null,
|
||||
error: null,
|
||||
state: 'timeout',
|
||||
};
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
this._emitToSuccessCb(snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal verification complete event handler
|
||||
* @param credential
|
||||
* @private
|
||||
*/
|
||||
_verificationCompleteHandler(credential) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: credential.verificationId,
|
||||
code: credential.code || null,
|
||||
error: null,
|
||||
state: 'verified',
|
||||
};
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
this._emitToSuccessCb(snapshot);
|
||||
this._removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal verification failed event handler
|
||||
* @param state
|
||||
* @private
|
||||
*/
|
||||
_verificationFailedHandler(state) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: state.verificationId,
|
||||
code: null,
|
||||
error: null,
|
||||
state: 'error',
|
||||
};
|
||||
|
||||
const { code, message, nativeErrorMessage } = state.error;
|
||||
snapshot.error = nativeToJSError(code, message, { nativeErrorMessage });
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
this._emitToErrorCb(snapshot);
|
||||
this._removeAllListeners();
|
||||
}
|
||||
|
||||
/* -------------
|
||||
-- PUBLIC API
|
||||
--------------*/
|
||||
|
||||
on(
|
||||
event: string,
|
||||
observer: () => PhoneAuthSnapshot,
|
||||
errorCb?: () => PhoneAuthError,
|
||||
successCb?: () => PhoneAuthSnapshot
|
||||
): this {
|
||||
if (!isString(event)) {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('event', 'string', 'on')
|
||||
);
|
||||
}
|
||||
|
||||
if (event !== 'state_changed') {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_ARG_INVALID_VALUE(
|
||||
'event',
|
||||
'state_changed',
|
||||
event
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFunction(observer)) {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('observer', 'function', 'on')
|
||||
);
|
||||
}
|
||||
|
||||
this._addUserObserver(observer);
|
||||
|
||||
if (isFunction(errorCb)) {
|
||||
SharedEventEmitter.once(this._publicEvents.error, errorCb);
|
||||
}
|
||||
|
||||
if (isFunction(successCb)) {
|
||||
SharedEventEmitter.once(this._publicEvents.success, successCb);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise .then proxy
|
||||
* @param fn
|
||||
*/
|
||||
then(fn: () => PhoneAuthSnapshot) {
|
||||
this._promiseDeferred();
|
||||
// $FlowFixMe: Unsure how to annotate `bind` here
|
||||
if (this._promise) return this._promise.then.bind(this._promise)(fn);
|
||||
return undefined; // will never get here - just to keep flow happy
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise .catch proxy
|
||||
* @param fn
|
||||
*/
|
||||
catch(fn: () => Error) {
|
||||
this._promiseDeferred();
|
||||
// $FlowFixMe: Unsure how to annotate `bind` here
|
||||
if (this._promise) return this._promise.catch.bind(this._promise)(fn);
|
||||
return undefined; // will never get here - just to keep flow happy
|
||||
}
|
||||
}
|
334
bridge/firebase/modules/auth/User.js
Normal file
334
bridge/firebase/modules/auth/User.js
Normal file
@ -0,0 +1,334 @@
|
||||
/**
|
||||
* @flow
|
||||
* User representation wrapper
|
||||
*/
|
||||
import INTERNALS from '../../utils/internals';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type Auth from './';
|
||||
import type {
|
||||
ActionCodeSettings,
|
||||
AuthCredential,
|
||||
NativeUser,
|
||||
UserCredential,
|
||||
UserInfo,
|
||||
UserMetadata,
|
||||
} from './types';
|
||||
|
||||
type UpdateProfile = {
|
||||
displayName?: string,
|
||||
photoURL?: string,
|
||||
};
|
||||
|
||||
export default class User {
|
||||
_auth: Auth;
|
||||
_user: NativeUser;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param auth Instance of Authentication class
|
||||
* @param user user result object from native
|
||||
*/
|
||||
constructor(auth: Auth, user: NativeUser) {
|
||||
this._auth = auth;
|
||||
this._user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* PROPERTIES
|
||||
*/
|
||||
|
||||
get displayName(): ?string {
|
||||
return this._user.displayName || null;
|
||||
}
|
||||
|
||||
get email(): ?string {
|
||||
return this._user.email || null;
|
||||
}
|
||||
|
||||
get emailVerified(): boolean {
|
||||
return this._user.emailVerified || false;
|
||||
}
|
||||
|
||||
get isAnonymous(): boolean {
|
||||
return this._user.isAnonymous || false;
|
||||
}
|
||||
|
||||
get metadata(): UserMetadata {
|
||||
return this._user.metadata;
|
||||
}
|
||||
|
||||
get phoneNumber(): ?string {
|
||||
return this._user.phoneNumber || null;
|
||||
}
|
||||
|
||||
get photoURL(): ?string {
|
||||
return this._user.photoURL || null;
|
||||
}
|
||||
|
||||
get providerData(): Array<UserInfo> {
|
||||
return this._user.providerData;
|
||||
}
|
||||
|
||||
get providerId(): string {
|
||||
return this._user.providerId;
|
||||
}
|
||||
|
||||
get uid(): string {
|
||||
return this._user.uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* METHODS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Delete the current user
|
||||
* @return {Promise}
|
||||
*/
|
||||
delete(): Promise<void> {
|
||||
return getNativeModule(this._auth)
|
||||
.delete()
|
||||
.then(() => {
|
||||
this._auth._setUser();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get the token of current user
|
||||
* @return {Promise}
|
||||
*/
|
||||
getIdToken(forceRefresh: boolean = false): Promise<string> {
|
||||
return getNativeModule(this._auth).getToken(forceRefresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the token of current user
|
||||
* @deprecated Deprecated getToken in favor of getIdToken.
|
||||
* @return {Promise}
|
||||
*/
|
||||
getToken(forceRefresh: boolean = false): Promise<Object> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.getToken in favor of firebase.User.prototype.getIdToken.'
|
||||
);
|
||||
return getNativeModule(this._auth).getToken(forceRefresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated linkWithCredential in favor of linkAndRetrieveDataWithCredential.
|
||||
* @param credential
|
||||
*/
|
||||
linkWithCredential(credential: AuthCredential): Promise<User> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.linkWithCredential in favor of firebase.User.prototype.linkAndRetrieveDataWithCredential.'
|
||||
);
|
||||
return getNativeModule(this._auth)
|
||||
.linkWithCredential(
|
||||
credential.providerId,
|
||||
credential.token,
|
||||
credential.secret
|
||||
)
|
||||
.then(user => this._auth._setUser(user));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param credential
|
||||
*/
|
||||
linkAndRetrieveDataWithCredential(
|
||||
credential: AuthCredential
|
||||
): Promise<UserCredential> {
|
||||
return getNativeModule(this._auth)
|
||||
.linkAndRetrieveDataWithCredential(
|
||||
credential.providerId,
|
||||
credential.token,
|
||||
credential.secret
|
||||
)
|
||||
.then(userCredential => this._auth._setUserCredential(userCredential));
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-authenticate a user with a third-party authentication provider
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
reauthenticateWithCredential(credential: AuthCredential): Promise<void> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.reauthenticateWithCredential in favor of firebase.User.prototype.reauthenticateAndRetrieveDataWithCredential.'
|
||||
);
|
||||
return getNativeModule(this._auth)
|
||||
.reauthenticateWithCredential(
|
||||
credential.providerId,
|
||||
credential.token,
|
||||
credential.secret
|
||||
)
|
||||
.then(user => {
|
||||
this._auth._setUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-authenticate a user with a third-party authentication provider
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
reauthenticateAndRetrieveDataWithCredential(
|
||||
credential: AuthCredential
|
||||
): Promise<UserCredential> {
|
||||
return getNativeModule(this._auth)
|
||||
.reauthenticateAndRetrieveDataWithCredential(
|
||||
credential.providerId,
|
||||
credential.token,
|
||||
credential.secret
|
||||
)
|
||||
.then(userCredential => this._auth._setUserCredential(userCredential));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the current user
|
||||
* @return {Promise}
|
||||
*/
|
||||
reload(): Promise<void> {
|
||||
return getNativeModule(this._auth)
|
||||
.reload()
|
||||
.then(user => {
|
||||
this._auth._setUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send verification email to current user.
|
||||
*/
|
||||
sendEmailVerification(
|
||||
actionCodeSettings?: ActionCodeSettings
|
||||
): Promise<void> {
|
||||
return getNativeModule(this._auth)
|
||||
.sendEmailVerification(actionCodeSettings)
|
||||
.then(user => {
|
||||
this._auth._setUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
toJSON(): Object {
|
||||
return Object.assign({}, this._user);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param providerId
|
||||
* @return {Promise.<TResult>|*}
|
||||
*/
|
||||
unlink(providerId: string): Promise<User> {
|
||||
return getNativeModule(this._auth)
|
||||
.unlink(providerId)
|
||||
.then(user => this._auth._setUser(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current user's email
|
||||
*
|
||||
* @param {string} email The user's _new_ email
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
updateEmail(email: string): Promise<void> {
|
||||
return getNativeModule(this._auth)
|
||||
.updateEmail(email)
|
||||
.then(user => {
|
||||
this._auth._setUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current user's password
|
||||
* @param {string} password the new password
|
||||
* @return {Promise}
|
||||
*/
|
||||
updatePassword(password: string): Promise<void> {
|
||||
return getNativeModule(this._auth)
|
||||
.updatePassword(password)
|
||||
.then(user => {
|
||||
this._auth._setUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current user's profile
|
||||
* @param {Object} updates An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile)
|
||||
* @return {Promise}
|
||||
*/
|
||||
updateProfile(updates: UpdateProfile = {}): Promise<void> {
|
||||
return getNativeModule(this._auth)
|
||||
.updateProfile(updates)
|
||||
.then(user => {
|
||||
this._auth._setUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* KNOWN UNSUPPORTED METHODS
|
||||
*/
|
||||
|
||||
linkWithPhoneNumber() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD(
|
||||
'User',
|
||||
'linkWithPhoneNumber'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
linkWithPopup() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('User', 'linkWithPopup')
|
||||
);
|
||||
}
|
||||
|
||||
linkWithRedirect() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD(
|
||||
'User',
|
||||
'linkWithRedirect'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
reauthenticateWithPhoneNumber() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD(
|
||||
'User',
|
||||
'reauthenticateWithPhoneNumber'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
reauthenticateWithPopup() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD(
|
||||
'User',
|
||||
'reauthenticateWithPopup'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
reauthenticateWithRedirect() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD(
|
||||
'User',
|
||||
'reauthenticateWithRedirect'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
updatePhoneNumber() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD(
|
||||
'User',
|
||||
'updatePhoneNumber'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get refreshToken(): string {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('User', 'refreshToken')
|
||||
);
|
||||
}
|
||||
}
|
526
bridge/firebase/modules/auth/index.js
Normal file
526
bridge/firebase/modules/auth/index.js
Normal file
@ -0,0 +1,526 @@
|
||||
/**
|
||||
* @flow
|
||||
* Auth representation wrapper
|
||||
*/
|
||||
import User from './User';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import INTERNALS from '../../utils/internals';
|
||||
import ConfirmationResult from './phone/ConfirmationResult';
|
||||
import PhoneAuthListener from './phone/PhoneAuthListener';
|
||||
|
||||
// providers
|
||||
import EmailAuthProvider from './providers/EmailAuthProvider';
|
||||
import PhoneAuthProvider from './providers/PhoneAuthProvider';
|
||||
import GoogleAuthProvider from './providers/GoogleAuthProvider';
|
||||
import GithubAuthProvider from './providers/GithubAuthProvider';
|
||||
import OAuthProvider from './providers/OAuthProvider';
|
||||
import TwitterAuthProvider from './providers/TwitterAuthProvider';
|
||||
import FacebookAuthProvider from './providers/FacebookAuthProvider';
|
||||
|
||||
import type {
|
||||
ActionCodeInfo,
|
||||
ActionCodeSettings,
|
||||
AuthCredential,
|
||||
NativeUser,
|
||||
NativeUserCredential,
|
||||
UserCredential,
|
||||
} from './types';
|
||||
import type App from '../core/app';
|
||||
|
||||
type AuthState = {
|
||||
user?: NativeUser,
|
||||
};
|
||||
|
||||
const NATIVE_EVENTS = [
|
||||
'auth_state_changed',
|
||||
'auth_id_token_changed',
|
||||
'phone_auth_state_changed',
|
||||
];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseAuth';
|
||||
export const NAMESPACE = 'auth';
|
||||
|
||||
export default class Auth extends ModuleBase {
|
||||
_authResult: boolean;
|
||||
_languageCode: string;
|
||||
_user: User | null;
|
||||
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
events: NATIVE_EVENTS,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: true,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
this._user = null;
|
||||
this._authResult = false;
|
||||
this._languageCode =
|
||||
getNativeModule(this).APP_LANGUAGE[app._name] ||
|
||||
getNativeModule(this).APP_LANGUAGE['[DEFAULT]'];
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onAuthStateChanged
|
||||
getAppEventName(this, 'auth_state_changed'),
|
||||
(state: AuthState) => {
|
||||
this._setUser(state.user);
|
||||
SharedEventEmitter.emit(
|
||||
getAppEventName(this, 'onAuthStateChanged'),
|
||||
this._user
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public events based on event.type
|
||||
getAppEventName(this, 'phone_auth_state_changed'),
|
||||
(event: Object) => {
|
||||
const eventKey = `phone:auth:${event.requestKey}:${event.type}`;
|
||||
SharedEventEmitter.emit(eventKey, event.state);
|
||||
}
|
||||
);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onIdTokenChanged
|
||||
getAppEventName(this, 'auth_id_token_changed'),
|
||||
(auth: AuthState) => {
|
||||
this._setUser(auth.user);
|
||||
SharedEventEmitter.emit(
|
||||
getAppEventName(this, 'onIdTokenChanged'),
|
||||
this._user
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
getNativeModule(this).addAuthStateListener();
|
||||
getNativeModule(this).addIdTokenListener();
|
||||
}
|
||||
|
||||
_setUser(user: ?NativeUser): ?User {
|
||||
this._authResult = true;
|
||||
this._user = user ? new User(this, user) : null;
|
||||
SharedEventEmitter.emit(getAppEventName(this, 'onUserChanged'), this._user);
|
||||
return this._user;
|
||||
}
|
||||
|
||||
_setUserCredential(userCredential: NativeUserCredential): UserCredential {
|
||||
const user = new User(this, userCredential.user);
|
||||
this._authResult = true;
|
||||
this._user = user;
|
||||
SharedEventEmitter.emit(getAppEventName(this, 'onUserChanged'), this._user);
|
||||
return {
|
||||
additionalUserInfo: userCredential.additionalUserInfo,
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* WEB API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Listen for auth changes.
|
||||
* @param listener
|
||||
*/
|
||||
onAuthStateChanged(listener: Function) {
|
||||
getLogger(this).info('Creating onAuthStateChanged listener');
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this, 'onAuthStateChanged'),
|
||||
listener
|
||||
);
|
||||
if (this._authResult) listener(this._user || null);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onAuthStateChanged listener');
|
||||
SharedEventEmitter.removeListener(
|
||||
getAppEventName(this, 'onAuthStateChanged'),
|
||||
listener
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for id token changes.
|
||||
* @param listener
|
||||
*/
|
||||
onIdTokenChanged(listener: Function) {
|
||||
getLogger(this).info('Creating onIdTokenChanged listener');
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this, 'onIdTokenChanged'),
|
||||
listener
|
||||
);
|
||||
if (this._authResult) listener(this._user || null);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onIdTokenChanged listener');
|
||||
SharedEventEmitter.removeListener(
|
||||
getAppEventName(this, 'onIdTokenChanged'),
|
||||
listener
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for user changes.
|
||||
* @param listener
|
||||
*/
|
||||
onUserChanged(listener: Function) {
|
||||
getLogger(this).info('Creating onUserChanged listener');
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this, 'onUserChanged'),
|
||||
listener
|
||||
);
|
||||
if (this._authResult) listener(this._user || null);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onUserChanged listener');
|
||||
SharedEventEmitter.removeListener(
|
||||
getAppEventName(this, 'onUserChanged'),
|
||||
listener
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the current user out
|
||||
* @return {Promise}
|
||||
*/
|
||||
signOut(): Promise<void> {
|
||||
return getNativeModule(this)
|
||||
.signOut()
|
||||
.then(() => {
|
||||
this._setUser();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a user in anonymously
|
||||
* @deprecated Deprecated signInAnonymously in favor of signInAnonymouslyAndRetrieveData.
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
signInAnonymously(): Promise<User> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.signInAnonymously in favor of firebase.User.prototype.signInAnonymouslyAndRetrieveData.'
|
||||
);
|
||||
return getNativeModule(this)
|
||||
.signInAnonymously()
|
||||
.then(user => this._setUser(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a user in anonymously
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
signInAnonymouslyAndRetrieveData(): Promise<UserCredential> {
|
||||
return getNativeModule(this)
|
||||
.signInAnonymouslyAndRetrieveData()
|
||||
.then(userCredential => this._setUserCredential(userCredential));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user with the email/password functionality
|
||||
* @deprecated Deprecated createUserWithEmailAndPassword in favor of createUserAndRetrieveDataWithEmailAndPassword.
|
||||
* @param {string} email The user's email
|
||||
* @param {string} password The user's password
|
||||
* @return {Promise} A promise indicating the completion
|
||||
*/
|
||||
createUserWithEmailAndPassword(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<User> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.createUserWithEmailAndPassword in favor of firebase.User.prototype.createUserAndRetrieveDataWithEmailAndPassword.'
|
||||
);
|
||||
return getNativeModule(this)
|
||||
.createUserWithEmailAndPassword(email, password)
|
||||
.then(user => this._setUser(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user with the email/password functionality
|
||||
* @param {string} email The user's email
|
||||
* @param {string} password The user's password
|
||||
* @return {Promise} A promise indicating the completion
|
||||
*/
|
||||
createUserAndRetrieveDataWithEmailAndPassword(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<UserCredential> {
|
||||
return getNativeModule(this)
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, password)
|
||||
.then(userCredential => this._setUserCredential(userCredential));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a user in with email/password
|
||||
* @deprecated Deprecated signInWithEmailAndPassword in favor of signInAndRetrieveDataWithEmailAndPassword
|
||||
* @param {string} email The user's email
|
||||
* @param {string} password The user's password
|
||||
* @return {Promise} A promise that is resolved upon completion
|
||||
*/
|
||||
signInWithEmailAndPassword(email: string, password: string): Promise<User> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.signInWithEmailAndPassword in favor of firebase.User.prototype.signInAndRetrieveDataWithEmailAndPassword.'
|
||||
);
|
||||
return getNativeModule(this)
|
||||
.signInWithEmailAndPassword(email, password)
|
||||
.then(user => this._setUser(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a user in with email/password
|
||||
* @param {string} email The user's email
|
||||
* @param {string} password The user's password
|
||||
* @return {Promise} A promise that is resolved upon completion
|
||||
*/
|
||||
signInAndRetrieveDataWithEmailAndPassword(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<UserCredential> {
|
||||
return getNativeModule(this)
|
||||
.signInAndRetrieveDataWithEmailAndPassword(email, password)
|
||||
.then(userCredential => this._setUserCredential(userCredential));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the user in with a custom auth token
|
||||
* @deprecated Deprecated signInWithCustomToken in favor of signInAndRetrieveDataWithCustomToken
|
||||
* @param {string} customToken A self-signed custom auth token.
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
signInWithCustomToken(customToken: string): Promise<User> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.signInWithCustomToken in favor of firebase.User.prototype.signInAndRetrieveDataWithCustomToken.'
|
||||
);
|
||||
return getNativeModule(this)
|
||||
.signInWithCustomToken(customToken)
|
||||
.then(user => this._setUser(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the user in with a custom auth token
|
||||
* @param {string} customToken A self-signed custom auth token.
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
signInAndRetrieveDataWithCustomToken(
|
||||
customToken: string
|
||||
): Promise<UserCredential> {
|
||||
return getNativeModule(this)
|
||||
.signInAndRetrieveDataWithCustomToken(customToken)
|
||||
.then(userCredential => this._setUserCredential(userCredential));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the user in with a third-party authentication provider
|
||||
* @deprecated Deprecated signInWithCredential in favor of signInAndRetrieveDataWithCredential.
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
signInWithCredential(credential: AuthCredential): Promise<User> {
|
||||
console.warn(
|
||||
'Deprecated firebase.User.prototype.signInWithCredential in favor of firebase.User.prototype.signInAndRetrieveDataWithCredential.'
|
||||
);
|
||||
return getNativeModule(this)
|
||||
.signInWithCredential(
|
||||
credential.providerId,
|
||||
credential.token,
|
||||
credential.secret
|
||||
)
|
||||
.then(user => this._setUser(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the user in with a third-party authentication provider
|
||||
* @return {Promise} A promise resolved upon completion
|
||||
*/
|
||||
signInAndRetrieveDataWithCredential(
|
||||
credential: AuthCredential
|
||||
): Promise<UserCredential> {
|
||||
return getNativeModule(this)
|
||||
.signInAndRetrieveDataWithCredential(
|
||||
credential.providerId,
|
||||
credential.token,
|
||||
credential.secret
|
||||
)
|
||||
.then(userCredential => this._setUserCredential(userCredential));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously signs in using a phone number.
|
||||
*
|
||||
*/
|
||||
signInWithPhoneNumber(phoneNumber: string): Promise<ConfirmationResult> {
|
||||
return getNativeModule(this)
|
||||
.signInWithPhoneNumber(phoneNumber)
|
||||
.then(result => new ConfirmationResult(this, result.verificationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PhoneAuthListener to listen to phone verification events,
|
||||
* on the final completion event a PhoneAuthCredential can be generated for
|
||||
* authentication purposes.
|
||||
*
|
||||
* @param phoneNumber
|
||||
* @param autoVerifyTimeout Android Only
|
||||
* @returns {PhoneAuthListener}
|
||||
*/
|
||||
verifyPhoneNumber(
|
||||
phoneNumber: string,
|
||||
autoVerifyTimeout?: number
|
||||
): PhoneAuthListener {
|
||||
return new PhoneAuthListener(this, phoneNumber, autoVerifyTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send reset password instructions via email
|
||||
* @param {string} email The email to send password reset instructions
|
||||
*/
|
||||
sendPasswordResetEmail(
|
||||
email: string,
|
||||
actionCodeSettings?: ActionCodeSettings
|
||||
): Promise<void> {
|
||||
return getNativeModule(this).sendPasswordResetEmail(
|
||||
email,
|
||||
actionCodeSettings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the password reset process, given a confirmation code and new password.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset
|
||||
* @param code
|
||||
* @param newPassword
|
||||
* @return {Promise.<Null>}
|
||||
*/
|
||||
confirmPasswordReset(code: string, newPassword: string): Promise<void> {
|
||||
return getNativeModule(this).confirmPasswordReset(code, newPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a verification code sent to the user by email or other out-of-band mechanism.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.auth.Auth#applyActionCode
|
||||
* @param code
|
||||
* @return {Promise.<Null>}
|
||||
*/
|
||||
applyActionCode(code: string): Promise<void> {
|
||||
return getNativeModule(this).applyActionCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a verification code sent to the user by email or other out-of-band mechanism.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.auth.Auth#checkActionCode
|
||||
* @param code
|
||||
* @return {Promise.<any>|Promise<ActionCodeInfo>}
|
||||
*/
|
||||
checkActionCode(code: string): Promise<ActionCodeInfo> {
|
||||
return getNativeModule(this).checkActionCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of authentication providers that can be used to sign in a given user (identified by its main email address).
|
||||
* @return {Promise}
|
||||
*/
|
||||
fetchProvidersForEmail(email: string): Promise<string[]> {
|
||||
return getNativeModule(this).fetchProvidersForEmail(email);
|
||||
}
|
||||
|
||||
verifyPasswordResetCode(code: string): Promise<string> {
|
||||
return getNativeModule(this).verifyPasswordResetCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the language for the auth module
|
||||
* @param code
|
||||
* @returns {*}
|
||||
*/
|
||||
set languageCode(code: string) {
|
||||
this._languageCode = code;
|
||||
getNativeModule(this).setLanguageCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently signed in user
|
||||
* @return {Promise}
|
||||
*/
|
||||
get currentUser(): User | null {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
get languageCode(): string {
|
||||
return this._languageCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* KNOWN UNSUPPORTED METHODS
|
||||
*/
|
||||
|
||||
getRedirectResult() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'auth',
|
||||
'getRedirectResult'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setPersistence() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'auth',
|
||||
'setPersistence'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
signInWithPopup() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'auth',
|
||||
'signInWithPopup'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
signInWithRedirect() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'auth',
|
||||
'signInWithRedirect'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// firebase issue - https://github.com/invertase/react-native-firebase/pull/655#issuecomment-349904680
|
||||
useDeviceLanguage() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'auth',
|
||||
'useDeviceLanguage'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
EmailAuthProvider,
|
||||
PhoneAuthProvider,
|
||||
GoogleAuthProvider,
|
||||
GithubAuthProvider,
|
||||
TwitterAuthProvider,
|
||||
FacebookAuthProvider,
|
||||
OAuthProvider,
|
||||
PhoneAuthState: {
|
||||
CODE_SENT: 'sent',
|
||||
AUTO_VERIFY_TIMEOUT: 'timeout',
|
||||
AUTO_VERIFIED: 'verified',
|
||||
ERROR: 'error',
|
||||
},
|
||||
};
|
37
bridge/firebase/modules/auth/phone/ConfirmationResult.js
Normal file
37
bridge/firebase/modules/auth/phone/ConfirmationResult.js
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @flow
|
||||
* ConfirmationResult representation wrapper
|
||||
*/
|
||||
import { getNativeModule } from '../../../utils/native';
|
||||
import type Auth from '../';
|
||||
import type User from '../User';
|
||||
|
||||
export default class ConfirmationResult {
|
||||
_auth: Auth;
|
||||
_verificationId: string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param auth
|
||||
* @param verificationId The phone number authentication operation's verification ID.
|
||||
*/
|
||||
constructor(auth: Auth, verificationId: string) {
|
||||
this._auth = auth;
|
||||
this._verificationId = verificationId;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param verificationCode
|
||||
* @return {*}
|
||||
*/
|
||||
confirm(verificationCode: string): Promise<User> {
|
||||
return getNativeModule(this._auth)
|
||||
._confirmVerificationCode(verificationCode)
|
||||
.then(user => this._auth._setUser(user));
|
||||
}
|
||||
|
||||
get verificationId(): string | null {
|
||||
return this._verificationId;
|
||||
}
|
||||
}
|
347
bridge/firebase/modules/auth/phone/PhoneAuthListener.js
Normal file
347
bridge/firebase/modules/auth/phone/PhoneAuthListener.js
Normal file
@ -0,0 +1,347 @@
|
||||
// @flow
|
||||
import INTERNALS from '../../../utils/internals';
|
||||
import { SharedEventEmitter } from '../../../utils/events';
|
||||
import {
|
||||
generatePushID,
|
||||
isFunction,
|
||||
isAndroid,
|
||||
isIOS,
|
||||
isString,
|
||||
nativeToJSError,
|
||||
} from '../../../utils';
|
||||
import { getNativeModule } from '../../../utils/native';
|
||||
|
||||
import type Auth from '../';
|
||||
|
||||
type PhoneAuthSnapshot = {
|
||||
state: 'sent' | 'timeout' | 'verified' | 'error',
|
||||
verificationId: string,
|
||||
code: string | null,
|
||||
error: Error | null,
|
||||
};
|
||||
|
||||
type PhoneAuthError = {
|
||||
code: string | null,
|
||||
verificationId: string,
|
||||
message: string | null,
|
||||
stack: string | null,
|
||||
};
|
||||
|
||||
export default class PhoneAuthListener {
|
||||
_auth: Auth;
|
||||
_timeout: number;
|
||||
_publicEvents: Object;
|
||||
_internalEvents: Object;
|
||||
_reject: Function | null;
|
||||
_resolve: Function | null;
|
||||
_credential: Object | null;
|
||||
_promise: Promise<*> | null;
|
||||
_phoneAuthRequestKey: string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param auth
|
||||
* @param phoneNumber
|
||||
* @param timeout
|
||||
*/
|
||||
constructor(auth: Auth, phoneNumber: string, timeout?: number) {
|
||||
this._auth = auth;
|
||||
this._reject = null;
|
||||
this._resolve = null;
|
||||
this._promise = null;
|
||||
this._credential = null;
|
||||
|
||||
this._timeout = timeout || 20; // 20 secs
|
||||
this._phoneAuthRequestKey = generatePushID();
|
||||
|
||||
// internal events
|
||||
this._internalEvents = {
|
||||
codeSent: `phone:auth:${this._phoneAuthRequestKey}:onCodeSent`,
|
||||
verificationFailed: `phone:auth:${
|
||||
this._phoneAuthRequestKey
|
||||
}:onVerificationFailed`,
|
||||
verificationComplete: `phone:auth:${
|
||||
this._phoneAuthRequestKey
|
||||
}:onVerificationComplete`,
|
||||
codeAutoRetrievalTimeout: `phone:auth:${
|
||||
this._phoneAuthRequestKey
|
||||
}:onCodeAutoRetrievalTimeout`,
|
||||
};
|
||||
|
||||
// user observer events
|
||||
this._publicEvents = {
|
||||
// error cb
|
||||
error: `phone:auth:${this._phoneAuthRequestKey}:error`,
|
||||
// observer
|
||||
event: `phone:auth:${this._phoneAuthRequestKey}:event`,
|
||||
// success cb
|
||||
success: `phone:auth:${this._phoneAuthRequestKey}:success`,
|
||||
};
|
||||
|
||||
// setup internal event listeners
|
||||
this._subscribeToEvents();
|
||||
|
||||
// start verification flow natively
|
||||
if (isAndroid) {
|
||||
getNativeModule(this._auth).verifyPhoneNumber(
|
||||
phoneNumber,
|
||||
this._phoneAuthRequestKey,
|
||||
this._timeout
|
||||
);
|
||||
}
|
||||
|
||||
if (isIOS) {
|
||||
getNativeModule(this._auth).verifyPhoneNumber(
|
||||
phoneNumber,
|
||||
this._phoneAuthRequestKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to all EE events on this._internalEvents
|
||||
* @private
|
||||
*/
|
||||
_subscribeToEvents() {
|
||||
const events = Object.keys(this._internalEvents);
|
||||
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
const type = events[i];
|
||||
SharedEventEmitter.once(
|
||||
this._internalEvents[type],
|
||||
// $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
this[`_${type}Handler`].bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe a users listener cb to the snapshot events.
|
||||
* @param observer
|
||||
* @private
|
||||
*/
|
||||
_addUserObserver(observer) {
|
||||
SharedEventEmitter.addListener(this._publicEvents.event, observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a snapshot event to users event observer.
|
||||
* @param snapshot PhoneAuthSnapshot
|
||||
* @private
|
||||
*/
|
||||
_emitToObservers(snapshot: PhoneAuthSnapshot) {
|
||||
SharedEventEmitter.emit(this._publicEvents.event, snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a error snapshot event to any subscribed errorCb's
|
||||
* @param snapshot
|
||||
* @private
|
||||
*/
|
||||
_emitToErrorCb(snapshot) {
|
||||
const { error } = snapshot;
|
||||
if (this._reject) this._reject(error);
|
||||
SharedEventEmitter.emit(this._publicEvents.error, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a success snapshot event to any subscribed completeCb's
|
||||
* @param snapshot
|
||||
* @private
|
||||
*/
|
||||
_emitToSuccessCb(snapshot) {
|
||||
if (this._resolve) this._resolve(snapshot);
|
||||
SharedEventEmitter.emit(this._publicEvents.success, snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all listeners for this phone auth instance
|
||||
* @private
|
||||
*/
|
||||
_removeAllListeners() {
|
||||
setTimeout(() => {
|
||||
// move to next event loop - not sure if needed
|
||||
// internal listeners
|
||||
Object.values(this._internalEvents).forEach(event => {
|
||||
SharedEventEmitter.removeAllListeners(event);
|
||||
});
|
||||
|
||||
// user observer listeners
|
||||
Object.values(this._publicEvents).forEach(publicEvent => {
|
||||
SharedEventEmitter.removeAllListeners(publicEvent);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new internal deferred promise, if not already created
|
||||
* @private
|
||||
*/
|
||||
_promiseDeferred() {
|
||||
if (!this._promise) {
|
||||
this._promise = new Promise((resolve, reject) => {
|
||||
this._resolve = result => {
|
||||
this._resolve = null;
|
||||
return resolve(result);
|
||||
};
|
||||
|
||||
this._reject = possibleError => {
|
||||
this._reject = null;
|
||||
return reject(possibleError);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------
|
||||
--- INTERNAL EVENT HANDLERS
|
||||
---------------------------- */
|
||||
|
||||
/**
|
||||
* Internal code sent event handler
|
||||
* @private
|
||||
* @param credential
|
||||
*/
|
||||
_codeSentHandler(credential) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: credential.verificationId,
|
||||
code: null,
|
||||
error: null,
|
||||
state: 'sent',
|
||||
};
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
|
||||
if (isIOS) {
|
||||
this._emitToSuccessCb(snapshot);
|
||||
}
|
||||
|
||||
if (isAndroid) {
|
||||
// android can auto retrieve so we don't emit to successCb immediately,
|
||||
// if auto retrieve times out then that will emit to successCb
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal code auto retrieve timeout event handler
|
||||
* @private
|
||||
* @param credential
|
||||
*/
|
||||
_codeAutoRetrievalTimeoutHandler(credential) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: credential.verificationId,
|
||||
code: null,
|
||||
error: null,
|
||||
state: 'timeout',
|
||||
};
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
this._emitToSuccessCb(snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal verification complete event handler
|
||||
* @param credential
|
||||
* @private
|
||||
*/
|
||||
_verificationCompleteHandler(credential) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: credential.verificationId,
|
||||
code: credential.code || null,
|
||||
error: null,
|
||||
state: 'verified',
|
||||
};
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
this._emitToSuccessCb(snapshot);
|
||||
this._removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal verification failed event handler
|
||||
* @param state
|
||||
* @private
|
||||
*/
|
||||
_verificationFailedHandler(state) {
|
||||
const snapshot: PhoneAuthSnapshot = {
|
||||
verificationId: state.verificationId,
|
||||
code: null,
|
||||
error: null,
|
||||
state: 'error',
|
||||
};
|
||||
|
||||
const { code, message, nativeErrorMessage } = state.error;
|
||||
snapshot.error = nativeToJSError(code, message, { nativeErrorMessage });
|
||||
|
||||
this._emitToObservers(snapshot);
|
||||
this._emitToErrorCb(snapshot);
|
||||
this._removeAllListeners();
|
||||
}
|
||||
|
||||
/* -------------
|
||||
-- PUBLIC API
|
||||
--------------*/
|
||||
|
||||
on(
|
||||
event: string,
|
||||
observer: () => PhoneAuthSnapshot,
|
||||
errorCb?: () => PhoneAuthError,
|
||||
successCb?: () => PhoneAuthSnapshot
|
||||
): this {
|
||||
if (!isString(event)) {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('event', 'string', 'on')
|
||||
);
|
||||
}
|
||||
|
||||
if (event !== 'state_changed') {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_ARG_INVALID_VALUE(
|
||||
'event',
|
||||
'state_changed',
|
||||
event
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFunction(observer)) {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('observer', 'function', 'on')
|
||||
);
|
||||
}
|
||||
|
||||
this._addUserObserver(observer);
|
||||
|
||||
if (isFunction(errorCb)) {
|
||||
SharedEventEmitter.once(this._publicEvents.error, errorCb);
|
||||
}
|
||||
|
||||
if (isFunction(successCb)) {
|
||||
SharedEventEmitter.once(this._publicEvents.success, successCb);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise .then proxy
|
||||
* @param fn
|
||||
*/
|
||||
then(fn: () => PhoneAuthSnapshot) {
|
||||
this._promiseDeferred();
|
||||
// $FlowFixMe: Unsure how to annotate `bind` here
|
||||
if (this._promise) return this._promise.then.bind(this._promise)(fn);
|
||||
return undefined; // will never get here - just to keep flow happy
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise .catch proxy
|
||||
* @param fn
|
||||
*/
|
||||
catch(fn: () => Error) {
|
||||
this._promiseDeferred();
|
||||
// $FlowFixMe: Unsure how to annotate `bind` here
|
||||
if (this._promise) return this._promise.catch.bind(this._promise)(fn);
|
||||
return undefined; // will never get here - just to keep flow happy
|
||||
}
|
||||
}
|
27
bridge/firebase/modules/auth/providers/EmailAuthProvider.js
Normal file
27
bridge/firebase/modules/auth/providers/EmailAuthProvider.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @flow
|
||||
* EmailAuthProvider representation wrapper
|
||||
*/
|
||||
import type { AuthCredential } from '../types';
|
||||
|
||||
const providerId = 'password';
|
||||
|
||||
export default class EmailAuthProvider {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'`new EmailAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
}
|
||||
|
||||
static get PROVIDER_ID(): string {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
static credential(email: string, password: string): AuthCredential {
|
||||
return {
|
||||
token: email,
|
||||
secret: password,
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @flow
|
||||
* FacebookAuthProvider representation wrapper
|
||||
*/
|
||||
import type { AuthCredential } from '../types';
|
||||
|
||||
const providerId = 'facebook.com';
|
||||
|
||||
export default class FacebookAuthProvider {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'`new FacebookAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
}
|
||||
|
||||
static get PROVIDER_ID(): string {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
static credential(token: string): AuthCredential {
|
||||
return {
|
||||
token,
|
||||
secret: '',
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
}
|
27
bridge/firebase/modules/auth/providers/GithubAuthProvider.js
Normal file
27
bridge/firebase/modules/auth/providers/GithubAuthProvider.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @flow
|
||||
* GithubAuthProvider representation wrapper
|
||||
*/
|
||||
import type { AuthCredential } from '../types';
|
||||
|
||||
const providerId = 'github.com';
|
||||
|
||||
export default class GithubAuthProvider {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'`new GithubAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
}
|
||||
|
||||
static get PROVIDER_ID(): string {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
static credential(token: string): AuthCredential {
|
||||
return {
|
||||
token,
|
||||
secret: '',
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
}
|
27
bridge/firebase/modules/auth/providers/GoogleAuthProvider.js
Normal file
27
bridge/firebase/modules/auth/providers/GoogleAuthProvider.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @flow
|
||||
* EmailAuthProvider representation wrapper
|
||||
*/
|
||||
import type { AuthCredential } from '../types';
|
||||
|
||||
const providerId = 'google.com';
|
||||
|
||||
export default class GoogleAuthProvider {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'`new GoogleAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
}
|
||||
|
||||
static get PROVIDER_ID(): string {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
static credential(token: string, secret: string): AuthCredential {
|
||||
return {
|
||||
token,
|
||||
secret,
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
}
|
27
bridge/firebase/modules/auth/providers/OAuthProvider.js
Normal file
27
bridge/firebase/modules/auth/providers/OAuthProvider.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @flow
|
||||
* OAuthProvider representation wrapper
|
||||
*/
|
||||
import type { AuthCredential } from '../types';
|
||||
|
||||
const providerId = 'oauth';
|
||||
|
||||
export default class OAuthProvider {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'`new OAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
}
|
||||
|
||||
static get PROVIDER_ID(): string {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
static credential(idToken: string, accessToken: string): AuthCredential {
|
||||
return {
|
||||
token: idToken,
|
||||
secret: accessToken,
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
}
|
27
bridge/firebase/modules/auth/providers/PhoneAuthProvider.js
Normal file
27
bridge/firebase/modules/auth/providers/PhoneAuthProvider.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @flow
|
||||
* PhoneAuthProvider representation wrapper
|
||||
*/
|
||||
import type { AuthCredential } from '../types';
|
||||
|
||||
const providerId = 'phone';
|
||||
|
||||
export default class PhoneAuthProvider {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'`new PhoneAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
}
|
||||
|
||||
static get PROVIDER_ID(): string {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
static credential(verificationId: string, code: string): AuthCredential {
|
||||
return {
|
||||
token: verificationId,
|
||||
secret: code,
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @flow
|
||||
* TwitterAuthProvider representation wrapper
|
||||
*/
|
||||
import type { AuthCredential } from '../types';
|
||||
|
||||
const providerId = 'twitter.com';
|
||||
|
||||
export default class TwitterAuthProvider {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'`new TwitterAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
}
|
||||
|
||||
static get PROVIDER_ID(): string {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
static credential(token: string, secret: string): AuthCredential {
|
||||
return {
|
||||
token,
|
||||
secret,
|
||||
providerId,
|
||||
};
|
||||
}
|
||||
}
|
75
bridge/firebase/modules/auth/types.js
Normal file
75
bridge/firebase/modules/auth/types.js
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
import type User from './User';
|
||||
|
||||
export type ActionCodeInfo = {
|
||||
data: {
|
||||
email?: string,
|
||||
fromEmail?: string,
|
||||
},
|
||||
operation: 'PASSWORD_RESET' | 'VERIFY_EMAIL' | 'RECOVER_EMAIL',
|
||||
};
|
||||
|
||||
export type ActionCodeSettings = {
|
||||
android: {
|
||||
installApp?: boolean,
|
||||
minimumVersion?: string,
|
||||
packageName: string,
|
||||
},
|
||||
handleCodeInApp?: boolean,
|
||||
iOS: {
|
||||
bundleId?: string,
|
||||
},
|
||||
url: string,
|
||||
};
|
||||
|
||||
export type AdditionalUserInfo = {
|
||||
isNewUser: boolean,
|
||||
profile?: Object,
|
||||
providerId: string,
|
||||
username?: string,
|
||||
};
|
||||
|
||||
export type AuthCredential = {
|
||||
providerId: string,
|
||||
token: string,
|
||||
secret: string,
|
||||
};
|
||||
|
||||
export type UserCredential = {|
|
||||
additionalUserInfo?: AdditionalUserInfo,
|
||||
user: User,
|
||||
|};
|
||||
|
||||
export type UserInfo = {
|
||||
displayName?: string,
|
||||
email?: string,
|
||||
phoneNumber?: string,
|
||||
photoURL?: string,
|
||||
providerId: string,
|
||||
uid: string,
|
||||
};
|
||||
|
||||
export type UserMetadata = {
|
||||
creationTime?: string,
|
||||
lastSignInTime?: string,
|
||||
};
|
||||
|
||||
export type NativeUser = {
|
||||
displayName?: string,
|
||||
email?: string,
|
||||
emailVerified?: boolean,
|
||||
isAnonymous?: boolean,
|
||||
metadata: UserMetadata,
|
||||
phoneNumber?: string,
|
||||
photoURL?: string,
|
||||
providerData: UserInfo[],
|
||||
providerId: string,
|
||||
uid: string,
|
||||
};
|
||||
|
||||
export type NativeUserCredential = {|
|
||||
additionalUserInfo?: AdditionalUserInfo,
|
||||
user: NativeUser,
|
||||
|};
|
21
bridge/firebase/modules/base.js
Normal file
21
bridge/firebase/modules/base.js
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// todo move out
|
||||
export class ReferenceBase extends Base {
|
||||
constructor(path: string) {
|
||||
super();
|
||||
this.path = path || '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* The last part of a Reference's path (after the last '/')
|
||||
* The key of a root Reference is null.
|
||||
* @type {String}
|
||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#key}
|
||||
*/
|
||||
get key(): string | null {
|
||||
return this.path === '/' ? null : this.path.substring(this.path.lastIndexOf('/') + 1);
|
||||
}
|
||||
}
|
185
bridge/firebase/modules/config/index.js
Normal file
185
bridge/firebase/modules/config/index.js
Normal file
@ -0,0 +1,185 @@
|
||||
/**
|
||||
* @flow
|
||||
* Remote Config representation wrapper
|
||||
*/
|
||||
import { getLogger } from '../../utils/log';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type App from '../core/app';
|
||||
|
||||
type NativeValue = {
|
||||
stringValue?: string,
|
||||
numberValue?: number,
|
||||
dataValue?: Object,
|
||||
boolValue?: boolean,
|
||||
source:
|
||||
| 'remoteConfigSourceRemote'
|
||||
| 'remoteConfigSourceDefault'
|
||||
| ' remoteConfigSourceStatic',
|
||||
};
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseRemoteConfig';
|
||||
export const NAMESPACE = 'config';
|
||||
|
||||
/**
|
||||
* @class Config
|
||||
*/
|
||||
export default class RemoteConfig extends ModuleBase {
|
||||
_developerModeEnabled: boolean;
|
||||
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
this._developerModeEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a native map to single JS value
|
||||
* @param nativeValue
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_nativeValueToJS(nativeValue: NativeValue) {
|
||||
return {
|
||||
source: nativeValue.source,
|
||||
val() {
|
||||
if (
|
||||
nativeValue.boolValue !== null &&
|
||||
(nativeValue.stringValue === 'true' ||
|
||||
nativeValue.stringValue === 'false' ||
|
||||
nativeValue.stringValue === null)
|
||||
)
|
||||
return nativeValue.boolValue;
|
||||
if (
|
||||
nativeValue.numberValue !== null &&
|
||||
nativeValue.numberValue !== undefined &&
|
||||
(nativeValue.stringValue == null ||
|
||||
nativeValue.stringValue === '' ||
|
||||
nativeValue.numberValue.toString() === nativeValue.stringValue)
|
||||
)
|
||||
return nativeValue.numberValue;
|
||||
if (
|
||||
nativeValue.dataValue !== nativeValue.stringValue &&
|
||||
(nativeValue.stringValue == null || nativeValue.stringValue === '')
|
||||
)
|
||||
return nativeValue.dataValue;
|
||||
return nativeValue.stringValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Remote Config developer mode to allow for frequent refreshes of the cache
|
||||
*/
|
||||
enableDeveloperMode() {
|
||||
if (!this._developerModeEnabled) {
|
||||
getLogger(this).debug('Enabled developer mode');
|
||||
getNativeModule(this).enableDeveloperMode();
|
||||
this._developerModeEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Remote Config data
|
||||
* Call activateFetched to make fetched data available in app
|
||||
* @returns {*|Promise.<String>}:
|
||||
*/
|
||||
fetch(expiration?: number) {
|
||||
if (expiration !== undefined) {
|
||||
getLogger(this).debug(
|
||||
`Fetching remote config data with expiration ${expiration.toString()}`
|
||||
);
|
||||
return getNativeModule(this).fetchWithExpirationDuration(expiration);
|
||||
}
|
||||
getLogger(this).debug('Fetching remote config data');
|
||||
return getNativeModule(this).fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Fetched Config data to the Active Config
|
||||
* @returns {*|Promise.<Bool>}
|
||||
* resolves if there was a Fetched Config, and it was activated,
|
||||
* rejects if no Fetched Config was found, or the Fetched Config was already activated.
|
||||
*/
|
||||
activateFetched() {
|
||||
getLogger(this).debug('Activating remote config');
|
||||
return getNativeModule(this).activateFetched();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config value of the default namespace.
|
||||
* @param key: Config key
|
||||
* @returns {*|Promise.<Object>}, will always resolve
|
||||
* Object looks like
|
||||
* {
|
||||
* "stringValue" : stringValue,
|
||||
* "numberValue" : numberValue,
|
||||
* "dataValue" : dataValue,
|
||||
* "boolValue" : boolValue,
|
||||
* "source" : OneOf<String>(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic)
|
||||
* }
|
||||
*/
|
||||
getValue(key: string) {
|
||||
return getNativeModule(this)
|
||||
.getValue(key || '')
|
||||
.then(this._nativeValueToJS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config value of the default namespace.
|
||||
* @param keys: Config key
|
||||
* @returns {*|Promise.<Object>}, will always resolve.
|
||||
* Result will be a dictionary of key and config objects
|
||||
* Object looks like
|
||||
* {
|
||||
* "stringValue" : stringValue,
|
||||
* "numberValue" : numberValue,
|
||||
* "dataValue" : dataValue,
|
||||
* "boolValue" : boolValue,
|
||||
* "source" : OneOf<String>(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic)
|
||||
* }
|
||||
*/
|
||||
getValues(keys: Array<string>) {
|
||||
return getNativeModule(this)
|
||||
.getValues(keys || [])
|
||||
.then(nativeValues => {
|
||||
const values: { [string]: Object } = {};
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
values[keys[i]] = this._nativeValueToJS(nativeValues[i]);
|
||||
}
|
||||
return values;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of parameter keys that start with the given prefix, from the default namespace
|
||||
* @param prefix: The key prefix to look for. If prefix is nil or empty, returns all the keys.
|
||||
* @returns {*|Promise.<Array<String>>}
|
||||
*/
|
||||
getKeysByPrefix(prefix?: string) {
|
||||
return getNativeModule(this).getKeysByPrefix(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets config defaults for parameter keys and values in the default namespace config.
|
||||
* @param defaults: A dictionary mapping a String key to a Object values.
|
||||
*/
|
||||
setDefaults(defaults: Object) {
|
||||
getNativeModule(this).setDefaults(defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default configs from plist for default namespace;
|
||||
* @param resource: The plist file name or resource ID
|
||||
*/
|
||||
setDefaultsFromResource(resource: string | number) {
|
||||
getNativeModule(this).setDefaultsFromResource(resource);
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {};
|
196
bridge/firebase/modules/core/app.js
Normal file
196
bridge/firebase/modules/core/app.js
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* @flow
|
||||
*/
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import APPS from '../../utils/apps';
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import INTERNALS from '../../utils/internals';
|
||||
import { isObject } from '../../utils';
|
||||
|
||||
import AdMob, { NAMESPACE as AdmobNamespace } from '../admob';
|
||||
import Auth, { NAMESPACE as AuthNamespace } from '../auth';
|
||||
import Analytics, { NAMESPACE as AnalyticsNamespace } from '../analytics';
|
||||
import Config, { NAMESPACE as ConfigNamespace } from '../config';
|
||||
import Crash, { NAMESPACE as CrashNamespace } from '../crash';
|
||||
import Crashlytics, {
|
||||
NAMESPACE as CrashlyticsNamespace,
|
||||
} from '../fabric/crashlytics';
|
||||
import Database, { NAMESPACE as DatabaseNamespace } from '../database';
|
||||
import Firestore, { NAMESPACE as FirestoreNamespace } from '../firestore';
|
||||
import InstanceId, { NAMESPACE as InstanceIdNamespace } from '../instanceid';
|
||||
import Invites, { NAMESPACE as InvitesNamespace } from '../invites';
|
||||
import Links, { NAMESPACE as LinksNamespace } from '../links';
|
||||
import Messaging, { NAMESPACE as MessagingNamespace } from '../messaging';
|
||||
import Notifications, {
|
||||
NAMESPACE as NotificationsNamespace,
|
||||
} from '../notifications';
|
||||
import Performance, { NAMESPACE as PerfNamespace } from '../perf';
|
||||
import Storage, { NAMESPACE as StorageNamespace } from '../storage';
|
||||
import Utils, { NAMESPACE as UtilsNamespace } from '../utils';
|
||||
|
||||
import type { FirebaseOptions } from '../../types';
|
||||
|
||||
const FirebaseCoreModule = NativeModules.RNFirebase;
|
||||
|
||||
export default class App {
|
||||
_extendedProps: { [string]: boolean };
|
||||
_initialized: boolean = false;
|
||||
_name: string;
|
||||
_nativeInitialized: boolean = false;
|
||||
_options: FirebaseOptions;
|
||||
admob: () => AdMob;
|
||||
analytics: () => Analytics;
|
||||
auth: () => Auth;
|
||||
config: () => Config;
|
||||
crash: () => Crash;
|
||||
database: () => Database;
|
||||
fabric: {
|
||||
crashlytics: () => Crashlytics,
|
||||
};
|
||||
firestore: () => Firestore;
|
||||
instanceid: () => InstanceId;
|
||||
invites: () => Invites;
|
||||
links: () => Links;
|
||||
messaging: () => Messaging;
|
||||
notifications: () => Notifications;
|
||||
perf: () => Performance;
|
||||
storage: () => Storage;
|
||||
utils: () => Utils;
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
options: FirebaseOptions,
|
||||
fromNative: boolean = false
|
||||
) {
|
||||
this._name = name;
|
||||
this._options = Object.assign({}, options);
|
||||
|
||||
if (fromNative) {
|
||||
this._initialized = true;
|
||||
this._nativeInitialized = true;
|
||||
} else if (options.databaseURL && options.apiKey) {
|
||||
FirebaseCoreModule.initializeApp(
|
||||
this._name,
|
||||
this._options,
|
||||
(error, result) => {
|
||||
this._initialized = true;
|
||||
SharedEventEmitter.emit(`AppReady:${this._name}`, { error, result });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// modules
|
||||
this.admob = APPS.appModule(this, AdmobNamespace, AdMob);
|
||||
this.analytics = APPS.appModule(this, AnalyticsNamespace, Analytics);
|
||||
this.auth = APPS.appModule(this, AuthNamespace, Auth);
|
||||
this.config = APPS.appModule(this, ConfigNamespace, Config);
|
||||
this.crash = APPS.appModule(this, CrashNamespace, Crash);
|
||||
this.database = APPS.appModule(this, DatabaseNamespace, Database);
|
||||
this.fabric = {
|
||||
crashlytics: APPS.appModule(this, CrashlyticsNamespace, Crashlytics),
|
||||
};
|
||||
this.firestore = APPS.appModule(this, FirestoreNamespace, Firestore);
|
||||
this.instanceid = APPS.appModule(this, InstanceIdNamespace, InstanceId);
|
||||
this.invites = APPS.appModule(this, InvitesNamespace, Invites);
|
||||
this.links = APPS.appModule(this, LinksNamespace, Links);
|
||||
this.messaging = APPS.appModule(this, MessagingNamespace, Messaging);
|
||||
this.notifications = APPS.appModule(
|
||||
this,
|
||||
NotificationsNamespace,
|
||||
Notifications
|
||||
);
|
||||
this.perf = APPS.appModule(this, PerfNamespace, Performance);
|
||||
this.storage = APPS.appModule(this, StorageNamespace, Storage);
|
||||
this.utils = APPS.appModule(this, UtilsNamespace, Utils);
|
||||
this._extendedProps = {};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
get options(): FirebaseOptions {
|
||||
return Object.assign({}, this._options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented firebase web sdk method that allows adding additional properties onto
|
||||
* a firebase app instance.
|
||||
*
|
||||
* See: https://github.com/firebase/firebase-js-sdk/blob/master/tests/app/firebase_app.test.ts#L328
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
extendApp(props: Object) {
|
||||
if (!isObject(props)) {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_MISSING_ARG('Object', 'extendApp')
|
||||
);
|
||||
}
|
||||
|
||||
const keys = Object.keys(props);
|
||||
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
const key = keys[i];
|
||||
|
||||
if (!this._extendedProps[key] && Object.hasOwnProperty.call(this, key)) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_PROTECTED_PROP(key));
|
||||
}
|
||||
|
||||
// $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
this[key] = props[key];
|
||||
this._extendedProps[key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
delete() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('app', 'delete')
|
||||
);
|
||||
// TODO only the ios sdk currently supports delete, add back in when android also supports it
|
||||
// if (this._name === APPS.DEFAULT_APP_NAME && this._nativeInitialized) {
|
||||
// return Promise.reject(
|
||||
// new Error('Unable to delete the default native firebase app instance.'),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// return FirebaseCoreModule.deleteApp(this._name);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
onReady(): Promise<App> {
|
||||
if (this._initialized) return Promise.resolve(this);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
SharedEventEmitter.once(`AppReady:${this._name}`, ({ error }) => {
|
||||
if (error) return reject(new Error(error)); // error is a string as it's from native
|
||||
return resolve(this); // return app
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* toString returns the name of the app.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
toString() {
|
||||
return this._name;
|
||||
}
|
||||
}
|
226
bridge/firebase/modules/core/firebase.js
Normal file
226
bridge/firebase/modules/core/firebase.js
Normal file
@ -0,0 +1,226 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import APPS from '../../utils/apps';
|
||||
import INTERNALS from '../../utils/internals';
|
||||
import App from './app';
|
||||
import VERSION from '../../version';
|
||||
|
||||
// module imports
|
||||
import {
|
||||
statics as AdMobStatics,
|
||||
MODULE_NAME as AdmobModuleName,
|
||||
} from '../admob';
|
||||
import { statics as AuthStatics, MODULE_NAME as AuthModuleName } from '../auth';
|
||||
import {
|
||||
statics as AnalyticsStatics,
|
||||
MODULE_NAME as AnalyticsModuleName,
|
||||
} from '../analytics';
|
||||
import {
|
||||
statics as ConfigStatics,
|
||||
MODULE_NAME as ConfigModuleName,
|
||||
} from '../config';
|
||||
import {
|
||||
statics as CrashStatics,
|
||||
MODULE_NAME as CrashModuleName,
|
||||
} from '../crash';
|
||||
import {
|
||||
statics as CrashlyticsStatics,
|
||||
MODULE_NAME as CrashlyticsModuleName,
|
||||
} from '../fabric/crashlytics';
|
||||
import {
|
||||
statics as DatabaseStatics,
|
||||
MODULE_NAME as DatabaseModuleName,
|
||||
} from '../database';
|
||||
import {
|
||||
statics as FirestoreStatics,
|
||||
MODULE_NAME as FirestoreModuleName,
|
||||
} from '../firestore';
|
||||
import {
|
||||
statics as InstanceIdStatics,
|
||||
MODULE_NAME as InstanceIdModuleName,
|
||||
} from '../instanceid';
|
||||
import {
|
||||
statics as InvitesStatics,
|
||||
MODULE_NAME as InvitesModuleName,
|
||||
} from '../invites';
|
||||
import {
|
||||
statics as LinksStatics,
|
||||
MODULE_NAME as LinksModuleName,
|
||||
} from '../links';
|
||||
import {
|
||||
statics as MessagingStatics,
|
||||
MODULE_NAME as MessagingModuleName,
|
||||
} from '../messaging';
|
||||
import {
|
||||
statics as NotificationsStatics,
|
||||
MODULE_NAME as NotificationsModuleName,
|
||||
} from '../notifications';
|
||||
import {
|
||||
statics as PerformanceStatics,
|
||||
MODULE_NAME as PerfModuleName,
|
||||
} from '../perf';
|
||||
import {
|
||||
statics as StorageStatics,
|
||||
MODULE_NAME as StorageModuleName,
|
||||
} from '../storage';
|
||||
import {
|
||||
statics as UtilsStatics,
|
||||
MODULE_NAME as UtilsModuleName,
|
||||
} from '../utils';
|
||||
|
||||
import type {
|
||||
AdMobModule,
|
||||
AnalyticsModule,
|
||||
AuthModule,
|
||||
ConfigModule,
|
||||
CrashModule,
|
||||
DatabaseModule,
|
||||
FabricModule,
|
||||
FirebaseOptions,
|
||||
FirestoreModule,
|
||||
InstanceIdModule,
|
||||
InvitesModule,
|
||||
LinksModule,
|
||||
MessagingModule,
|
||||
NotificationsModule,
|
||||
PerformanceModule,
|
||||
StorageModule,
|
||||
UtilsModule,
|
||||
} from '../../types';
|
||||
|
||||
const FirebaseCoreModule = NativeModules.RNFirebase;
|
||||
|
||||
class Firebase {
|
||||
admob: AdMobModule;
|
||||
analytics: AnalyticsModule;
|
||||
auth: AuthModule;
|
||||
config: ConfigModule;
|
||||
crash: CrashModule;
|
||||
database: DatabaseModule;
|
||||
fabric: FabricModule;
|
||||
firestore: FirestoreModule;
|
||||
instanceid: InstanceIdModule;
|
||||
invites: InvitesModule;
|
||||
links: LinksModule;
|
||||
messaging: MessagingModule;
|
||||
notifications: NotificationsModule;
|
||||
perf: PerformanceModule;
|
||||
storage: StorageModule;
|
||||
utils: UtilsModule;
|
||||
|
||||
constructor() {
|
||||
if (!FirebaseCoreModule) {
|
||||
throw new Error(INTERNALS.STRINGS.ERROR_MISSING_CORE);
|
||||
}
|
||||
APPS.initializeNativeApps();
|
||||
|
||||
// modules
|
||||
this.admob = APPS.moduleAndStatics('admob', AdMobStatics, AdmobModuleName);
|
||||
this.analytics = APPS.moduleAndStatics(
|
||||
'analytics',
|
||||
AnalyticsStatics,
|
||||
AnalyticsModuleName
|
||||
);
|
||||
this.auth = APPS.moduleAndStatics('auth', AuthStatics, AuthModuleName);
|
||||
this.config = APPS.moduleAndStatics(
|
||||
'config',
|
||||
ConfigStatics,
|
||||
ConfigModuleName
|
||||
);
|
||||
this.crash = APPS.moduleAndStatics('crash', CrashStatics, CrashModuleName);
|
||||
this.database = APPS.moduleAndStatics(
|
||||
'database',
|
||||
DatabaseStatics,
|
||||
DatabaseModuleName
|
||||
);
|
||||
this.fabric = {
|
||||
crashlytics: APPS.moduleAndStatics(
|
||||
'crashlytics',
|
||||
CrashlyticsStatics,
|
||||
CrashlyticsModuleName
|
||||
),
|
||||
};
|
||||
this.firestore = APPS.moduleAndStatics(
|
||||
'firestore',
|
||||
FirestoreStatics,
|
||||
FirestoreModuleName
|
||||
);
|
||||
this.instanceid = APPS.moduleAndStatics(
|
||||
'instanceid',
|
||||
InstanceIdStatics,
|
||||
InstanceIdModuleName
|
||||
);
|
||||
this.invites = APPS.moduleAndStatics(
|
||||
'invites',
|
||||
InvitesStatics,
|
||||
InvitesModuleName
|
||||
);
|
||||
this.links = APPS.moduleAndStatics('links', LinksStatics, LinksModuleName);
|
||||
this.messaging = APPS.moduleAndStatics(
|
||||
'messaging',
|
||||
MessagingStatics,
|
||||
MessagingModuleName
|
||||
);
|
||||
this.notifications = APPS.moduleAndStatics(
|
||||
'notifications',
|
||||
NotificationsStatics,
|
||||
NotificationsModuleName
|
||||
);
|
||||
this.perf = APPS.moduleAndStatics(
|
||||
'perf',
|
||||
PerformanceStatics,
|
||||
PerfModuleName
|
||||
);
|
||||
this.storage = APPS.moduleAndStatics(
|
||||
'storage',
|
||||
StorageStatics,
|
||||
StorageModuleName
|
||||
);
|
||||
this.utils = APPS.moduleAndStatics('utils', UtilsStatics, UtilsModuleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Web SDK initializeApp
|
||||
*
|
||||
* @param options
|
||||
* @param name
|
||||
* @return {*}
|
||||
*/
|
||||
initializeApp(options: FirebaseOptions, name: string): App {
|
||||
return APPS.initializeApp(options, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Firebase app instance.
|
||||
*
|
||||
* When called with no arguments, the default app is returned.
|
||||
* When an app name is provided, the app corresponding to that name is returned.
|
||||
*
|
||||
* @param name
|
||||
* @return {*}
|
||||
*/
|
||||
app(name?: string): App {
|
||||
return APPS.app(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* A (read-only) array of all initialized apps.
|
||||
* @return {Array}
|
||||
*/
|
||||
get apps(): Array<App> {
|
||||
return APPS.apps();
|
||||
}
|
||||
|
||||
/**
|
||||
* The current SDK version.
|
||||
* @return {string}
|
||||
*/
|
||||
get SDK_VERSION(): string {
|
||||
return VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Firebase();
|
87
bridge/firebase/modules/crash/index.js
Normal file
87
bridge/firebase/modules/crash/index.js
Normal file
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @flow
|
||||
* Crash Reporting representation wrapper
|
||||
*/
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type App from '../core/app';
|
||||
import type { FirebaseError } from '../../types';
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseCrash';
|
||||
export const NAMESPACE = 'crash';
|
||||
|
||||
export default class Crash extends ModuleBase {
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/Disables crash reporting
|
||||
* @param enabled
|
||||
*/
|
||||
setCrashCollectionEnabled(enabled: boolean): void {
|
||||
getNativeModule(this).setCrashCollectionEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not crash reporting is currently enabled
|
||||
* @returns {Promise.<boolean>}
|
||||
*/
|
||||
isCrashCollectionEnabled(): Promise<boolean> {
|
||||
return getNativeModule(this).isCrashCollectionEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message that will appear in a subsequent crash report.
|
||||
* @param {string} message
|
||||
*/
|
||||
log(message: string): void {
|
||||
getNativeModule(this).log(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message that will appear in a subsequent crash report as well as in logcat.
|
||||
* NOTE: Android only functionality. iOS will just log the message.
|
||||
* @param {string} message
|
||||
* @param {number} level
|
||||
* @param {string} tag
|
||||
*/
|
||||
logcat(level: number, tag: string, message: string): void {
|
||||
getNativeModule(this).logcat(level, tag, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a crash report for the given message. This method should be used for unexpected
|
||||
* exceptions where recovery is not possible.
|
||||
* NOTE: on iOS, this will cause the app to crash as it's the only way to ensure the exception
|
||||
* gets sent to Firebase. Otherwise it just gets lost as a log message.
|
||||
* @param {Error} error
|
||||
* @param maxStackSize
|
||||
*/
|
||||
report(error: FirebaseError, maxStackSize: number = 10): void {
|
||||
if (!error || !error.message) return;
|
||||
|
||||
let errorMessage = `Message: ${error.message}\r\n`;
|
||||
|
||||
if (error.code) {
|
||||
errorMessage = `${errorMessage}Code: ${error.code}\r\n`;
|
||||
}
|
||||
|
||||
const stackRows = error.stack.split('\n');
|
||||
errorMessage = `${errorMessage}\r\nStack: \r\n`;
|
||||
for (let i = 0, len = stackRows.length; i < len; i++) {
|
||||
if (i === maxStackSize) break;
|
||||
errorMessage = `${errorMessage} - ${stackRows[i]}\r\n`;
|
||||
}
|
||||
|
||||
getNativeModule(this).report(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {};
|
146
bridge/firebase/modules/database/DataSnapshot.js
Normal file
146
bridge/firebase/modules/database/DataSnapshot.js
Normal file
@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @flow
|
||||
* DataSnapshot representation wrapper
|
||||
*/
|
||||
import { isObject, deepGet, deepExists } from './../../utils';
|
||||
import type Reference from './Reference';
|
||||
|
||||
/**
|
||||
* @class DataSnapshot
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot
|
||||
*/
|
||||
export default class DataSnapshot {
|
||||
ref: Reference;
|
||||
key: string;
|
||||
|
||||
_value: any;
|
||||
_priority: any;
|
||||
_childKeys: Array<string>;
|
||||
|
||||
constructor(ref: Reference, snapshot: Object) {
|
||||
this.key = snapshot.key;
|
||||
|
||||
if (ref.key !== snapshot.key) {
|
||||
this.ref = ref.child(snapshot.key);
|
||||
} else {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
// internal use only
|
||||
this._value = snapshot.value;
|
||||
this._priority = snapshot.priority === undefined ? null : snapshot.priority;
|
||||
this._childKeys = snapshot.childKeys || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a JavaScript value from a DataSnapshot.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#val
|
||||
* @returns {any}
|
||||
*/
|
||||
val(): any {
|
||||
// clone via JSON stringify/parse - prevent modification of this._value
|
||||
if (isObject(this._value) || Array.isArray(this._value))
|
||||
return JSON.parse(JSON.stringify(this._value));
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets another DataSnapshot for the location at the specified relative path.
|
||||
* @param path
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach
|
||||
* @returns {Snapshot}
|
||||
*/
|
||||
child(path: string): DataSnapshot {
|
||||
const value = deepGet(this._value, path);
|
||||
const childRef = this.ref.child(path);
|
||||
return new DataSnapshot(childRef, {
|
||||
value,
|
||||
key: childRef.key,
|
||||
exists: value !== null,
|
||||
|
||||
// todo this is wrong - child keys needs to be the ordered keys, from FB
|
||||
// todo potential solution is build up a tree/map of a snapshot and its children
|
||||
// todo natively and send that back to JS to be use in this class.
|
||||
childKeys: isObject(value) ? Object.keys(value) : [],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this DataSnapshot contains any data.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#exists
|
||||
* @returns {boolean}
|
||||
*/
|
||||
exists(): boolean {
|
||||
return this._value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates the top-level children in the DataSnapshot.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach
|
||||
* @param action
|
||||
*/
|
||||
forEach(action: (key: any) => any): boolean {
|
||||
if (!this._childKeys.length) return false;
|
||||
let cancelled = false;
|
||||
|
||||
for (let i = 0, len = this._childKeys.length; i < len; i++) {
|
||||
const key = this._childKeys[i];
|
||||
const childSnapshot = this.child(key);
|
||||
const returnValue = action(childSnapshot);
|
||||
|
||||
if (returnValue === true) {
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the priority value of the data in this DataSnapshot.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#getPriority
|
||||
* @returns {String|Number|null}
|
||||
*/
|
||||
getPriority(): string | number | null {
|
||||
return this._priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified child path has (non-null) data.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#hasChild
|
||||
* @param path
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasChild(path: string): boolean {
|
||||
return deepExists(this._value, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the DataSnapshot has any non-null child properties.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#hasChildren
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasChildren(): boolean {
|
||||
return this.numChildren() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of child properties of this DataSnapshot.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#numChildren
|
||||
* @returns {Number}
|
||||
*/
|
||||
numChildren(): number {
|
||||
if (!isObject(this._value)) return 0;
|
||||
return Object.keys(this._value).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable representation of this object.
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#toJSON
|
||||
* @returns {any}
|
||||
*/
|
||||
toJSON(): Object {
|
||||
return this.val();
|
||||
}
|
||||
}
|
68
bridge/firebase/modules/database/OnDisconnect.js
Normal file
68
bridge/firebase/modules/database/OnDisconnect.js
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @flow
|
||||
* OnDisconnect representation wrapper
|
||||
*/
|
||||
import { typeOf } from '../../utils';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import type Database from './';
|
||||
import type Reference from './Reference';
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect
|
||||
* @class OmDisconnect
|
||||
*/
|
||||
export default class OnDisconnect {
|
||||
_database: Database;
|
||||
ref: Reference;
|
||||
path: string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ref
|
||||
*/
|
||||
constructor(ref: Reference) {
|
||||
this.ref = ref;
|
||||
this.path = ref.path;
|
||||
this._database = ref._database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#set
|
||||
* @param value
|
||||
* @returns {*}
|
||||
*/
|
||||
set(value: string | Object): Promise<void> {
|
||||
return getNativeModule(this._database).onDisconnectSet(this.path, {
|
||||
type: typeOf(value),
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#update
|
||||
* @param values
|
||||
* @returns {*}
|
||||
*/
|
||||
update(values: Object): Promise<void> {
|
||||
return getNativeModule(this._database).onDisconnectUpdate(
|
||||
this.path,
|
||||
values
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#remove
|
||||
* @returns {*}
|
||||
*/
|
||||
remove(): Promise<void> {
|
||||
return getNativeModule(this._database).onDisconnectRemove(this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect#cancel
|
||||
* @returns {*}
|
||||
*/
|
||||
cancel(): Promise<void> {
|
||||
return getNativeModule(this._database).onDisconnectCancel(this.path);
|
||||
}
|
||||
}
|
108
bridge/firebase/modules/database/Query.js
Normal file
108
bridge/firebase/modules/database/Query.js
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @flow
|
||||
* Query representation wrapper
|
||||
*/
|
||||
import { objectToUniqueId } from '../../utils';
|
||||
|
||||
import type { DatabaseModifier } from '../../types';
|
||||
import type Reference from './Reference';
|
||||
|
||||
// todo doc methods
|
||||
|
||||
/**
|
||||
* @class Query
|
||||
*/
|
||||
export default class Query {
|
||||
_reference: Reference;
|
||||
modifiers: Array<DatabaseModifier>;
|
||||
|
||||
constructor(ref: Reference, existingModifiers?: Array<DatabaseModifier>) {
|
||||
this.modifiers = existingModifiers ? [...existingModifiers] : [];
|
||||
this._reference = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param key
|
||||
* @return {Reference|*}
|
||||
*/
|
||||
orderBy(name: string, key?: string) {
|
||||
this.modifiers.push({
|
||||
id: `orderBy-${name}:${key || ''}`,
|
||||
type: 'orderBy',
|
||||
name,
|
||||
key,
|
||||
});
|
||||
|
||||
return this._reference;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param limit
|
||||
* @return {Reference|*}
|
||||
*/
|
||||
limit(name: string, limit: number) {
|
||||
this.modifiers.push({
|
||||
id: `limit-${name}:${limit}`,
|
||||
type: 'limit',
|
||||
name,
|
||||
limit,
|
||||
});
|
||||
|
||||
return this._reference;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param key
|
||||
* @return {Reference|*}
|
||||
*/
|
||||
filter(name: string, value: any, key?: string) {
|
||||
this.modifiers.push({
|
||||
id: `filter-${name}:${objectToUniqueId(value)}:${key || ''}`,
|
||||
type: 'filter',
|
||||
name,
|
||||
value,
|
||||
valueType: typeof value,
|
||||
key,
|
||||
});
|
||||
|
||||
return this._reference;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {[*]}
|
||||
*/
|
||||
getModifiers(): Array<DatabaseModifier> {
|
||||
return [...this.modifiers];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
queryIdentifier() {
|
||||
// sort modifiers to enforce ordering
|
||||
const sortedModifiers = this.getModifiers().sort((a, b) => {
|
||||
if (a.id < b.id) return -1;
|
||||
if (a.id > b.id) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Convert modifiers to unique key
|
||||
let key = '{';
|
||||
for (let i = 0; i < sortedModifiers.length; i++) {
|
||||
if (i !== 0) key += ',';
|
||||
key += sortedModifiers[i].id;
|
||||
}
|
||||
key += '}';
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
894
bridge/firebase/modules/database/Reference.js
Normal file
894
bridge/firebase/modules/database/Reference.js
Normal file
@ -0,0 +1,894 @@
|
||||
/**
|
||||
* @flow
|
||||
* Database Reference representation wrapper
|
||||
*/
|
||||
import Query from './Query';
|
||||
import DataSnapshot from './DataSnapshot';
|
||||
import OnDisconnect from './OnDisconnect';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import ReferenceBase from '../../utils/ReferenceBase';
|
||||
|
||||
import {
|
||||
promiseOrCallback,
|
||||
isFunction,
|
||||
isObject,
|
||||
isString,
|
||||
tryJSONParse,
|
||||
tryJSONStringify,
|
||||
generatePushID,
|
||||
} from '../../utils';
|
||||
|
||||
import SyncTree from '../../utils/SyncTree';
|
||||
|
||||
import type Database from './';
|
||||
import type { DatabaseModifier, FirebaseError } from '../../types';
|
||||
|
||||
// track all event registrations by path
|
||||
let listeners = 0;
|
||||
|
||||
/**
|
||||
* Enum for event types
|
||||
* @readonly
|
||||
* @enum {String}
|
||||
*/
|
||||
const ReferenceEventTypes = {
|
||||
value: 'value',
|
||||
child_added: 'child_added',
|
||||
child_removed: 'child_removed',
|
||||
child_changed: 'child_changed',
|
||||
child_moved: 'child_moved',
|
||||
};
|
||||
|
||||
type DatabaseListener = {
|
||||
listenerId: number,
|
||||
eventName: string,
|
||||
successCallback: Function,
|
||||
failureCallback?: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {String} ReferenceLocation - Path to location in the database, relative
|
||||
* to the root reference. Consists of a path where segments are separated by a
|
||||
* forward slash (/) and ends in a ReferenceKey - except the root location, which
|
||||
* has no ReferenceKey.
|
||||
*
|
||||
* @example
|
||||
* // root reference location: '/'
|
||||
* // non-root reference: '/path/to/referenceKey'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {String} ReferenceKey - Identifier for each location that is unique to that
|
||||
* location, within the scope of its parent. The last part of a ReferenceLocation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a specific location in your Database that can be used for
|
||||
* reading or writing data.
|
||||
*
|
||||
* You can reference the root using firebase.database().ref() or a child location
|
||||
* by calling firebase.database().ref("child/path").
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference
|
||||
* @class Reference
|
||||
* @extends ReferenceBase
|
||||
*/
|
||||
export default class Reference extends ReferenceBase {
|
||||
_database: Database;
|
||||
_promise: ?Promise<*>;
|
||||
_query: Query;
|
||||
_refListeners: { [listenerId: number]: DatabaseListener };
|
||||
|
||||
constructor(
|
||||
database: Database,
|
||||
path: string,
|
||||
existingModifiers?: Array<DatabaseModifier>
|
||||
) {
|
||||
super(path);
|
||||
this._promise = null;
|
||||
this._refListeners = {};
|
||||
this._database = database;
|
||||
this._query = new Query(this, existingModifiers);
|
||||
getLogger(database).debug('Created new Reference', this._getRefKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* By calling `keepSynced(true)` on a location, the data for that location will
|
||||
* automatically be downloaded and kept in sync, even when no listeners are
|
||||
* attached for that location. Additionally, while a location is kept synced,
|
||||
* it will not be evicted from the persistent disk cache.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/android/com/google/firebase/database/Query.html#keepSynced(boolean)
|
||||
* @param bool
|
||||
* @returns {*}
|
||||
*/
|
||||
keepSynced(bool: boolean): Promise<void> {
|
||||
return getNativeModule(this._database).keepSynced(
|
||||
this._getRefKey(),
|
||||
this.path,
|
||||
this._query.getModifiers(),
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data to this Database location.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#set
|
||||
* @param value
|
||||
* @param onComplete
|
||||
* @returns {Promise}
|
||||
*/
|
||||
set(value: any, onComplete?: Function): Promise<void> {
|
||||
return promiseOrCallback(
|
||||
getNativeModule(this._database).set(
|
||||
this.path,
|
||||
this._serializeAnyType(value)
|
||||
),
|
||||
onComplete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a priority for the data at this Database location.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#setPriority
|
||||
* @param priority
|
||||
* @param onComplete
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setPriority(
|
||||
priority: string | number | null,
|
||||
onComplete?: Function
|
||||
): Promise<void> {
|
||||
const _priority = this._serializeAnyType(priority);
|
||||
|
||||
return promiseOrCallback(
|
||||
getNativeModule(this._database).setPriority(this.path, _priority),
|
||||
onComplete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data the Database location. Like set() but also specifies the priority for that data.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#setWithPriority
|
||||
* @param value
|
||||
* @param priority
|
||||
* @param onComplete
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setWithPriority(
|
||||
value: any,
|
||||
priority: string | number | null,
|
||||
onComplete?: Function
|
||||
): Promise<void> {
|
||||
const _value = this._serializeAnyType(value);
|
||||
const _priority = this._serializeAnyType(priority);
|
||||
|
||||
return promiseOrCallback(
|
||||
getNativeModule(this._database).setWithPriority(
|
||||
this.path,
|
||||
_value,
|
||||
_priority
|
||||
),
|
||||
onComplete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes multiple values to the Database at once.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#update
|
||||
* @param val
|
||||
* @param onComplete
|
||||
* @returns {Promise}
|
||||
*/
|
||||
update(val: Object, onComplete?: Function): Promise<void> {
|
||||
const value = this._serializeObject(val);
|
||||
|
||||
return promiseOrCallback(
|
||||
getNativeModule(this._database).update(this.path, value),
|
||||
onComplete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the data at this Database location.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#remove
|
||||
* @param onComplete
|
||||
* @return {Promise}
|
||||
*/
|
||||
remove(onComplete?: Function): Promise<void> {
|
||||
return promiseOrCallback(
|
||||
getNativeModule(this._database).remove(this.path),
|
||||
onComplete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically modifies the data at this location.
|
||||
*
|
||||
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
|
||||
* @param transactionUpdate
|
||||
* @param onComplete
|
||||
* @param applyLocally
|
||||
*/
|
||||
transaction(
|
||||
transactionUpdate: Function,
|
||||
onComplete: (
|
||||
error: ?Error,
|
||||
committed: boolean,
|
||||
snapshot: ?DataSnapshot
|
||||
) => *,
|
||||
applyLocally: boolean = false
|
||||
) {
|
||||
if (!isFunction(transactionUpdate)) {
|
||||
return Promise.reject(
|
||||
new Error('Missing transactionUpdate function argument.')
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const onCompleteWrapper = (error, committed, snapshotData) => {
|
||||
if (isFunction(onComplete)) {
|
||||
if (error) {
|
||||
onComplete(error, committed, null);
|
||||
} else {
|
||||
onComplete(null, committed, new DataSnapshot(this, snapshotData));
|
||||
}
|
||||
}
|
||||
|
||||
if (error) return reject(error);
|
||||
return resolve({
|
||||
committed,
|
||||
snapshot: new DataSnapshot(this, snapshotData),
|
||||
});
|
||||
};
|
||||
|
||||
// start the transaction natively
|
||||
this._database._transactionHandler.add(
|
||||
this,
|
||||
transactionUpdate,
|
||||
onCompleteWrapper,
|
||||
applyLocally
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param eventName
|
||||
* @param successCallback
|
||||
* @param cancelOrContext
|
||||
* @param context
|
||||
* @returns {Promise.<any>}
|
||||
*/
|
||||
once(
|
||||
eventName: string = 'value',
|
||||
successCallback: (snapshot: DataSnapshot) => void,
|
||||
cancelOrContext: (error: FirebaseError) => void,
|
||||
context?: Object
|
||||
) {
|
||||
return getNativeModule(this._database)
|
||||
.once(this._getRefKey(), this.path, this._query.getModifiers(), eventName)
|
||||
.then(({ snapshot }) => {
|
||||
const _snapshot = new DataSnapshot(this, snapshot);
|
||||
|
||||
if (isFunction(successCallback)) {
|
||||
if (isObject(cancelOrContext))
|
||||
successCallback.bind(cancelOrContext)(_snapshot);
|
||||
if (context && isObject(context))
|
||||
successCallback.bind(context)(_snapshot);
|
||||
successCallback(_snapshot);
|
||||
}
|
||||
|
||||
return _snapshot;
|
||||
})
|
||||
.catch(error => {
|
||||
if (isFunction(cancelOrContext)) return cancelOrContext(error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param onComplete
|
||||
* @returns {*}
|
||||
*/
|
||||
push(value: any, onComplete?: Function): Reference | Promise<void> {
|
||||
if (value === null || value === undefined) {
|
||||
return new Reference(
|
||||
this._database,
|
||||
`${this.path}/${generatePushID(this._database._serverTimeOffset)}`
|
||||
);
|
||||
}
|
||||
|
||||
const newRef = new Reference(
|
||||
this._database,
|
||||
`${this.path}/${generatePushID(this._database._serverTimeOffset)}`
|
||||
);
|
||||
const promise = newRef.set(value);
|
||||
|
||||
// if callback provided then internally call the set promise with value
|
||||
if (isFunction(onComplete)) {
|
||||
return (
|
||||
promise
|
||||
// $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
.then(() => onComplete(null, newRef))
|
||||
// $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
.catch(error => onComplete(error, null))
|
||||
);
|
||||
}
|
||||
|
||||
// otherwise attach promise to 'thenable' reference and return the
|
||||
// new reference
|
||||
newRef._setThenable(promise);
|
||||
return newRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* MODIFIERS
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Reference}
|
||||
*/
|
||||
orderByKey(): Reference {
|
||||
return this.orderBy('orderByKey');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Reference}
|
||||
*/
|
||||
orderByPriority(): Reference {
|
||||
return this.orderBy('orderByPriority');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Reference}
|
||||
*/
|
||||
orderByValue(): Reference {
|
||||
return this.orderBy('orderByValue');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key
|
||||
* @returns {Reference}
|
||||
*/
|
||||
orderByChild(key: string): Reference {
|
||||
return this.orderBy('orderByChild', key);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param key
|
||||
* @returns {Reference}
|
||||
*/
|
||||
orderBy(name: string, key?: string): Reference {
|
||||
const newRef = new Reference(
|
||||
this._database,
|
||||
this.path,
|
||||
this._query.getModifiers()
|
||||
);
|
||||
newRef._query.orderBy(name, key);
|
||||
return newRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* LIMITS
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param limit
|
||||
* @returns {Reference}
|
||||
*/
|
||||
limitToLast(limit: number): Reference {
|
||||
return this.limit('limitToLast', limit);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param limit
|
||||
* @returns {Reference}
|
||||
*/
|
||||
limitToFirst(limit: number): Reference {
|
||||
return this.limit('limitToFirst', limit);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param limit
|
||||
* @returns {Reference}
|
||||
*/
|
||||
limit(name: string, limit: number): Reference {
|
||||
const newRef = new Reference(
|
||||
this._database,
|
||||
this.path,
|
||||
this._query.getModifiers()
|
||||
);
|
||||
newRef._query.limit(name, limit);
|
||||
return newRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* FILTERS
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param key
|
||||
* @returns {Reference}
|
||||
*/
|
||||
equalTo(value: any, key?: string): Reference {
|
||||
return this.filter('equalTo', value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param key
|
||||
* @returns {Reference}
|
||||
*/
|
||||
endAt(value: any, key?: string): Reference {
|
||||
return this.filter('endAt', value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param key
|
||||
* @returns {Reference}
|
||||
*/
|
||||
startAt(value: any, key?: string): Reference {
|
||||
return this.filter('startAt', value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param key
|
||||
* @returns {Reference}
|
||||
*/
|
||||
filter(name: string, value: any, key?: string): Reference {
|
||||
const newRef = new Reference(
|
||||
this._database,
|
||||
this.path,
|
||||
this._query.getModifiers()
|
||||
);
|
||||
newRef._query.filter(name, value, key);
|
||||
return newRef;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {OnDisconnect}
|
||||
*/
|
||||
onDisconnect(): OnDisconnect {
|
||||
return new OnDisconnect(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Reference to a child of the current Reference, using a relative path.
|
||||
* No validation is performed on the path to ensure it has a valid format.
|
||||
* @param {String} path relative to current ref's location
|
||||
* @returns {!Reference} A new Reference to the path provided, relative to the current
|
||||
* Reference
|
||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#child}
|
||||
*/
|
||||
child(path: string): Reference {
|
||||
return new Reference(this._database, `${this.path}/${path}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ref as a path string
|
||||
* @returns {string}
|
||||
*/
|
||||
toString(): string {
|
||||
return `${this._database.databaseUrl}/${this.path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether another Reference represent the same location and are from the
|
||||
* same instance of firebase.app.App - multiple firebase apps not currently supported.
|
||||
* @param {Reference} otherRef - Other reference to compare to this one
|
||||
* @return {Boolean} Whether otherReference is equal to this one
|
||||
*
|
||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#isEqual}
|
||||
*/
|
||||
isEqual(otherRef: Reference): boolean {
|
||||
return (
|
||||
!!otherRef &&
|
||||
otherRef.constructor === Reference &&
|
||||
otherRef.key === this.key &&
|
||||
this._query.queryIdentifier() === otherRef._query.queryIdentifier()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GETTERS
|
||||
*/
|
||||
|
||||
/**
|
||||
* The parent location of a Reference, or null for the root Reference.
|
||||
* @type {Reference}
|
||||
*
|
||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#parent}
|
||||
*/
|
||||
get parent(): Reference | null {
|
||||
if (this.path === '/') return null;
|
||||
return new Reference(
|
||||
this._database,
|
||||
this.path.substring(0, this.path.lastIndexOf('/'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to itself
|
||||
* @type {!Reference}
|
||||
*
|
||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#ref}
|
||||
*/
|
||||
get ref(): Reference {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference to the root of the database: '/'
|
||||
* @type {!Reference}
|
||||
*
|
||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#root}
|
||||
*/
|
||||
get root(): Reference {
|
||||
return new Reference(this._database, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Access then method of promise if set
|
||||
* @return {*}
|
||||
*/
|
||||
then(fnResolve: any => any, fnReject: any => any) {
|
||||
if (isFunction(fnResolve) && this._promise && this._promise.then) {
|
||||
return this._promise.then.bind(this._promise)(
|
||||
result => {
|
||||
this._promise = null;
|
||||
return fnResolve(result);
|
||||
},
|
||||
possibleErr => {
|
||||
this._promise = null;
|
||||
|
||||
if (isFunction(fnReject)) {
|
||||
return fnReject(possibleErr);
|
||||
}
|
||||
|
||||
throw possibleErr;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error("Cannot read property 'then' of undefined.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Access catch method of promise if set
|
||||
* @return {*}
|
||||
*/
|
||||
catch(fnReject: any => any) {
|
||||
if (isFunction(fnReject) && this._promise && this._promise.catch) {
|
||||
return this._promise.catch.bind(this._promise)(possibleErr => {
|
||||
this._promise = null;
|
||||
return fnReject(possibleErr);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error("Cannot read property 'catch' of undefined.");
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNALS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate a unique registration key.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
_getRegistrationKey(eventType: string): string {
|
||||
return `$${this._database.databaseUrl}$/${
|
||||
this.path
|
||||
}$${this._query.queryIdentifier()}$${listeners}$${eventType}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a string that uniquely identifies this
|
||||
* combination of path and query modifiers
|
||||
*
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
_getRefKey() {
|
||||
return `$${this._database.databaseUrl}$/${
|
||||
this.path
|
||||
}$${this._query.queryIdentifier()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the promise this 'thenable' reference relates to
|
||||
* @param promise
|
||||
* @private
|
||||
*/
|
||||
_setThenable(promise: Promise<*>) {
|
||||
this._promise = promise;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param obj
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
_serializeObject(obj: Object) {
|
||||
if (!isObject(obj)) return obj;
|
||||
|
||||
// json stringify then parse it calls toString on Objects / Classes
|
||||
// that support it i.e new Date() becomes a ISO string.
|
||||
return tryJSONParse(tryJSONStringify(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_serializeAnyType(value: any) {
|
||||
if (isObject(value)) {
|
||||
return {
|
||||
type: 'object',
|
||||
value: this._serializeObject(value),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: typeof value,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener for data changes at the current ref's location.
|
||||
* The primary method of reading data from a Database.
|
||||
*
|
||||
* Listeners can be unbound using {@link off}.
|
||||
*
|
||||
* Event Types:
|
||||
*
|
||||
* - value: {@link callback}.
|
||||
* - child_added: {@link callback}
|
||||
* - child_removed: {@link callback}
|
||||
* - child_changed: {@link callback}
|
||||
* - child_moved: {@link callback}
|
||||
*
|
||||
* @param {ReferenceEventType} eventType - Type of event to attach a callback for.
|
||||
* @param {ReferenceEventCallback} callback - Function that will be called
|
||||
* when the event occurs with the new data.
|
||||
* @param {cancelCallbackOrContext=} cancelCallbackOrContext - Optional callback that is called
|
||||
* if the event subscription fails. {@link cancelCallbackOrContext}
|
||||
* @param {*=} context - Optional object to bind the callbacks to when calling them.
|
||||
* @returns {ReferenceEventCallback} callback function, unmodified (unbound), for
|
||||
* convenience if you want to pass an inline function to on() and store it later for
|
||||
* removing using off().
|
||||
*
|
||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#on}
|
||||
*/
|
||||
on(
|
||||
eventType: string,
|
||||
callback: DataSnapshot => any,
|
||||
cancelCallbackOrContext?: Object => any | Object,
|
||||
context?: Object
|
||||
): Function {
|
||||
if (!eventType) {
|
||||
throw new Error(
|
||||
'Query.on failed: Function called with 0 arguments. Expects at least 2.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!isString(eventType) || !ReferenceEventTypes[eventType]) {
|
||||
throw new Error(
|
||||
`Query.on failed: First argument must be a valid string event type: "${Object.keys(
|
||||
ReferenceEventTypes
|
||||
).join(', ')}"`
|
||||
);
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
throw new Error(
|
||||
'Query.on failed: Function called with 1 argument. Expects at least 2.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFunction(callback)) {
|
||||
throw new Error(
|
||||
'Query.on failed: Second argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
cancelCallbackOrContext &&
|
||||
!isFunction(cancelCallbackOrContext) &&
|
||||
!isObject(context) &&
|
||||
!isObject(cancelCallbackOrContext)
|
||||
) {
|
||||
throw new Error(
|
||||
'Query.on failed: Function called with 3 arguments, but third optional argument `cancelCallbackOrContext` was not a function.'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
cancelCallbackOrContext &&
|
||||
!isFunction(cancelCallbackOrContext) &&
|
||||
context
|
||||
) {
|
||||
throw new Error(
|
||||
'Query.on failed: Function called with 4 arguments, but third optional argument `cancelCallbackOrContext` was not a function.'
|
||||
);
|
||||
}
|
||||
|
||||
const eventRegistrationKey = this._getRegistrationKey(eventType);
|
||||
const registrationCancellationKey = `${eventRegistrationKey}$cancelled`;
|
||||
const _context =
|
||||
cancelCallbackOrContext && !isFunction(cancelCallbackOrContext)
|
||||
? cancelCallbackOrContext
|
||||
: context;
|
||||
const registrationObj = {
|
||||
eventType,
|
||||
ref: this,
|
||||
path: this.path,
|
||||
key: this._getRefKey(),
|
||||
appName: this._database.app.name,
|
||||
dbURL: this._database.databaseUrl,
|
||||
eventRegistrationKey,
|
||||
};
|
||||
|
||||
SyncTree.addRegistration({
|
||||
...registrationObj,
|
||||
listener: _context ? callback.bind(_context) : callback,
|
||||
});
|
||||
|
||||
if (cancelCallbackOrContext && isFunction(cancelCallbackOrContext)) {
|
||||
// cancellations have their own separate registration
|
||||
// as these are one off events, and they're not guaranteed
|
||||
// to occur either, only happens on failure to register on native
|
||||
SyncTree.addRegistration({
|
||||
ref: this,
|
||||
once: true,
|
||||
path: this.path,
|
||||
key: this._getRefKey(),
|
||||
appName: this._database.app.name,
|
||||
dbURL: this._database.databaseUrl,
|
||||
eventType: `${eventType}$cancelled`,
|
||||
eventRegistrationKey: registrationCancellationKey,
|
||||
listener: _context
|
||||
? cancelCallbackOrContext.bind(_context)
|
||||
: cancelCallbackOrContext,
|
||||
});
|
||||
}
|
||||
|
||||
// initialise the native listener if not already listening
|
||||
getNativeModule(this._database).on({
|
||||
eventType,
|
||||
path: this.path,
|
||||
key: this._getRefKey(),
|
||||
appName: this._database.app.name,
|
||||
modifiers: this._query.getModifiers(),
|
||||
hasCancellationCallback: isFunction(cancelCallbackOrContext),
|
||||
registration: {
|
||||
eventRegistrationKey,
|
||||
key: registrationObj.key,
|
||||
registrationCancellationKey,
|
||||
},
|
||||
});
|
||||
|
||||
// increment number of listeners - just s short way of making
|
||||
// every registration unique per .on() call
|
||||
listeners += 1;
|
||||
|
||||
// return original unbound successCallback for
|
||||
// the purposes of calling .off(eventType, callback) at a later date
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches a callback previously attached with on().
|
||||
*
|
||||
* Detach a callback previously attached with on(). Note that if on() was called
|
||||
* multiple times with the same eventType and callback, the callback will be called
|
||||
* multiple times for each event, and off() must be called multiple times to
|
||||
* remove the callback. Calling off() on a parent listener will not automatically
|
||||
* remove listeners registered on child nodes, off() must also be called on any
|
||||
* child listeners to remove the callback.
|
||||
*
|
||||
* If a callback is not specified, all callbacks for the specified eventType will be removed.
|
||||
* Similarly, if no eventType or callback is specified, all callbacks for the Reference will be removed.
|
||||
* @param eventType
|
||||
* @param originalCallback
|
||||
*/
|
||||
off(eventType?: string = '', originalCallback?: () => any) {
|
||||
if (!arguments.length) {
|
||||
// Firebase Docs:
|
||||
// if no eventType or callback is specified, all callbacks for the Reference will be removed.
|
||||
return SyncTree.removeListenersForRegistrations(
|
||||
SyncTree.getRegistrationsByPath(this.path)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* VALIDATE ARGS
|
||||
*/
|
||||
if (
|
||||
eventType &&
|
||||
(!isString(eventType) || !ReferenceEventTypes[eventType])
|
||||
) {
|
||||
throw new Error(
|
||||
`Query.off failed: First argument must be a valid string event type: "${Object.keys(
|
||||
ReferenceEventTypes
|
||||
).join(', ')}"`
|
||||
);
|
||||
}
|
||||
|
||||
if (originalCallback && !isFunction(originalCallback)) {
|
||||
throw new Error(
|
||||
'Query.off failed: Function called with 2 arguments, but second optional argument was not a function.'
|
||||
);
|
||||
}
|
||||
|
||||
// Firebase Docs:
|
||||
// Note that if on() was called
|
||||
// multiple times with the same eventType and callback, the callback will be called
|
||||
// multiple times for each event, and off() must be called multiple times to
|
||||
// remove the callback.
|
||||
// Remove only a single registration
|
||||
if (eventType && originalCallback) {
|
||||
const registration = SyncTree.getOneByPathEventListener(
|
||||
this.path,
|
||||
eventType,
|
||||
originalCallback
|
||||
);
|
||||
if (!registration) return [];
|
||||
|
||||
// remove the paired cancellation registration if any exist
|
||||
SyncTree.removeListenersForRegistrations([`${registration}$cancelled`]);
|
||||
|
||||
// remove only the first registration to match firebase web sdk
|
||||
// call multiple times to remove multiple registrations
|
||||
return SyncTree.removeListenerRegistrations(originalCallback, [
|
||||
registration,
|
||||
]);
|
||||
}
|
||||
|
||||
// Firebase Docs:
|
||||
// If a callback is not specified, all callbacks for the specified eventType will be removed.
|
||||
const registrations = SyncTree.getRegistrationsByPathEvent(
|
||||
this.path,
|
||||
eventType
|
||||
);
|
||||
|
||||
SyncTree.removeListenersForRegistrations(
|
||||
SyncTree.getRegistrationsByPathEvent(this.path, `${eventType}$cancelled`)
|
||||
);
|
||||
|
||||
return SyncTree.removeListenersForRegistrations(registrations);
|
||||
}
|
||||
}
|
128
bridge/firebase/modules/database/index.js
Normal file
128
bridge/firebase/modules/database/index.js
Normal file
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @flow
|
||||
* Database representation wrapper
|
||||
*/
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import Reference from './Reference';
|
||||
import TransactionHandler from './transaction';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type App from '../core/app';
|
||||
import firebase from '../core/firebase';
|
||||
|
||||
const NATIVE_EVENTS = [
|
||||
'database_transaction_event',
|
||||
// 'database_server_offset', // TODO
|
||||
];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseDatabase';
|
||||
export const NAMESPACE = 'database';
|
||||
|
||||
/**
|
||||
* @class Database
|
||||
*/
|
||||
export default class Database extends ModuleBase {
|
||||
_offsetRef: Reference;
|
||||
_serverTimeOffset: number;
|
||||
_transactionHandler: TransactionHandler;
|
||||
_serviceUrl: string;
|
||||
|
||||
constructor(appOrUrl: App | string, options: Object = {}) {
|
||||
let app;
|
||||
let serviceUrl;
|
||||
if (typeof appOrUrl === 'string') {
|
||||
app = firebase.app();
|
||||
serviceUrl = appOrUrl.endsWith('/') ? appOrUrl : `${appOrUrl}/`;
|
||||
} else {
|
||||
app = appOrUrl;
|
||||
serviceUrl = app.options.databaseURL;
|
||||
}
|
||||
|
||||
super(
|
||||
app,
|
||||
{
|
||||
events: NATIVE_EVENTS,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: true,
|
||||
hasShards: true,
|
||||
namespace: NAMESPACE,
|
||||
},
|
||||
serviceUrl
|
||||
);
|
||||
|
||||
this._serviceUrl = serviceUrl;
|
||||
this._transactionHandler = new TransactionHandler(this);
|
||||
|
||||
if (options.persistence) {
|
||||
getNativeModule(this).setPersistence(options.persistence);
|
||||
}
|
||||
|
||||
// server time listener
|
||||
// setTimeout used to avoid setPersistence race conditions
|
||||
// todo move this and persistence to native side, create a db configure() method natively perhaps?
|
||||
// todo and then native can call setPersistence and then emit offset events
|
||||
setTimeout(() => {
|
||||
this._serverTimeOffset = 0;
|
||||
this._offsetRef = this.ref('.info/serverTimeOffset');
|
||||
this._offsetRef.on('value', snapshot => {
|
||||
this._serverTimeOffset = snapshot.val() || this._serverTimeOffset;
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
getServerTime(): number {
|
||||
return new Date(Date.now() + this._serverTimeOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
goOnline(): void {
|
||||
getNativeModule(this).goOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
goOffline(): void {
|
||||
getNativeModule(this).goOffline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new firebase reference instance
|
||||
* @param path
|
||||
* @returns {Reference}
|
||||
*/
|
||||
ref(path: string): Reference {
|
||||
return new Reference(this, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database url
|
||||
* @returns {string}
|
||||
*/
|
||||
get databaseUrl(): string {
|
||||
return this._serviceUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
ServerValue: NativeModules.RNFirebaseDatabase
|
||||
? {
|
||||
TIMESTAMP: NativeModules.RNFirebaseDatabase.serverValueTimestamp || {
|
||||
'.sv': 'timestamp',
|
||||
},
|
||||
}
|
||||
: {},
|
||||
enableLogging(enabled: boolean) {
|
||||
if (NativeModules[MODULE_NAME]) {
|
||||
NativeModules[MODULE_NAME].enableLogging(enabled);
|
||||
}
|
||||
},
|
||||
};
|
164
bridge/firebase/modules/database/transaction.js
Normal file
164
bridge/firebase/modules/database/transaction.js
Normal file
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @flow
|
||||
* Database Transaction representation wrapper
|
||||
*/
|
||||
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import type Database from './';
|
||||
|
||||
let transactionId = 0;
|
||||
|
||||
/**
|
||||
* Uses the push id generator to create a transaction id
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
const generateTransactionId = (): number => transactionId++;
|
||||
|
||||
/**
|
||||
* @class TransactionHandler
|
||||
*/
|
||||
export default class TransactionHandler {
|
||||
_database: Database;
|
||||
_transactions: { [number]: Object };
|
||||
|
||||
constructor(database: Database) {
|
||||
this._transactions = {};
|
||||
this._database = database;
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this._database, 'database_transaction_event'),
|
||||
this._handleTransactionEvent.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new transaction and start it natively.
|
||||
* @param reference
|
||||
* @param transactionUpdater
|
||||
* @param onComplete
|
||||
* @param applyLocally
|
||||
*/
|
||||
add(
|
||||
reference: Object,
|
||||
transactionUpdater: Function,
|
||||
onComplete?: Function,
|
||||
applyLocally?: boolean = false
|
||||
) {
|
||||
const id = generateTransactionId();
|
||||
|
||||
this._transactions[id] = {
|
||||
id,
|
||||
reference,
|
||||
transactionUpdater,
|
||||
onComplete,
|
||||
applyLocally,
|
||||
completed: false,
|
||||
started: true,
|
||||
};
|
||||
|
||||
getNativeModule(this._database).transactionStart(
|
||||
reference.path,
|
||||
id,
|
||||
applyLocally
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNALS
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_handleTransactionEvent(event: Object = {}) {
|
||||
switch (event.type) {
|
||||
case 'update':
|
||||
return this._handleUpdate(event);
|
||||
case 'error':
|
||||
return this._handleError(event);
|
||||
case 'complete':
|
||||
return this._handleComplete(event);
|
||||
default:
|
||||
getLogger(this._database).warn(
|
||||
`Unknown transaction event type: '${event.type}'`,
|
||||
event
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleUpdate(event: Object = {}) {
|
||||
let newValue;
|
||||
const { id, value } = event;
|
||||
|
||||
try {
|
||||
const transaction = this._transactions[id];
|
||||
if (!transaction) return;
|
||||
|
||||
newValue = transaction.transactionUpdater(value);
|
||||
} finally {
|
||||
let abort = false;
|
||||
|
||||
if (newValue === undefined) {
|
||||
abort = true;
|
||||
}
|
||||
|
||||
getNativeModule(this._database).transactionTryCommit(id, {
|
||||
value: newValue,
|
||||
abort,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleError(event: Object = {}) {
|
||||
const transaction = this._transactions[event.id];
|
||||
if (transaction && !transaction.completed) {
|
||||
transaction.completed = true;
|
||||
try {
|
||||
transaction.onComplete(event.error, false, null);
|
||||
} finally {
|
||||
setImmediate(() => {
|
||||
delete this._transactions[event.id];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleComplete(event: Object = {}) {
|
||||
const transaction = this._transactions[event.id];
|
||||
if (transaction && !transaction.completed) {
|
||||
transaction.completed = true;
|
||||
try {
|
||||
transaction.onComplete(
|
||||
null,
|
||||
event.committed,
|
||||
Object.assign({}, event.snapshot)
|
||||
);
|
||||
} finally {
|
||||
setImmediate(() => {
|
||||
delete this._transactions[event.id];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
83
bridge/firebase/modules/fabric/crashlytics/index.js
Normal file
83
bridge/firebase/modules/fabric/crashlytics/index.js
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @flow
|
||||
* Crash Reporting representation wrapper
|
||||
*/
|
||||
import ModuleBase from '../../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../../utils/native';
|
||||
|
||||
import type App from '../../core/app';
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseCrashlytics';
|
||||
export const NAMESPACE = 'crashlytics';
|
||||
|
||||
export default class Crashlytics extends ModuleBase {
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a crash. Useful for testing your application is set up correctly.
|
||||
*/
|
||||
crash(): void {
|
||||
getNativeModule(this).crash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message that will appear in any subsequent crash reports.
|
||||
* @param {string} message
|
||||
*/
|
||||
log(message: string): void {
|
||||
getNativeModule(this).log(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a non fatal exception.
|
||||
* @param {string} code
|
||||
* @param {string} message
|
||||
*/
|
||||
recordError(code: number, message: string): void {
|
||||
getNativeModule(this).recordError(code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a boolean value to show alongside any subsequent crash reports.
|
||||
*/
|
||||
setBoolValue(key: string, value: boolean): void {
|
||||
getNativeModule(this).setBoolValue(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a float value to show alongside any subsequent crash reports.
|
||||
*/
|
||||
setFloatValue(key: string, value: number): void {
|
||||
getNativeModule(this).setFloatValue(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an integer value to show alongside any subsequent crash reports.
|
||||
*/
|
||||
setIntValue(key: string, value: number): void {
|
||||
getNativeModule(this).setIntValue(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a string value to show alongside any subsequent crash reports.
|
||||
*/
|
||||
setStringValue(key: string, value: string): void {
|
||||
getNativeModule(this).setStringValue(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user ID to show alongside any subsequent crash reports.
|
||||
*/
|
||||
setUserIdentifier(userId: string): void {
|
||||
getNativeModule(this).setUserIdentifier(userId);
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {};
|
109
bridge/firebase/modules/firestore/CollectionReference.js
Normal file
109
bridge/firebase/modules/firestore/CollectionReference.js
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @flow
|
||||
* CollectionReference representation wrapper
|
||||
*/
|
||||
import DocumentReference from './DocumentReference';
|
||||
import Query from './Query';
|
||||
import { firestoreAutoId } from '../../utils';
|
||||
|
||||
import type Firestore from './';
|
||||
import type {
|
||||
QueryDirection,
|
||||
QueryListenOptions,
|
||||
QueryOperator,
|
||||
} from './types';
|
||||
import type FieldPath from './FieldPath';
|
||||
import type Path from './Path';
|
||||
import type { Observer, ObserverOnError, ObserverOnNext } from './Query';
|
||||
import type QuerySnapshot from './QuerySnapshot';
|
||||
|
||||
/**
|
||||
* @class CollectionReference
|
||||
*/
|
||||
export default class CollectionReference {
|
||||
_collectionPath: Path;
|
||||
_firestore: Firestore;
|
||||
_query: Query;
|
||||
|
||||
constructor(firestore: Firestore, collectionPath: Path) {
|
||||
this._collectionPath = collectionPath;
|
||||
this._firestore = firestore;
|
||||
this._query = new Query(firestore, collectionPath);
|
||||
}
|
||||
|
||||
get firestore(): Firestore {
|
||||
return this._firestore;
|
||||
}
|
||||
|
||||
get id(): string | null {
|
||||
return this._collectionPath.id;
|
||||
}
|
||||
|
||||
get parent(): DocumentReference | null {
|
||||
const parentPath = this._collectionPath.parent();
|
||||
return parentPath
|
||||
? new DocumentReference(this._firestore, parentPath)
|
||||
: null;
|
||||
}
|
||||
|
||||
add(data: Object): Promise<DocumentReference> {
|
||||
const documentRef = this.doc();
|
||||
return documentRef.set(data).then(() => Promise.resolve(documentRef));
|
||||
}
|
||||
|
||||
doc(documentPath?: string): DocumentReference {
|
||||
const newPath = documentPath || firestoreAutoId();
|
||||
|
||||
const path = this._collectionPath.child(newPath);
|
||||
if (!path.isDocument) {
|
||||
throw new Error('Argument "documentPath" must point to a document.');
|
||||
}
|
||||
|
||||
return new DocumentReference(this._firestore, path);
|
||||
}
|
||||
|
||||
// From Query
|
||||
endAt(...snapshotOrVarArgs: any[]): Query {
|
||||
return this._query.endAt(snapshotOrVarArgs);
|
||||
}
|
||||
|
||||
endBefore(...snapshotOrVarArgs: any[]): Query {
|
||||
return this._query.endBefore(snapshotOrVarArgs);
|
||||
}
|
||||
|
||||
get(): Promise<QuerySnapshot> {
|
||||
return this._query.get();
|
||||
}
|
||||
|
||||
limit(limit: number): Query {
|
||||
return this._query.limit(limit);
|
||||
}
|
||||
|
||||
onSnapshot(
|
||||
optionsOrObserverOrOnNext: QueryListenOptions | Observer | ObserverOnNext,
|
||||
observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError,
|
||||
onError?: ObserverOnError
|
||||
): () => void {
|
||||
return this._query.onSnapshot(
|
||||
optionsOrObserverOrOnNext,
|
||||
observerOrOnNextOrOnError,
|
||||
onError
|
||||
);
|
||||
}
|
||||
|
||||
orderBy(fieldPath: string | FieldPath, directionStr?: QueryDirection): Query {
|
||||
return this._query.orderBy(fieldPath, directionStr);
|
||||
}
|
||||
|
||||
startAfter(...snapshotOrVarArgs: any[]): Query {
|
||||
return this._query.startAfter(snapshotOrVarArgs);
|
||||
}
|
||||
|
||||
startAt(...snapshotOrVarArgs: any[]): Query {
|
||||
return this._query.startAt(snapshotOrVarArgs);
|
||||
}
|
||||
|
||||
where(fieldPath: string, opStr: QueryOperator, value: any): Query {
|
||||
return this._query.where(fieldPath, opStr, value);
|
||||
}
|
||||
}
|
41
bridge/firebase/modules/firestore/DocumentChange.js
Normal file
41
bridge/firebase/modules/firestore/DocumentChange.js
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @flow
|
||||
* DocumentChange representation wrapper
|
||||
*/
|
||||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
|
||||
import type Firestore from './';
|
||||
import type { NativeDocumentChange } from './types';
|
||||
|
||||
/**
|
||||
* @class DocumentChange
|
||||
*/
|
||||
export default class DocumentChange {
|
||||
_document: DocumentSnapshot;
|
||||
_newIndex: number;
|
||||
_oldIndex: number;
|
||||
_type: string;
|
||||
|
||||
constructor(firestore: Firestore, nativeData: NativeDocumentChange) {
|
||||
this._document = new DocumentSnapshot(firestore, nativeData.document);
|
||||
this._newIndex = nativeData.newIndex;
|
||||
this._oldIndex = nativeData.oldIndex;
|
||||
this._type = nativeData.type;
|
||||
}
|
||||
|
||||
get doc(): DocumentSnapshot {
|
||||
return this._document;
|
||||
}
|
||||
|
||||
get newIndex(): number {
|
||||
return this._newIndex;
|
||||
}
|
||||
|
||||
get oldIndex(): number {
|
||||
return this._oldIndex;
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
return this._type;
|
||||
}
|
||||
}
|
260
bridge/firebase/modules/firestore/DocumentReference.js
Normal file
260
bridge/firebase/modules/firestore/DocumentReference.js
Normal file
@ -0,0 +1,260 @@
|
||||
/**
|
||||
* @flow
|
||||
* DocumentReference representation wrapper
|
||||
*/
|
||||
import CollectionReference from './CollectionReference';
|
||||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
import { parseUpdateArgs } from './utils';
|
||||
import { buildNativeMap } from './utils/serialize';
|
||||
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import { firestoreAutoId, isFunction, isObject } from '../../utils';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type Firestore from './';
|
||||
import type {
|
||||
DocumentListenOptions,
|
||||
NativeDocumentSnapshot,
|
||||
SetOptions,
|
||||
} from './types';
|
||||
import type Path from './Path';
|
||||
|
||||
type ObserverOnError = Object => void;
|
||||
type ObserverOnNext = DocumentSnapshot => void;
|
||||
|
||||
type Observer = {
|
||||
error?: ObserverOnError,
|
||||
next: ObserverOnNext,
|
||||
};
|
||||
|
||||
/**
|
||||
* @class DocumentReference
|
||||
*/
|
||||
export default class DocumentReference {
|
||||
_documentPath: Path;
|
||||
_firestore: Firestore;
|
||||
|
||||
constructor(firestore: Firestore, documentPath: Path) {
|
||||
this._documentPath = documentPath;
|
||||
this._firestore = firestore;
|
||||
}
|
||||
|
||||
get firestore(): Firestore {
|
||||
return this._firestore;
|
||||
}
|
||||
|
||||
get id(): string | null {
|
||||
return this._documentPath.id;
|
||||
}
|
||||
|
||||
get parent(): CollectionReference {
|
||||
const parentPath = this._documentPath.parent();
|
||||
// $FlowExpectedError: parentPath can never be null
|
||||
return new CollectionReference(this._firestore, parentPath);
|
||||
}
|
||||
|
||||
get path(): string {
|
||||
return this._documentPath.relativeName;
|
||||
}
|
||||
|
||||
collection(collectionPath: string): CollectionReference {
|
||||
const path = this._documentPath.child(collectionPath);
|
||||
if (!path.isCollection) {
|
||||
throw new Error('Argument "collectionPath" must point to a collection.');
|
||||
}
|
||||
|
||||
return new CollectionReference(this._firestore, path);
|
||||
}
|
||||
|
||||
delete(): Promise<void> {
|
||||
return getNativeModule(this._firestore).documentDelete(this.path);
|
||||
}
|
||||
|
||||
get(): Promise<DocumentSnapshot> {
|
||||
return getNativeModule(this._firestore)
|
||||
.documentGet(this.path)
|
||||
.then(result => new DocumentSnapshot(this._firestore, result));
|
||||
}
|
||||
|
||||
onSnapshot(
|
||||
optionsOrObserverOrOnNext:
|
||||
| DocumentListenOptions
|
||||
| Observer
|
||||
| ObserverOnNext,
|
||||
observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError,
|
||||
onError?: ObserverOnError
|
||||
) {
|
||||
let observer: Observer;
|
||||
let docListenOptions = {};
|
||||
// Called with: onNext, ?onError
|
||||
if (isFunction(optionsOrObserverOrOnNext)) {
|
||||
if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Second argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext,
|
||||
error: observerOrOnNextOrOnError,
|
||||
};
|
||||
} else if (
|
||||
optionsOrObserverOrOnNext &&
|
||||
isObject(optionsOrObserverOrOnNext)
|
||||
) {
|
||||
// Called with: Observer
|
||||
if (optionsOrObserverOrOnNext.next) {
|
||||
if (isFunction(optionsOrObserverOrOnNext.next)) {
|
||||
if (
|
||||
optionsOrObserverOrOnNext.error &&
|
||||
!isFunction(optionsOrObserverOrOnNext.error)
|
||||
) {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext.next,
|
||||
error: optionsOrObserverOrOnNext.error,
|
||||
};
|
||||
} else {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
optionsOrObserverOrOnNext,
|
||||
'includeMetadataChanges'
|
||||
)
|
||||
) {
|
||||
docListenOptions = optionsOrObserverOrOnNext;
|
||||
// Called with: Options, onNext, ?onError
|
||||
if (isFunction(observerOrOnNextOrOnError)) {
|
||||
if (onError && !isFunction(onError)) {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Third argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: observerOrOnNextOrOnError,
|
||||
error: onError,
|
||||
};
|
||||
// Called with Options, Observer
|
||||
} else if (
|
||||
observerOrOnNextOrOnError &&
|
||||
isObject(observerOrOnNextOrOnError) &&
|
||||
observerOrOnNextOrOnError.next
|
||||
) {
|
||||
if (isFunction(observerOrOnNextOrOnError.next)) {
|
||||
if (
|
||||
observerOrOnNextOrOnError.error &&
|
||||
!isFunction(observerOrOnNextOrOnError.error)
|
||||
) {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
}
|
||||
observer = {
|
||||
next: observerOrOnNextOrOnError.next,
|
||||
error: observerOrOnNextOrOnError.error,
|
||||
};
|
||||
} else {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Second argument must be a function or observer.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: First argument must be a function, observer or options.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'DocumentReference.onSnapshot failed: Called with invalid arguments.'
|
||||
);
|
||||
}
|
||||
const listenerId = firestoreAutoId();
|
||||
|
||||
const listener = (nativeDocumentSnapshot: NativeDocumentSnapshot) => {
|
||||
const documentSnapshot = new DocumentSnapshot(
|
||||
this.firestore,
|
||||
nativeDocumentSnapshot
|
||||
);
|
||||
observer.next(documentSnapshot);
|
||||
};
|
||||
|
||||
// Listen to snapshot events
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this._firestore, `onDocumentSnapshot:${listenerId}`),
|
||||
listener
|
||||
);
|
||||
|
||||
// Listen for snapshot error events
|
||||
if (observer.error) {
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(
|
||||
this._firestore,
|
||||
`onDocumentSnapshotError:${listenerId}`
|
||||
),
|
||||
observer.error
|
||||
);
|
||||
}
|
||||
|
||||
// Add the native listener
|
||||
getNativeModule(this._firestore).documentOnSnapshot(
|
||||
this.path,
|
||||
listenerId,
|
||||
docListenOptions
|
||||
);
|
||||
|
||||
// Return an unsubscribe method
|
||||
return this._offDocumentSnapshot.bind(this, listenerId, listener);
|
||||
}
|
||||
|
||||
set(data: Object, options?: SetOptions): Promise<void> {
|
||||
const nativeData = buildNativeMap(data);
|
||||
return getNativeModule(this._firestore).documentSet(
|
||||
this.path,
|
||||
nativeData,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
update(...args: any[]): Promise<void> {
|
||||
const data = parseUpdateArgs(args, 'DocumentReference.update');
|
||||
const nativeData = buildNativeMap(data);
|
||||
return getNativeModule(this._firestore).documentUpdate(
|
||||
this.path,
|
||||
nativeData
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNALS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Remove document snapshot listener
|
||||
* @param listener
|
||||
*/
|
||||
_offDocumentSnapshot(listenerId: string, listener: Function) {
|
||||
getLogger(this._firestore).info('Removing onDocumentSnapshot listener');
|
||||
SharedEventEmitter.removeListener(
|
||||
getAppEventName(this._firestore, `onDocumentSnapshot:${listenerId}`),
|
||||
listener
|
||||
);
|
||||
SharedEventEmitter.removeListener(
|
||||
getAppEventName(this._firestore, `onDocumentSnapshotError:${listenerId}`),
|
||||
listener
|
||||
);
|
||||
getNativeModule(this._firestore).documentOffSnapshot(this.path, listenerId);
|
||||
}
|
||||
}
|
68
bridge/firebase/modules/firestore/DocumentSnapshot.js
Normal file
68
bridge/firebase/modules/firestore/DocumentSnapshot.js
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @flow
|
||||
* DocumentSnapshot representation wrapper
|
||||
*/
|
||||
import DocumentReference from './DocumentReference';
|
||||
import FieldPath from './FieldPath';
|
||||
import Path from './Path';
|
||||
import { isObject } from '../../utils';
|
||||
import { parseNativeMap } from './utils/serialize';
|
||||
|
||||
import type Firestore from './';
|
||||
import type { NativeDocumentSnapshot, SnapshotMetadata } from './types';
|
||||
|
||||
const extractFieldPathData = (data: Object | void, segments: string[]): any => {
|
||||
if (!data || !isObject(data)) {
|
||||
return undefined;
|
||||
}
|
||||
const pathValue = data[segments[0]];
|
||||
if (segments.length === 1) {
|
||||
return pathValue;
|
||||
}
|
||||
return extractFieldPathData(pathValue, segments.slice(1));
|
||||
};
|
||||
|
||||
/**
|
||||
* @class DocumentSnapshot
|
||||
*/
|
||||
export default class DocumentSnapshot {
|
||||
_data: Object | void;
|
||||
_metadata: SnapshotMetadata;
|
||||
_ref: DocumentReference;
|
||||
|
||||
constructor(firestore: Firestore, nativeData: NativeDocumentSnapshot) {
|
||||
this._data = parseNativeMap(firestore, nativeData.data);
|
||||
this._metadata = nativeData.metadata;
|
||||
this._ref = new DocumentReference(
|
||||
firestore,
|
||||
Path.fromName(nativeData.path)
|
||||
);
|
||||
}
|
||||
|
||||
get exists(): boolean {
|
||||
return this._data !== undefined;
|
||||
}
|
||||
|
||||
get id(): string | null {
|
||||
return this._ref.id;
|
||||
}
|
||||
|
||||
get metadata(): SnapshotMetadata {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
get ref(): DocumentReference {
|
||||
return this._ref;
|
||||
}
|
||||
|
||||
data(): Object | void {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
get(fieldPath: string | FieldPath): any {
|
||||
if (fieldPath instanceof FieldPath) {
|
||||
return extractFieldPathData(this._data, fieldPath._segments);
|
||||
}
|
||||
return this._data ? this._data[fieldPath] : undefined;
|
||||
}
|
||||
}
|
22
bridge/firebase/modules/firestore/FieldPath.js
Normal file
22
bridge/firebase/modules/firestore/FieldPath.js
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @flow
|
||||
* FieldPath representation wrapper
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class FieldPath
|
||||
*/
|
||||
export default class FieldPath {
|
||||
_segments: string[];
|
||||
|
||||
constructor(...segments: string[]) {
|
||||
// TODO: Validation
|
||||
this._segments = segments;
|
||||
}
|
||||
|
||||
static documentId(): FieldPath {
|
||||
return DOCUMENT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
export const DOCUMENT_ID = new FieldPath('__name__');
|
17
bridge/firebase/modules/firestore/FieldValue.js
Normal file
17
bridge/firebase/modules/firestore/FieldValue.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @flow
|
||||
* FieldValue representation wrapper
|
||||
*/
|
||||
|
||||
export default class FieldValue {
|
||||
static delete(): FieldValue {
|
||||
return DELETE_FIELD_VALUE;
|
||||
}
|
||||
|
||||
static serverTimestamp(): FieldValue {
|
||||
return SERVER_TIMESTAMP_FIELD_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
export const DELETE_FIELD_VALUE = new FieldValue();
|
||||
export const SERVER_TIMESTAMP_FIELD_VALUE = new FieldValue();
|
29
bridge/firebase/modules/firestore/GeoPoint.js
Normal file
29
bridge/firebase/modules/firestore/GeoPoint.js
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @flow
|
||||
* GeoPoint representation wrapper
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class GeoPoint
|
||||
*/
|
||||
export default class GeoPoint {
|
||||
_latitude: number;
|
||||
_longitude: number;
|
||||
|
||||
constructor(latitude: number, longitude: number) {
|
||||
// TODO: Validation
|
||||
// validate.isNumber('latitude', latitude);
|
||||
// validate.isNumber('longitude', longitude);
|
||||
|
||||
this._latitude = latitude;
|
||||
this._longitude = longitude;
|
||||
}
|
||||
|
||||
get latitude(): number {
|
||||
return this._latitude;
|
||||
}
|
||||
|
||||
get longitude(): number {
|
||||
return this._longitude;
|
||||
}
|
||||
}
|
50
bridge/firebase/modules/firestore/Path.js
Normal file
50
bridge/firebase/modules/firestore/Path.js
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @flow
|
||||
* Path representation wrapper
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class Path
|
||||
*/
|
||||
export default class Path {
|
||||
_parts: string[];
|
||||
|
||||
constructor(pathComponents: string[]) {
|
||||
this._parts = pathComponents;
|
||||
}
|
||||
|
||||
get id(): string | null {
|
||||
return this._parts.length > 0 ? this._parts[this._parts.length - 1] : null;
|
||||
}
|
||||
|
||||
get isDocument(): boolean {
|
||||
return this._parts.length > 0 && this._parts.length % 2 === 0;
|
||||
}
|
||||
|
||||
get isCollection(): boolean {
|
||||
return this._parts.length % 2 === 1;
|
||||
}
|
||||
|
||||
get relativeName(): string {
|
||||
return this._parts.join('/');
|
||||
}
|
||||
|
||||
child(relativePath: string): Path {
|
||||
return new Path(this._parts.concat(relativePath.split('/')));
|
||||
}
|
||||
|
||||
parent(): Path | null {
|
||||
return this._parts.length > 0
|
||||
? new Path(this._parts.slice(0, this._parts.length - 1))
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @package
|
||||
*/
|
||||
static fromName(name: string): Path {
|
||||
const parts = name.split('/');
|
||||
return parts.length === 0 ? new Path([]) : new Path(parts);
|
||||
}
|
||||
}
|
459
bridge/firebase/modules/firestore/Query.js
Normal file
459
bridge/firebase/modules/firestore/Query.js
Normal file
@ -0,0 +1,459 @@
|
||||
/**
|
||||
* @flow
|
||||
* Query representation wrapper
|
||||
*/
|
||||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
import FieldPath from './FieldPath';
|
||||
import QuerySnapshot from './QuerySnapshot';
|
||||
import { buildNativeArray, buildTypeMap } from './utils/serialize';
|
||||
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import { firestoreAutoId, isFunction, isObject } from '../../utils';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type Firestore from './';
|
||||
import type Path from './Path';
|
||||
import type {
|
||||
QueryDirection,
|
||||
QueryOperator,
|
||||
QueryListenOptions,
|
||||
} from './types';
|
||||
|
||||
const DIRECTIONS: { [QueryDirection]: string } = {
|
||||
ASC: 'ASCENDING',
|
||||
asc: 'ASCENDING',
|
||||
DESC: 'DESCENDING',
|
||||
desc: 'DESCENDING',
|
||||
};
|
||||
|
||||
const OPERATORS: { [QueryOperator]: string } = {
|
||||
'=': 'EQUAL',
|
||||
'==': 'EQUAL',
|
||||
'>': 'GREATER_THAN',
|
||||
'>=': 'GREATER_THAN_OR_EQUAL',
|
||||
'<': 'LESS_THAN',
|
||||
'<=': 'LESS_THAN_OR_EQUAL',
|
||||
};
|
||||
|
||||
type NativeFieldPath = {|
|
||||
elements?: string[],
|
||||
string?: string,
|
||||
type: 'fieldpath' | 'string',
|
||||
|};
|
||||
type FieldFilter = {|
|
||||
fieldPath: NativeFieldPath,
|
||||
operator: string,
|
||||
value: any,
|
||||
|};
|
||||
type FieldOrder = {|
|
||||
direction: string,
|
||||
fieldPath: NativeFieldPath,
|
||||
|};
|
||||
type QueryOptions = {
|
||||
endAt?: any[],
|
||||
endBefore?: any[],
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
selectFields?: string[],
|
||||
startAfter?: any[],
|
||||
startAt?: any[],
|
||||
};
|
||||
|
||||
export type ObserverOnError = Object => void;
|
||||
export type ObserverOnNext = QuerySnapshot => void;
|
||||
|
||||
export type Observer = {
|
||||
error?: ObserverOnError,
|
||||
next: ObserverOnNext,
|
||||
};
|
||||
|
||||
const buildNativeFieldPath = (
|
||||
fieldPath: string | FieldPath
|
||||
): NativeFieldPath => {
|
||||
if (fieldPath instanceof FieldPath) {
|
||||
return {
|
||||
elements: fieldPath._segments,
|
||||
type: 'fieldpath',
|
||||
};
|
||||
}
|
||||
return {
|
||||
string: fieldPath,
|
||||
type: 'string',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @class Query
|
||||
*/
|
||||
export default class Query {
|
||||
_fieldFilters: FieldFilter[];
|
||||
_fieldOrders: FieldOrder[];
|
||||
_firestore: Firestore;
|
||||
_iid: number;
|
||||
_queryOptions: QueryOptions;
|
||||
_referencePath: Path;
|
||||
|
||||
constructor(
|
||||
firestore: Firestore,
|
||||
path: Path,
|
||||
fieldFilters?: FieldFilter[],
|
||||
fieldOrders?: FieldOrder[],
|
||||
queryOptions?: QueryOptions
|
||||
) {
|
||||
this._fieldFilters = fieldFilters || [];
|
||||
this._fieldOrders = fieldOrders || [];
|
||||
this._firestore = firestore;
|
||||
this._queryOptions = queryOptions || {};
|
||||
this._referencePath = path;
|
||||
}
|
||||
|
||||
get firestore(): Firestore {
|
||||
return this._firestore;
|
||||
}
|
||||
|
||||
endAt(...snapshotOrVarArgs: any[]): Query {
|
||||
const options = {
|
||||
...this._queryOptions,
|
||||
endAt: this._buildOrderByOption(snapshotOrVarArgs),
|
||||
};
|
||||
|
||||
return new Query(
|
||||
this.firestore,
|
||||
this._referencePath,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
endBefore(...snapshotOrVarArgs: any[]): Query {
|
||||
const options = {
|
||||
...this._queryOptions,
|
||||
endBefore: this._buildOrderByOption(snapshotOrVarArgs),
|
||||
};
|
||||
|
||||
return new Query(
|
||||
this.firestore,
|
||||
this._referencePath,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
get(): Promise<QuerySnapshot> {
|
||||
return getNativeModule(this._firestore)
|
||||
.collectionGet(
|
||||
this._referencePath.relativeName,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
this._queryOptions
|
||||
)
|
||||
.then(nativeData => new QuerySnapshot(this._firestore, this, nativeData));
|
||||
}
|
||||
|
||||
limit(limit: number): Query {
|
||||
// TODO: Validation
|
||||
// validate.isInteger('n', n);
|
||||
|
||||
const options = {
|
||||
...this._queryOptions,
|
||||
limit,
|
||||
};
|
||||
return new Query(
|
||||
this.firestore,
|
||||
this._referencePath,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
onSnapshot(
|
||||
optionsOrObserverOrOnNext: QueryListenOptions | Observer | ObserverOnNext,
|
||||
observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError,
|
||||
onError?: ObserverOnError
|
||||
) {
|
||||
let observer: Observer;
|
||||
let queryListenOptions = {};
|
||||
// Called with: onNext, ?onError
|
||||
if (isFunction(optionsOrObserverOrOnNext)) {
|
||||
if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Second argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext,
|
||||
error: observerOrOnNextOrOnError,
|
||||
};
|
||||
} else if (
|
||||
optionsOrObserverOrOnNext &&
|
||||
isObject(optionsOrObserverOrOnNext)
|
||||
) {
|
||||
// Called with: Observer
|
||||
if (optionsOrObserverOrOnNext.next) {
|
||||
if (isFunction(optionsOrObserverOrOnNext.next)) {
|
||||
if (
|
||||
optionsOrObserverOrOnNext.error &&
|
||||
!isFunction(optionsOrObserverOrOnNext.error)
|
||||
) {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext.next,
|
||||
error: optionsOrObserverOrOnNext.error,
|
||||
};
|
||||
} else {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
optionsOrObserverOrOnNext,
|
||||
'includeDocumentMetadataChanges'
|
||||
) ||
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
optionsOrObserverOrOnNext,
|
||||
'includeQueryMetadataChanges'
|
||||
)
|
||||
) {
|
||||
queryListenOptions = optionsOrObserverOrOnNext;
|
||||
// Called with: Options, onNext, ?onError
|
||||
if (isFunction(observerOrOnNextOrOnError)) {
|
||||
if (onError && !isFunction(onError)) {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Third argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: observerOrOnNextOrOnError,
|
||||
error: onError,
|
||||
};
|
||||
// Called with Options, Observer
|
||||
} else if (
|
||||
observerOrOnNextOrOnError &&
|
||||
isObject(observerOrOnNextOrOnError) &&
|
||||
observerOrOnNextOrOnError.next
|
||||
) {
|
||||
if (isFunction(observerOrOnNextOrOnError.next)) {
|
||||
if (
|
||||
observerOrOnNextOrOnError.error &&
|
||||
!isFunction(observerOrOnNextOrOnError.error)
|
||||
) {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
}
|
||||
observer = {
|
||||
next: observerOrOnNextOrOnError.next,
|
||||
error: observerOrOnNextOrOnError.error,
|
||||
};
|
||||
} else {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Second argument must be a function or observer.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: First argument must be a function, observer or options.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'Query.onSnapshot failed: Called with invalid arguments.'
|
||||
);
|
||||
}
|
||||
const listenerId = firestoreAutoId();
|
||||
|
||||
const listener = nativeQuerySnapshot => {
|
||||
const querySnapshot = new QuerySnapshot(
|
||||
this._firestore,
|
||||
this,
|
||||
nativeQuerySnapshot
|
||||
);
|
||||
observer.next(querySnapshot);
|
||||
};
|
||||
|
||||
// Listen to snapshot events
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`),
|
||||
listener
|
||||
);
|
||||
|
||||
// Listen for snapshot error events
|
||||
if (observer.error) {
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`),
|
||||
observer.error
|
||||
);
|
||||
}
|
||||
|
||||
// Add the native listener
|
||||
getNativeModule(this._firestore).collectionOnSnapshot(
|
||||
this._referencePath.relativeName,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
this._queryOptions,
|
||||
listenerId,
|
||||
queryListenOptions
|
||||
);
|
||||
|
||||
// Return an unsubscribe method
|
||||
return this._offCollectionSnapshot.bind(this, listenerId, listener);
|
||||
}
|
||||
|
||||
orderBy(
|
||||
fieldPath: string | FieldPath,
|
||||
directionStr?: QueryDirection = 'asc'
|
||||
): Query {
|
||||
// TODO: Validation
|
||||
// validate.isFieldPath('fieldPath', fieldPath);
|
||||
// validate.isOptionalFieldOrder('directionStr', directionStr);
|
||||
|
||||
if (
|
||||
this._queryOptions.startAt ||
|
||||
this._queryOptions.startAfter ||
|
||||
this._queryOptions.endAt ||
|
||||
this._queryOptions.endBefore
|
||||
) {
|
||||
throw new Error(
|
||||
'Cannot specify an orderBy() constraint after calling ' +
|
||||
'startAt(), startAfter(), endBefore() or endAt().'
|
||||
);
|
||||
}
|
||||
|
||||
const newOrder: FieldOrder = {
|
||||
direction: DIRECTIONS[directionStr],
|
||||
fieldPath: buildNativeFieldPath(fieldPath),
|
||||
};
|
||||
const combinedOrders = this._fieldOrders.concat(newOrder);
|
||||
return new Query(
|
||||
this.firestore,
|
||||
this._referencePath,
|
||||
this._fieldFilters,
|
||||
combinedOrders,
|
||||
this._queryOptions
|
||||
);
|
||||
}
|
||||
|
||||
startAfter(...snapshotOrVarArgs: any[]): Query {
|
||||
const options = {
|
||||
...this._queryOptions,
|
||||
startAfter: this._buildOrderByOption(snapshotOrVarArgs),
|
||||
};
|
||||
|
||||
return new Query(
|
||||
this.firestore,
|
||||
this._referencePath,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
startAt(...snapshotOrVarArgs: any[]): Query {
|
||||
const options = {
|
||||
...this._queryOptions,
|
||||
startAt: this._buildOrderByOption(snapshotOrVarArgs),
|
||||
};
|
||||
|
||||
return new Query(
|
||||
this.firestore,
|
||||
this._referencePath,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
where(
|
||||
fieldPath: string | FieldPath,
|
||||
opStr: QueryOperator,
|
||||
value: any
|
||||
): Query {
|
||||
// TODO: Validation
|
||||
// validate.isFieldPath('fieldPath', fieldPath);
|
||||
// validate.isFieldFilter('fieldFilter', opStr, value);
|
||||
const nativeValue = buildTypeMap(value);
|
||||
const newFilter: FieldFilter = {
|
||||
fieldPath: buildNativeFieldPath(fieldPath),
|
||||
operator: OPERATORS[opStr],
|
||||
value: nativeValue,
|
||||
};
|
||||
const combinedFilters = this._fieldFilters.concat(newFilter);
|
||||
return new Query(
|
||||
this.firestore,
|
||||
this._referencePath,
|
||||
combinedFilters,
|
||||
this._fieldOrders,
|
||||
this._queryOptions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNALS
|
||||
*/
|
||||
|
||||
_buildOrderByOption(snapshotOrVarArgs: any[]) {
|
||||
// TODO: Validation
|
||||
let values;
|
||||
if (
|
||||
snapshotOrVarArgs.length === 1 &&
|
||||
snapshotOrVarArgs[0] instanceof DocumentSnapshot
|
||||
) {
|
||||
const docSnapshot: DocumentSnapshot = snapshotOrVarArgs[0];
|
||||
values = [];
|
||||
for (let i = 0; i < this._fieldOrders.length; i++) {
|
||||
const fieldOrder = this._fieldOrders[i];
|
||||
if (
|
||||
fieldOrder.fieldPath.type === 'string' &&
|
||||
fieldOrder.fieldPath.string
|
||||
) {
|
||||
values.push(docSnapshot.get(fieldOrder.fieldPath.string));
|
||||
} else if (fieldOrder.fieldPath.fieldpath) {
|
||||
const fieldPath = new FieldPath(...fieldOrder.fieldPath.fieldpath);
|
||||
values.push(docSnapshot.get(fieldPath));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
values = snapshotOrVarArgs;
|
||||
}
|
||||
|
||||
return buildNativeArray(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove query snapshot listener
|
||||
* @param listener
|
||||
*/
|
||||
_offCollectionSnapshot(listenerId: string, listener: Function) {
|
||||
getLogger(this._firestore).info('Removing onQuerySnapshot listener');
|
||||
SharedEventEmitter.removeListener(
|
||||
getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`),
|
||||
listener
|
||||
);
|
||||
SharedEventEmitter.removeListener(
|
||||
getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`),
|
||||
listener
|
||||
);
|
||||
getNativeModule(this._firestore).collectionOffSnapshot(
|
||||
this._referencePath.relativeName,
|
||||
this._fieldFilters,
|
||||
this._fieldOrders,
|
||||
this._queryOptions,
|
||||
listenerId
|
||||
);
|
||||
}
|
||||
}
|
78
bridge/firebase/modules/firestore/QuerySnapshot.js
Normal file
78
bridge/firebase/modules/firestore/QuerySnapshot.js
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @flow
|
||||
* QuerySnapshot representation wrapper
|
||||
*/
|
||||
import DocumentChange from './DocumentChange';
|
||||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
|
||||
import type Firestore from './';
|
||||
import type {
|
||||
NativeDocumentChange,
|
||||
NativeDocumentSnapshot,
|
||||
SnapshotMetadata,
|
||||
} from './types';
|
||||
import type Query from './Query';
|
||||
|
||||
type NativeQuerySnapshot = {
|
||||
changes: NativeDocumentChange[],
|
||||
documents: NativeDocumentSnapshot[],
|
||||
metadata: SnapshotMetadata,
|
||||
};
|
||||
|
||||
/**
|
||||
* @class QuerySnapshot
|
||||
*/
|
||||
export default class QuerySnapshot {
|
||||
_changes: DocumentChange[];
|
||||
_docs: DocumentSnapshot[];
|
||||
_metadata: SnapshotMetadata;
|
||||
_query: Query;
|
||||
|
||||
constructor(
|
||||
firestore: Firestore,
|
||||
query: Query,
|
||||
nativeData: NativeQuerySnapshot
|
||||
) {
|
||||
this._changes = nativeData.changes.map(
|
||||
change => new DocumentChange(firestore, change)
|
||||
);
|
||||
this._docs = nativeData.documents.map(
|
||||
doc => new DocumentSnapshot(firestore, doc)
|
||||
);
|
||||
this._metadata = nativeData.metadata;
|
||||
this._query = query;
|
||||
}
|
||||
|
||||
get docChanges(): DocumentChange[] {
|
||||
return this._changes;
|
||||
}
|
||||
|
||||
get docs(): DocumentSnapshot[] {
|
||||
return this._docs;
|
||||
}
|
||||
|
||||
get empty(): boolean {
|
||||
return this._docs.length === 0;
|
||||
}
|
||||
|
||||
get metadata(): SnapshotMetadata {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
get query(): Query {
|
||||
return this._query;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._docs.length;
|
||||
}
|
||||
|
||||
forEach(callback: DocumentSnapshot => any) {
|
||||
// TODO: Validation
|
||||
// validate.isFunction('callback', callback);
|
||||
|
||||
this._docs.forEach(doc => {
|
||||
callback(doc);
|
||||
});
|
||||
}
|
||||
}
|
151
bridge/firebase/modules/firestore/Transaction.js
Normal file
151
bridge/firebase/modules/firestore/Transaction.js
Normal file
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* @flow
|
||||
* Firestore Transaction representation wrapper
|
||||
*/
|
||||
import { parseUpdateArgs } from './utils';
|
||||
import { buildNativeMap } from './utils/serialize';
|
||||
|
||||
import type Firestore from './';
|
||||
import type { TransactionMeta } from './TransactionHandler';
|
||||
import type DocumentReference from './DocumentReference';
|
||||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
type Command = {
|
||||
type: 'set' | 'update' | 'delete',
|
||||
path: string,
|
||||
data?: { [string]: any },
|
||||
options?: SetOptions | {},
|
||||
};
|
||||
|
||||
type SetOptions = {
|
||||
merge: boolean,
|
||||
};
|
||||
|
||||
// TODO docs state all get requests must be made FIRST before any modifications
|
||||
// TODO so need to validate that
|
||||
|
||||
/**
|
||||
* @class Transaction
|
||||
*/
|
||||
export default class Transaction {
|
||||
_pendingResult: ?any;
|
||||
_firestore: Firestore;
|
||||
_meta: TransactionMeta;
|
||||
_commandBuffer: Array<Command>;
|
||||
|
||||
constructor(firestore: Firestore, meta: TransactionMeta) {
|
||||
this._meta = meta;
|
||||
this._commandBuffer = [];
|
||||
this._firestore = firestore;
|
||||
this._pendingResult = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* INTERNAL API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clears the command buffer and any pending result in prep for
|
||||
* the next transaction iteration attempt.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_prepare() {
|
||||
this._commandBuffer = [];
|
||||
this._pendingResult = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* PUBLIC API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reads the document referenced by the provided DocumentReference.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be retrieved. Value must not be null.
|
||||
*
|
||||
* @returns Promise<DocumentSnapshot>
|
||||
*/
|
||||
get(documentRef: DocumentReference): Promise<DocumentSnapshot> {
|
||||
// todo validate doc ref
|
||||
return getNativeModule(this._firestore)
|
||||
.transactionGetDocument(this._meta.id, documentRef.path)
|
||||
.then(result => new DocumentSnapshot(this._firestore, result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the document referred to by the provided DocumentReference.
|
||||
* If the document does not exist yet, it will be created. If you pass options,
|
||||
* the provided data can be merged into the existing document.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be created. Value must not be null.
|
||||
* @param data Object An object of the fields and values for the document.
|
||||
* @param options SetOptions An object to configure the set behavior.
|
||||
* Pass {merge: true} to only replace the values specified in the data argument.
|
||||
* Fields omitted will remain untouched.
|
||||
*
|
||||
* @returns {Transaction}
|
||||
*/
|
||||
set(
|
||||
documentRef: DocumentReference,
|
||||
data: Object,
|
||||
options?: SetOptions
|
||||
): Transaction {
|
||||
// todo validate doc ref
|
||||
// todo validate data is object
|
||||
this._commandBuffer.push({
|
||||
type: 'set',
|
||||
path: documentRef.path,
|
||||
data: buildNativeMap(data),
|
||||
options: options || {},
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates fields in the document referred to by this DocumentReference.
|
||||
* The update will fail if applied to a document that does not exist. Nested
|
||||
* fields can be updated by providing dot-separated field path strings or by providing FieldPath objects.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be updated. Value must not be null.
|
||||
* @param args any Either an object containing all of the fields and values to update,
|
||||
* or a series of arguments alternating between fields (as string or FieldPath
|
||||
* objects) and values.
|
||||
*
|
||||
* @returns {Transaction}
|
||||
*/
|
||||
update(documentRef: DocumentReference, ...args: Array<any>): Transaction {
|
||||
// todo validate doc ref
|
||||
const data = parseUpdateArgs(args, 'Transaction.update');
|
||||
this._commandBuffer.push({
|
||||
type: 'update',
|
||||
path: documentRef.path,
|
||||
data: buildNativeMap(data),
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the document referred to by the provided DocumentReference.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be deleted. Value must not be null.
|
||||
*
|
||||
* @returns {Transaction}
|
||||
*/
|
||||
delete(documentRef: DocumentReference): Transaction {
|
||||
// todo validate doc ref
|
||||
this._commandBuffer.push({
|
||||
type: 'delete',
|
||||
path: documentRef.path,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
241
bridge/firebase/modules/firestore/TransactionHandler.js
Normal file
241
bridge/firebase/modules/firestore/TransactionHandler.js
Normal file
@ -0,0 +1,241 @@
|
||||
/**
|
||||
* @flow
|
||||
* Firestore Transaction representation wrapper
|
||||
*/
|
||||
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import Transaction from './Transaction';
|
||||
import type Firestore from './';
|
||||
|
||||
let transactionId = 0;
|
||||
|
||||
/**
|
||||
* Uses the push id generator to create a transaction id
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
const generateTransactionId = (): number => transactionId++;
|
||||
|
||||
export type TransactionMeta = {
|
||||
id: number,
|
||||
stack: string[],
|
||||
reject?: Function,
|
||||
resolve?: Function,
|
||||
transaction: Transaction,
|
||||
updateFunction: (transaction: Transaction) => Promise<any>,
|
||||
};
|
||||
|
||||
type TransactionEvent = {
|
||||
id: number,
|
||||
type: 'update' | 'error' | 'complete',
|
||||
error: ?{ code: string, message: string },
|
||||
};
|
||||
|
||||
/**
|
||||
* @class TransactionHandler
|
||||
*/
|
||||
export default class TransactionHandler {
|
||||
_firestore: Firestore;
|
||||
_pending: {
|
||||
[number]: {
|
||||
meta: TransactionMeta,
|
||||
transaction: Transaction,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(firestore: Firestore) {
|
||||
this._pending = {};
|
||||
this._firestore = firestore;
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this._firestore, 'firestore_transaction_event'),
|
||||
this._handleTransactionEvent.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* INTERNAL API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add a new transaction and start it natively.
|
||||
* @param updateFunction
|
||||
*/
|
||||
_add(
|
||||
updateFunction: (transaction: Transaction) => Promise<any>
|
||||
): Promise<any> {
|
||||
const id = generateTransactionId();
|
||||
// $FlowExpectedError: Transaction has to be populated
|
||||
const meta: TransactionMeta = {
|
||||
id,
|
||||
updateFunction,
|
||||
stack: new Error().stack
|
||||
.split('\n')
|
||||
.slice(4)
|
||||
.join('\n'),
|
||||
};
|
||||
|
||||
this._pending[id] = {
|
||||
meta,
|
||||
transaction: new Transaction(this._firestore, meta),
|
||||
};
|
||||
|
||||
// deferred promise
|
||||
return new Promise((resolve, reject) => {
|
||||
getNativeModule(this._firestore).transactionBegin(id);
|
||||
meta.resolve = r => {
|
||||
resolve(r);
|
||||
this._remove(id);
|
||||
};
|
||||
meta.reject = e => {
|
||||
reject(e);
|
||||
this._remove(id);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a local instance of a transaction meta
|
||||
*
|
||||
* @param id
|
||||
* @private
|
||||
*/
|
||||
_remove(id) {
|
||||
getNativeModule(this._firestore).transactionDispose(id);
|
||||
delete this._pending[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* EVENTS
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction events and distributes to correct
|
||||
* internal handler by event.type
|
||||
*
|
||||
* @param event
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_handleTransactionEvent(event: TransactionEvent) {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (event.type) {
|
||||
case 'update':
|
||||
this._handleUpdate(event);
|
||||
break;
|
||||
case 'error':
|
||||
this._handleError(event);
|
||||
break;
|
||||
case 'complete':
|
||||
this._handleComplete(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction update events
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
async _handleUpdate(event: TransactionEvent) {
|
||||
const { id } = event;
|
||||
// abort if no longer exists js side
|
||||
if (!this._pending[id]) return this._remove(id);
|
||||
|
||||
const { meta, transaction } = this._pending[id];
|
||||
const { updateFunction, reject } = meta;
|
||||
|
||||
// clear any saved state from previous transaction runs
|
||||
transaction._prepare();
|
||||
|
||||
let finalError;
|
||||
let updateFailed;
|
||||
let pendingResult;
|
||||
|
||||
// run the users custom update functionality
|
||||
try {
|
||||
const possiblePromise = updateFunction(transaction);
|
||||
|
||||
// validate user has returned a promise in their update function
|
||||
// TODO must it actually return a promise? Can't find any usages of it without one...
|
||||
if (!possiblePromise || !possiblePromise.then) {
|
||||
finalError = new Error(
|
||||
'Update function for `firestore.runTransaction(updateFunction)` must return a Promise.'
|
||||
);
|
||||
} else {
|
||||
pendingResult = await possiblePromise;
|
||||
}
|
||||
} catch (exception) {
|
||||
// exception can still be falsey if user `Promise.reject();` 's with no args
|
||||
// so we track the exception with a updateFailed boolean to ensure no fall-through
|
||||
updateFailed = true;
|
||||
finalError = exception;
|
||||
}
|
||||
|
||||
// reject the final promise and remove from native
|
||||
// update is failed when either the users updateFunction
|
||||
// throws an error or rejects a promise
|
||||
if (updateFailed) {
|
||||
// $FlowExpectedError: Reject will always be present
|
||||
return reject(finalError);
|
||||
}
|
||||
|
||||
// capture the resolved result as we'll need this
|
||||
// to resolve the runTransaction() promise when
|
||||
// native emits that the transaction is final
|
||||
transaction._pendingResult = pendingResult;
|
||||
|
||||
// send the buffered update/set/delete commands for native to process
|
||||
return getNativeModule(this._firestore).transactionApplyBuffer(
|
||||
id,
|
||||
transaction._commandBuffer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction error events
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleError(event: TransactionEvent) {
|
||||
const { id, error } = event;
|
||||
const { meta } = this._pending[id];
|
||||
|
||||
if (meta && error) {
|
||||
const { code, message } = error;
|
||||
// build a JS error and replace its stack
|
||||
// with the captured one at start of transaction
|
||||
// so it's actually relevant to the user
|
||||
const errorWithStack = new Error(message);
|
||||
// $FlowExpectedError: code is needed for Firebase errors
|
||||
errorWithStack.code = code;
|
||||
// $FlowExpectedError: stack should be a stack trace
|
||||
errorWithStack.stack = meta.stack;
|
||||
|
||||
// $FlowExpectedError: Reject will always be present
|
||||
meta.reject(errorWithStack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction complete events
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleComplete(event: TransactionEvent) {
|
||||
const { id } = event;
|
||||
const { meta, transaction } = this._pending[id];
|
||||
|
||||
if (meta) {
|
||||
const pendingResult = transaction._pendingResult;
|
||||
// $FlowExpectedError: Resolve will always be present
|
||||
meta.resolve(pendingResult);
|
||||
}
|
||||
}
|
||||
}
|
76
bridge/firebase/modules/firestore/WriteBatch.js
Normal file
76
bridge/firebase/modules/firestore/WriteBatch.js
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @flow
|
||||
* WriteBatch representation wrapper
|
||||
*/
|
||||
import { parseUpdateArgs } from './utils';
|
||||
import { buildNativeMap } from './utils/serialize';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type DocumentReference from './DocumentReference';
|
||||
import type Firestore from './';
|
||||
import type { SetOptions } from './types';
|
||||
|
||||
type DocumentWrite = {
|
||||
data?: Object,
|
||||
options?: Object,
|
||||
path: string,
|
||||
type: 'DELETE' | 'SET' | 'UPDATE',
|
||||
};
|
||||
|
||||
/**
|
||||
* @class WriteBatch
|
||||
*/
|
||||
export default class WriteBatch {
|
||||
_firestore: Firestore;
|
||||
_writes: DocumentWrite[];
|
||||
|
||||
constructor(firestore: Firestore) {
|
||||
this._firestore = firestore;
|
||||
this._writes = [];
|
||||
}
|
||||
|
||||
commit(): Promise<void> {
|
||||
return getNativeModule(this._firestore).documentBatch(this._writes);
|
||||
}
|
||||
|
||||
delete(docRef: DocumentReference): WriteBatch {
|
||||
// TODO: Validation
|
||||
// validate.isDocumentReference('docRef', docRef);
|
||||
// validate.isOptionalPrecondition('deleteOptions', deleteOptions);
|
||||
this._writes.push({
|
||||
path: docRef.path,
|
||||
type: 'DELETE',
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
set(docRef: DocumentReference, data: Object, options?: SetOptions) {
|
||||
// TODO: Validation
|
||||
// validate.isDocumentReference('docRef', docRef);
|
||||
// validate.isDocument('data', data);
|
||||
// validate.isOptionalPrecondition('options', writeOptions);
|
||||
const nativeData = buildNativeMap(data);
|
||||
this._writes.push({
|
||||
data: nativeData,
|
||||
options,
|
||||
path: docRef.path,
|
||||
type: 'SET',
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
update(docRef: DocumentReference, ...args: any[]): WriteBatch {
|
||||
// TODO: Validation
|
||||
// validate.isDocumentReference('docRef', docRef);
|
||||
const data = parseUpdateArgs(args, 'WriteBatch.update');
|
||||
this._writes.push({
|
||||
data: buildNativeMap(data),
|
||||
path: docRef.path,
|
||||
type: 'UPDATE',
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
247
bridge/firebase/modules/firestore/index.js
Normal file
247
bridge/firebase/modules/firestore/index.js
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @flow
|
||||
* Firestore representation wrapper
|
||||
*/
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import CollectionReference from './CollectionReference';
|
||||
import DocumentReference from './DocumentReference';
|
||||
import FieldPath from './FieldPath';
|
||||
import FieldValue from './FieldValue';
|
||||
import GeoPoint from './GeoPoint';
|
||||
import Path from './Path';
|
||||
import WriteBatch from './WriteBatch';
|
||||
import TransactionHandler from './TransactionHandler';
|
||||
import Transaction from './Transaction';
|
||||
import INTERNALS from '../../utils/internals';
|
||||
|
||||
import type DocumentSnapshot from './DocumentSnapshot';
|
||||
import type App from '../core/app';
|
||||
import type QuerySnapshot from './QuerySnapshot';
|
||||
|
||||
type CollectionSyncEvent = {
|
||||
appName: string,
|
||||
querySnapshot?: QuerySnapshot,
|
||||
error?: Object,
|
||||
listenerId: string,
|
||||
path: string,
|
||||
};
|
||||
|
||||
type DocumentSyncEvent = {
|
||||
appName: string,
|
||||
documentSnapshot?: DocumentSnapshot,
|
||||
error?: Object,
|
||||
listenerId: string,
|
||||
path: string,
|
||||
};
|
||||
|
||||
const NATIVE_EVENTS = [
|
||||
'firestore_transaction_event',
|
||||
'firestore_document_sync_event',
|
||||
'firestore_collection_sync_event',
|
||||
];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseFirestore';
|
||||
export const NAMESPACE = 'firestore';
|
||||
|
||||
/**
|
||||
* @class Firestore
|
||||
*/
|
||||
export default class Firestore extends ModuleBase {
|
||||
_referencePath: Path;
|
||||
_transactionHandler: TransactionHandler;
|
||||
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
events: NATIVE_EVENTS,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: true,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
|
||||
this._referencePath = new Path([]);
|
||||
this._transactionHandler = new TransactionHandler(this);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onCollectionSnapshot
|
||||
getAppEventName(this, 'firestore_collection_sync_event'),
|
||||
this._onCollectionSyncEvent.bind(this)
|
||||
);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onDocumentSnapshot
|
||||
getAppEventName(this, 'firestore_document_sync_event'),
|
||||
this._onDocumentSyncEvent.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* PUBLIC API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a write batch, used for performing multiple writes as a single atomic operation.
|
||||
*
|
||||
* @returns {WriteBatch}
|
||||
*/
|
||||
batch(): WriteBatch {
|
||||
return new WriteBatch(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a CollectionReference instance that refers to the collection at the specified path.
|
||||
*
|
||||
* @param collectionPath
|
||||
* @returns {CollectionReference}
|
||||
*/
|
||||
collection(collectionPath: string): CollectionReference {
|
||||
const path = this._referencePath.child(collectionPath);
|
||||
if (!path.isCollection) {
|
||||
throw new Error('Argument "collectionPath" must point to a collection.');
|
||||
}
|
||||
|
||||
return new CollectionReference(this, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a DocumentReference instance that refers to the document at the specified path.
|
||||
*
|
||||
* @param documentPath
|
||||
* @returns {DocumentReference}
|
||||
*/
|
||||
doc(documentPath: string): DocumentReference {
|
||||
const path = this._referencePath.child(documentPath);
|
||||
if (!path.isDocument) {
|
||||
throw new Error('Argument "documentPath" must point to a document.');
|
||||
}
|
||||
|
||||
return new DocumentReference(this, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given updateFunction and then attempts to commit the
|
||||
* changes applied within the transaction. If any document read within
|
||||
* the transaction has changed, Cloud Firestore retries the updateFunction.
|
||||
*
|
||||
* If it fails to commit after 5 attempts, the transaction fails.
|
||||
*
|
||||
* @param updateFunction
|
||||
* @returns {void|Promise<any>}
|
||||
*/
|
||||
runTransaction(
|
||||
updateFunction: (transaction: Transaction) => Promise<any>
|
||||
): Promise<any> {
|
||||
return this._transactionHandler._add(updateFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* UNSUPPORTED
|
||||
* -------------
|
||||
*/
|
||||
|
||||
setLogLevel(): void {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'firestore',
|
||||
'setLogLevel'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
enableNetwork(): void {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'firestore',
|
||||
'enableNetwork'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
disableNetwork(): void {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'firestore',
|
||||
'disableNetwork'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* MISC
|
||||
* -------------
|
||||
*/
|
||||
|
||||
enablePersistence(): Promise<void> {
|
||||
throw new Error('Persistence is enabled by default on the Firestore SDKs');
|
||||
}
|
||||
|
||||
settings(): void {
|
||||
throw new Error('firebase.firestore().settings() coming soon');
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* INTERNALS
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal collection sync listener
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onCollectionSyncEvent(event: CollectionSyncEvent) {
|
||||
if (event.error) {
|
||||
SharedEventEmitter.emit(
|
||||
getAppEventName(this, `onQuerySnapshotError:${event.listenerId}`),
|
||||
event.error
|
||||
);
|
||||
} else {
|
||||
SharedEventEmitter.emit(
|
||||
getAppEventName(this, `onQuerySnapshot:${event.listenerId}`),
|
||||
event.querySnapshot
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal document sync listener
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onDocumentSyncEvent(event: DocumentSyncEvent) {
|
||||
if (event.error) {
|
||||
SharedEventEmitter.emit(
|
||||
getAppEventName(this, `onDocumentSnapshotError:${event.listenerId}`),
|
||||
event.error
|
||||
);
|
||||
} else {
|
||||
SharedEventEmitter.emit(
|
||||
getAppEventName(this, `onDocumentSnapshot:${event.listenerId}`),
|
||||
event.documentSnapshot
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
FieldPath,
|
||||
FieldValue,
|
||||
GeoPoint,
|
||||
enableLogging(enabled: boolean) {
|
||||
if (NativeModules[MODULE_NAME]) {
|
||||
NativeModules[MODULE_NAME].enableLogging(enabled);
|
||||
}
|
||||
},
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user