Merge commit '49d29b53f21e530f5c918e472db93ee856947426'

This commit is contained in:
Chris Bianca 2017-10-03 10:33:18 +01:00
commit 51074efdca
27 changed files with 1998 additions and 971 deletions

View File

@ -64,3 +64,6 @@ yarn.lock
tests
lib/.watchmanconfig
buddybuild_postclone.sh
bin/test.js
.github
example

View File

@ -3,13 +3,16 @@
[![npm version](https://img.shields.io/npm/v/react-native-firebase.svg?style=flat-square)](https://www.npmjs.com/package/react-native-firebase)
[![NPM downloads](https://img.shields.io/npm/dm/react-native-firebase.svg?style=flat-square)](https://www.npmjs.com/package/react-native-firebase)
[![Package Quality](http://npm.packagequality.com/shield/react-native-firebase.svg?style=flat-square)](http://packagequality.com/#?package=react-native-firebase)
[![License](https://img.shields.io/npm/l/react-native-firebase.svg?style=flat-square)](/LICENSE)
[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square)](https://discord.gg/t6bdqMs)
[![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](https://www.patreon.com/invertase)
**RNFirebase** makes using [Firebase](http://firebase.com) with React Native simple.
<!---
[![License](https://img.shields.io/npm/l/react-native-firebase.svg?style=flat-square)](/LICENSE)
-->
<hr>
> [Current Docs](http://invertase.link/docs) <b>|</b> [@next Docs](http://invertase.link/v3) <b>|</b> [iOS Install Guide](http://invertase.link/ios) <b>|</b> [Android Install Guide](http://invertase.link/android) <b>|</b> [FAQs](http://invertase.io/react-native-firebase/#/faqs) <b>|</b> [Feature Requests](http://invertase.link/requests)

View File

@ -1,5 +1,7 @@
package io.invertase.firebase.analytics;
import android.support.annotation.RequiresPermission;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
@ -13,6 +15,9 @@ import java.util.List;
@SuppressWarnings("unused")
public class RNFirebaseAnalyticsPackage implements ReactPackage {
@RequiresPermission(
allOf = {"android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE", "android.permission.WAKE_LOCK"}
)
public RNFirebaseAnalyticsPackage() {
}

View File

@ -1,5 +1,7 @@
package io.invertase.firebase.auth;
import android.app.Activity;
import android.os.Parcel;
import android.util.Log;
import android.net.Uri;
import android.support.annotation.NonNull;
@ -721,6 +723,101 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
});
}
/**
* verifyPhoneNumber
*
* @param appName
* @param phoneNumber
* @param timeout
*/
@ReactMethod
public void verifyPhoneNumber(final String appName, final String phoneNumber, final String requestKey, final int timeout) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
final FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
final Activity activity = mReactContext.getCurrentActivity();
Log.d(TAG, "verifyPhoneNumber:" + phoneNumber);
PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@Override
public void onVerificationCompleted(final PhoneAuthCredential phoneAuthCredential) {
Log.d(TAG, "verifyPhoneNumber:verification:onVerificationCompleted");
WritableMap state = Arguments.createMap();
Parcel parcel = Parcel.obtain();
phoneAuthCredential.writeToParcel(parcel, 0);
// verificationId
parcel.setDataPosition(16);
String verificationId = parcel.readString();
// sms Code
parcel.setDataPosition(parcel.dataPosition() + 8);
String code = parcel.readString();
state.putString("code", code);
state.putString("verificationId", verificationId);
parcel.recycle();
sendPhoneStateEvent(appName, requestKey, "onVerificationComplete", state);
}
@Override
public void onVerificationFailed(FirebaseException e) {
// This callback is invoked in an invalid request for verification is made,
// e.g. phone number format is incorrect, or the SMS quota for the project
// has been exceeded
Log.d(TAG, "verifyPhoneNumber:verification:onVerificationFailed");
WritableMap state = Arguments.createMap();
state.putMap("error", getJSError(e));
sendPhoneStateEvent(appName, requestKey, "onVerificationFailed", state);
}
@Override
public void onCodeSent(String verificationId, PhoneAuthProvider.ForceResendingToken forceResendingToken) {
Log.d(TAG, "verifyPhoneNumber:verification:onCodeSent");
WritableMap state = Arguments.createMap();
state.putString("verificationId", verificationId);
// todo forceResendingToken - it's actually just an empty class ... no actual token >.>
// Parcel parcel = Parcel.obtain();
// forceResendingToken.writeToParcel(parcel, 0);
//
// // verificationId
// parcel.setDataPosition(0);
// int int1 = parcel.readInt();
// String token = parcel.readString();
//
// state.putString("refreshToken", token);
// parcel.recycle();
state.putString("verificationId", verificationId);
sendPhoneStateEvent(appName, requestKey, "onCodeSent", state);
}
@Override
public void onCodeAutoRetrievalTimeOut(String verificationId) {
super.onCodeAutoRetrievalTimeOut(verificationId);
Log.d(TAG, "verifyPhoneNumber:verification:onCodeAutoRetrievalTimeOut");
WritableMap state = Arguments.createMap();
state.putString("verificationId", verificationId);
sendPhoneStateEvent(appName, requestKey, "onCodeAutoRetrievalTimeout", state);
}
};
if (activity != null) {
PhoneAuthProvider.getInstance(firebaseAuth)
.verifyPhoneNumber(
phoneNumber,
timeout,
TimeUnit.SECONDS,
activity,
callbacks
//, PhoneAuthProvider.ForceResendingToken.zzboe() // TODO FORCE RESENDING
);
}
}
/**
* confirmPasswordReset
*
@ -1270,4 +1367,19 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
return userMap;
}
/**
* @param appName
* @param requestKey
* @param type
* @param state
*/
private void sendPhoneStateEvent(String appName, String requestKey, String type, WritableMap state) {
WritableMap eventMap = Arguments.createMap();
eventMap.putString("appName", appName);
eventMap.putString("requestKey", requestKey);
eventMap.putString("type", type);
eventMap.putMap("state", state);
Utils.sendEvent(mReactContext, "phone_auth_state_changed", eventMap);
}
}

View File

@ -27,6 +27,7 @@ import com.google.firebase.messaging.RemoteMessage.Notification;
import io.invertase.firebase.Utils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
@ -72,6 +73,18 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements L
promise.resolve(FirebaseInstanceId.getInstance().getToken());
}
@ReactMethod
public void deleteInstanceId(Promise promise){
try {
Log.d(TAG, "Deleting instance id");
FirebaseInstanceId.getInstance().deleteInstanceId();
promise.resolve(null);
} catch (IOException e) {
Log.e(TAG, e.getMessage());
promise.reject(null, e.getMessage());
}
}
@ReactMethod
public void createLocalNotification(ReadableMap details) {
Bundle bundle = Arguments.toBundle(details);

View File

@ -1,5 +1,7 @@
package io.invertase.firebase.storage;
import android.support.annotation.RequiresPermission;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
@ -13,6 +15,9 @@ import java.util.List;
@SuppressWarnings("unused")
public class RNFirebaseStoragePackage implements ReactPackage {
@RequiresPermission(
allOf = {"android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE"}
)
public RNFirebaseStoragePackage() {
}
@ -35,7 +40,7 @@ public class RNFirebaseStoragePackage implements ReactPackage {
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
* will be automatically included in the JS bundle.
*/
// TODO: Removed in 0.47.0. Here for backwards compatability
// TODO: Removed in 0.47.0. Here for backwards compatibility
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}

52
bin/test.js Normal file
View File

@ -0,0 +1,52 @@
const shell = require('shelljs');
const WATCH_SRC = require('path').resolve('./lib');
const WATCH_OUT = require('path').resolve('./tests/firebase');
/*
"tests-watch-init": "wml add $(node --eval \"console.log(require('path').resolve('./lib'));\") $(node --eval \"console.log(require('path').resolve('./tests/firebase'));\")",
"tests-watch-start": "watchman watch $(node --eval \"console.log(require('path').resolve('./lib'));\") && wml start",
"tests-watch-stop": "watchman watch-del $(node --eval \"console.log(require('path').resolve('./lib'));\") && wml stop"
*/
if (process.argv.includes('watch')) {
if (!shell.which('wml')) {
shell.echo('');
shell.echo('---------------------------------------------------');
shell.echo(' Missing required npm global from library wix/wml. ');
shell.echo('---------------------------------------------------');
shell.echo('');
shell.exit(1);
}
if (!shell.which('watchman')) {
shell.echo('');
shell.echo('---------------------------------------------------');
shell.echo(' Missing required executable: watchman ');
shell.echo('---------------------------------------------------');
shell.echo('');
shell.exit(1);
}
if (process.argv.includes('init')) {
console.log(`wml add ${WATCH_SRC} ${WATCH_OUT}`);
if (shell.exec(`wml add ${WATCH_SRC} ${WATCH_OUT}`).code !== 0) {
shell.echo('Error setting up watched location via WML.');
shell.exit(1);
}
}
if (process.argv.includes('start')) {
console.log(`watchman watch ${WATCH_SRC} && wml start`);
const watcher = shell.exec(`watchman watch ${WATCH_SRC} && wml start`, { async: true });
watcher.stdout.on('data', console.log);
watcher.stderr.on('data', console.error);
}
if (process.argv.includes('stop')) {
console.log(`watchman watch-del ${WATCH_SRC} && wml stop && wml rm all`);
const watcher = shell.exec(`watchman watch-del ${WATCH_SRC} && wml stop && wml rm all`, { async: true });
watcher.stdout.on('data', console.log);
watcher.stderr.on('data', console.error);
}
}

View File

@ -53,6 +53,20 @@ firebase.messaging().getToken()
});
```
### deleteInstanceId(): `Promise<any>`
Reset Instance ID and revokes all tokens.
```javascript
firebase.messaging().deleteInstanceId()
.then(() => {
console.log('Deleted instance id successfully');
})
.catch((error: any) => {
console.log(`Cannot delete instance id: ${error.message}`);
});
```
### onTokenRefresh(listener: `Function<string>`)
On the event a devices FCM token is refreshed by Google, the new token is returned in a callback listener.

8
index.d.ts vendored
View File

@ -8,7 +8,7 @@ declare module "react-native-firebase" {
export default class FireBase {
constructor(config?: RNFirebase.configurationOptions)
log: any
log: any;
analytics(): RNFirebase.Analytics;
@ -22,7 +22,7 @@ declare module "react-native-firebase" {
ServerValue: {
TIMESTAMP: number
}
}
};
/**RNFirebase mimics the Web Firebase SDK Storage,
* whilst providing some iOS and Android specific functionality.
@ -608,6 +608,10 @@ declare module "react-native-firebase" {
* This token can be used in the Firebase console to send messages to directly.
*/
getToken(forceRefresh?: Boolean): Promise<string>
/**
* Reset Instance ID and revokes all tokens.
*/
deleteInstanceId(): Promise<any>
/**
* On the event a devices FCM token is refreshed by Google,
* the new token is returned in a callback listener.

View File

@ -79,7 +79,7 @@ RCT_EXPORT_METHOD(deleteApp:
/**
* React native constant exports - exports native firebase apps mainly
* @return
* @return NSDictionary
*/
- (NSDictionary *)constantsToExport {
NSMutableDictionary *constants = [NSMutableDictionary new];

View File

@ -5,6 +5,7 @@
static NSString *const AUTH_CHANGED_EVENT = @"auth_state_changed";
static NSString *const AUTH_ID_TOKEN_CHANGED_EVENT = @"auth_id_token_changed";
static NSString *const PHONE_AUTH_STATE_CHANGED_EVENT = @"phone_auth_state_changed";
// Database
static NSString *const DATABASE_SYNC_EVENT = @"database_sync_event";

View File

@ -7,15 +7,15 @@
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(log:(NSString *)message) {
FIRCrashLog(message);
FIRCrashLog(@"%@", message);
}
RCT_EXPORT_METHOD(logcat:(nonnull NSNumber *) level tag:(NSString *) tag message:(NSString *) message) {
FIRCrashLog(message);
FIRCrashLog(@"%@", message);
}
RCT_EXPORT_METHOD(report:(NSString *) message) {
FIRCrashLog(message);
FIRCrashLog(@"%@", message);
assert(NO);
}

View File

@ -4,6 +4,7 @@
#if __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
#import "RNFirebaseEvents.h"
#import <FirebaseMessaging/FirebaseMessaging.h>
#import <FirebaseInstanceID/FIRInstanceID.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTConvert.h>
@ -257,6 +258,16 @@ RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseR
resolve([FIRMessaging messaging].FCMToken);
}
RCT_EXPORT_METHOD(deleteInstanceId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
[[FIRInstanceID instanceID] deleteIDWithHandler:^(NSError * _Nullable error) {
if (!error) {
resolve(nil);
} else {
reject(@"instance_id_error", @"Failed to delete instance id", error);
}
}];
}
RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (RCTRunningInAppExtension()) {
return;

View File

@ -221,8 +221,22 @@ RCT_EXPORT_METHOD(putFile:(NSString *) appName
options.networkAccessAllowed = true;
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
if (info[PHImageErrorKey] == nil) {
firmetadata.contentType = [self utiToMimeType:dataUTI];
[self uploadData:appName data:imageData firmetadata:firmetadata path:path resolver:resolve rejecter:reject];
if (UTTypeConformsTo((__bridge CFStringRef)dataUTI, kUTTypeJPEG)) {
firmetadata.contentType = [self utiToMimeType:dataUTI];
[self uploadData:appName data:imageData firmetadata:firmetadata path:path resolver:resolve rejecter:reject];
} else {
// if the image UTI is not JPEG then convert to JPEG, e.g. HEI
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
NSDictionary *imageInfo = (__bridge NSDictionary*)CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
NSDictionary *imageMetadata = [imageInfo copy];
NSMutableData *imageDataJPEG = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataJPEG, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef)imageMetadata);
CGImageDestinationFinalize(destination);
// Manually set mimetype to JPEG
firmetadata.contentType = @"image/jpeg";
[self uploadData:appName data:[NSData dataWithData:imageDataJPEG] firmetadata:firmetadata path:path resolver:resolve rejecter:reject];
}
} else {
reject(@"storage/request-image-data-failed", @"Could not obtain image data for the specified file.", nil);
}

View File

@ -1,6 +1,6 @@
// @flow
import INTERNALS from './../../internals';
import { generatePushID, isFunction, isAndroid, isIOS, isString } from './../../utils';
import { generatePushID, isFunction, isAndroid, isIOS, isString, nativeToJSError } from './../../utils';
type PhoneAuthSnapshot = {
state: 'sent' | 'timeout' | 'verified' | 'error',
@ -17,15 +17,16 @@ type PhoneAuthError = {
};
export default class PhoneAuthListener {
_auth: Object;
_reject: Function | null;
_resolve: Function | null;
_promise: Promise | null;
_credential: Object | null;
_timeout: number;
_phoneAuthRequestKey: string;
_publicEvents: Object;
_internalEvents: Object;
_reject: Function | null;
_resolve: Function | null;
_credential: Object | null;
_promise: Promise<*> | null;
_phoneAuthRequestKey: string;
/**
*
@ -33,14 +34,14 @@ export default class PhoneAuthListener {
* @param phoneNumber
* @param timeout
*/
constructor(auth: Object, phoneNumber: string, timeout): PhoneAuthListener {
constructor(auth: Object, phoneNumber: string, timeout?: number) {
this._auth = auth;
this._reject = null;
this._resolve = null;
this._promise = null;
this._credential = null;
this._timeout = timeout || 20000; // 20 secs
this._timeout = timeout || 20; // 20 secs
this._phoneAuthRequestKey = generatePushID();
// internal events
@ -65,11 +66,20 @@ export default class PhoneAuthListener {
this._subscribeToEvents();
// start verification flow natively
this._auth._native.verifyPhoneNumber(
phoneNumber,
this._phoneAuthRequestKey,
this._timeout,
);
if (isAndroid) {
this._auth._native.verifyPhoneNumber(
phoneNumber,
this._phoneAuthRequestKey,
this._timeout,
);
}
if (isIOS) {
this._auth._native.verifyPhoneNumber(
phoneNumber,
this._phoneAuthRequestKey,
);
}
}
/**
@ -110,8 +120,6 @@ export default class PhoneAuthListener {
*/
_emitToErrorCb(snapshot) {
const error = snapshot.error;
error.verificationId = snapshot.verificationId;
if (this._reject) this._reject(error);
this._auth.emit(this._publicEvents.error, error);
}
@ -174,12 +182,12 @@ export default class PhoneAuthListener {
/**
* Internal code sent event handler
* @param verificationId
* @private
* @param credential
*/
_codeSentHandler(verificationId) {
_codeSentHandler(credential) {
const snapshot: PhoneAuthSnapshot = {
verificationId,
verificationId: credential.verificationId,
code: null,
error: null,
state: 'sent',
@ -199,12 +207,12 @@ export default class PhoneAuthListener {
/**
* Internal code auto retrieve timeout event handler
* @param verificationId
* @private
* @param credential
*/
_codeAutoRetrievalTimeoutHandler(verificationId) {
_codeAutoRetrievalTimeoutHandler(credential) {
const snapshot: PhoneAuthSnapshot = {
verificationId,
verificationId: credential.verificationId,
code: null,
error: null,
state: 'timeout',
@ -234,17 +242,19 @@ export default class PhoneAuthListener {
/**
* Internal verification failed event handler
* @param errObject
* @param state
* @private
*/
_verificationFailedHandler(errObject) {
_verificationFailedHandler(state) {
const snapshot: PhoneAuthSnapshot = {
verificationId: errObject.verificationId,
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);
@ -256,7 +266,7 @@ export default class PhoneAuthListener {
-- PUBLIC API
--------------*/
on(event: string, observer: () => PhoneAuthSnapshot, errorCb?: () => PhoneAuthError, successCb?: () => PhoneAuthSnapshot) {
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'));
}
@ -286,17 +296,19 @@ export default class PhoneAuthListener {
* Promise .then proxy
* @param fn
*/
then(fn) {
then(fn: () => PhoneAuthSnapshot) {
this._promiseDeferred();
return this._promise.then.bind(this._promise)(fn);
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) {
catch(fn: () => Error) {
this._promiseDeferred();
return this._promise.catch.bind(this._promise)(fn);
if (this._promise) return this._promise.catch.bind(this._promise)(fn);
return undefined; // will never get here - just to keep flow happy
}
}

View File

@ -19,6 +19,8 @@ export default class Auth extends ModuleBase {
static _NATIVE_MODULE = 'RNFirebaseAuth';
_user: User | null;
_native: Object;
_getAppEventName: Function;
_authResult: AuthResultType | null;
authenticated: boolean;
@ -35,6 +37,13 @@ export default class Auth extends ModuleBase {
this._onAuthStateChanged.bind(this),
);
this.addListener(
// sub to internal native event - this fans out to
// public events based on event.type
this._getAppEventName('phone_auth_state_changed'),
this._onPhoneAuthStateChanged.bind(this),
);
this.addListener(
// sub to internal native event - this fans out to
// public event name: onIdTokenChanged
@ -46,6 +55,16 @@ export default class Auth extends ModuleBase {
this._native.addIdTokenListener();
}
/**
* Route a phone state change event to the correct listeners
* @param event
* @private
*/
_onPhoneAuthStateChanged(event: Object) {
const eventKey = `phone:auth:${event.requestKey}:${event.type}`;
this.emit(eventKey, event.state);
}
/**
* Internal auth changed listener
* @param auth
@ -100,7 +119,7 @@ export default class Auth extends ModuleBase {
* Intercept all user actions and send their results to
* auth state change before resolving
* @param promise
* @returns {Promise.<TResult>|*}
* @returns {Promise.<*>}
* @private
*/
_interceptUserValue(promise) {
@ -234,7 +253,7 @@ export default class Auth extends ModuleBase {
* @param newPassword
* @return {Promise.<Null>}
*/
confirmPasswordReset(code: string, newPassword: string): Promise<Null> {
confirmPasswordReset(code: string, newPassword: string): Promise<null> {
return this._native.confirmPasswordReset(code, newPassword);
}
@ -245,7 +264,7 @@ export default class Auth extends ModuleBase {
* @param code
* @return {Promise.<Null>}
*/
applyActionCode(code: string): Promise<Any> {
applyActionCode(code: string): Promise<any> {
return this._native.applyActionCode(code);
}
@ -254,9 +273,9 @@ export default class Auth extends ModuleBase {
*
* @link https://firebase.google.com/docs/reference/js/firebase.auth.Auth#checkActionCode
* @param code
* @return {Promise.<Any>|Promise<ActionCodeInfo>}
* @return {Promise.<any>|Promise<ActionCodeInfo>}
*/
checkActionCode(code: string): Promise<Any> {
checkActionCode(code: string): Promise<any> {
return this._native.checkActionCode(code);
}

View File

@ -1,4 +1,4 @@
import { Platform } from 'react-native';
import { Platform, NativeModules } from 'react-native';
import ModuleBase from './../../utils/ModuleBase';
import RemoteMessage from './RemoteMessage';
@ -25,6 +25,8 @@ const WILL_PRESENT_RESULT = {
None: 'UNNotificationPresentationOptionNone',
};
const FirebaseMessaging = NativeModules.FirebaseMessaging;
/**
* IOS only finish function
* @param data
@ -109,6 +111,14 @@ export default class Messaging extends ModuleBase {
return this._native.getToken();
}
/**
* Reset Instance ID and revokes all tokens.
* @returns {*|Promise.<*>}
*/
deleteInstanceId() {
return this._native.deleteInstanceId();
}
/**
* Create and display a local notification
* @param notification

View File

@ -6,6 +6,8 @@ export default class PerformanceMonitoring extends ModuleBase {
static _NAMESPACE = 'perf';
static _NATIVE_MODULE = 'RNFirebasePerformance';
_native: Object;
constructor(firebaseApp: Object, options: Object = {}) {
super(firebaseApp, options);
}
@ -26,8 +28,4 @@ export default class PerformanceMonitoring extends ModuleBase {
newTrace(trace: string): void {
return new Trace(this, trace);
}
get namespace(): string {
return 'firebase:perf';
}
}

View File

@ -2,10 +2,12 @@
* @flow
*/
import { NativeModules } from 'react-native';
import Log from '../utils/log';
import { nativeWithApp } from './../utils';
import INTERNALS from './../internals';
import FirebaseCore from './../firebase';
import FirebaseApp from '../firebase-app';
import { nativeWithApp } from './../utils';
const logs = {};
@ -24,6 +26,7 @@ const NATIVE_MODULE_EVENTS = {
],
Auth: [
'auth_state_changed',
'phone_auth_state_changed',
],
Database: [
'database_transaction_event',
@ -42,14 +45,23 @@ const DEFAULTS = {
};
export default class ModuleBase {
_native: Object;
_module: string;
_options: Object;
_appName: string;
_namespace: string;
_firebaseApp: Object;
_eventEmitter: Object;
static _NAMESPACE: string;
static _NATIVE_MODULE: string;
/**
*
* @param firebaseApp
* @param options
* @param moduleName
* @param withEventEmitter
*/
constructor(firebaseApp, options, withEventEmitter = false) {
constructor(firebaseApp: Object, options: Object, withEventEmitter: boolean = false) {
this._module = this.constructor._NATIVE_MODULE.replace('RNFirebase', '');
this._firebaseApp = firebaseApp;
this._appName = firebaseApp._name;
@ -113,7 +125,7 @@ export default class ModuleBase {
* Returns the FirebaseApp instance for current module
* @return {*}
*/
get app() {
get app(): FirebaseApp {
return this._firebaseApp;
}
@ -128,27 +140,27 @@ export default class ModuleBase {
* Proxy functions to shared event emitter instance
* https://github.com/facebook/react-native/blob/master/Libraries/EventEmitter/EventEmitter.js
*/
get sharedEventEmitter() {
get sharedEventEmitter(): Object {
return INTERNALS.SharedEventEmitter;
}
get addListener() {
get addListener(): Function {
return INTERNALS.SharedEventEmitter.addListener.bind(INTERNALS.SharedEventEmitter);
}
get once() {
get once(): Function {
return INTERNALS.SharedEventEmitter.once.bind(INTERNALS.SharedEventEmitter);
}
get on() {
get on(): Function {
return INTERNALS.SharedEventEmitter.addListener.bind(INTERNALS.SharedEventEmitter);
}
get emit() {
get emit(): Function {
return INTERNALS.SharedEventEmitter.emit.bind(INTERNALS.SharedEventEmitter);
}
get listeners() {
get listeners(): Function {
return INTERNALS.SharedEventEmitter.listeners.bind(INTERNALS.SharedEventEmitter);
}
@ -157,11 +169,11 @@ export default class ModuleBase {
return subscriptions && subscriptions.length;
}
get removeListener() {
get removeListener(): Function {
return INTERNALS.SharedEventEmitter.removeListener.bind(INTERNALS.SharedEventEmitter);
}
get removeAllListeners() {
get removeAllListeners(): Function {
return INTERNALS.SharedEventEmitter.removeAllListeners.bind(INTERNALS.SharedEventEmitter);
}
}

View File

@ -7,7 +7,8 @@ import { Platform } from 'react-native';
const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
const AUTO_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const hasOwnProperty = Object.hasOwnProperty;
const DEFAULT_CHUNK_SIZE = 50;
// const DEFAULT_CHUNK_SIZE = 50;
// Source: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical
const REGEXP_FIELD_NAME = new RegExp(
@ -92,7 +93,7 @@ export function isFunction(item?: any): boolean {
* @param value
* @return {boolean}
*/
export function isString(value): Boolean {
export function isString(value: any): boolean {
return typeof value === 'string';
}
@ -148,63 +149,63 @@ export function noop(): void {
}
/**
* Delays chunks based on sizes per event loop.
* @param collection
* @param chunkSize
* @param operation
* @param callback
* @private
*/
function _delayChunk(collection: Array<*>,
chunkSize: number,
operation: Function,
callback: Function): void {
const length = collection.length;
const iterations = Math.ceil(length / chunkSize);
// noinspection ES6ConvertVarToLetConst
let thisIteration = 0;
setImmediate(function next() {
const start = thisIteration * chunkSize;
const _end = start + chunkSize;
const end = _end >= length ? length : _end;
const result = operation(collection.slice(start, end), start, end);
if (thisIteration++ > iterations) {
callback(null, result);
} else {
setImmediate(next);
}
});
}
/**
* Async each with optional chunk size limit
* @param array
* @param chunkSize
* @param iterator
* @param cb
*/
export function each(array: Array<*>,
chunkSize: number | Function,
iterator: Function,
cb?: Function): void {
if (typeof chunkSize === 'function') {
cb = iterator;
iterator = chunkSize;
chunkSize = DEFAULT_CHUNK_SIZE;
}
if (cb) {
_delayChunk(array, chunkSize, (slice, start) => {
for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
iterator(slice[ii], start + ii);
}
}, cb);
}
}
// /**
// * Delays chunks based on sizes per event loop.
// * @param collection
// * @param chunkSize
// * @param operation
// * @param callback
// * @private
// */
// function _delayChunk(collection: Array<*>,
// chunkSize: number,
// operation: Function,
// callback: Function): void {
// const length = collection.length;
// const iterations = Math.ceil(length / chunkSize);
//
// // noinspection ES6ConvertVarToLetConst
// let thisIteration = 0;
//
// setImmediate(function next() {
// const start = thisIteration * chunkSize;
// const _end = start + chunkSize;
// const end = _end >= length ? length : _end;
// const result = operation(collection.slice(start, end), start, end);
//
// if (thisIteration++ > iterations) {
// callback(null, result);
// } else {
// setImmediate(next);
// }
// });
// }
//
// /**
// * Async each with optional chunk size limit
// * @param array
// * @param chunkSize
// * @param iterator
// * @param cb
// */
// export function each(array: Array<*>,
// chunkSize: number | Function,
// iterator: Function,
// cb?: Function): void {
// if (typeof chunkSize === 'function') {
// cb = iterator;
// iterator = chunkSize;
// chunkSize = DEFAULT_CHUNK_SIZE;
// }
//
// if (cb) {
// _delayChunk(array, chunkSize, (slice, start) => {
// for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
// iterator(slice[ii], start + ii);
// }
// }, cb);
// }
// }
/**
* Returns a string typeof that's valid for Firebase usage
@ -217,41 +218,41 @@ export function typeOf(value: any): string {
return typeof value;
}
/**
* Async map with optional chunk size limit
* @param array
* @param chunkSize
* @param iterator
* @param cb
* @returns {*}
*/
export function map(array: Array<*>,
chunkSize: number | Function,
iterator: Function,
cb?: Function): void {
if (typeof chunkSize === 'function') {
cb = iterator;
iterator = chunkSize;
chunkSize = DEFAULT_CHUNK_SIZE;
}
// /**
// * Async map with optional chunk size limit
// * @param array
// * @param chunkSize
// * @param iterator
// * @param cb
// * @returns {*}
// */
// export function map(array: Array<*>,
// chunkSize: number | Function,
// iterator: Function,
// cb?: Function): void {
// if (typeof chunkSize === 'function') {
// cb = iterator;
// iterator = chunkSize;
// chunkSize = DEFAULT_CHUNK_SIZE;
// }
//
// const result = [];
// _delayChunk(array, chunkSize, (slice, start) => {
// for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
// result.push(iterator(slice[ii], start + ii, array));
// }
// return result;
// }, () => cb && cb(result));
// }
const result = [];
_delayChunk(array, chunkSize, (slice, start) => {
for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
result.push(iterator(slice[ii], start + ii, array));
}
return result;
}, () => cb && cb(result));
}
/**
*
* @param string
* @return {string}
*/
export function capitalizeFirstLetter(string: String) {
return `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
}
// /**
// *
// * @param string
// * @return {string}
// */
// export function capitalizeFirstLetter(string: String) {
// return `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
// }
// timestamp of last push, used to prevent local collisions if you push twice in one ms.
let lastPushTime = 0;
@ -315,7 +316,7 @@ export function generatePushID(serverTimeOffset?: number = 0): string {
* @returns {Error}
*/
export function nativeToJSError(code: string, message: string, additionalProps?: Object = {}) {
const error = new Error(message);
const error: Object = new Error(message);
error.code = code;
Object.assign(error, additionalProps);
// exclude this function from the stack
@ -329,7 +330,7 @@ export function nativeToJSError(code: string, message: string, additionalProps?:
* @param appName
* @param NativeModule
*/
export function nativeWithApp(appName, NativeModule) {
export function nativeWithApp(appName: string, NativeModule: Object) {
const native = {};
const methods = Object.keys(NativeModule);
@ -348,7 +349,7 @@ export function nativeWithApp(appName, NativeModule) {
* @param object
* @return {string}
*/
export function objectToUniqueId(object: Object): String {
export function objectToUniqueId(object: Object): string {
if (!isObject(object) || object === null) return JSON.stringify(object);
const keys = Object.keys(object).sort();
@ -374,7 +375,7 @@ export function objectToUniqueId(object: Object): String {
* @param optionalCallback
* @return {Promise}
*/
export function promiseOrCallback(promise: Promise, optionalCallback?: Function) {
export function promiseOrCallback(promise: Promise<*>, optionalCallback?: Function) {
if (!isFunction(optionalCallback)) return promise;
return promise.then((result) => {

View File

@ -11,12 +11,12 @@
"build": "./node_modules/.bin/babel --source-maps=true --out-dir=dist .",
"publish_pages": "gh-pages -d public/",
"docs-serve-local": "docsify serve docs",
"test-cli": "node ./bin/test.js",
"tests-packager": "cd tests && npm run start",
"tests-npm-install": "cd tests && npm install",
"tests-pod-install": "cd tests && npm run ios:pod:install",
"tests-watch-init": "wml add $(node --eval \"console.log(require('path').resolve('./lib'));\") $(node --eval \"console.log(require('path').resolve('./tests/firebase'));\")",
"tests-watch-start": "watchman watch $(node --eval \"console.log(require('path').resolve('./lib'));\") && wml start",
"tests-watch-stop": "watchman watch-del $(node --eval \"console.log(require('path').resolve('./lib'));\") && wml stop"
"tests-watch-start": "npm run test-cli watch init start",
"tests-watch-stop": "npm run test-cli watch stop"
},
"repository": {
"type": "git",
@ -74,6 +74,7 @@
"react": "^15.3.0",
"react-dom": "^15.3.0",
"react-native": "^0.44.0",
"shelljs": "^0.7.8",
"wml": "0.0.82"
},
"dependencies": {

View File

@ -1,5 +1,16 @@
{
"presets": [
"react-native"
]
],
"env": {
"development": {
"plugins": [
["istanbul", {
"include": [
"**/firebase/**.js"
]
}]
]
}
}
}

View File

@ -1,15 +1,80 @@
package com.reactnativefirebasedemo;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import com.facebook.react.ReactActivity;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "ReactNativeFirebaseDemo";
public static final int PERMISSION_REQ_CODE = 1234;
public static final int OVERLAY_PERMISSION_REQ_CODE = 1235;
String[] perms = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE"
};
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "ReactNativeFirebaseDemo";
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkWindowPerms();
}
public void checkWindowPerms() {
// Checking if device version > 22 and we need to use new permission model
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
// Checking if we can draw window overlay
if (!Settings.canDrawOverlays(this)) {
// Requesting permission for window overlay(needed for all react-native apps)
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}
for (String perm : perms) {
// Checking each permission and if denied then requesting permissions
if (checkSelfPermission(perm) == PackageManager.PERMISSION_DENIED) {
requestPermissions(perms, PERMISSION_REQ_CODE);
break;
}
}
}
}
// Window overlay permission intent result
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
checkWindowPerms();
}
}
// Permission results
@Override
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) {
switch (permsRequestCode) {
case PERMISSION_REQ_CODE:
// example how to get result of permissions requests (there can be more then one permission dialog)
// boolean readAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED;
// boolean writeAccepted = grantResults[1]==PackageManager.PERMISSION_GRANTED;
// checking permissions to prevent situation when user denied some permission
checkWindowPerms();
break;
}
}
}

2005
tests/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,8 +32,9 @@
"js-beautify": "^1.6.11",
"lodash.groupby": "^4.6.0",
"lodash.some": "^4.6.0",
"react": "16.0.0-alpha.6",
"react-native": "^0.44.0",
"react": "16.0.0-alpha.12",
"react-native": "^0.48.4",
"react-test-renderer": "16.0.0-alpha.12",
"react-native-simple-toast": "0.0.5",
"react-native-vector-icons": "^4.0.0",
"react-navigation": "^1.0.0-beta.9",
@ -51,6 +52,7 @@
"babel-eslint": "^7.1.1",
"babel-jest": "19.0.0",
"babel-plugin-flow-react-proptypes": "^0.21.0",
"babel-plugin-istanbul": "^4.1.5",
"babel-preset-react-native": "1.9.1",
"colors": "^1.1.2",
"eslint": "^3.16.1",
@ -60,7 +62,6 @@
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.10.0",
"jest": "19.0.2",
"react-test-renderer": "~15.4.1",
"redux-immutable-state-invariant": "^1.2.4"
},
"jest": {

249
tests/src/phone-auth.js Normal file
View File

@ -0,0 +1,249 @@
import React, { Component } from 'react';
import { View, Button, Text, TextInput, Image, ActivityIndicator, Platform } from 'react-native';
import fb from './firebase';
const firebase = fb.native;
const imageUrl = 'https://www.shareicon.net/data/512x512/2016/07/19/798524_sms_512x512.png';
export default class PhoneAuth extends Component {
static getDefaultState() {
return {
message: '',
error: '',
codeInput: '',
phoneNumber: '+44',
auto: Platform.OS === 'android',
autoVerifyCountDown: 0,
sent: false,
started: false,
user: null,
};
}
constructor(props) {
super(props);
this.timeout = 20;
this._autoVerifyInterval = null;
this.state = PhoneAuth.getDefaultState();
}
_tick() {
this.setState({
autoVerifyCountDown: this.state.autoVerifyCountDown - 1,
});
}
/**
* Called when confirm code is pressed - we should have the code and verificationId now in state.
*/
afterVerify = () => {
const { codeInput, verificationId } = this.state;
const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, codeInput);
// TODO do something with credential for example:
firebase.auth()
.signInWithCredential(credential)
.then((user) => {
console.log('PHONE AUTH USER ->>>>>', user.toJSON());
this.setState({ user: user.toJSON() });
}).catch(console.error);
};
signIn = () => {
const { phoneNumber } = this.state;
this.setState({ message: 'Sending code ...', error: '', started: true, autoVerifyCountDown: this.timeout }, () => {
firebase.auth()
.verifyPhoneNumber(phoneNumber)
.on('state_changed', (phoneAuthSnapshot) => {
console.log(phoneAuthSnapshot);
switch (phoneAuthSnapshot.state) {
case firebase.auth.PhoneAuthState.CODE_SENT: // or 'sent'
// update state with code sent and if android start a interval timer
// for auto verify - to provide visual feedback
this.setState({
sent: true,
message: 'Code Sent!',
verificationId: phoneAuthSnapshot.verificationId,
autoVerifyCountDown: this.timeout,
}, () => {
if (this.state.auto) {
this._autoVerifyInterval = setInterval(this._tick.bind(this), 1000);
}
});
break;
case firebase.auth.PhoneAuthState.ERROR: // or 'error'
// restart the phone flow again on error
clearInterval(this._autoVerifyInterval);
this.setState({
...PhoneAuth.getDefaultState(),
error: phoneAuthSnapshot.error.message,
});
break;
// ---------------------
// ANDROID ONLY EVENTS
// ---------------------
case firebase.auth.PhoneAuthState.AUTO_VERIFY_TIMEOUT: // or 'timeout'
clearInterval(this._autoVerifyInterval);
this.setState({
sent: true,
auto: false,
verificationId: phoneAuthSnapshot.verificationId,
});
break;
case firebase.auth.PhoneAuthState.AUTO_VERIFIED: // or 'verified'
clearInterval(this._autoVerifyInterval);
this.setState({
sent: true,
codeInput: phoneAuthSnapshot.code,
verificationId: phoneAuthSnapshot.verificationId,
});
break;
default:
// will never get here - just for linting
}
});
});
};
renderInputPhoneNumber() {
const { phoneNumber } = this.state;
return (
<View style={{ flex: 1 }}>
<Text>Enter phone number:</Text>
<TextInput
autoFocus
style={{ height: 40, marginTop: 15, marginBottom: 15 }}
onChangeText={value => this.setState({ phoneNumber: value })}
placeholder={'Phone number ... '}
value={phoneNumber}
/>
<Button title="Begin Verification" color="green" onPress={this.signIn} />
</View>
);
}
renderSendingCode() {
const { phoneNumber } = this.state;
return (
<View style={{ paddingBottom: 15 }}>
<Text
style={{ paddingBottom: 25 }}
>
{`Sending verification code to '${phoneNumber}'.`}
</Text>
<ActivityIndicator animating style={{ padding: 50 }} size={'large'} />
</View>
);
}
renderAutoVerifyProgress() {
const { autoVerifyCountDown, started, error, sent, phoneNumber } = this.state;
if (!sent && started && !error.length) {
return this.renderSendingCode();
}
return (
<View style={{ padding: 0 }}>
<Text
style={{ paddingBottom: 25 }}
>
{`Verification code has been successfully sent to '${phoneNumber}'.`}
</Text>
<Text
style={{ marginBottom: 25 }}
>
{`We'll now attempt to automatically verify the code for you. This will timeout in ${autoVerifyCountDown} seconds.`}
</Text>
<Button
style={{ paddingTop: 25 }} title="I have a code already" color="green"
onPress={() => this.setState({ auto: false })}
/>
</View>
);
}
renderError() {
const { error } = this.state;
return (
<View style={{ padding: 10, borderRadius: 5, margin: 10, backgroundColor: 'rgb(255,0,0)' }}>
<Text
style={{ color: '#fff' }}
>
{error}
</Text>
</View>
);
}
render() {
const { started, error, codeInput, sent, auto, user } = this.state;
return (
<View style={{ flex: 1, backgroundColor: user ? 'rgb(0, 200, 0)' : '#fff' }}>
<View
style={{
padding: 5,
justifyContent: 'center',
alignItems: 'center',
flex: 1,
}}
>
<Image source={{ uri: imageUrl }} style={{ width: 128, height: 128, marginTop: 25, marginBottom: 15 }} />
<Text style={{ fontSize: 25, marginBottom: 20 }}>Phone Auth Example</Text>
{error && error.length ? this.renderError() : null}
{!started && !sent ? this.renderInputPhoneNumber() : null}
{started && auto && !codeInput.length ? this.renderAutoVerifyProgress() : null}
{!user && started && sent && (codeInput.length || !auto) ? (
<View style={{ marginTop: 15 }}>
<Text>Enter verification code below:</Text>
<TextInput
autoFocus
style={{ height: 40, marginTop: 15, marginBottom: 15 }}
onChangeText={value => this.setState({ codeInput: value })}
placeholder={'Code ... '}
value={codeInput}
/>
<Button title="Confirm Code" color="#841584" onPress={this.afterVerify} />
</View>
) : null}
{user ? (
<View style={{ marginTop: 15 }}>
<Text>{`Signed in with new user id: '${user.uid}'`}</Text>
</View>
) : null}
</View>
</View>
);
}
}
/*
{ user ? (
<View
style={{
padding: 15,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#77dd77',
flex: 1,
}}
>
<Image source={{ uri: successImageUri }} style={{ width: 100, height: 100, marginBottom: 25 }} />
<Text style={{ fontSize: 25 }}>Signed In!</Text>
<Text>{JSON.stringify(user)}</Text>
</View>
) : null}
*/
// Example usage if handling here and not in optionalCompleteCb:
// const { verificationId, code } = phoneAuthSnapshot;
// const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, code);
// Do something with your new credential, e.g.:
// firebase.auth().signInWithCredential(credential);
// firebase.auth().linkWithCredential(credential);
// etc ...

View File

@ -11,6 +11,10 @@ import performance from './perf';
import admob from './admob';
import firestore from './firestore';
window.getCoverage = function getCoverage() {
return (JSON.stringify(global.__coverage__));
};
const testSuiteInstances = [
admob,
analytics,