[wip] new testing infra
This commit is contained in:
parent
a174c48cd5
commit
6b8556ef7c
|
@ -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
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"presets": [
|
||||
"react-native"
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": [
|
||||
["istanbul", {
|
||||
"include": [
|
||||
"**/firebase/**.js"
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,18 +1,17 @@
|
|||
> detox
|
||||
|
||||
# React Native Demo Project
|
||||
# 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 for native async-await support, otherwise you'll have to babel the tests).
|
||||
* 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
|
||||
|
||||
* Make sure you're in folder `examples/demo-react-native`.
|
||||
* Run `npm install`.
|
||||
|
||||
## To test Release build of your app
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
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'
|
||||
|
@ -21,11 +30,12 @@ android {
|
|||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
missingDimensionStrategy "minReactNative", "minReactNative46"
|
||||
multiDexEnabled true
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
reset()
|
||||
enable false
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk false // If true, also generate a universal APK
|
||||
include "armeabi-v7a", "x86"
|
||||
}
|
||||
|
@ -68,10 +78,14 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
project.ext.firebaseVersion = '11.8.0'
|
||||
|
||||
dependencies {
|
||||
implementation "com.android.support:appcompat-v7:27.0.2"
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
|
||||
compile project(':react-native-firebase')
|
||||
|
||||
androidTestImplementation(project(path: ":detox"))
|
||||
androidTestImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.1'
|
||||
|
@ -84,3 +98,5 @@ task copyDownloadableDepsToLibs(type: Copy) {
|
|||
from configurations.compile
|
||||
into 'libs'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"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-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"
|
||||
}
|
|
@ -2,9 +2,15 @@ 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'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,3 +24,26 @@ allprojects {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,3 +19,4 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
|
|||
|
||||
android.useDeprecatedNdk=true
|
||||
android.enableAapt2=false
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
rootProject.name = 'DetoxRNExample'
|
||||
include ':react-native-firebase'
|
||||
project(':react-native-firebase').projectDir = new File(rootProject.projectDir, './../../android')
|
||||
|
||||
include ':app'
|
||||
|
||||
include ':detox'
|
||||
project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')
|
||||
project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
const {device, expect, element, by, waitFor} = require('detox');
|
||||
|
||||
describe('Example', () => {
|
||||
beforeEach(async () => {
|
||||
await device.reloadReactNative();
|
||||
});
|
||||
|
||||
it('should have welcome screen', async () => {
|
||||
await expect(element(by.id('welcome'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should show hello screen after tap', async () => {
|
||||
await element(by.id('hello_button')).tap();
|
||||
await expect(element(by.text('Hello!!!'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should show world screen after tap', async () => {
|
||||
await element(by.id('world_button')).tap();
|
||||
await expect(element(by.text('World!!!'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('waitFor should be exported', async () => {
|
||||
await waitFor(element(by.id('welcome'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('welcome'))).toExist();
|
||||
});
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
const detox = require('detox');
|
||||
const config = require('../package.json').detox;
|
||||
|
||||
/*
|
||||
Example showing how to use Detox with required objects rather than globally exported.
|
||||
e.g `const {device, expect, element, by, waitFor} = require('detox');`
|
||||
*/
|
||||
before(async () => {
|
||||
await detox.init(config, {initGlobals: false});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await detox.cleanup();
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
--recursive
|
||||
--timeout 120000
|
||||
--bail
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -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';
|
|
@ -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;
|
||||
});
|
||||
},
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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',
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
|
@ -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 = {};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 = {};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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 = {};
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = {};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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__');
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type DocumentListenOptions = {
|
||||
includeMetadataChanges: boolean,
|
||||
};
|
||||
|
||||
export type QueryDirection = 'DESC' | 'desc' | 'ASC' | 'asc';
|
||||
|
||||
export type QueryListenOptions = {|
|
||||
includeDocumentMetadataChanges: boolean,
|
||||
includeQueryMetadataChanges: boolean,
|
||||
|};
|
||||
|
||||
export type QueryOperator = '<' | '<=' | '=' | '==' | '>' | '>=';
|
||||
|
||||
export type SetOptions = {
|
||||
merge?: boolean,
|
||||
};
|
||||
|
||||
export type SnapshotMetadata = {
|
||||
fromCache: boolean,
|
||||
hasPendingWrites: boolean,
|
||||
};
|
||||
|
||||
export type NativeDocumentChange = {
|
||||
document: NativeDocumentSnapshot,
|
||||
newIndex: number,
|
||||
oldIndex: number,
|
||||
type: string,
|
||||
};
|
||||
|
||||
export type NativeDocumentSnapshot = {
|
||||
data: { [string]: NativeTypeMap },
|
||||
metadata: SnapshotMetadata,
|
||||
path: string,
|
||||
};
|
||||
|
||||
export type NativeTypeMap = {
|
||||
type:
|
||||
| 'array'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'documentid'
|
||||
| 'fieldvalue'
|
||||
| 'geopoint'
|
||||
| 'null'
|
||||
| 'number'
|
||||
| 'object'
|
||||
| 'reference'
|
||||
| 'string',
|
||||
value: any,
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
import FieldPath from '../FieldPath';
|
||||
import { isObject, isString } from '../../../utils';
|
||||
|
||||
const buildFieldPathData = (segments: string[], value: any): Object => {
|
||||
if (segments.length === 1) {
|
||||
return {
|
||||
[segments[0]]: value,
|
||||
};
|
||||
}
|
||||
return {
|
||||
[segments[0]]: buildFieldPathData(segments.slice(1), value),
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const mergeFieldPathData = (
|
||||
data: Object,
|
||||
segments: string[],
|
||||
value: any
|
||||
): Object => {
|
||||
if (segments.length === 1) {
|
||||
return {
|
||||
...data,
|
||||
[segments[0]]: value,
|
||||
};
|
||||
} else if (data[segments[0]]) {
|
||||
return {
|
||||
...data,
|
||||
[segments[0]]: mergeFieldPathData(
|
||||
data[segments[0]],
|
||||
segments.slice(1),
|
||||
value
|
||||
),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...data,
|
||||
[segments[0]]: buildFieldPathData(segments.slice(1), value),
|
||||
};
|
||||
};
|
||||
|
||||
export const parseUpdateArgs = (args: any[], methodName: string) => {
|
||||
let data = {};
|
||||
if (args.length === 1) {
|
||||
if (!isObject(args[0])) {
|
||||
throw new Error(
|
||||
`${methodName} failed: If using a single update argument, it must be an object.`
|
||||
);
|
||||
}
|
||||
[data] = args;
|
||||
} else if (args.length % 2 === 1) {
|
||||
throw new Error(
|
||||
`${methodName} failed: The update arguments must be either a single object argument, or equal numbers of key/value pairs.`
|
||||
);
|
||||
} else {
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
const key = args[i];
|
||||
const value = args[i + 1];
|
||||
if (isString(key)) {
|
||||
data[key] = value;
|
||||
} else if (key instanceof FieldPath) {
|
||||
data = mergeFieldPathData(data, key._segments, value);
|
||||
} else {
|
||||
throw new Error(
|
||||
`${methodName} failed: Argument at index ${i} must be a string or FieldPath`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import DocumentReference from '../DocumentReference';
|
||||
import { DOCUMENT_ID } from '../FieldPath';
|
||||
import {
|
||||
DELETE_FIELD_VALUE,
|
||||
SERVER_TIMESTAMP_FIELD_VALUE,
|
||||
} from '../FieldValue';
|
||||
import GeoPoint from '../GeoPoint';
|
||||
import Path from '../Path';
|
||||
import { typeOf } from '../../../utils';
|
||||
|
||||
import type Firestore from '../';
|
||||
import type { NativeTypeMap } from '../types';
|
||||
|
||||
/*
|
||||
* Functions that build up the data needed to represent
|
||||
* the different types available within Firestore
|
||||
* for transmission to the native side
|
||||
*/
|
||||
|
||||
export const buildNativeMap = (data: Object): { [string]: NativeTypeMap } => {
|
||||
const nativeData = {};
|
||||
if (data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
const typeMap = buildTypeMap(data[key]);
|
||||
if (typeMap) {
|
||||
nativeData[key] = typeMap;
|
||||
}
|
||||
});
|
||||
}
|
||||
return nativeData;
|
||||
};
|
||||
|
||||
export const buildNativeArray = (array: Object[]): NativeTypeMap[] => {
|
||||
const nativeArray = [];
|
||||
if (array) {
|
||||
array.forEach(value => {
|
||||
const typeMap = buildTypeMap(value);
|
||||
if (typeMap) {
|
||||
nativeArray.push(typeMap);
|
||||
}
|
||||
});
|
||||
}
|
||||
return nativeArray;
|
||||
};
|
||||
|
||||
export const buildTypeMap = (value: any): NativeTypeMap | null => {
|
||||
const type = typeOf(value);
|
||||
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||
return {
|
||||
type: 'null',
|
||||
value: null,
|
||||
};
|
||||
} else if (value === DELETE_FIELD_VALUE) {
|
||||
return {
|
||||
type: 'fieldvalue',
|
||||
value: 'delete',
|
||||
};
|
||||
} else if (value === SERVER_TIMESTAMP_FIELD_VALUE) {
|
||||
return {
|
||||
type: 'fieldvalue',
|
||||
value: 'timestamp',
|
||||
};
|
||||
} else if (value === DOCUMENT_ID) {
|
||||
return {
|
||||
type: 'documentid',
|
||||
value: null,
|
||||
};
|
||||
} else if (type === 'boolean' || type === 'number' || type === 'string') {
|
||||
return {
|
||||
type,
|
||||
value,
|
||||
};
|
||||
} else if (type === 'array') {
|
||||
return {
|
||||
type,
|
||||
value: buildNativeArray(value),
|
||||
};
|
||||
} else if (type === 'object') {
|
||||
if (value instanceof DocumentReference) {
|
||||
return {
|
||||
type: 'reference',
|
||||
value: value.path,
|
||||
};
|
||||
} else if (value instanceof GeoPoint) {
|
||||
return {
|
||||
type: 'geopoint',
|
||||
value: {
|
||||
latitude: value.latitude,
|
||||
longitude: value.longitude,
|
||||
},
|
||||
};
|
||||
} else if (value instanceof Date) {
|
||||
return {
|
||||
type: 'date',
|
||||
value: value.getTime(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'object',
|
||||
value: buildNativeMap(value),
|
||||
};
|
||||
}
|
||||
console.warn(`Unknown data type received ${type}`);
|
||||
return null;
|
||||
};
|
||||
|
||||
/*
|
||||
* Functions that parse the received from the native
|
||||
* side and converts to the correct Firestore JS types
|
||||
*/
|
||||
|
||||
export const parseNativeMap = (
|
||||
firestore: Firestore,
|
||||
nativeData: { [string]: NativeTypeMap }
|
||||
): Object | void => {
|
||||
let data;
|
||||
if (nativeData) {
|
||||
data = {};
|
||||
Object.keys(nativeData).forEach(key => {
|
||||
data[key] = parseTypeMap(firestore, nativeData[key]);
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const parseNativeArray = (
|
||||
firestore: Firestore,
|
||||
nativeArray: NativeTypeMap[]
|
||||
): any[] => {
|
||||
const array = [];
|
||||
if (nativeArray) {
|
||||
nativeArray.forEach(typeMap => {
|
||||
array.push(parseTypeMap(firestore, typeMap));
|
||||
});
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
const parseTypeMap = (firestore: Firestore, typeMap: NativeTypeMap): any => {
|
||||
const { type, value } = typeMap;
|
||||
if (type === 'null') {
|
||||
return null;
|
||||
} else if (type === 'boolean' || type === 'number' || type === 'string') {
|
||||
return value;
|
||||
} else if (type === 'array') {
|
||||
return parseNativeArray(firestore, value);
|
||||
} else if (type === 'object') {
|
||||
return parseNativeMap(firestore, value);
|
||||
} else if (type === 'reference') {
|
||||
return new DocumentReference(firestore, Path.fromName(value));
|
||||
} else if (type === 'geopoint') {
|
||||
return new GeoPoint(value.latitude, value.longitude);
|
||||
} else if (type === 'date') {
|
||||
return new Date(value);
|
||||
}
|
||||
console.warn(`Unknown data type received ${type}`);
|
||||
return value;
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* @flow
|
||||
* Instance ID representation wrapper
|
||||
*/
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type App from '../core/app';
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseInstanceId';
|
||||
export const NAMESPACE = 'instanceid';
|
||||
|
||||
export default class InstanceId extends ModuleBase {
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
hasShards: false,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
}
|
||||
|
||||
delete(): Promise<void> {
|
||||
return getNativeModule(this).delete();
|
||||
}
|
||||
|
||||
get(): Promise<string> {
|
||||
return getNativeModule(this).get();
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {};
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidInvitation representation wrapper
|
||||
*/
|
||||
import type Invitation from './Invitation';
|
||||
import type { NativeAndroidInvitation } from './types';
|
||||
|
||||
export default class AndroidInvitation {
|
||||
_additionalReferralParameters: { [string]: string } | void;
|
||||
_emailHtmlContent: string | void;
|
||||
_emailSubject: string | void;
|
||||
_googleAnalyticsTrackingId: string | void;
|
||||
_invitation: Invitation;
|
||||
|
||||
constructor(invitation: Invitation) {
|
||||
this._invitation = invitation;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param additionalReferralParameters
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setAdditionalReferralParameters(additionalReferralParameters: {
|
||||
[string]: string,
|
||||
}): Invitation {
|
||||
this._additionalReferralParameters = additionalReferralParameters;
|
||||
return this._invitation;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param emailHtmlContent
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setEmailHtmlContent(emailHtmlContent: string): Invitation {
|
||||
this._emailHtmlContent = emailHtmlContent;
|
||||
return this._invitation;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param emailSubject
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setEmailSubject(emailSubject: string): Invitation {
|
||||
this._emailSubject = emailSubject;
|
||||
return this._invitation;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param googleAnalyticsTrackingId
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setGoogleAnalyticsTrackingId(googleAnalyticsTrackingId: string): Invitation {
|
||||
this._googleAnalyticsTrackingId = googleAnalyticsTrackingId;
|
||||
return this._invitation;
|
||||
}
|
||||
|
||||
build(): NativeAndroidInvitation {
|
||||
return {
|
||||
additionalReferralParameters: this._additionalReferralParameters,
|
||||
emailHtmlContent: this._emailHtmlContent,
|
||||
emailSubject: this._emailSubject,
|
||||
googleAnalyticsTrackingId: this._googleAnalyticsTrackingId,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* @flow
|
||||
* Invitation representation wrapper
|
||||
*/
|
||||
import { Platform } from 'react-native';
|
||||
import AndroidInvitation from './AndroidInvitation';
|
||||
|
||||
import type { NativeInvitation } from './types';
|
||||
|
||||
export default class Invitation {
|
||||
_android: AndroidInvitation;
|
||||
_androidClientId: string | void;
|
||||
_androidMinimumVersionCode: number | void;
|
||||
_callToActionText: string | void;
|
||||
_customImage: string | void;
|
||||
_deepLink: string | void;
|
||||
_iosClientId: string | void;
|
||||
_message: string;
|
||||
_title: string;
|
||||
|
||||
constructor(title: string, message: string) {
|
||||
this._android = new AndroidInvitation(this);
|
||||
this._message = message;
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
get android(): AndroidInvitation {
|
||||
return this._android;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param androidClientId
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setAndroidClientId(androidClientId: string): Invitation {
|
||||
this._androidClientId = androidClientId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param androidMinimumVersionCode
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setAndroidMinimumVersionCode(androidMinimumVersionCode: number): Invitation {
|
||||
this._androidMinimumVersionCode = androidMinimumVersionCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param callToActionText
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setCallToActionText(callToActionText: string): Invitation {
|
||||
this._callToActionText = callToActionText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param customImage
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setCustomImage(customImage: string): Invitation {
|
||||
this._customImage = customImage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param deepLink
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setDeepLink(deepLink: string): Invitation {
|
||||
this._deepLink = deepLink;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param iosClientId
|
||||
* @returns {Invitation}
|
||||
*/
|
||||
setIOSClientId(iosClientId: string): Invitation {
|
||||
this._iosClientId = iosClientId;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeInvitation {
|
||||
if (!this._message) {
|
||||
throw new Error('Invitation: Missing required `message` property');
|
||||
} else if (!this._title) {
|
||||
throw new Error('Invitation: Missing required `title` property');
|
||||
}
|
||||
|
||||
return {
|
||||
android: Platform.OS === 'android' ? this._android.build() : undefined,
|
||||
androidClientId: this._androidClientId,
|
||||
androidMinimumVersionCode: this._androidMinimumVersionCode,
|
||||
callToActionText: this._callToActionText,
|
||||
customImage: this._customImage,
|
||||
deepLink: this._deepLink,
|
||||
iosClientId: this._iosClientId,
|
||||
message: this._message,
|
||||
title: this._title,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* @flow
|
||||
* Invites representation wrapper
|
||||
*/
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import Invitation from './Invitation';
|
||||
|
||||
import type App from '../core/app';
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseInvites';
|
||||
export const NAMESPACE = 'invites';
|
||||
const NATIVE_EVENTS = ['invites_invitation_received'];
|
||||
|
||||
type InvitationOpen = {
|
||||
deepLink: string,
|
||||
invitationId: string,
|
||||
};
|
||||
|
||||
export default class Invites extends ModuleBase {
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
events: NATIVE_EVENTS,
|
||||
hasShards: false,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onMessage
|
||||
'invites_invitation_received',
|
||||
(invitation: InvitationOpen) => {
|
||||
SharedEventEmitter.emit('onInvitation', invitation);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the invitation that triggered application open
|
||||
* @returns {Promise.<Object>}
|
||||
*/
|
||||
getInitialInvitation(): Promise<string> {
|
||||
return getNativeModule(this).getInitialInvitation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to invites
|
||||
* @param listener
|
||||
* @returns {Function}
|
||||
*/
|
||||
onInvitation(listener: InvitationOpen => any) {
|
||||
getLogger(this).info('Creating onInvitation listener');
|
||||
|
||||
SharedEventEmitter.addListener('onInvitation', listener);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onInvitation listener');
|
||||
SharedEventEmitter.removeListener('onInvitation', listener);
|
||||
};
|
||||
}
|
||||
|
||||
sendInvitation(invitation: Invitation): Promise<string[]> {
|
||||
if (!(invitation instanceof Invitation)) {
|
||||
throw new Error(
|
||||
`Invites:sendInvitation expects an 'Invitation' but got type ${typeof invitation}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this).sendInvitation(invitation.build());
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
Invitation,
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
export type NativeAndroidInvitation = {|
|
||||
additionalReferralParameters?: { [string]: string },
|
||||
emailHtmlContent?: string,
|
||||
emailSubject?: string,
|
||||
googleAnalyticsTrackingId?: string,
|
||||
|};
|
||||
|
||||
export type NativeInvitation = {|
|
||||
android?: NativeAndroidInvitation,
|
||||
androidClientId?: string,
|
||||
androidMinimumVersionCode?: number,
|
||||
callToActionText?: string,
|
||||
customImage?: string,
|
||||
deepLink?: string,
|
||||
iosClientId?: string,
|
||||
message: string,
|
||||
title: string,
|
||||
|};
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @flow
|
||||
* AnalyticsParameters representation wrapper
|
||||
*/
|
||||
import type DynamicLink from './DynamicLink';
|
||||
import type { NativeAnalyticsParameters } from './types';
|
||||
|
||||
export default class AnalyticsParameters {
|
||||
_campaign: string | void;
|
||||
_content: string | void;
|
||||
_link: DynamicLink;
|
||||
_medium: string | void;
|
||||
_source: string | void;
|
||||
_term: string | void;
|
||||
|
||||
constructor(link: DynamicLink) {
|
||||
this._link = link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param campaign
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setCampaign(campaign: string): DynamicLink {
|
||||
this._campaign = campaign;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param content
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setContent(content: string): DynamicLink {
|
||||
this._content = content;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param medium
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setMedium(medium: string): DynamicLink {
|
||||
this._medium = medium;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param source
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setSource(source: string): DynamicLink {
|
||||
this._source = source;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param term
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setTerm(term: string): DynamicLink {
|
||||
this._term = term;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
build(): NativeAnalyticsParameters {
|
||||
return {
|
||||
campaign: this._campaign,
|
||||
content: this._content,
|
||||
medium: this._medium,
|
||||
source: this._source,
|
||||
term: this._term,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidParameters representation wrapper
|
||||
*/
|
||||
import type DynamicLink from './DynamicLink';
|
||||
import type { NativeAndroidParameters } from './types';
|
||||
|
||||
export default class AndroidParameters {
|
||||
_fallbackUrl: string | void;
|
||||
_link: DynamicLink;
|
||||
_minimumVersion: number | void;
|
||||
_packageName: string | void;
|
||||
|
||||
constructor(link: DynamicLink) {
|
||||
this._link = link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fallbackUrl
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setFallbackUrl(fallbackUrl: string): DynamicLink {
|
||||
this._fallbackUrl = fallbackUrl;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param minimumVersion
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setMinimumVersion(minimumVersion: number): DynamicLink {
|
||||
this._minimumVersion = minimumVersion;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param packageName
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setPackageName(packageName: string): DynamicLink {
|
||||
this._packageName = packageName;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
build(): NativeAndroidParameters {
|
||||
if ((this._fallbackUrl || this._minimumVersion) && !this._packageName) {
|
||||
throw new Error(
|
||||
'AndroidParameters: Missing required `packageName` property'
|
||||
);
|
||||
}
|
||||
return {
|
||||
fallbackUrl: this._fallbackUrl,
|
||||
minimumVersion: this._minimumVersion,
|
||||
packageName: this._packageName,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @flow
|
||||
* DynamicLink representation wrapper
|
||||
*/
|
||||
import AnalyticsParameters from './AnalyticsParameters';
|
||||
import AndroidParameters from './AndroidParameters';
|
||||
import IOSParameters from './IOSParameters';
|
||||
import ITunesParameters from './ITunesParameters';
|
||||
import NavigationParameters from './NavigationParameters';
|
||||
import SocialParameters from './SocialParameters';
|
||||
|
||||
import type { NativeDynamicLink } from './types';
|
||||
|
||||
export default class DynamicLink {
|
||||
_analytics: AnalyticsParameters;
|
||||
_android: AndroidParameters;
|
||||
_dynamicLinkDomain: string;
|
||||
_ios: IOSParameters;
|
||||
_itunes: ITunesParameters;
|
||||
_link: string;
|
||||
_navigation: NavigationParameters;
|
||||
_social: SocialParameters;
|
||||
|
||||
constructor(link: string, dynamicLinkDomain: string) {
|
||||
this._analytics = new AnalyticsParameters(this);
|
||||
this._android = new AndroidParameters(this);
|
||||
this._dynamicLinkDomain = dynamicLinkDomain;
|
||||
this._ios = new IOSParameters(this);
|
||||
this._itunes = new ITunesParameters(this);
|
||||
this._link = link;
|
||||
this._navigation = new NavigationParameters(this);
|
||||
this._social = new SocialParameters(this);
|
||||
}
|
||||
|
||||
get analytics(): AnalyticsParameters {
|
||||
return this._analytics;
|
||||
}
|
||||
|
||||
get android(): AndroidParameters {
|
||||
return this._android;
|
||||
}
|
||||
|
||||
get ios(): IOSParameters {
|
||||
return this._ios;
|
||||
}
|
||||
|
||||
get itunes(): ITunesParameters {
|
||||
return this._itunes;
|
||||
}
|
||||
|
||||
get navigation(): NavigationParameters {
|
||||
return this._navigation;
|
||||
}
|
||||
|
||||
get social(): SocialParameters {
|
||||
return this._social;
|
||||
}
|
||||
|
||||
build(): NativeDynamicLink {
|
||||
if (!this._link) {
|
||||
throw new Error('DynamicLink: Missing required `link` property');
|
||||
} else if (!this._dynamicLinkDomain) {
|
||||
throw new Error(
|
||||
'DynamicLink: Missing required `dynamicLinkDomain` property'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
analytics: this._analytics.build(),
|
||||
android: this._android.build(),
|
||||
dynamicLinkDomain: this._dynamicLinkDomain,
|
||||
ios: this._ios.build(),
|
||||
itunes: this._itunes.build(),
|
||||
link: this._link,
|
||||
navigation: this._navigation.build(),
|
||||
social: this._social.build(),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* @flow
|
||||
* IOSParameters representation wrapper
|
||||
*/
|
||||
import type DynamicLink from './DynamicLink';
|
||||
import type { NativeIOSParameters } from './types';
|
||||
|
||||
export default class IOSParameters {
|
||||
_appStoreId: string | void;
|
||||
_bundleId: string | void;
|
||||
_customScheme: string | void;
|
||||
_fallbackUrl: string | void;
|
||||
_iPadBundleId: string | void;
|
||||
_iPadFallbackUrl: string | void;
|
||||
_link: DynamicLink;
|
||||
_minimumVersion: string | void;
|
||||
|
||||
constructor(link: DynamicLink) {
|
||||
this._link = link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param appStoreId
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setAppStoreId(appStoreId: string): DynamicLink {
|
||||
this._appStoreId = appStoreId;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bundleId
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setBundleId(bundleId: string): DynamicLink {
|
||||
this._bundleId = bundleId;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param customScheme
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setCustomScheme(customScheme: string): DynamicLink {
|
||||
this._customScheme = customScheme;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fallbackUrl
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setFallbackUrl(fallbackUrl: string): DynamicLink {
|
||||
this._fallbackUrl = fallbackUrl;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param iPadBundleId
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setIPadBundleId(iPadBundleId: string): DynamicLink {
|
||||
this._iPadBundleId = iPadBundleId;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param iPadFallbackUrl
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setIPadFallbackUrl(iPadFallbackUrl: string): DynamicLink {
|
||||
this._iPadFallbackUrl = iPadFallbackUrl;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param minimumVersion
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setMinimumVersion(minimumVersion: string): DynamicLink {
|
||||
this._minimumVersion = minimumVersion;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
build(): NativeIOSParameters {
|
||||
if (
|
||||
(this._appStoreId ||
|
||||
this._customScheme ||
|
||||
this._fallbackUrl ||
|
||||
this._iPadBundleId ||
|
||||
this._iPadFallbackUrl ||
|
||||
this._minimumVersion) &&
|
||||
!this._bundleId
|
||||
) {
|
||||
throw new Error('IOSParameters: Missing required `bundleId` property');
|
||||
}
|
||||
return {
|
||||
appStoreId: this._appStoreId,
|
||||
bundleId: this._bundleId,
|
||||
customScheme: this._customScheme,
|
||||
fallbackUrl: this._fallbackUrl,
|
||||
iPadBundleId: this._iPadBundleId,
|
||||
iPadFallbackUrl: this._iPadFallbackUrl,
|
||||
minimumVersion: this._minimumVersion,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @flow
|
||||
* ITunesParameters representation wrapper
|
||||
*/
|
||||
import type DynamicLink from './DynamicLink';
|
||||
import type { NativeITunesParameters } from './types';
|
||||
|
||||
export default class ITunesParameters {
|
||||
_affiliateToken: string | void;
|
||||
_campaignToken: string | void;
|
||||
_link: DynamicLink;
|
||||
_providerToken: string | void;
|
||||
|
||||
constructor(link: DynamicLink) {
|
||||
this._link = link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param affiliateToken
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setAffiliateToken(affiliateToken: string): DynamicLink {
|
||||
this._affiliateToken = affiliateToken;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param campaignToken
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setCampaignToken(campaignToken: string): DynamicLink {
|
||||
this._campaignToken = campaignToken;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param providerToken
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setProviderToken(providerToken: string): DynamicLink {
|
||||
this._providerToken = providerToken;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
build(): NativeITunesParameters {
|
||||
return {
|
||||
affiliateToken: this._affiliateToken,
|
||||
campaignToken: this._campaignToken,
|
||||
providerToken: this._providerToken,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* @flow
|
||||
* NavigationParameters representation wrapper
|
||||
*/
|
||||
import type DynamicLink from './DynamicLink';
|
||||
import type { NativeNavigationParameters } from './types';
|
||||
|
||||
export default class NavigationParameters {
|
||||
_forcedRedirectEnabled: string | void;
|
||||
_link: DynamicLink;
|
||||
|
||||
constructor(link: DynamicLink) {
|
||||
this._link = link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param forcedRedirectEnabled
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setForcedRedirectEnabled(forcedRedirectEnabled: string): DynamicLink {
|
||||
this._forcedRedirectEnabled = forcedRedirectEnabled;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
build(): NativeNavigationParameters {
|
||||
return {
|
||||
forcedRedirectEnabled: this._forcedRedirectEnabled,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @flow
|
||||
* SocialParameters representation wrapper
|
||||
*/
|
||||
import type DynamicLink from './DynamicLink';
|
||||
import type { NativeSocialParameters } from './types';
|
||||
|
||||
export default class SocialParameters {
|
||||
_descriptionText: string | void;
|
||||
_imageUrl: string | void;
|
||||
_link: DynamicLink;
|
||||
_title: string | void;
|
||||
|
||||
constructor(link: DynamicLink) {
|
||||
this._link = link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param descriptionText
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setDescriptionText(descriptionText: string): DynamicLink {
|
||||
this._descriptionText = descriptionText;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param imageUrl
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setImageUrl(imageUrl: string): DynamicLink {
|
||||
this._imageUrl = imageUrl;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param title
|
||||
* @returns {DynamicLink}
|
||||
*/
|
||||
setTitle(title: string): DynamicLink {
|
||||
this._title = title;
|
||||
return this._link;
|
||||
}
|
||||
|
||||
build(): NativeSocialParameters {
|
||||
return {
|
||||
descriptionText: this._descriptionText,
|
||||
imageUrl: this._imageUrl,
|
||||
title: this._title,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* @flow
|
||||
* Dynamic Links representation wrapper
|
||||
*/
|
||||
import DynamicLink from './DynamicLink';
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type App from '../core/app';
|
||||
|
||||
const NATIVE_EVENTS = ['links_link_received'];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseLinks';
|
||||
export const NAMESPACE = 'links';
|
||||
|
||||
/**
|
||||
* @class Links
|
||||
*/
|
||||
export default class Links extends ModuleBase {
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
events: NATIVE_EVENTS,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onMessage
|
||||
'links_link_received',
|
||||
(link: string) => {
|
||||
SharedEventEmitter.emit('onLink', link);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create long Dynamic Link from parameters
|
||||
* @param parameters
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
createDynamicLink(link: DynamicLink): Promise<string> {
|
||||
if (!(link instanceof DynamicLink)) {
|
||||
throw new Error(
|
||||
`Links:createDynamicLink expects a 'DynamicLink' but got type ${typeof link}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this).createDynamicLink(link.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create short Dynamic Link from parameters
|
||||
* @param parameters
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
createShortDynamicLink(
|
||||
link: DynamicLink,
|
||||
type?: 'SHORT' | 'UNGUESSABLE'
|
||||
): Promise<String> {
|
||||
if (!(link instanceof DynamicLink)) {
|
||||
throw new Error(
|
||||
`Links:createShortDynamicLink expects a 'DynamicLink' but got type ${typeof link}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this).createShortDynamicLink(link.build(), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link that triggered application open
|
||||
* @returns {Promise.<String>}
|
||||
*/
|
||||
getInitialLink(): Promise<string> {
|
||||
return getNativeModule(this).getInitialLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to dynamic links
|
||||
* @param listener
|
||||
* @returns {Function}
|
||||
*/
|
||||
onLink(listener: string => any): () => any {
|
||||
getLogger(this).info('Creating onLink listener');
|
||||
|
||||
SharedEventEmitter.addListener('onLink', listener);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onLink listener');
|
||||
SharedEventEmitter.removeListener('onLink', listener);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {};
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
export type NativeAnalyticsParameters = {|
|
||||
campaign?: string,
|
||||
content?: string,
|
||||
medium?: string,
|
||||
source?: string,
|
||||
term?: string,
|
||||
|};
|
||||
|
||||
export type NativeAndroidParameters = {|
|
||||
fallbackUrl?: string,
|
||||
minimumVersion?: number,
|
||||
packageName?: string,
|
||||
|};
|
||||
|
||||
export type NativeIOSParameters = {|
|
||||
appStoreId?: string,
|
||||
bundleId?: string,
|
||||
customScheme?: string,
|
||||
fallbackUrl?: string,
|
||||
iPadBundleId?: string,
|
||||
iPadFallbackUrl?: string,
|
||||
minimumVersion?: string,
|
||||
|};
|
||||
|
||||
export type NativeITunesParameters = {|
|
||||
affiliateToken?: string,
|
||||
campaignToken?: string,
|
||||
providerToken?: string,
|
||||
|};
|
||||
|
||||
export type NativeNavigationParameters = {|
|
||||
forcedRedirectEnabled?: string,
|
||||
|};
|
||||
|
||||
export type NativeSocialParameters = {|
|
||||
descriptionText?: string,
|
||||
imageUrl?: string,
|
||||
title?: string,
|
||||
|};
|
||||
|
||||
export type NativeDynamicLink = {|
|
||||
analytics: NativeAnalyticsParameters,
|
||||
android: NativeAndroidParameters,
|
||||
dynamicLinkDomain: string,
|
||||
ios: NativeIOSParameters,
|
||||
itunes: NativeITunesParameters,
|
||||
link: string,
|
||||
navigation: NativeNavigationParameters,
|
||||
social: NativeSocialParameters,
|
||||
|};
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* @flow
|
||||
* RemoteMessage representation wrapper
|
||||
*/
|
||||
import { isObject, generatePushID } from './../../utils';
|
||||
|
||||
import type {
|
||||
NativeInboundRemoteMessage,
|
||||
NativeOutboundRemoteMessage,
|
||||
} from './types';
|
||||
|
||||
export default class RemoteMessage {
|
||||
_collapseKey: string | void;
|
||||
_data: { [string]: string };
|
||||
_from: string | void;
|
||||
_messageId: string;
|
||||
_messageType: string | void;
|
||||
_sentTime: number | void;
|
||||
_to: string;
|
||||
_ttl: number;
|
||||
|
||||
constructor(inboundMessage?: NativeInboundRemoteMessage) {
|
||||
if (inboundMessage) {
|
||||
this._collapseKey = inboundMessage.collapseKey;
|
||||
this._data = inboundMessage.data;
|
||||
this._from = inboundMessage.from;
|
||||
this._messageId = inboundMessage.messageId;
|
||||
this._messageType = inboundMessage.messageType;
|
||||
this._sentTime = inboundMessage.sentTime;
|
||||
}
|
||||
// defaults
|
||||
this._data = this._data || {};
|
||||
// TODO: Is this the best way to generate an ID?
|
||||
this._messageId = this._messageId || generatePushID();
|
||||
this._ttl = 3600;
|
||||
}
|
||||
|
||||
get collapseKey(): ?string {
|
||||
return this._collapseKey;
|
||||
}
|
||||
|
||||
get data(): { [string]: string } {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
get from(): ?string {
|
||||
return this._from;
|
||||
}
|
||||
|
||||
get messageId(): ?string {
|
||||
return this._messageId;
|
||||
}
|
||||
|
||||
get messageType(): ?string {
|
||||
return this._messageType;
|
||||
}
|
||||
|
||||
get sentTime(): ?number {
|
||||
return this._sentTime;
|
||||
}
|
||||
|
||||
get to(): ?string {
|
||||
return this._to;
|
||||
}
|
||||
|
||||
get ttl(): ?number {
|
||||
return this._ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param collapseKey
|
||||
* @returns {RemoteMessage}
|
||||
*/
|
||||
setCollapseKey(collapseKey: string): RemoteMessage {
|
||||
this._collapseKey = collapseKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @returns {RemoteMessage}
|
||||
*/
|
||||
setData(data: { [string]: string } = {}) {
|
||||
if (!isObject(data)) {
|
||||
throw new Error(
|
||||
`RemoteMessage:setData expects an object but got type '${typeof data}'.`
|
||||
);
|
||||
}
|
||||
this._data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param messageId
|
||||
* @returns {RemoteMessage}
|
||||
*/
|
||||
setMessageId(messageId: string): RemoteMessage {
|
||||
this._messageId = messageId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param messageType
|
||||
* @returns {RemoteMessage}
|
||||
*/
|
||||
setMessageType(messageType: string): RemoteMessage {
|
||||
this._messageType = messageType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param to
|
||||
* @returns {RemoteMessage}
|
||||
*/
|
||||
setTo(to: string): RemoteMessage {
|
||||
this._to = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ttl
|
||||
* @returns {RemoteMessage}
|
||||
*/
|
||||
setTtl(ttl: number): RemoteMessage {
|
||||
this._ttl = ttl;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeOutboundRemoteMessage {
|
||||
if (!this._data) {
|
||||
throw new Error('RemoteMessage: Missing required `data` property');
|
||||
} else if (!this._messageId) {
|
||||
throw new Error('RemoteMessage: Missing required `messageId` property');
|
||||
} else if (!this._to) {
|
||||
throw new Error('RemoteMessage: Missing required `to` property');
|
||||
} else if (!this._ttl) {
|
||||
throw new Error('RemoteMessage: Missing required `ttl` property');
|
||||
}
|
||||
|
||||
return {
|
||||
collapseKey: this._collapseKey,
|
||||
data: this._data,
|
||||
messageId: this._messageId,
|
||||
messageType: this._messageType,
|
||||
to: this._to,
|
||||
ttl: this._ttl,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* @flow
|
||||
* Messaging (FCM) representation wrapper
|
||||
*/
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import INTERNALS from '../../utils/internals';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import { isFunction, isObject } from '../../utils';
|
||||
import RemoteMessage from './RemoteMessage';
|
||||
|
||||
import type App from '../core/app';
|
||||
import type { NativeInboundRemoteMessage } from './types';
|
||||
|
||||
type OnMessage = RemoteMessage => any;
|
||||
|
||||
type OnMessageObserver = {
|
||||
next: OnMessage,
|
||||
};
|
||||
|
||||
type OnTokenRefresh = String => any;
|
||||
|
||||
type OnTokenRefreshObserver = {
|
||||
next: OnTokenRefresh,
|
||||
};
|
||||
|
||||
const NATIVE_EVENTS = [
|
||||
'messaging_message_received',
|
||||
'messaging_token_refreshed',
|
||||
];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseMessaging';
|
||||
export const NAMESPACE = 'messaging';
|
||||
|
||||
/**
|
||||
* @class Messaging
|
||||
*/
|
||||
export default class Messaging extends ModuleBase {
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
events: NATIVE_EVENTS,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
hasShards: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onMessage
|
||||
'messaging_message_received',
|
||||
(message: NativeInboundRemoteMessage) => {
|
||||
SharedEventEmitter.emit('onMessage', new RemoteMessage(message));
|
||||
}
|
||||
);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onMessage
|
||||
'messaging_token_refreshed',
|
||||
(token: string) => {
|
||||
SharedEventEmitter.emit('onTokenRefresh', token);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getToken(): Promise<string> {
|
||||
return getNativeModule(this).getToken();
|
||||
}
|
||||
|
||||
onMessage(nextOrObserver: OnMessage | OnMessageObserver): () => any {
|
||||
let listener: RemoteMessage => any;
|
||||
if (isFunction(nextOrObserver)) {
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
listener = nextOrObserver;
|
||||
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
|
||||
listener = nextOrObserver.next;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Messaging.onMessage failed: First argument must be a function or observer object with a `next` function.'
|
||||
);
|
||||
}
|
||||
|
||||
getLogger(this).info('Creating onMessage listener');
|
||||
|
||||
SharedEventEmitter.addListener('onMessage', listener);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onMessage listener');
|
||||
SharedEventEmitter.removeListener('onMessage', listener);
|
||||
};
|
||||
}
|
||||
|
||||
onTokenRefresh(
|
||||
nextOrObserver: OnTokenRefresh | OnTokenRefreshObserver
|
||||
): () => any {
|
||||
let listener: String => any;
|
||||
if (isFunction(nextOrObserver)) {
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
listener = nextOrObserver;
|
||||
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
|
||||
listener = nextOrObserver.next;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Messaging.OnTokenRefresh failed: First argument must be a function or observer object with a `next` function.'
|
||||
);
|
||||
}
|
||||
|
||||
getLogger(this).info('Creating onTokenRefresh listener');
|
||||
SharedEventEmitter.addListener('onTokenRefresh', listener);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onTokenRefresh listener');
|
||||
SharedEventEmitter.removeListener('onTokenRefresh', listener);
|
||||
};
|
||||
}
|
||||
|
||||
requestPermission(): Promise<void> {
|
||||
return getNativeModule(this).requestPermission();
|
||||
}
|
||||
|
||||
/**
|
||||
* NON WEB-SDK METHODS
|
||||
*/
|
||||
hasPermission(): Promise<boolean> {
|
||||
return getNativeModule(this).hasPermission();
|
||||
}
|
||||
|
||||
sendMessage(remoteMessage: RemoteMessage): Promise<void> {
|
||||
if (!(remoteMessage instanceof RemoteMessage)) {
|
||||
throw new Error(
|
||||
`Messaging:sendMessage expects a 'RemoteMessage' but got type ${typeof remoteMessage}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this).sendMessage(remoteMessage.build());
|
||||
}
|
||||
|
||||
subscribeToTopic(topic: string): void {
|
||||
getNativeModule(this).subscribeToTopic(topic);
|
||||
}
|
||||
|
||||
unsubscribeFromTopic(topic: string): void {
|
||||
getNativeModule(this).unsubscribeFromTopic(topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* KNOWN UNSUPPORTED METHODS
|
||||
*/
|
||||
|
||||
deleteToken() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'messaging',
|
||||
'deleteToken'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setBackgroundMessageHandler() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'messaging',
|
||||
'setBackgroundMessageHandler'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
useServiceWorker() {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'messaging',
|
||||
'useServiceWorker'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
RemoteMessage,
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
export type Notification = {
|
||||
body: string,
|
||||
bodyLocalizationArgs?: string[],
|
||||
bodyLocalizationKey?: string,
|
||||
clickAction?: string,
|
||||
color?: string,
|
||||
icon?: string,
|
||||
link?: string,
|
||||
sound: string,
|
||||
subtitle?: string,
|
||||
tag?: string,
|
||||
title: string,
|
||||
titleLocalizationArgs?: string[],
|
||||
titleLocalizationKey?: string,
|
||||
};
|
||||
|
||||
export type NativeInboundRemoteMessage = {
|
||||
collapseKey?: string,
|
||||
data: { [string]: string },
|
||||
from?: string,
|
||||
messageId: string,
|
||||
messageType?: string,
|
||||
sentTime?: number,
|
||||
to?: string,
|
||||
ttl?: number,
|
||||
};
|
||||
|
||||
export type NativeOutboundRemoteMessage = {
|
||||
collapseKey?: string,
|
||||
data: { [string]: string },
|
||||
messageId: string,
|
||||
messageType?: string,
|
||||
to: string,
|
||||
ttl: number,
|
||||
};
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidAction representation wrapper
|
||||
*/
|
||||
import RemoteInput, {
|
||||
fromNativeAndroidRemoteInput,
|
||||
} from './AndroidRemoteInput';
|
||||
import { SemanticAction } from './types';
|
||||
import type { NativeAndroidAction, SemanticActionType } from './types';
|
||||
|
||||
export default class AndroidAction {
|
||||
_action: string;
|
||||
_allowGeneratedReplies: boolean | void;
|
||||
_icon: string;
|
||||
_remoteInputs: RemoteInput[];
|
||||
_semanticAction: SemanticActionType | void;
|
||||
_showUserInterface: boolean | void;
|
||||
_title: string;
|
||||
|
||||
constructor(action: string, icon: string, title: string) {
|
||||
this._action = action;
|
||||
this._icon = icon;
|
||||
this._remoteInputs = [];
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
get action(): string {
|
||||
return this._action;
|
||||
}
|
||||
|
||||
get allowGeneratedReplies(): ?boolean {
|
||||
return this._allowGeneratedReplies;
|
||||
}
|
||||
|
||||
get icon(): string {
|
||||
return this._icon;
|
||||
}
|
||||
|
||||
get remoteInputs(): RemoteInput[] {
|
||||
return this._remoteInputs;
|
||||
}
|
||||
|
||||
get semanticAction(): ?SemanticActionType {
|
||||
return this._semanticAction;
|
||||
}
|
||||
|
||||
get showUserInterface(): ?boolean {
|
||||
return this._showUserInterface;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param remoteInput
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
addRemoteInput(remoteInput: RemoteInput): AndroidAction {
|
||||
if (!(remoteInput instanceof RemoteInput)) {
|
||||
throw new Error(
|
||||
`AndroidAction:addRemoteInput expects an 'RemoteInput' but got type ${typeof remoteInput}`
|
||||
);
|
||||
}
|
||||
this._remoteInputs.push(remoteInput);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param allowGeneratedReplies
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
setAllowGenerateReplies(allowGeneratedReplies: boolean): AndroidAction {
|
||||
this._allowGeneratedReplies = allowGeneratedReplies;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param semanticAction
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
setSemanticAction(semanticAction: SemanticActionType): AndroidAction {
|
||||
if (!Object.values(SemanticAction).includes(semanticAction)) {
|
||||
throw new Error(
|
||||
`AndroidAction:setSemanticAction Invalid Semantic Action: ${semanticAction}`
|
||||
);
|
||||
}
|
||||
this._semanticAction = semanticAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param showUserInterface
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
setShowUserInterface(showUserInterface: boolean): AndroidAction {
|
||||
this._showUserInterface = showUserInterface;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeAndroidAction {
|
||||
if (!this._action) {
|
||||
throw new Error('AndroidAction: Missing required `action` property');
|
||||
} else if (!this._icon) {
|
||||
throw new Error('AndroidAction: Missing required `icon` property');
|
||||
} else if (!this._title) {
|
||||
throw new Error('AndroidAction: Missing required `title` property');
|
||||
}
|
||||
|
||||
return {
|
||||
action: this._action,
|
||||
allowGeneratedReplies: this._allowGeneratedReplies,
|
||||
icon: this._icon,
|
||||
remoteInputs: this._remoteInputs.map(remoteInput => remoteInput.build()),
|
||||
semanticAction: this._semanticAction,
|
||||
showUserInterface: this._showUserInterface,
|
||||
title: this._title,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const fromNativeAndroidAction = (
|
||||
nativeAction: NativeAndroidAction
|
||||
): AndroidAction => {
|
||||
const action = new AndroidAction(
|
||||
nativeAction.action,
|
||||
nativeAction.icon,
|
||||
nativeAction.title
|
||||
);
|
||||
if (nativeAction.allowGeneratedReplies) {
|
||||
action.setAllowGenerateReplies(nativeAction.allowGeneratedReplies);
|
||||
}
|
||||
if (nativeAction.remoteInputs) {
|
||||
nativeAction.remoteInputs.forEach(remoteInput => {
|
||||
action.addRemoteInput(fromNativeAndroidRemoteInput(remoteInput));
|
||||
});
|
||||
}
|
||||
if (nativeAction.semanticAction) {
|
||||
action.setSemanticAction(nativeAction.semanticAction);
|
||||
}
|
||||
if (nativeAction.showUserInterface) {
|
||||
action.setShowUserInterface(nativeAction.showUserInterface);
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidChannel representation wrapper
|
||||
*/
|
||||
import { Importance, Visibility } from './types';
|
||||
import type { ImportanceType, VisibilityType } from './types';
|
||||
|
||||
type NativeAndroidChannel = {|
|
||||
bypassDnd?: boolean,
|
||||
channelId: string,
|
||||
description?: string,
|
||||
group?: string,
|
||||
importance: ImportanceType,
|
||||
lightColor?: string,
|
||||
lockScreenVisibility?: VisibilityType,
|
||||
name: string,
|
||||
showBadge?: boolean,
|
||||
sound?: string,
|
||||
vibrationPattern?: number[],
|
||||
|};
|
||||
|
||||
export default class AndroidChannel {
|
||||
_bypassDnd: boolean | void;
|
||||
_channelId: string;
|
||||
_description: string | void;
|
||||
_group: string | void;
|
||||
_importance: ImportanceType;
|
||||
_lightColor: string | void;
|
||||
_lockScreenVisibility: VisibilityType;
|
||||
_name: string;
|
||||
_showBadge: boolean | void;
|
||||
_sound: string | void;
|
||||
_vibrationPattern: number[] | void;
|
||||
|
||||
constructor(channelId: string, name: string, importance: ImportanceType) {
|
||||
if (!Object.values(Importance).includes(importance)) {
|
||||
throw new Error(`AndroidChannel() Invalid Importance: ${importance}`);
|
||||
}
|
||||
this._channelId = channelId;
|
||||
this._name = name;
|
||||
this._importance = importance;
|
||||
}
|
||||
|
||||
get bypassDnd(): ?boolean {
|
||||
return this._bypassDnd;
|
||||
}
|
||||
|
||||
get channelId(): string {
|
||||
return this._channelId;
|
||||
}
|
||||
|
||||
get description(): ?string {
|
||||
return this._description;
|
||||
}
|
||||
|
||||
get group(): ?string {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get importance(): ImportanceType {
|
||||
return this._importance;
|
||||
}
|
||||
|
||||
get lightColor(): ?string {
|
||||
return this._lightColor;
|
||||
}
|
||||
|
||||
get lockScreenVisibility(): ?VisibilityType {
|
||||
return this._lockScreenVisibility;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
get showBadge(): ?boolean {
|
||||
return this._showBadge;
|
||||
}
|
||||
|
||||
get sound(): ?string {
|
||||
return this._sound;
|
||||
}
|
||||
|
||||
get vibrationPattern(): ?(number[]) {
|
||||
return this._vibrationPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bypassDnd
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setBypassDnd(bypassDnd: boolean): AndroidChannel {
|
||||
this._bypassDnd = bypassDnd;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param description
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setDescription(description: string): AndroidChannel {
|
||||
this._description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param group
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setGroup(groupId: string): AndroidChannel {
|
||||
this._group = groupId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lightColor
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setLightColor(lightColor: string): AndroidChannel {
|
||||
this._lightColor = lightColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lockScreenVisibility
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setLockScreenVisibility(
|
||||
lockScreenVisibility: VisibilityType
|
||||
): AndroidChannel {
|
||||
if (!Object.values(Visibility).includes(lockScreenVisibility)) {
|
||||
throw new Error(
|
||||
`AndroidChannel:setLockScreenVisibility Invalid Visibility: ${lockScreenVisibility}`
|
||||
);
|
||||
}
|
||||
this._lockScreenVisibility = lockScreenVisibility;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param showBadge
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setShowBadge(showBadge: boolean): AndroidChannel {
|
||||
this._showBadge = showBadge;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sound
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setSound(sound: string): AndroidChannel {
|
||||
this._sound = sound;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param vibrationPattern
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setVibrationPattern(vibrationPattern: number[]): AndroidChannel {
|
||||
this._vibrationPattern = vibrationPattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeAndroidChannel {
|
||||
if (!this._channelId) {
|
||||
throw new Error('AndroidChannel: Missing required `channelId` property');
|
||||
} else if (!this._importance) {
|
||||
throw new Error('AndroidChannel: Missing required `importance` property');
|
||||
} else if (!this._name) {
|
||||
throw new Error('AndroidChannel: Missing required `name` property');
|
||||
}
|
||||
|
||||
return {
|
||||
bypassDnd: this._bypassDnd,
|
||||
channelId: this._channelId,
|
||||
description: this._description,
|
||||
group: this._group,
|
||||
importance: this._importance,
|
||||
lightColor: this._lightColor,
|
||||
lockScreenVisibility: this._lockScreenVisibility,
|
||||
name: this._name,
|
||||
showBadge: this._showBadge,
|
||||
sound: this._sound,
|
||||
vibrationPattern: this._vibrationPattern,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidChannelGroup representation wrapper
|
||||
*/
|
||||
|
||||
type NativeAndroidChannelGroup = {|
|
||||
groupId: string,
|
||||
name: string,
|
||||
|};
|
||||
|
||||
export default class AndroidChannelGroup {
|
||||
_groupId: string;
|
||||
_name: string;
|
||||
|
||||
constructor(groupId: string, name: string) {
|
||||
this._groupId = groupId;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
get groupId(): string {
|
||||
return this._groupId;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
build(): NativeAndroidChannelGroup {
|
||||
if (!this._groupId) {
|
||||
throw new Error(
|
||||
'AndroidChannelGroup: Missing required `groupId` property'
|
||||
);
|
||||
} else if (!this._name) {
|
||||
throw new Error('AndroidChannelGroup: Missing required `name` property');
|
||||
}
|
||||
|
||||
return {
|
||||
groupId: this._groupId,
|
||||
name: this._name,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,676 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidNotification representation wrapper
|
||||
*/
|
||||
import AndroidAction, { fromNativeAndroidAction } from './AndroidAction';
|
||||
import { BadgeIconType, Category, GroupAlert, Priority } from './types';
|
||||
import type Notification from './Notification';
|
||||
import type {
|
||||
BadgeIconTypeType,
|
||||
CategoryType,
|
||||
DefaultsType,
|
||||
GroupAlertType,
|
||||
Lights,
|
||||
NativeAndroidNotification,
|
||||
PriorityType,
|
||||
Progress,
|
||||
SmallIcon,
|
||||
VisibilityType,
|
||||
} from './types';
|
||||
|
||||
export default class AndroidNotification {
|
||||
_actions: AndroidAction[];
|
||||
_autoCancel: boolean | void;
|
||||
_badgeIconType: BadgeIconTypeType | void;
|
||||
_category: CategoryType | void;
|
||||
_channelId: string;
|
||||
_clickAction: string | void;
|
||||
_color: string | void;
|
||||
_colorized: boolean | void;
|
||||
_contentInfo: string | void;
|
||||
_defaults: DefaultsType[] | void;
|
||||
_group: string | void;
|
||||
_groupAlertBehaviour: GroupAlertType | void;
|
||||
_groupSummary: boolean | void;
|
||||
_largeIcon: string | void;
|
||||
_lights: Lights | void;
|
||||
_localOnly: boolean | void;
|
||||
_notification: Notification;
|
||||
_number: number | void;
|
||||
_ongoing: boolean | void;
|
||||
_onlyAlertOnce: boolean | void;
|
||||
_people: string[];
|
||||
_priority: PriorityType | void;
|
||||
_progress: Progress | void;
|
||||
// _publicVersion: Notification;
|
||||
_remoteInputHistory: string[] | void;
|
||||
_shortcutId: string | void;
|
||||
_showWhen: boolean | void;
|
||||
_smallIcon: SmallIcon;
|
||||
_sortKey: string | void;
|
||||
// TODO: style: Style; // Need to figure out if this can work
|
||||
_ticker: string | void;
|
||||
_timeoutAfter: number | void;
|
||||
_usesChronometer: boolean | void;
|
||||
_vibrate: number[] | void;
|
||||
_visibility: VisibilityType | void;
|
||||
_when: number | void;
|
||||
|
||||
// android unsupported
|
||||
// content: RemoteViews
|
||||
// contentIntent: PendingIntent - need to look at what this is
|
||||
// customBigContentView: RemoteViews
|
||||
// customContentView: RemoteViews
|
||||
// customHeadsUpContentView: RemoteViews
|
||||
// deleteIntent: PendingIntent
|
||||
// fullScreenIntent: PendingIntent
|
||||
// sound.streamType
|
||||
|
||||
constructor(notification: Notification, data?: NativeAndroidNotification) {
|
||||
this._notification = notification;
|
||||
|
||||
if (data) {
|
||||
this._actions = data.actions
|
||||
? data.actions.map(action => fromNativeAndroidAction(action))
|
||||
: [];
|
||||
this._autoCancel = data.autoCancel;
|
||||
this._badgeIconType = data.badgeIconType;
|
||||
this._category = data.category;
|
||||
this._channelId = data.channelId;
|
||||
this._clickAction = data.clickAction;
|
||||
this._color = data.color;
|
||||
this._colorized = data.colorized;
|
||||
this._contentInfo = data.contentInfo;
|
||||
this._defaults = data.defaults;
|
||||
this._group = data.group;
|
||||
this._groupAlertBehaviour = data.groupAlertBehaviour;
|
||||
this._groupSummary = data.groupSummary;
|
||||
this._largeIcon = data.largeIcon;
|
||||
this._lights = data.lights;
|
||||
this._localOnly = data.localOnly;
|
||||
this._number = data.number;
|
||||
this._ongoing = data.ongoing;
|
||||
this._onlyAlertOnce = data.onlyAlertOnce;
|
||||
this._people = data.people;
|
||||
this._priority = data.priority;
|
||||
this._progress = data.progress;
|
||||
// _publicVersion: Notification;
|
||||
this._remoteInputHistory = data.remoteInputHistory;
|
||||
this._shortcutId = data.shortcutId;
|
||||
this._showWhen = data.showWhen;
|
||||
this._smallIcon = data.smallIcon;
|
||||
this._sortKey = data.sortKey;
|
||||
this._ticker = data.ticker;
|
||||
this._timeoutAfter = data.timeoutAfter;
|
||||
this._usesChronometer = data.usesChronometer;
|
||||
this._vibrate = data.vibrate;
|
||||
this._visibility = data.visibility;
|
||||
this._when = data.when;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
this._actions = this._actions || [];
|
||||
this._people = this._people || [];
|
||||
this._smallIcon = this._smallIcon || {
|
||||
icon: 'ic_launcher',
|
||||
};
|
||||
}
|
||||
|
||||
get actions(): AndroidAction[] {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
get autoCancel(): ?boolean {
|
||||
return this._autoCancel;
|
||||
}
|
||||
|
||||
get badgeIconType(): ?BadgeIconTypeType {
|
||||
return this._badgeIconType;
|
||||
}
|
||||
|
||||
get category(): ?CategoryType {
|
||||
return this._category;
|
||||
}
|
||||
|
||||
get channelId(): string {
|
||||
return this._channelId;
|
||||
}
|
||||
|
||||
get clickAction(): ?string {
|
||||
return this._clickAction;
|
||||
}
|
||||
|
||||
get color(): ?string {
|
||||
return this._color;
|
||||
}
|
||||
|
||||
get colorized(): ?boolean {
|
||||
return this._colorized;
|
||||
}
|
||||
|
||||
get contentInfo(): ?string {
|
||||
return this._contentInfo;
|
||||
}
|
||||
|
||||
get defaults(): ?(DefaultsType[]) {
|
||||
return this._defaults;
|
||||
}
|
||||
|
||||
get group(): ?string {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get groupAlertBehaviour(): ?GroupAlertType {
|
||||
return this._groupAlertBehaviour;
|
||||
}
|
||||
|
||||
get groupSummary(): ?boolean {
|
||||
return this._groupSummary;
|
||||
}
|
||||
|
||||
get largeIcon(): ?string {
|
||||
return this._largeIcon;
|
||||
}
|
||||
|
||||
get lights(): ?Lights {
|
||||
return this._lights;
|
||||
}
|
||||
|
||||
get localOnly(): ?boolean {
|
||||
return this._localOnly;
|
||||
}
|
||||
|
||||
get number(): ?number {
|
||||
return this._number;
|
||||
}
|
||||
|
||||
get ongoing(): ?boolean {
|
||||
return this._ongoing;
|
||||
}
|
||||
|
||||
get onlyAlertOnce(): ?boolean {
|
||||
return this._onlyAlertOnce;
|
||||
}
|
||||
|
||||
get people(): string[] {
|
||||
return this._people;
|
||||
}
|
||||
|
||||
get priority(): ?PriorityType {
|
||||
return this._priority;
|
||||
}
|
||||
|
||||
get progress(): ?Progress {
|
||||
return this._progress;
|
||||
}
|
||||
|
||||
get remoteInputHistory(): ?(string[]) {
|
||||
return this._remoteInputHistory;
|
||||
}
|
||||
|
||||
get shortcutId(): ?string {
|
||||
return this._shortcutId;
|
||||
}
|
||||
|
||||
get showWhen(): ?boolean {
|
||||
return this._showWhen;
|
||||
}
|
||||
|
||||
get smallIcon(): SmallIcon {
|
||||
return this._smallIcon;
|
||||
}
|
||||
|
||||
get sortKey(): ?string {
|
||||
return this._sortKey;
|
||||
}
|
||||
|
||||
get ticker(): ?string {
|
||||
return this._ticker;
|
||||
}
|
||||
|
||||
get timeoutAfter(): ?number {
|
||||
return this._timeoutAfter;
|
||||
}
|
||||
|
||||
get usesChronometer(): ?boolean {
|
||||
return this._usesChronometer;
|
||||
}
|
||||
|
||||
get vibrate(): ?(number[]) {
|
||||
return this._vibrate;
|
||||
}
|
||||
|
||||
get visibility(): ?VisibilityType {
|
||||
return this._visibility;
|
||||
}
|
||||
|
||||
get when(): ?number {
|
||||
return this._when;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param action
|
||||
* @returns {Notification}
|
||||
*/
|
||||
addAction(action: AndroidAction): Notification {
|
||||
if (!(action instanceof AndroidAction)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:addAction expects an 'AndroidAction' but got type ${typeof action}`
|
||||
);
|
||||
}
|
||||
this._actions.push(action);
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param person
|
||||
* @returns {Notification}
|
||||
*/
|
||||
addPerson(person: string): Notification {
|
||||
this._people.push(person);
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param autoCancel
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setAutoCancel(autoCancel: boolean): Notification {
|
||||
this._autoCancel = autoCancel;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param badgeIconType
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setBadgeIconType(badgeIconType: BadgeIconTypeType): Notification {
|
||||
if (!Object.values(BadgeIconType).includes(badgeIconType)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:setBadgeIconType Invalid BadgeIconType: ${badgeIconType}`
|
||||
);
|
||||
}
|
||||
this._badgeIconType = badgeIconType;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param category
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setCategory(category: CategoryType): Notification {
|
||||
if (!Object.values(Category).includes(category)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:setCategory Invalid Category: ${category}`
|
||||
);
|
||||
}
|
||||
this._category = category;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param channelId
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setChannelId(channelId: string): Notification {
|
||||
this._channelId = channelId;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clickAction
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setClickAction(clickAction: string): Notification {
|
||||
this._clickAction = clickAction;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param color
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setColor(color: string): Notification {
|
||||
this._color = color;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param colorized
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setColorized(colorized: boolean): Notification {
|
||||
this._colorized = colorized;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param contentInfo
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setContentInfo(contentInfo: string): Notification {
|
||||
this._contentInfo = contentInfo;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param defaults
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setDefaults(defaults: DefaultsType[]): Notification {
|
||||
this._defaults = defaults;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param group
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setGroup(group: string): Notification {
|
||||
this._group = group;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param groupAlertBehaviour
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setGroupAlertBehaviour(groupAlertBehaviour: GroupAlertType): Notification {
|
||||
if (!Object.values(GroupAlert).includes(groupAlertBehaviour)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:setGroupAlertBehaviour Invalid GroupAlert: ${groupAlertBehaviour}`
|
||||
);
|
||||
}
|
||||
this._groupAlertBehaviour = groupAlertBehaviour;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param groupSummary
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setGroupSummary(groupSummary: boolean): Notification {
|
||||
this._groupSummary = groupSummary;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param largeIcon
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setLargeIcon(largeIcon: string): Notification {
|
||||
this._largeIcon = largeIcon;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param argb
|
||||
* @param onMs
|
||||
* @param offMs
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setLights(argb: number, onMs: number, offMs: number): Notification {
|
||||
this._lights = {
|
||||
argb,
|
||||
onMs,
|
||||
offMs,
|
||||
};
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param localOnly
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setLocalOnly(localOnly: boolean): Notification {
|
||||
this._localOnly = localOnly;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param number
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setNumber(number: number): Notification {
|
||||
this._number = number;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ongoing
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setOngoing(ongoing: boolean): Notification {
|
||||
this._ongoing = ongoing;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param onlyAlertOnce
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setOnlyAlertOnce(onlyAlertOnce: boolean): Notification {
|
||||
this._onlyAlertOnce = onlyAlertOnce;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param priority
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setPriority(priority: PriorityType): Notification {
|
||||
if (!Object.values(Priority).includes(priority)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:setPriority Invalid Priority: ${priority}`
|
||||
);
|
||||
}
|
||||
this._priority = priority;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param max
|
||||
* @param progress
|
||||
* @param indeterminate
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setProgress(
|
||||
max: number,
|
||||
progress: number,
|
||||
indeterminate: boolean
|
||||
): Notification {
|
||||
this._progress = {
|
||||
max,
|
||||
progress,
|
||||
indeterminate,
|
||||
};
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param publicVersion
|
||||
* @returns {Notification}
|
||||
*/
|
||||
/* setPublicVersion(publicVersion: Notification): Notification {
|
||||
this._publicVersion = publicVersion;
|
||||
return this._notification;
|
||||
} */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param remoteInputHistory
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setRemoteInputHistory(remoteInputHistory: string[]): Notification {
|
||||
this._remoteInputHistory = remoteInputHistory;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shortcutId
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setShortcutId(shortcutId: string): Notification {
|
||||
this._shortcutId = shortcutId;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param showWhen
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setShowWhen(showWhen: boolean): Notification {
|
||||
this._showWhen = showWhen;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param icon
|
||||
* @param level
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setSmallIcon(icon: string, level?: number): Notification {
|
||||
this._smallIcon = {
|
||||
icon,
|
||||
level,
|
||||
};
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sortKey
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setSortKey(sortKey: string): Notification {
|
||||
this._sortKey = sortKey;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ticker
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setTicker(ticker: string): Notification {
|
||||
this._ticker = ticker;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param timeoutAfter
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setTimeoutAfter(timeoutAfter: number): Notification {
|
||||
this._timeoutAfter = timeoutAfter;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param usesChronometer
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setUsesChronometer(usesChronometer: boolean): Notification {
|
||||
this._usesChronometer = usesChronometer;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param vibrate
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setVibrate(vibrate: number[]): Notification {
|
||||
this._vibrate = vibrate;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param when
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setWhen(when: number): Notification {
|
||||
this._when = when;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
build(): NativeAndroidNotification {
|
||||
// TODO: Validation of required fields
|
||||
if (!this._channelId) {
|
||||
throw new Error(
|
||||
'AndroidNotification: Missing required `channelId` property'
|
||||
);
|
||||
} else if (!this._smallIcon) {
|
||||
throw new Error(
|
||||
'AndroidNotification: Missing required `smallIcon` property'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
actions: this._actions.map(action => action.build()),
|
||||
autoCancel: this._autoCancel,
|
||||
badgeIconType: this._badgeIconType,
|
||||
category: this._category,
|
||||
channelId: this._channelId,
|
||||
clickAction: this._clickAction,
|
||||
color: this._color,
|
||||
colorized: this._colorized,
|
||||
contentInfo: this._contentInfo,
|
||||
defaults: this._defaults,
|
||||
group: this._group,
|
||||
groupAlertBehaviour: this._groupAlertBehaviour,
|
||||
groupSummary: this._groupSummary,
|
||||
largeIcon: this._largeIcon,
|
||||
lights: this._lights,
|
||||
localOnly: this._localOnly,
|
||||
number: this._number,
|
||||
ongoing: this._ongoing,
|
||||
onlyAlertOnce: this._onlyAlertOnce,
|
||||
people: this._people,
|
||||
priority: this._priority,
|
||||
progress: this._progress,
|
||||
// publicVersion: this._publicVersion,
|
||||
remoteInputHistory: this._remoteInputHistory,
|
||||
shortcutId: this._shortcutId,
|
||||
showWhen: this._showWhen,
|
||||
smallIcon: this._smallIcon,
|
||||
sortKey: this._sortKey,
|
||||
// TODO: style: Style,
|
||||
ticker: this._ticker,
|
||||
timeoutAfter: this._timeoutAfter,
|
||||
usesChronometer: this._usesChronometer,
|
||||
vibrate: this._vibrate,
|
||||
visibility: this._visibility,
|
||||
when: this._when,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidNotifications representation wrapper
|
||||
*/
|
||||
import { Platform } from 'react-native';
|
||||
import AndroidChannel from './AndroidChannel';
|
||||
import AndroidChannelGroup from './AndroidChannelGroup';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
import type Notifications from './';
|
||||
|
||||
export default class AndroidNotifications {
|
||||
_notifications: Notifications;
|
||||
|
||||
constructor(notifications: Notifications) {
|
||||
this._notifications = notifications;
|
||||
}
|
||||
|
||||
createChannel(channel: AndroidChannel): Promise<void> {
|
||||
if (Platform.OS === 'android') {
|
||||
if (!(channel instanceof AndroidChannel)) {
|
||||
throw new Error(
|
||||
`AndroidNotifications:createChannel expects an 'AndroidChannel' but got type ${typeof channel}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this._notifications).createChannel(
|
||||
channel.build()
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
createChannelGroup(channelGroup: AndroidChannelGroup): Promise<void> {
|
||||
if (Platform.OS === 'android') {
|
||||
if (!(channelGroup instanceof AndroidChannelGroup)) {
|
||||
throw new Error(
|
||||
`AndroidNotifications:createChannelGroup expects an 'AndroidChannelGroup' but got type ${typeof channelGroup}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this._notifications).createChannelGroup(
|
||||
channelGroup.build()
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
createChannelGroups(channelGroups: AndroidChannelGroup[]): Promise<void> {
|
||||
if (Platform.OS === 'android') {
|
||||
if (!Array.isArray(channelGroups)) {
|
||||
throw new Error(
|
||||
`AndroidNotifications:createChannelGroups expects an 'Array' but got type ${typeof channelGroups}`
|
||||
);
|
||||
}
|
||||
const nativeChannelGroups = [];
|
||||
for (let i = 0; i < channelGroups.length; i++) {
|
||||
const channelGroup = channelGroups[i];
|
||||
if (!(channelGroup instanceof AndroidChannelGroup)) {
|
||||
throw new Error(
|
||||
`AndroidNotifications:createChannelGroups expects array items of type 'AndroidChannelGroup' but got type ${typeof channelGroup}`
|
||||
);
|
||||
}
|
||||
nativeChannelGroups.push(channelGroup.build());
|
||||
}
|
||||
return getNativeModule(this._notifications).createChannelGroups(
|
||||
nativeChannelGroups
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
createChannels(channels: AndroidChannel[]): Promise<void> {
|
||||
if (Platform.OS === 'android') {
|
||||
if (!Array.isArray(channels)) {
|
||||
throw new Error(
|
||||
`AndroidNotifications:createChannels expects an 'Array' but got type ${typeof channels}`
|
||||
);
|
||||
}
|
||||
const nativeChannels = [];
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
const channel = channels[i];
|
||||
if (!(channel instanceof AndroidChannel)) {
|
||||
throw new Error(
|
||||
`AndroidNotifications:createChannels expects array items of type 'AndroidChannel' but got type ${typeof channel}`
|
||||
);
|
||||
}
|
||||
nativeChannels.push(channel.build());
|
||||
}
|
||||
return getNativeModule(this._notifications).createChannels(
|
||||
nativeChannels
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* @flow
|
||||
* AndroidRemoteInput representation wrapper
|
||||
*/
|
||||
|
||||
import type { AndroidAllowDataType, NativeAndroidRemoteInput } from './types';
|
||||
|
||||
export default class AndroidRemoteInput {
|
||||
_allowedDataTypes: AndroidAllowDataType[];
|
||||
_allowFreeFormInput: boolean | void;
|
||||
_choices: string[];
|
||||
_label: string | void;
|
||||
_resultKey: string;
|
||||
|
||||
constructor(resultKey: string) {
|
||||
this._allowedDataTypes = [];
|
||||
this._choices = [];
|
||||
this._resultKey = resultKey;
|
||||
}
|
||||
|
||||
get allowedDataTypes(): AndroidAllowDataType[] {
|
||||
return this._allowedDataTypes;
|
||||
}
|
||||
|
||||
get allowFreeFormInput(): ?boolean {
|
||||
return this._allowFreeFormInput;
|
||||
}
|
||||
|
||||
get choices(): string[] {
|
||||
return this._choices;
|
||||
}
|
||||
|
||||
get label(): ?string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
get resultKey(): string {
|
||||
return this._resultKey;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mimeType
|
||||
* @param allow
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setAllowDataType(mimeType: string, allow: boolean): AndroidRemoteInput {
|
||||
this._allowedDataTypes.push({
|
||||
allow,
|
||||
mimeType,
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param allowFreeFormInput
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setAllowFreeFormInput(allowFreeFormInput: boolean): AndroidRemoteInput {
|
||||
this._allowFreeFormInput = allowFreeFormInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param choices
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setChoices(choices: string[]): AndroidRemoteInput {
|
||||
this._choices = choices;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param label
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setLabel(label: string): AndroidRemoteInput {
|
||||
this._label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeAndroidRemoteInput {
|
||||
if (!this._resultKey) {
|
||||
throw new Error(
|
||||
'AndroidRemoteInput: Missing required `resultKey` property'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
allowedDataTypes: this._allowedDataTypes,
|
||||
allowFreeFormInput: this._allowFreeFormInput,
|
||||
choices: this._choices,
|
||||
label: this._label,
|
||||
resultKey: this._resultKey,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const fromNativeAndroidRemoteInput = (
|
||||
nativeRemoteInput: NativeAndroidRemoteInput
|
||||
): AndroidRemoteInput => {
|
||||
const remoteInput = new AndroidRemoteInput(nativeRemoteInput.resultKey);
|
||||
if (nativeRemoteInput.allowDataType) {
|
||||
for (let i = 0; i < nativeRemoteInput.allowDataType.length; i++) {
|
||||
const allowDataType = nativeRemoteInput.allowDataType[i];
|
||||
remoteInput.setAllowDataType(allowDataType.mimeType, allowDataType.allow);
|
||||
}
|
||||
}
|
||||
if (nativeRemoteInput.allowFreeFormInput) {
|
||||
remoteInput.setAllowFreeFormInput(nativeRemoteInput.allowFreeFormInput);
|
||||
}
|
||||
if (nativeRemoteInput.choices) {
|
||||
remoteInput.setChoices(nativeRemoteInput.choices);
|
||||
}
|
||||
if (nativeRemoteInput.label) {
|
||||
remoteInput.setLabel(nativeRemoteInput.label);
|
||||
}
|
||||
|
||||
return remoteInput;
|
||||
};
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* @flow
|
||||
* IOSNotification representation wrapper
|
||||
*/
|
||||
import type Notification from './Notification';
|
||||
import type {
|
||||
IOSAttachment,
|
||||
IOSAttachmentOptions,
|
||||
NativeIOSNotification,
|
||||
} from './types';
|
||||
|
||||
export default class IOSNotification {
|
||||
_alertAction: string | void; // alertAction | N/A
|
||||
_attachments: IOSAttachment[]; // N/A | attachments
|
||||
_badge: number | void; // applicationIconBadgeNumber | badge
|
||||
_category: string | void;
|
||||
_hasAction: boolean | void; // hasAction | N/A
|
||||
_launchImage: string | void; // alertLaunchImage | launchImageName
|
||||
_notification: Notification;
|
||||
_threadIdentifier: string | void; // N/A | threadIdentifier
|
||||
|
||||
constructor(notification: Notification, data?: NativeIOSNotification) {
|
||||
this._notification = notification;
|
||||
|
||||
if (data) {
|
||||
this._alertAction = data.alertAction;
|
||||
this._attachments = data.attachments;
|
||||
this._badge = data.badge;
|
||||
this._category = data.category;
|
||||
this._hasAction = data.hasAction;
|
||||
this._launchImage = data.launchImage;
|
||||
this._threadIdentifier = data.threadIdentifier;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
this._attachments = this._attachments || [];
|
||||
}
|
||||
|
||||
get alertAction(): ?string {
|
||||
return this._alertAction;
|
||||
}
|
||||
|
||||
get attachments(): IOSAttachment[] {
|
||||
return this._attachments;
|
||||
}
|
||||
|
||||
get badge(): ?number {
|
||||
return this._badge;
|
||||
}
|
||||
|
||||
get category(): ?string {
|
||||
return this._category;
|
||||
}
|
||||
|
||||
get hasAction(): ?boolean {
|
||||
return this._hasAction;
|
||||
}
|
||||
|
||||
get launchImage(): ?string {
|
||||
return this._launchImage;
|
||||
}
|
||||
|
||||
get threadIdentifier(): ?string {
|
||||
return this._threadIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param identifier
|
||||
* @param url
|
||||
* @param options
|
||||
* @returns {Notification}
|
||||
*/
|
||||
addAttachment(
|
||||
identifier: string,
|
||||
url: string,
|
||||
options?: IOSAttachmentOptions
|
||||
): Notification {
|
||||
this._attachments.push({
|
||||
identifier,
|
||||
options,
|
||||
url,
|
||||
});
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param alertAction
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setAlertAction(alertAction: string): Notification {
|
||||
this._alertAction = alertAction;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param badge
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setBadge(badge: number): Notification {
|
||||
this._badge = badge;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param category
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setCategory(category: string): Notification {
|
||||
this._category = category;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param hasAction
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setHasAction(hasAction: boolean): Notification {
|
||||
this._hasAction = hasAction;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param launchImage
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setLaunchImage(launchImage: string): Notification {
|
||||
this._launchImage = launchImage;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param threadIdentifier
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setThreadIdentifier(threadIdentifier: string): Notification {
|
||||
this._threadIdentifier = threadIdentifier;
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
build(): NativeIOSNotification {
|
||||
// TODO: Validation of required fields
|
||||
|
||||
return {
|
||||
alertAction: this._alertAction,
|
||||
attachments: this._attachments,
|
||||
badge: this._badge,
|
||||
category: this._category,
|
||||
hasAction: this._hasAction,
|
||||
launchImage: this._launchImage,
|
||||
threadIdentifier: this._threadIdentifier,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* @flow
|
||||
* Notification representation wrapper
|
||||
*/
|
||||
import { Platform } from 'react-native';
|
||||
import AndroidNotification from './AndroidNotification';
|
||||
import IOSNotification from './IOSNotification';
|
||||
import { generatePushID, isObject } from '../../utils';
|
||||
|
||||
import type { NativeNotification } from './types';
|
||||
|
||||
export type NotificationOpen = {|
|
||||
action: string,
|
||||
notification: Notification,
|
||||
results?: { [string]: string },
|
||||
|};
|
||||
|
||||
export default class Notification {
|
||||
// iOS 8/9 | 10+ | Android
|
||||
_android: AndroidNotification;
|
||||
_body: string; // alertBody | body | contentText
|
||||
_data: { [string]: string }; // userInfo | userInfo | extras
|
||||
_ios: IOSNotification;
|
||||
_notificationId: string;
|
||||
_sound: string | void; // soundName | sound | sound
|
||||
_subtitle: string | void; // N/A | subtitle | subText
|
||||
_title: string; // alertTitle | title | contentTitle
|
||||
|
||||
constructor(data?: NativeNotification) {
|
||||
this._android = new AndroidNotification(this, data && data.android);
|
||||
this._ios = new IOSNotification(this, data && data.ios);
|
||||
|
||||
if (data) {
|
||||
this._body = data.body;
|
||||
this._data = data.data;
|
||||
this._notificationId = data.notificationId;
|
||||
this._sound = data.sound;
|
||||
this._subtitle = data.subtitle;
|
||||
this._title = data.title;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
this._data = this._data || {};
|
||||
// TODO: Is this the best way to generate an ID?
|
||||
this._notificationId = this._notificationId || generatePushID();
|
||||
}
|
||||
|
||||
get android(): AndroidNotification {
|
||||
return this._android;
|
||||
}
|
||||
|
||||
get body(): string {
|
||||
return this._body;
|
||||
}
|
||||
|
||||
get data(): { [string]: string } {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
get ios(): IOSNotification {
|
||||
return this._ios;
|
||||
}
|
||||
|
||||
get notificationId(): string {
|
||||
return this._notificationId;
|
||||
}
|
||||
|
||||
get sound(): ?string {
|
||||
return this._sound;
|
||||
}
|
||||
|
||||
get subtitle(): ?string {
|
||||
return this._subtitle;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param body
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setBody(body: string): Notification {
|
||||
this._body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setData(data: Object = {}): Notification {
|
||||
if (!isObject(data)) {
|
||||
throw new Error(
|
||||
`Notification:withData expects an object but got type '${typeof data}'.`
|
||||
);
|
||||
}
|
||||
this._data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param notificationId
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setNotificationId(notificationId: string): Notification {
|
||||
this._notificationId = notificationId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sound
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setSound(sound: string): Notification {
|
||||
this._sound = sound;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param subtitle
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setSubtitle(subtitle: string): Notification {
|
||||
this._subtitle = subtitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param title
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setTitle(title: string): Notification {
|
||||
this._title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeNotification {
|
||||
// Android required fields: body, title, smallicon
|
||||
// iOS required fields: TODO
|
||||
if (!this._body) {
|
||||
throw new Error('Notification: Missing required `body` property');
|
||||
} else if (!this._notificationId) {
|
||||
throw new Error(
|
||||
'Notification: Missing required `notificationId` property'
|
||||
);
|
||||
} else if (!this._title) {
|
||||
throw new Error('Notification: Missing required `title` property');
|
||||
}
|
||||
|
||||
return {
|
||||
android: Platform.OS === 'android' ? this._android.build() : undefined,
|
||||
body: this._body,
|
||||
data: this._data,
|
||||
ios: Platform.OS === 'ios' ? this._ios.build() : undefined,
|
||||
notificationId: this._notificationId,
|
||||
sound: this._sound,
|
||||
subtitle: this._subtitle,
|
||||
title: this._title,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
/**
|
||||
* @flow
|
||||
* Notifications representation wrapper
|
||||
*/
|
||||
import { SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import { isFunction, isObject } from '../../utils';
|
||||
import AndroidAction from './AndroidAction';
|
||||
import AndroidChannel from './AndroidChannel';
|
||||
import AndroidChannelGroup from './AndroidChannelGroup';
|
||||
import AndroidNotifications from './AndroidNotifications';
|
||||
import AndroidRemoteInput from './AndroidRemoteInput';
|
||||
import Notification from './Notification';
|
||||
import {
|
||||
BadgeIconType,
|
||||
Category,
|
||||
Defaults,
|
||||
GroupAlert,
|
||||
Importance,
|
||||
Priority,
|
||||
SemanticAction,
|
||||
Visibility,
|
||||
} from './types';
|
||||
|
||||
import type App from '../core/app';
|
||||
import type { NotificationOpen } from './Notification';
|
||||
import type {
|
||||
NativeNotification,
|
||||
NativeNotificationOpen,
|
||||
Schedule,
|
||||
} from './types';
|
||||
|
||||
type OnNotification = Notification => any;
|
||||
|
||||
type OnNotificationObserver = {
|
||||
next: OnNotification,
|
||||
};
|
||||
|
||||
type OnNotificationOpened = NotificationOpen => any;
|
||||
|
||||
type OnNotificationOpenedObserver = {
|
||||
next: NotificationOpen,
|
||||
};
|
||||
|
||||
const NATIVE_EVENTS = [
|
||||
'notifications_notification_displayed',
|
||||
'notifications_notification_opened',
|
||||
'notifications_notification_received',
|
||||
];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseNotifications';
|
||||
export const NAMESPACE = 'notifications';
|
||||
|
||||
// iOS 8/9 scheduling
|
||||
// fireDate: Date;
|
||||
// timeZone: TimeZone;
|
||||
// repeatInterval: NSCalendar.Unit;
|
||||
// repeatCalendar: Calendar;
|
||||
// region: CLRegion;
|
||||
// regionTriggersOnce: boolean;
|
||||
|
||||
// iOS 10 scheduling
|
||||
// TODO
|
||||
|
||||
// Android scheduling
|
||||
// TODO
|
||||
|
||||
/**
|
||||
* @class Notifications
|
||||
*/
|
||||
export default class Notifications extends ModuleBase {
|
||||
_android: AndroidNotifications;
|
||||
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
events: NATIVE_EVENTS,
|
||||
hasShards: false,
|
||||
moduleName: MODULE_NAME,
|
||||
multiApp: false,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
this._android = new AndroidNotifications(this);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onNotificationDisplayed
|
||||
'notifications_notification_displayed',
|
||||
(notification: NativeNotification) => {
|
||||
SharedEventEmitter.emit(
|
||||
'onNotificationDisplayed',
|
||||
new Notification(notification)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onNotificationOpened
|
||||
'notifications_notification_opened',
|
||||
(notificationOpen: NativeNotificationOpen) => {
|
||||
SharedEventEmitter.emit('onNotificationOpened', {
|
||||
action: notificationOpen.action,
|
||||
notification: new Notification(notificationOpen.notification),
|
||||
results: notificationOpen.results,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
// public event name: onNotification
|
||||
'notifications_notification_received',
|
||||
(notification: NativeNotification) => {
|
||||
SharedEventEmitter.emit(
|
||||
'onNotification',
|
||||
new Notification(notification)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
get android(): AndroidNotifications {
|
||||
return this._android;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all notifications
|
||||
*/
|
||||
cancelAllNotifications(): void {
|
||||
getNativeModule(this).cancelAllNotifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a notification by id.
|
||||
* @param notificationId
|
||||
*/
|
||||
cancelNotification(notificationId: string): void {
|
||||
if (!notificationId) {
|
||||
throw new Error(
|
||||
'Notifications: cancelNotification expects a `notificationId`'
|
||||
);
|
||||
}
|
||||
getNativeModule(this).cancelNotification(notificationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a notification
|
||||
* @param notification
|
||||
* @returns {*}
|
||||
*/
|
||||
displayNotification(notification: Notification): Promise<void> {
|
||||
if (!(notification instanceof Notification)) {
|
||||
throw new Error(
|
||||
`Notifications:displayNotification expects a 'Notification' but got type ${typeof notification}`
|
||||
);
|
||||
}
|
||||
return getNativeModule(this).displayNotification(notification.build());
|
||||
}
|
||||
|
||||
getBadge(): Promise<number> {
|
||||
return getNativeModule(this).getBadge();
|
||||
}
|
||||
|
||||
getInitialNotification(): Promise<NotificationOpen> {
|
||||
return getNativeModule(this)
|
||||
.getInitialNotification()
|
||||
.then((notificationOpen: NativeNotificationOpen) => {
|
||||
if (notificationOpen) {
|
||||
return {
|
||||
action: notificationOpen.action,
|
||||
notification: new Notification(notificationOpen.notification),
|
||||
results: notificationOpen.results,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all scheduled notifications
|
||||
* @returns {Promise.<Array>}
|
||||
*/
|
||||
getScheduledNotifications(): Promise<Notification[]> {
|
||||
return getNativeModule(this).getScheduledNotifications();
|
||||
}
|
||||
|
||||
onNotification(
|
||||
nextOrObserver: OnNotification | OnNotificationObserver
|
||||
): () => any {
|
||||
let listener;
|
||||
if (isFunction(nextOrObserver)) {
|
||||
listener = nextOrObserver;
|
||||
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
|
||||
listener = nextOrObserver.next;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Notifications.onNotification failed: First argument must be a function or observer object with a `next` function.'
|
||||
);
|
||||
}
|
||||
|
||||
getLogger(this).info('Creating onNotification listener');
|
||||
SharedEventEmitter.addListener('onNotification', listener);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onNotification listener');
|
||||
SharedEventEmitter.removeListener('onNotification', listener);
|
||||
};
|
||||
}
|
||||
|
||||
onNotificationDisplayed(
|
||||
nextOrObserver: OnNotification | OnNotificationObserver
|
||||
): () => any {
|
||||
let listener;
|
||||
if (isFunction(nextOrObserver)) {
|
||||
listener = nextOrObserver;
|
||||
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
|
||||
listener = nextOrObserver.next;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Notifications.onNotificationDisplayed failed: First argument must be a function or observer object with a `next` function.'
|
||||
);
|
||||
}
|
||||
|
||||
getLogger(this).info('Creating onNotificationDisplayed listener');
|
||||
SharedEventEmitter.addListener('onNotificationDisplayed', listener);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onNotificationDisplayed listener');
|
||||
SharedEventEmitter.removeListener('onNotificationDisplayed', listener);
|
||||
};
|
||||
}
|
||||
|
||||
onNotificationOpened(
|
||||
nextOrObserver: OnNotificationOpened | OnNotificationOpenedObserver
|
||||
): () => any {
|
||||
let listener;
|
||||
if (isFunction(nextOrObserver)) {
|
||||
listener = nextOrObserver;
|
||||
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
|
||||
listener = nextOrObserver.next;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Notifications.onNotificationOpened failed: First argument must be a function or observer object with a `next` function.'
|
||||
);
|
||||
}
|
||||
|
||||
getLogger(this).info('Creating onNotificationOpened listener');
|
||||
SharedEventEmitter.addListener('onNotificationOpened', listener);
|
||||
|
||||
return () => {
|
||||
getLogger(this).info('Removing onNotificationOpened listener');
|
||||
SharedEventEmitter.removeListener('onNotificationOpened', listener);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all delivered notifications.
|
||||
*/
|
||||
removeAllDeliveredNotifications(): void {
|
||||
getNativeModule(this).removeAllDeliveredNotifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a delivered notification.
|
||||
* @param notificationId
|
||||
*/
|
||||
removeDeliveredNotification(notificationId: string): void {
|
||||
if (!notificationId) {
|
||||
throw new Error(
|
||||
'Notifications: removeDeliveredNotification expects a `notificationId`'
|
||||
);
|
||||
}
|
||||
getNativeModule(this).removeDeliveredNotification(notificationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a notification
|
||||
* @param notification
|
||||
* @returns {*}
|
||||
*/
|
||||
scheduleNotification(
|
||||
notification: Notification,
|
||||
schedule: Schedule
|
||||
): Promise<void> {
|
||||
if (!(notification instanceof Notification)) {
|
||||
throw new Error(
|
||||
`Notifications:scheduleNotification expects a 'Notification' but got type ${typeof notification}`
|
||||
);
|
||||
}
|
||||
const nativeNotification = notification.build();
|
||||
nativeNotification.schedule = schedule;
|
||||
return getNativeModule(this).scheduleNotification(nativeNotification);
|
||||
}
|
||||
|
||||
setBadge(badge: number): void {
|
||||
getNativeModule(this).setBadge(badge);
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
Android: {
|
||||
Action: AndroidAction,
|
||||
BadgeIconType,
|
||||
Category,
|
||||
Channel: AndroidChannel,
|
||||
ChannelGroup: AndroidChannelGroup,
|
||||
Defaults,
|
||||
GroupAlert,
|
||||
Importance,
|
||||
Priority,
|
||||
RemoteInput: AndroidRemoteInput,
|
||||
SemanticAction,
|
||||
Visibility,
|
||||
},
|
||||
Notification,
|
||||
};
|
|
@ -0,0 +1,216 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
export const BadgeIconType = {
|
||||
Large: 2,
|
||||
None: 0,
|
||||
Small: 1,
|
||||
};
|
||||
|
||||
export const Category = {
|
||||
Alarm: 'alarm',
|
||||
Call: 'call',
|
||||
Email: 'email',
|
||||
Error: 'err',
|
||||
Event: 'event',
|
||||
Message: 'msg',
|
||||
Progress: 'progress',
|
||||
Promo: 'promo',
|
||||
Recommendation: 'recommendation',
|
||||
Reminder: 'reminder',
|
||||
Service: 'service',
|
||||
Social: 'social',
|
||||
Status: 'status',
|
||||
System: 'system',
|
||||
Transport: 'transport',
|
||||
};
|
||||
|
||||
export const Defaults = {
|
||||
All: -1,
|
||||
Lights: 4,
|
||||
Sound: 1,
|
||||
Vibrate: 2,
|
||||
};
|
||||
|
||||
export const GroupAlert = {
|
||||
All: 0,
|
||||
Children: 2,
|
||||
Summary: 1,
|
||||
};
|
||||
|
||||
export const Importance = {
|
||||
Default: 3,
|
||||
High: 4,
|
||||
Low: 2,
|
||||
Max: 5,
|
||||
Min: 1,
|
||||
None: 3,
|
||||
Unspecified: -1000,
|
||||
};
|
||||
|
||||
export const Priority = {
|
||||
Default: 0,
|
||||
High: 1,
|
||||
Low: -1,
|
||||
Max: 2,
|
||||
Min: -2,
|
||||
};
|
||||
|
||||
export const SemanticAction = {
|
||||
Archive: 5,
|
||||
Call: 10,
|
||||
Delete: 4,
|
||||
MarkAsRead: 2,
|
||||
MarkAsUnread: 3,
|
||||
Mute: 6,
|
||||
None: 0,
|
||||
Reply: 1,
|
||||
ThumbsDown: 9,
|
||||
ThumbsUp: 8,
|
||||
Unmute: 7,
|
||||
};
|
||||
|
||||
export const Visibility = {
|
||||
Private: 0,
|
||||
Public: 1,
|
||||
Secret: -1,
|
||||
};
|
||||
|
||||
export type BadgeIconTypeType = $Values<typeof BadgeIconType>;
|
||||
export type CategoryType = $Values<typeof Category>;
|
||||
export type DefaultsType = $Values<typeof Defaults>;
|
||||
export type GroupAlertType = $Values<typeof GroupAlert>;
|
||||
export type ImportanceType = $Values<typeof Importance>;
|
||||
export type PriorityType = $Values<typeof Priority>;
|
||||
export type SemanticActionType = $Values<typeof SemanticAction>;
|
||||
export type VisibilityType = $Values<typeof Visibility>;
|
||||
|
||||
export type Lights = {|
|
||||
argb: number,
|
||||
onMs: number,
|
||||
offMs: number,
|
||||
|};
|
||||
|
||||
export type Progress = {|
|
||||
max: number,
|
||||
progress: number,
|
||||
indeterminate: boolean,
|
||||
|};
|
||||
|
||||
export type SmallIcon = {|
|
||||
icon: string,
|
||||
level?: number,
|
||||
|};
|
||||
|
||||
export type AndroidAllowDataType = {
|
||||
allow: boolean,
|
||||
mimeType: string,
|
||||
};
|
||||
|
||||
export type NativeAndroidRemoteInput = {|
|
||||
allowedDataTypes: AndroidAllowDataType[],
|
||||
allowFreeFormInput?: boolean,
|
||||
choices: string[],
|
||||
label?: string,
|
||||
resultKey: string,
|
||||
|};
|
||||
|
||||
export type NativeAndroidAction = {|
|
||||
action: string,
|
||||
allowGeneratedReplies?: boolean,
|
||||
icon: string,
|
||||
remoteInputs: NativeAndroidRemoteInput[],
|
||||
semanticAction?: SemanticActionType,
|
||||
showUserInterface?: boolean,
|
||||
title: string,
|
||||
|};
|
||||
|
||||
export type NativeAndroidNotification = {|
|
||||
actions?: NativeAndroidAction[],
|
||||
autoCancel?: boolean,
|
||||
badgeIconType?: BadgeIconTypeType,
|
||||
category?: CategoryType,
|
||||
channelId: string,
|
||||
clickAction?: string,
|
||||
color?: string,
|
||||
colorized?: boolean,
|
||||
contentInfo?: string,
|
||||
defaults?: DefaultsType[],
|
||||
group?: string,
|
||||
groupAlertBehaviour?: GroupAlertType,
|
||||
groupSummary?: boolean,
|
||||
largeIcon?: string,
|
||||
lights?: Lights,
|
||||
localOnly?: boolean,
|
||||
number?: number,
|
||||
ongoing?: boolean,
|
||||
onlyAlertOnce?: boolean,
|
||||
people: string[],
|
||||
priority?: PriorityType,
|
||||
progress?: Progress,
|
||||
// publicVersion: Notification,
|
||||
remoteInputHistory?: string[],
|
||||
shortcutId?: string,
|
||||
showWhen?: boolean,
|
||||
smallIcon: SmallIcon,
|
||||
sortKey?: string,
|
||||
// TODO: style: Style,
|
||||
ticker?: string,
|
||||
timeoutAfter?: number,
|
||||
usesChronometer?: boolean,
|
||||
vibrate?: number[],
|
||||
visibility?: VisibilityType,
|
||||
when?: number,
|
||||
|};
|
||||
|
||||
export type IOSAttachmentOptions = {|
|
||||
typeHint: string,
|
||||
thumbnailHidden: boolean,
|
||||
thumbnailClippingRect: {
|
||||
height: number,
|
||||
width: number,
|
||||
x: number,
|
||||
y: number,
|
||||
},
|
||||
thumbnailTime: number,
|
||||
|};
|
||||
|
||||
export type IOSAttachment = {|
|
||||
identifier: string,
|
||||
options?: IOSAttachmentOptions,
|
||||
url: string,
|
||||
|};
|
||||
|
||||
export type NativeIOSNotification = {|
|
||||
alertAction?: string,
|
||||
attachments: IOSAttachment[],
|
||||
badge?: number,
|
||||
category?: string,
|
||||
hasAction?: boolean,
|
||||
launchImage?: string,
|
||||
threadIdentifier?: string,
|
||||
|};
|
||||
|
||||
export type Schedule = {|
|
||||
exact?: boolean,
|
||||
fireDate: number,
|
||||
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
|
||||
|};
|
||||
|
||||
export type NativeNotification = {|
|
||||
android?: NativeAndroidNotification,
|
||||
body: string,
|
||||
data: { [string]: string },
|
||||
ios?: NativeIOSNotification,
|
||||
notificationId: string,
|
||||
schedule?: Schedule,
|
||||
sound?: string,
|
||||
subtitle?: string,
|
||||
title: string,
|
||||
|};
|
||||
|
||||
export type NativeNotificationOpen = {|
|
||||
action: string,
|
||||
notification: NativeNotification,
|
||||
results?: { [string]: string },
|
||||
|};
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* @flow
|
||||
* Trace representation wrapper
|
||||
*/
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import type PerformanceMonitoring from './';
|
||||
|
||||
export default class Trace {
|
||||
identifier: string;
|
||||
_perf: PerformanceMonitoring;
|
||||
|
||||
constructor(perf: PerformanceMonitoring, identifier: string) {
|
||||
this._perf = perf;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
getNativeModule(this._perf).start(this.identifier);
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
getNativeModule(this._perf).stop(this.identifier);
|
||||
}
|
||||
|
||||
incrementCounter(event: string): void {
|
||||
getNativeModule(this._perf).incrementCounter(this.identifier, event);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue