merge with master

This commit is contained in:
taljacobson 2017-03-29 00:46:23 +03:00
commit 3a0c06e3fd
25 changed files with 955 additions and 216 deletions

View File

@ -1,42 +1,53 @@
# React Native Firebase
RNFirebase makes using the latest [Firebase](http://firebase.com) with React Native straight-forward.
```
npm i react-native-firebase --save
```
# React Native Firebase<img align="left" src="http://i.imgur.com/01XQL0x.png">
[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/t6bdqMs)
[![Gitter](https://badges.gitter.im/invertase/react-native-firebase.svg)](https://gitter.im/invertase/react-native-firebase?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![npm version](https://img.shields.io/npm/v/react-native-firebase.svg)](https://www.npmjs.com/package/react-native-firebase)
[![License](https://img.shields.io/npm/l/react-native-firebase.svg)](/LICENSE)
**RNFirebase** makes using [Firebase](http://firebase.com) with React Native simple.
<hr>
### Install
```
npm i react-native-firebase --save
```
#### Platform specific setup guides:
[![ios](https://a.fsdn.com/sd/topics/ios_64.png)](docs/installation.ios.md) [![android](https://a.fsdn.com/sd/topics/android_64.png)](docs/installation.android.md)
<hr>
### Why
RNFirebase is a _light-weight_ layer sitting on-top of the native Firebase libraries for both iOS and Android which mirrors the Firebase Web SDK as closely as possible.
## RNFirebase vs Firebase Web SDK
Although the [Firebase Web SDK](https://www.npmjs.com/package/firebase) library will work with React Native, it is mainly built for the web.
Although the [Firebase Web SDK](https://www.npmjs.com/package/firebase) library will work with React Native, it is built for the web.
RNFirebase provides a JavaScript bridge to the native Firebase SDKs for both iOS and Android. Firebase will run on the native thread, allowing the rest of your app to run on the [JS thread](https://facebook.github.io/react-native/docs/performance.html#javascript-frame-rate). The Firebase Web SDK also runs on the JS thread, therefore potentially affecting the frame rate causing jank with animations, touch events etc. All in all, RNFirebase provides much faster performance (~2x) over the web SDK.
RNFirebase provides a JavaScript bridge to the native Firebase SDKs for both iOS and Android. The Firebase processes will run on the native thread, allowing the rest of your app to run on the [JS thread](https://facebook.github.io/react-native/docs/performance.html#javascript-frame-rate). The Firebase Web SDK also runs on the JS thread, therefore potentially affecting the frame rate causing jank with animations, touch events etc. All in, RNFirebase provides much faster performance (~2x) over the web SDK.
The native SDKs also allow us to hook into device sdk's which are not possible with the web SDK, for example crash reporting, offline realtime database support, analyics and more!
The native SDKs also allow us to hook into device events which are not possible with the web SDK, for example crash reporting, offline realtime database support, analyics and more!
<hr>
## Test app
### Test app
To help ensure changes and features work across both iOS & Android, we've developed an app specifically to test `react-native-firebase` against the [`firebase` web SDK](https://www.npmjs.com/package/firebase). Please see the [`react-native-firebase-tests`](https://github.com/invertase/react-native-firebase-tests) repository for more information.
## Examples app
<hr>
### Examples app
There's currently a work in progress [examples app](https://github.com/invertase/react-native-firebase-examples) which aims to demonstrate various real world use-case scenarios with React Native & Firebase. We welcome any new examples or updates to existing ones.
## Documentation
<hr>
### Documentation
RNFirebase aims to replicate the Firebase Web SDK as closely as possible. Because of this, the documentation focuses around the installation, differences & best practices of this library. Please see the [Firebase Web SDK](https://firebase.google.com/docs/reference/js/) documentation for Firebase functionality.
> If you find any discrepancies between the two libraries, please raise an issue or PR.
* Installation
* [iOS](docs/installation.ios.md)
* [Android](docs/installation.android.md)
* [Firebase Setup](docs/firebase-setup.md)
* API
* [Authentication](docs/api/authentication.md)
@ -45,13 +56,20 @@ RNFirebase aims to replicate the Firebase Web SDK as closely as possible. Becaus
* [Storage](docs/api/storage.md)
* [Messaging](docs/api/cloud-messaging.md)
* [Crash](docs/api/crash.md)
* [Transactions](docs/api/transactions.md)
## Contributing
<hr>
### Contributing
We welcome any contribution to the repository. Please ensure your changes to the JavaScript code follow the styling guides controlled by ESlint. Changes to native code should be kept clean and follow the standard of existing code.
Changes to existing code should ensure all relevant tests on the test app pass. Any new features should have new tests created and ensure all existing tests pass.
## License
**Project board:** https://github.com/invertase/react-native-firebase/projects
<hr>
### License
- MIT

View File

@ -14,7 +14,6 @@ Pod::Spec.new do |s|
s.source = { :git => "https://github.com/invertase/react-native-firebase.git", :tag => "v#{s.version}" }
s.social_media_url = 'http://twitter.com/mikediarmid'
s.platform = :ios, "8.0"
s.header_dir = 'ios/RNFirebase'
s.preserve_paths = 'README.md', 'package.json', '*.js'
s.source_files = 'ios/RNFirebase/*.{h,m}'
s.dependency 'React'

View File

@ -0,0 +1 @@
<svg width="183" height="197" viewBox="0 0 183 197" xmlns="http://www.w3.org/2000/svg"><title>Slice 1</title><g fill="none" fill-rule="evenodd"><path fill="#F4A93F" d="M80.95 76l19.202 33.45-24.243-.872z"/><path fill="#F4A83E" d="M90.16 82.4l14.234 27.05-29.091-.872z"/><path fill="#E88634" d="M87.489 87.038L99.94 109.45l-24.334-.243z"/><path fill="#F8CA51" d="M100.546 84.758l3.848 24.692L75 109.153z"/><path fill="#F8CA51" d="M90.16 117l14.555-7.847L75 108.875z"/><path d="M1.008 98C1.643 118.712 41.69 135 91 135v-8.002C51.309 126.8 9.675 114.642 9.008 98h-8z" fill="#E88634"/><path d="M135.523 21.782C117.27 11.976 83.14 38.515 58.484 81.218l6.93 4.001c20.016-34.275 51.364-64.252 66.11-56.508l4-6.929z" fill="#F9CB52"/><path d="M1.008 98C1.643 77.288 41.69 61 91 61v8.002C51.309 69.2 9.675 81.358 9.008 98h-8z" fill="#E88634"/><path d="M46.043 20c-17.62 10.906-11.702 53.732 12.953 96.436l6.93-4.002C46.251 77.963 35.964 35.827 50.043 26.93l-4-6.929z" fill="#F4A73E"/><path d="M46.043 176.436C28.423 165.53 34.34 122.703 58.996 80l6.93 4.001c-19.675 34.472-29.962 76.608-15.883 85.506l-4 6.929z" fill="#F9CB52"/><path d="M45 20.5c18.255-9.806 52.384 16.732 77.04 59.436l-6.931 4C95.093 49.662 63.746 19.687 49 27.43L45 20.5z" fill="#F4A73E"/><path d="M181 98c-.635-20.712-40.683-37-89.992-37v8.002C130.698 69.2 172.333 81.358 173 98h8z" fill="#E88634"/><path d="M45.241 176.501c.107.065.214.129.322.191 18.143 10.475 52.623-16.146 77.476-59.192l-6.892-4.067c-20.175 34.58-51.9 64.808-66.502 56.378a10.11 10.11 0 0 1-.437-.267L45.24 176.5z" fill="#F9CB52"/><path d="M136.036 175.938C117.78 185.738 83.653 159.2 59 116.5l-.26-.45 6.836-4.16c19.984 34.503 51.585 64.863 66.431 57.134l4.029 6.914z" fill="#F4A73E"/><path d="M181 97c.005.166.008.333.008.5 0 20.95-40.295 37.5-90 37.5v-8.002c40.012-.198 82-12.554 82-29.403 0-.2-.006-.398-.018-.595H181z" fill="#E88634"/><path d="M135.005 176.43c17.609-10.915 11.688-53.734-12.962-96.43l-.44-.76-7.022 3.835.047.082c19.952 34.558 30.52 77.214 16.45 86.298l3.927 6.975z" fill="#F4A73E"/><path d="M135.48 21.282c17.62 10.906 11.702 53.733-12.953 96.436l-6.93-4.001c19.676-34.472 29.962-76.608 15.883-85.506l4-6.929z" fill="#F9CB52"/></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -20,6 +20,7 @@ import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.ReadableArray;
import com.google.firebase.database.DataSnapshot;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.google.firebase.database.MutableData;
@SuppressWarnings("WeakerAccess")
public class Utils {
@ -94,7 +95,7 @@ public class Utils {
if (!dataSnapshot.hasChildren()) {
mapPutValue("value", dataSnapshot.getValue(), snapshot);
} else {
Object value = Utils.castSnapshotValue(dataSnapshot);
Object value = Utils.castValue(dataSnapshot);
if (value instanceof WritableNativeArray) {
snapshot.putArray("value", (WritableArray) value);
} else {
@ -113,13 +114,43 @@ public class Utils {
return eventMap;
}
/**
*
* @param dataSnapshot
* @return
*/
public static WritableMap snapshotToMap(DataSnapshot dataSnapshot) {
WritableMap snapshot = Arguments.createMap();
snapshot.putString("key", dataSnapshot.getKey());
snapshot.putBoolean("exists", dataSnapshot.exists());
snapshot.putBoolean("hasChildren", dataSnapshot.hasChildren());
snapshot.putDouble("childrenCount", dataSnapshot.getChildrenCount());
if (!dataSnapshot.hasChildren()) {
mapPutValue("value", dataSnapshot.getValue(), snapshot);
} else {
Object value = Utils.castValue(dataSnapshot);
if (value instanceof WritableNativeArray) {
snapshot.putArray("value", (WritableArray) value);
} else {
snapshot.putMap("value", (WritableMap) value);
}
}
snapshot.putArray("childKeys", Utils.getChildKeys(dataSnapshot));
mapPutValue("priority", dataSnapshot.getPriority(), snapshot);
return snapshot;
}
/**
*
* @param snapshot
* @param <Any>
* @return
*/
public static <Any> Any castSnapshotValue(DataSnapshot snapshot) {
public static <Any> Any castValue(DataSnapshot snapshot) {
if (snapshot.hasChildren()) {
if (isArray(snapshot)) {
return (Any) buildArray(snapshot);
@ -144,6 +175,37 @@ public class Utils {
}
}
/**
*
* @param mutableData
* @param <Any>
* @return
*/
public static <Any> Any castValue(MutableData mutableData) {
if (mutableData.hasChildren()) {
if (isArray(mutableData)) {
return (Any) buildArray(mutableData);
} else {
return (Any) buildMap(mutableData);
}
} else {
if (mutableData.getValue() != null) {
String type = mutableData.getValue().getClass().getName();
switch (type) {
case "java.lang.Boolean":
case "java.lang.Long":
case "java.lang.Double":
case "java.lang.String":
return (Any) (mutableData.getValue());
default:
Log.w(TAG, "Invalid type: " + type);
return null;
}
}
return null;
}
}
/**
*
* @param snapshot
@ -166,6 +228,28 @@ public class Utils {
return true;
}
/**
*
* @param mutableData
* @return
*/
private static boolean isArray(MutableData mutableData) {
long expectedKey = 0;
for (MutableData child : mutableData.getChildren()) {
try {
long key = Long.parseLong(child.getKey());
if (key == expectedKey) {
expectedKey++;
} else {
return false;
}
} catch (NumberFormatException ex) {
return false;
}
}
return true;
}
/**
*
* @param snapshot
@ -175,7 +259,45 @@ public class Utils {
private static <Any> WritableArray buildArray(DataSnapshot snapshot) {
WritableArray array = Arguments.createArray();
for (DataSnapshot child : snapshot.getChildren()) {
Any castedChild = castSnapshotValue(child);
Any castedChild = castValue(child);
switch (castedChild.getClass().getName()) {
case "java.lang.Boolean":
array.pushBoolean((Boolean) castedChild);
break;
case "java.lang.Long":
Long longVal = (Long) castedChild;
array.pushDouble((double) longVal);
break;
case "java.lang.Double":
array.pushDouble((Double) castedChild);
break;
case "java.lang.String":
array.pushString((String) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeMap":
array.pushMap((WritableMap) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeArray":
array.pushArray((WritableArray) castedChild);
break;
default:
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
break;
}
}
return array;
}
/**
*
* @param mutableData
* @param <Any>
* @return
*/
private static <Any> WritableArray buildArray(MutableData mutableData) {
WritableArray array = Arguments.createArray();
for (MutableData child : mutableData.getChildren()) {
Any castedChild = castValue(child);
switch (castedChild.getClass().getName()) {
case "java.lang.Boolean":
array.pushBoolean((Boolean) castedChild);
@ -213,7 +335,45 @@ public class Utils {
private static <Any> WritableMap buildMap(DataSnapshot snapshot) {
WritableMap map = Arguments.createMap();
for (DataSnapshot child : snapshot.getChildren()) {
Any castedChild = castSnapshotValue(child);
Any castedChild = castValue(child);
switch (castedChild.getClass().getName()) {
case "java.lang.Boolean":
map.putBoolean(child.getKey(), (Boolean) castedChild);
break;
case "java.lang.Long":
map.putDouble(child.getKey(), (double) ((Long) castedChild));
break;
case "java.lang.Double":
map.putDouble(child.getKey(), (Double) castedChild);
break;
case "java.lang.String":
map.putString(child.getKey(), (String) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeMap":
map.putMap(child.getKey(), (WritableMap) castedChild);
break;
case "com.facebook.react.bridge.WritableNativeArray":
map.putArray(child.getKey(), (WritableArray) castedChild);
break;
default:
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
break;
}
}
return map;
}
/**
*
* @param mutableData
* @param <Any>
* @return
*/
private static <Any> WritableMap buildMap(MutableData mutableData) {
WritableMap map = Arguments.createMap();
for (MutableData child : mutableData.getChildren()) {
Any castedChild = castValue(child);
switch (castedChild.getClass().getName()) {
case "java.lang.Boolean":

View File

@ -1,15 +1,16 @@
package io.invertase.firebase.database;
import java.util.List;
import java.util.Map;
import java.util.List;
import java.util.HashMap;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import java.util.HashMap;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactMethod;
@ -19,18 +20,22 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableNativeArray;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.MutableData;
import com.google.firebase.database.ServerValue;
import com.google.firebase.database.OnDisconnect;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ServerValue;
import com.google.firebase.database.Transaction;
import io.invertase.firebase.Utils;
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseDatabase";
private HashMap<String, RNFirebaseDatabaseReference> mDBListeners = new HashMap<String, RNFirebaseDatabaseReference>();
private HashMap<String, RNFirebaseDatabaseReference> mDBListeners = new HashMap<>();
private HashMap<String, RNFirebaseTransactionHandler> mTransactionHandlers = new HashMap<>();
private FirebaseDatabase mFirebaseDatabase;
public RNFirebaseDatabase(ReactApplicationContext reactContext) {
@ -170,6 +175,109 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
}
}
/**
* @param path
* @param id
* @param applyLocally
*/
@ReactMethod
public void startTransaction(final String path, final String id, final Boolean applyLocally) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
DatabaseReference transactionRef = FirebaseDatabase.getInstance().getReference(path);
transactionRef.runTransaction(new Transaction.Handler() {
@Override
public Transaction.Result doTransaction(MutableData mutableData) {
final WritableMap updatesMap = Arguments.createMap();
updatesMap.putString("id", id);
updatesMap.putString("type", "update");
if (!mutableData.hasChildren()) {
Utils.mapPutValue("value", mutableData.getValue(), updatesMap);
} else {
Object value = Utils.castValue(mutableData);
if (value instanceof WritableNativeArray) {
updatesMap.putArray("value", (WritableArray) value);
} else {
updatesMap.putMap("value", (WritableMap) value);
}
}
RNFirebaseTransactionHandler rnFirebaseTransactionHandler = new RNFirebaseTransactionHandler();
mTransactionHandlers.put(id, rnFirebaseTransactionHandler);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
Utils.sendEvent(getReactApplicationContext(), "database_transaction_event", updatesMap);
}
});
try {
rnFirebaseTransactionHandler.await();
} catch (InterruptedException e) {
rnFirebaseTransactionHandler.interrupted = true;
return Transaction.abort();
}
if (rnFirebaseTransactionHandler.abort) {
return Transaction.abort();
}
mutableData.setValue(rnFirebaseTransactionHandler.value);
return Transaction.success(mutableData);
}
@Override
public void onComplete(DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) {
final WritableMap updatesMap = Arguments.createMap();
updatesMap.putString("id", id);
RNFirebaseTransactionHandler rnFirebaseTransactionHandler = mTransactionHandlers.get(id);
// TODO error conversion util for database to create web sdk codes based on DatabaseError
if (databaseError != null) {
updatesMap.putString("type", "error");
updatesMap.putInt("code", databaseError.getCode());
updatesMap.putString("message", databaseError.getMessage());
} else if (rnFirebaseTransactionHandler.interrupted) {
updatesMap.putString("type", "error");
updatesMap.putInt("code", 666);
updatesMap.putString("message", "RNFirebase transaction was interrupted, aborting.");
} else {
updatesMap.putString("type", "complete");
updatesMap.putBoolean("committed", committed);
updatesMap.putMap("snapshot", Utils.snapshotToMap(dataSnapshot));
}
Utils.sendEvent(getReactApplicationContext(), "database_transaction_event", updatesMap);
mTransactionHandlers.remove(id);
}
}, applyLocally);
}
});
}
/**
*
* @param id
* @param updates
*/
@ReactMethod
public void tryCommitTransaction(final String id, final ReadableMap updates) {
Map<String, Object> updatesReturned = Utils.recursivelyDeconstructReadableMap(updates);
RNFirebaseTransactionHandler rnFirebaseTransactionHandler = mTransactionHandlers.get(id);
if (rnFirebaseTransactionHandler != null) {
rnFirebaseTransactionHandler.signalUpdateReceived(updatesReturned);
}
}
@ReactMethod
public void on(final String path, final String modifiersString, final ReadableArray modifiersArray, final String eventName, final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
@ -187,11 +295,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
}
@ReactMethod
public void once(final String path,
final String modifiersString,
final ReadableArray modifiersArray,
final String eventName,
final Callback callback) {
public void once(final String path, final String modifiersString, final ReadableArray modifiersArray, final String eventName, final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
ref.addOnceValueEventListener(callback);
}
@ -336,9 +440,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
}
}
private RNFirebaseDatabaseReference getDBHandle(final String path,
final ReadableArray modifiersArray,
final String modifiersString) {
private RNFirebaseDatabaseReference getDBHandle(final String path, final ReadableArray modifiersArray, final String modifiersString) {
String key = this.getDBListenerKey(path, modifiersString);
RNFirebaseDatabaseReference r = mDBListeners.get(key);

View File

@ -0,0 +1,70 @@
package io.invertase.firebase.database;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class RNFirebaseTransactionHandler {
private final ReentrantLock lock;
private final Condition condition;
private Map<String, Object> data;
private volatile boolean isReady;
public Object value;
public boolean interrupted;
public boolean abort = false;
RNFirebaseTransactionHandler() {
lock = new ReentrantLock();
condition = lock.newCondition();
}
/**
* Signal that the transaction data has been received
*
* @param updateData
*/
public void signalUpdateReceived(Map<String, Object> updateData) {
lock.lock();
abort = (Boolean) updateData.get("abort");
value = updateData.get("value");
try {
if (isReady)
throw new IllegalStateException("This transactionUpdateCallback has already been called.");
data = updateData;
isReady = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
/**
* Wait for transactionUpdateReceived to signal condition
* @throws InterruptedException
*/
void await() throws InterruptedException {
lock.lock();
Boolean notTimedOut = false;
try {
while (!notTimedOut && !isReady) {
notTimedOut = condition.await(30, TimeUnit.SECONDS);
}
} finally {
lock.unlock();
}
}
/**
* Get the
* @return
*/
Map<String, Object> getUpdates() {
return data;
}
}

View File

@ -0,0 +1,27 @@
package io.invertase.firebase.messaging;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.ArrayList;
import android.os.Bundle;
import android.util.Log;
/**
* Set alarms for scheduled notification after system reboot.
*/
public class RNFirebaseSystemBootEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("FCMSystemBootReceiver", "Received reboot event");
RNFirebaseLocalMessagingHelper helper = new RNFirebaseLocalMessagingHelper((Application) context.getApplicationContext());
ArrayList<Bundle> bundles = helper.getScheduledLocalNotifications();
for(Bundle bundle: bundles){
helper.sendNotificationScheduled(bundle);
}
}
}

5
docs/api/transactions.md Normal file
View File

@ -0,0 +1,5 @@
# Transactions
Transactions are currently an experimental feature as they can not be integrated as easily as the other Firebase features.
TODO

View File

@ -80,3 +80,16 @@ Add messaging service:
</intent-filter>
</service>
```
If you would like to schedule local notifications then you also need to add the following:
```
<receiver android:name="io.invertase.firebase.messaging.RNFirebaseLocalMessagingPublisher"/>
<receiver android:enabled="true" android:exported="true"android:name="io.invertase.firebase.messaging.RNFirebaseSystemBootEventReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
```

View File

@ -1,17 +1,41 @@
#iOS Installation
# iOS Installation
If you don't want to use cocoapods, you don't need to use it! Just make sure you link the Firebase libraries in your project manually. For more information, check out the relevant Firebase docs at [https://firebase.google.com/docs/ios/setup#frameworks](https://firebase.google.com/docs/ios/setup#frameworks).
## Firebase
## cocoapods
### Setup
Setup the Firebase ios frameworks first; check out the relevant Firebase docs [here](https://firebase.google.com/docs/ios/setup#frameworks).
Unfortunately, due to AppStore restrictions, we currently do _not_ package Firebase libraries in with Firebase. However, the good news is we've automated the process (with many thanks to the Auth0 team for inspiration) of setting up with cocoapods. This will happen automatically upon linking the package with `react-native-cli`.
### Initialisation
You need to add the following to the top of `ios/[YOUR APP NAME]]/AppDelegate.m`:
**Remember to use the `ios/[YOUR APP NAME].xcworkspace` instead of the `ios/[YOUR APP NAME].xcproj` file from now on**.
`#import <Firebase.h>`
We need to link the package with our development packaging. We have two options to handle linking:
and this to the `didFinishLaunchingWithOptions:(NSDictionary *)launchOptions` method:
#### Automatically with react-native-cli
`[FIRApp configure];`
## RNFirebase
There are multiple ways to install RNFirebase dependent on how your project is currently setup:
### 1) Existing Cocoapods setup, including React Native as a pod
Simply add the following to your `Podfile`:
```ruby
# Required by RNFirebase
pod 'Firebase/Auth'
pod 'Firebase/Analytics'
pod 'Firebase/AppIndexing'
pod 'Firebase/Core'
pod 'Firebase/Crash'
pod 'Firebase/Database'
pod 'Firebase/DynamicLinks'
pod 'Firebase/Messaging'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
pod 'RNFirebase', :path => '../node_modules/react-native-firebase'
```
### 2) Automatically with react-native-cli
React native ships with a `link` command that can be used to link the projects together, which can help automate the process of linking our package environments.
```bash
@ -24,17 +48,21 @@ Update the newly installed pods once the linking is done:
cd ios && pod update --verbose
```
#### Manually
#### cocoapods
We've automated the process of setting up with cocoapods. This will happen automatically upon linking the package with `react-native-cli`.
**Remember to use the `ios/[YOUR APP NAME].xcworkspace` instead of the `ios/[YOUR APP NAME].xcproj` file from now on**.
### 3) Manually
If you prefer not to use `react-native link`, we can manually link the package together with the following steps, after `npm install`:
**A.** In XCode, right click on `Libraries` and find the `Add Files to [project name]`.
![Add library to project](http://d.pr/i/2gEH.png)
![Firebase.xcodeproj add to files](https://cloud.githubusercontent.com/assets/5347038/24249673/0fccdbec-0fcc-11e7-83eb-c058f8898525.png)
**B.** Add the `node_modules/react-native-firebase/ios/Firebase.xcodeproj`
![Firebase.xcodeproj in Libraries listing](http://d.pr/i/19ktP.png)
![Firebase.xcodeproj in Libraries listing](https://cloud.githubusercontent.com/assets/21329063/24249440/9494e19c-0fd3-11e7-95c0-c2baa85092e8.png)
**C.** Ensure that the `Build Settings` of the `RNFirebase.xcodeproj` project is ticked to _All_ and it's `Header Search Paths` include both of the following paths _and_ are set to _recursive_:
@ -42,7 +70,7 @@ If you prefer not to use `react-native link`, we can manually link the package t
2. `$(SRCROOT)/../node_modules/react-native/React`
3. `${PROJECT_DIR}/../../../ios/Pods`
![Recursive paths](http://d.pr/i/1hAr1.png)
![Recursive paths](https://cloud.githubusercontent.com/assets/21329063/24250349/da91284c-0fd6-11e7-8328-6008e462039e.png)
**D.** Setting up cocoapods
@ -51,17 +79,17 @@ Since we're dependent upon cocoapods (or at least the Firebase libraries being a
Using cocoapods is the easiest way to get started with this linking. Add or update a `Podfile` at `ios/Podfile` in your app with the following:
```ruby
source 'https://github.com/CocoaPods/Specs.git'
[
'Firebase/Core',
'Firebase/Auth',
'Firebase/Storage',
'Firebase/Database',
'Firebase/RemoteConfig',
'Firebase/Messaging'
].each do |lib|
pod lib
end
# Required by RNFirebase
pod 'Firebase/Auth'
pod 'Firebase/Analytics'
pod 'Firebase/AppIndexing'
pod 'Firebase/Core'
pod 'Firebase/Crash'
pod 'Firebase/Database'
pod 'Firebase/DynamicLinks'
pod 'Firebase/Messaging'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
```
Then you can run `(cd ios && pod install)` to get the pods opened. If you do use this route, remember to use the `.xcworkspace` file.

View File

@ -10,6 +10,8 @@
}
@property NSMutableDictionary *dbReferences;
@property NSMutableDictionary *transactions;
@property dispatch_queue_t transactionQueue;
@end

View File

@ -13,6 +13,7 @@
@property FIRDatabaseHandle childRemovedHandler;
@property FIRDatabaseHandle childMovedHandler;
@property FIRDatabaseHandle childValueHandler;
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot;
@end
@implementation RNFirebaseDBReference
@ -23,22 +24,22 @@
modifiers:(NSArray *) modifiers
modifiersString:(NSString *) modifiersString
{
self = [super init];
if (self) {
_emitter = emitter;
_path = path;
_modifiersString = modifiersString;
_query = [self buildQueryAtPathWithModifiers:database path:path modifiers:modifiers];
_listeners = [[NSMutableDictionary alloc] init];
}
return self;
self = [super init];
if (self) {
_emitter = emitter;
_path = path;
_modifiersString = modifiersString;
_query = [self buildQueryAtPathWithModifiers:database path:path modifiers:modifiers];
_listeners = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) addEventHandler:(NSString *) eventName
{
if (![self isListeningTo:eventName]) {
id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) {
NSDictionary *props = [self snapshotToDict:snapshot];
NSDictionary *props = [RNFirebaseDBReference snapshotToDict:snapshot];
[self sendJSEvent:DATABASE_DATA_EVENT
title:eventName
props: @{
@ -68,15 +69,15 @@
- (void) addSingleEventHandler:(RCTResponseSenderBlock) callback
{
[_query observeSingleEventOfType:FIRDataEventTypeValue
withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
NSDictionary *props = [self snapshotToDict:snapshot];
callback(@[[NSNull null], @{
@"eventName": @"value",
@"path": _path,
@"modifiersString": _modifiersString,
@"snapshot": props
}]);
}
withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
NSDictionary *props = [RNFirebaseDBReference snapshotToDict:snapshot];
callback(@[[NSNull null], @{
@"eventName": @"value",
@"path": _path,
@"modifiersString": _modifiersString,
@"snapshot": props
}]);
}
withCancelBlock:^(NSError * _Nonnull error) {
NSLog(@"Error onDBEventOnce: %@", [error debugDescription]);
callback(@[@{
@ -87,8 +88,8 @@
@"details": [error debugDescription],
@"message": [error localizedDescription],
@"description": [error description]
}]);
}];
}]);
}];
}
- (void) removeEventHandler:(NSString *) name
@ -131,7 +132,7 @@
[self unsetListeningOn:name];
}
- (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:snapshot.key forKey:@"key"];
@ -162,17 +163,17 @@
modifiersString:(NSString *) modifiersString
{
NSDictionary *event = @{
@"eventName": DATABASE_ERROR_EVENT,
@"path": path,
@"modifiers": modifiersString,
@"code": @([error code]),
@"details": [error debugDescription],
@"message": [error localizedDescription],
@"description": [error description]
};
@"eventName": DATABASE_ERROR_EVENT,
@"path": path,
@"modifiers": modifiersString,
@"code": @([error code]),
@"details": [error debugDescription],
@"message": [error localizedDescription],
@"description": [error description]
};
// [self sendJSEvent:DATABASE_ERROR_EVENT title:DATABASE_ERROR_EVENT props: event];
@try {
[_emitter sendEventWithName:DATABASE_ERROR_EVENT body:event];
}
@ -180,22 +181,14 @@
NSLog(@"An error occurred in getAndSendDatabaseError: %@", [err debugDescription]);
NSLog(@"Tried to send: %@ with %@", DATABASE_ERROR_EVENT, event);
}
return event;
}
- (void) sendJSEvent:(NSString *)type
title:(NSString *)title
props:(NSDictionary *)props
{
- (void) sendJSEvent:(NSString *)type title:(NSString *)title props:(NSDictionary *)props {
@try {
[_emitter sendEventWithName:type
body:@{
@"eventName": title,
@"body": props
}];
}
@catch (NSException *err) {
[_emitter sendEventWithName:type body:@{ @"eventName": title, @"body": props }];
} @catch (NSException *err) {
NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]);
NSLog(@"Tried to send: %@ with %@", title, props);
}
@ -207,7 +200,7 @@
modifiers:(NSArray *) modifiers
{
FIRDatabaseQuery *query = [[database reference] child:path];
for (NSString *str in modifiers) {
if ([str isEqualToString:@"orderByKey"]) {
query = [query queryOrderedByKey];
@ -264,7 +257,7 @@
}
}
}
return query;
}
@ -319,7 +312,7 @@
- (BOOL) isListeningTo:(NSString *) name
{
return [_listeners valueForKey:name] != nil;
return [_listeners valueForKey:name] != nil;
}
- (BOOL) hasListeners
@ -335,7 +328,7 @@
- (int) eventTypeFromName:(NSString *)name
{
int eventType = FIRDataEventTypeValue;
if ([name isEqualToString:DATABASE_VALUE_EVENT]) {
eventType = FIRDataEventTypeValue;
} else if ([name isEqualToString:DATABASE_CHILD_ADDED_EVENT]) {
@ -370,42 +363,132 @@
@end
@implementation RNFirebaseDatabase
RCT_EXPORT_MODULE(RNFirebaseDatabase);
- (id) init
{
- (id) init {
self = [super init];
if (self != nil) {
_dbReferences = [[NSMutableDictionary alloc] init];
_transactions = [[NSMutableDictionary alloc] init];
_transactionQueue = dispatch_queue_create("io.invertase.react-native-firebase", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
RCT_EXPORT_METHOD(enablePersistence:(BOOL) enable
callback:(RCTResponseSenderBlock) callback)
{
- (void) sendTransactionEvent:(NSString *)type body:(id)body {
@try {
[self sendEventWithName:type body:body];
} @catch (NSException *err) {
NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]);
NSLog(@"Tried to send: %@ with %@", type, body);
}
}
RCT_EXPORT_METHOD(startTransaction:(NSString *) path identifier:(NSString *) identifier applyLocally:(BOOL) applyLocally) {
dispatch_async(_transactionQueue, ^{
NSMutableDictionary *transactionState = [NSMutableDictionary new];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[transactionState setObject:sema forKey:@"semaphore"];
FIRDatabaseReference *ref = [self getPathRef:path];
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
dispatch_barrier_async(_transactionQueue, ^{
[_transactions setValue:transactionState forKey:identifier];
[self sendTransactionEvent:DATABASE_TRANSACTION_EVENT body:@{ @"id": identifier, @"type": @"update", @"value": currentData.value }];
});
// wait for the js event handler to call tryCommitTransaction
// this wait occurs on the Firebase Worker Queue
// so if the tryCommitTransaction fails to signal the semaphore
// no further blocks will be executed by Firebase until the timeout expires
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
id value = [transactionState valueForKey:@"value"];
dispatch_barrier_async(_transactionQueue, ^{
[_transactions removeObjectForKey:identifier];
});
if (abort) {
return [FIRTransactionResult abort];
} else {
currentData.value = value;
return [FIRTransactionResult successWithValue:currentData];
}
} andCompletionBlock:^(NSError * _Nullable databaseError, BOOL committed, FIRDataSnapshot * _Nullable snapshot) {
if (databaseError != nil) {
[self sendTransactionEvent:DATABASE_TRANSACTION_EVENT body:@{
@"id": identifier,
@"type": @"error",
@"code": [NSNumber numberWithInt:[databaseError code]],
@"message": [databaseError description]
}];
} else {
[self sendTransactionEvent:DATABASE_TRANSACTION_EVENT body:@{
@"id": identifier,
@"type": @"complete",
@"committed": @(committed),
@"snapshot": [RNFirebaseDBReference snapshotToDict:snapshot],
}];
}
} withLocalEvents:applyLocally];
});
}
RCT_EXPORT_METHOD(tryCommitTransaction:(NSString *) identifier withData:(NSDictionary *) data) {
__block NSMutableDictionary *transactionState;
dispatch_sync(_transactionQueue, ^{
transactionState = [_transactions objectForKey: identifier];
});
if (!transactionState) {
NSLog(@"tryCommitTransaction for unknown ID %@", identifier);
return;
}
dispatch_semaphore_t sema = [transactionState valueForKey:@"semaphore"];
BOOL abort = [[data valueForKey:@"abort"] boolValue];
if (abort) {
[transactionState setValue:@true forKey:@"abort"];
} else {
id newValue = [data valueForKey:@"value"];
[transactionState setValue:newValue forKey:@"value"];
}
dispatch_semaphore_signal(sema);
}
RCT_EXPORT_METHOD(enablePersistence:(BOOL) enable
callback:(RCTResponseSenderBlock) callback)
{
BOOL isEnabled = [FIRDatabase database].persistenceEnabled;
if ( isEnabled != enable) {
[FIRDatabase database].persistenceEnabled = enable;
}
callback(@[[NSNull null], @{
@"result": @"success"
}]);
@"result": @"success"
}]);
}
RCT_EXPORT_METHOD(keepSynced:(NSString *) path
withEnable:(BOOL) enable
callback:(RCTResponseSenderBlock) callback)
withEnable:(BOOL) enable
callback:(RCTResponseSenderBlock) callback)
{
FIRDatabaseReference *ref = [self getPathRef:path];
[ref keepSynced:enable];
callback(@[[NSNull null], @{
@"status": @"success",
@"path": path
}]);
@"status": @"success",
@"path": path
}]);
}
RCT_EXPORT_METHOD(set:(NSString *) path
@ -443,21 +526,21 @@ RCT_EXPORT_METHOD(push:(NSString *) path
{
FIRDatabaseReference *ref = [self getPathRef:path];
FIRDatabaseReference *newRef = [ref childByAutoId];
NSURL *url = [NSURL URLWithString:newRef.URL];
NSString *newPath = [url path];
if ([data count] > 0) {
[newRef setValue:[data valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
if (error != nil) {
// Error handling
NSDictionary *evt = @{
@"code": @([error code]),
@"details": [error debugDescription],
@"message": [error localizedDescription],
@"description": [error description]
};
@"code": @([error code]),
@"details": [error debugDescription],
@"message": [error localizedDescription],
@"description": [error description]
};
callback(@[evt]);
} else {
callback(@[[NSNull null], @{
@ -468,9 +551,9 @@ RCT_EXPORT_METHOD(push:(NSString *) path
}];
} else {
callback(@[[NSNull null], @{
@"status": @"success",
@"ref": newPath
}]);
@"status": @"success",
@"ref": newPath
}]);
}
}
@ -491,10 +574,10 @@ RCT_EXPORT_METHOD(on:(NSString *) path
}
RCT_EXPORT_METHOD(once:(NSString *) path
modifiersString:(NSString *) modifiersString
modifiers:(NSArray *) modifiers
name:(NSString *) name
callback:(RCTResponseSenderBlock) callback)
modifiersString:(NSString *) modifiersString
modifiers:(NSArray *) modifiers
name:(NSString *) name
callback:(RCTResponseSenderBlock) callback)
{
RNFirebaseDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString];
[ref addSingleEventHandler:callback];
@ -609,18 +692,18 @@ RCT_EXPORT_METHOD(goOnline)
}
- (RNFirebaseDBReference *) getDBHandle:(NSString *) path
modifiers:modifiers
modifiersString:modifiersString
modifiers:modifiers
modifiersString:modifiersString
{
NSString *key = [self getDBListenerKey:path withModifiers:modifiersString];
RNFirebaseDBReference *ref = [_dbReferences objectForKey:key];
if (ref == nil) {
ref = [[RNFirebaseDBReference alloc] initWithPathAndModifiers:self
database:[FIRDatabase database]
path:path
modifiers:modifiers
modifiersString:modifiersString];
database:[FIRDatabase database]
path:path
modifiers:modifiers
modifiersString:modifiersString];
[_dbReferences setObject:ref forKey:key];
}
return ref;
@ -634,7 +717,7 @@ RCT_EXPORT_METHOD(goOnline)
// Not sure how to get away from this... yet
- (NSArray<NSString *> *)supportedEvents {
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT];
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT];
}

View File

@ -21,6 +21,7 @@ static NSString *const DEBUG_EVENT = @"debug";
// Database
static NSString *const DATABASE_DATA_EVENT = @"database_event";
static NSString *const DATABASE_ERROR_EVENT = @"database_error";
static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_event";
static NSString *const DATABASE_VALUE_EVENT = @"value";
static NSString *const DATABASE_CHILD_ADDED_EVENT = @"child_added";
@ -28,6 +29,7 @@ static NSString *const DATABASE_CHILD_MODIFIED_EVENT = @"child_changed";
static NSString *const DATABASE_CHILD_REMOVED_EVENT = @"child_removed";
static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved";
// Storage
static NSString *const STORAGE_EVENT = @"storage_event";
static NSString *const STORAGE_ERROR = @"storage_error";

View File

@ -247,7 +247,7 @@ RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseR
[_bridge.eventDispatcher sendDeviceEventWithName:@"FCMTokenRefreshed" body:[[FIRInstanceID instanceID] token]];
}
RCT_EXPORT_METHOD(requestPermissions)
RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
if (RCTRunningInAppExtension()) {
return;
@ -265,6 +265,9 @@ RCT_EXPORT_METHOD(requestPermissions)
//iOS 7 or below
[app registerForRemoteNotificationTypes:(NSUInteger)allNotificationTypes];
}
// Unfortunately on iOS 9 or below, there's no way to tell whether the user accepted or
// rejected the permissions popup
resolve(@{@"status":@"unknown"});
} else {
// iOS 10 or later
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@ -275,11 +278,12 @@ RCT_EXPORT_METHOD(requestPermissions)
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:authOptions
completionHandler:^(BOOL granted, NSError * _Nullable error) {
resolve(@{@"granted":@(granted)});
}
];
#endif
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
}

View File

@ -22,10 +22,19 @@ RCT_EXPORT_MODULE(RNFirebaseStorage);
NSString *code = @"storage/unknown";
NSString *message = [error localizedDescription];
NSDictionary *userInfo = [error userInfo];
NSError *underlyingError = [userInfo objectForKey:NSUnderlyingErrorKey];
NSString *underlyingErrorDescription = [underlyingError localizedDescription];
switch (error.code) {
case FIRStorageErrorCodeUnknown:
code = @"storage/unknown";
message = @"An unknown error has occurred.";
if ([underlyingErrorDescription isEqualToString:@"The operation couldnt be completed. Permission denied"]) {
code = @"storage/invalid-device-file-path";
message = @"The specified device file path is invalid or is restricted.";
} else {
code = @"storage/unknown";
message = @"An unknown error has occurred.";
}
break;
case FIRStorageErrorCodeObjectNotFound:
code = @"storage/object-not-found";
@ -71,7 +80,12 @@ RCT_EXPORT_MODULE(RNFirebaseStorage);
break;
}
reject(code, message, error);
if (userInfo != nil && [userInfo objectForKey:@"data"]) {
// errors with 'data' are unserializable - it breaks react so we send nil instead
reject(code, message, nil);
} else {
reject(code, message, error);
}
}
@ -358,7 +372,7 @@ RCT_EXPORT_METHOD(putFile:(NSString *) path localPath:(NSString *)localPath meta
return @{
@"bytesTransferred": @(task.progress.completedUnitCount),
@"ref": task.reference.fullPath,
@"status": [self getTaskStatus:task.status],
@"state": [self getTaskStatus:task.status],
@"totalBytes": @(task.progress.totalUnitCount)
};
}

View File

@ -8,7 +8,7 @@ import Log from './utils/log';
import { isObject } from './utils';
// modules
import Auth from './modules/auth';
import Auth, { statics as AuthStatics } from './modules/auth';
import Storage, { statics as StorageStatics } from './modules/storage';
import Database, { statics as DatabaseStatics } from './modules/database';
import Messaging from './modules/messaging';
@ -54,8 +54,12 @@ export default class Firebase {
throw new Error(`Google Play Services is required to run this application but no valid installation was found (Code ${this.googleApiAvailability.status}).`);
}
this.auth = this._staticsOrInstance('auth', StorageStatics, Auth);
this.storage = this._staticsOrInstance('storage', StorageStatics, Storage);
this.database = this._staticsOrInstance('database', DatabaseStatics, Database);
// init auth to stat listeners
this.auth();
}
/**
@ -81,10 +85,6 @@ export default class Firebase {
return instances[name];
}
auth() {
return this._auth;
}
analytics() {
if (!this._analytics) {
this._analytics = new Analytics(this);

View File

@ -25,13 +25,6 @@ export default class Auth extends Base {
this._authResult = null;
this.authenticated = false;
// attach auth providers
this.EmailAuthProvider = EmailAuthProvider;
this.GoogleAuthProvider = GoogleAuthProvider;
this.FacebookAuthProvider = FacebookAuthProvider;
this.TwitterAuthProvider = TwitterAuthProvider;
this.GithubAuthProvider = GithubAuthProvider;
// start listening immediately for auth changes
FirebaseAuthEvt.addListener('onAuthStateChanged', this._onAuthStateChanged.bind(this));
FirebaseAuth.addAuthStateListener();
@ -175,3 +168,11 @@ export default class Auth extends Base {
return 'firebase:auth';
}
}
export const statics = {
GoogleAuthProvider,
EmailAuthProvider,
FacebookAuthProvider,
TwitterAuthProvider,
GithubAuthProvider,
};

View File

@ -5,8 +5,9 @@
import { NativeModules, NativeEventEmitter } from 'react-native';
import { Base } from './../base';
import Snapshot from './snapshot.js';
import Reference from './reference.js';
import Snapshot from './snapshot';
import Reference from './reference';
import TransactionHandler from './transaction';
import { promisify } from './../../utils';
const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
@ -19,10 +20,11 @@ export default class Database extends Base {
constructor(firebase: Object, options: Object = {}) {
super(firebase, options);
this.subscriptions = {};
this.errorSubscriptions = {};
this.serverTimeOffset = 0;
this.errorSubscriptions = {};
this.persistenceEnabled = false;
this.namespace = 'firebase:database';
this.transaction = new TransactionHandler(firebase, this, FirebaseDatabaseEvt);
if (firebase.options.persistence === true) {
this._setPersistence(true);

View File

@ -16,7 +16,7 @@ export default class Query extends ReferenceBase {
ref: Reference;
constructor(ref: Reference, path: string, existingModifiers?: Array<string>) {
super(ref.db, path);
super(ref.database, path);
this.log.debug('creating Query ', path, existingModifiers);
this.ref = ref;
this.modifiers = existingModifiers ? [...existingModifiers] : [];

View File

@ -17,15 +17,15 @@ const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
*/
export default class Reference extends ReferenceBase {
db: FirebaseDatabase;
database: FirebaseDatabase;
query: Query;
constructor(db: FirebaseDatabase, path: string, existingModifiers?: Array<string>) {
super(db.firebase, path);
this.db = db;
constructor(database: FirebaseDatabase, path: string, existingModifiers?: Array<string>) {
super(database.firebase, path);
this.database = database;
this.namespace = 'firebase:db:ref';
this.query = new Query(this, path, existingModifiers);
this.log.debug('Created new Reference', this.db._handle(path, existingModifiers));
this.log.debug('Created new Reference', this.database._handle(path, existingModifiers));
}
/**
@ -34,7 +34,7 @@ export default class Reference extends ReferenceBase {
* @returns {*}
*/
keepSynced(bool: boolean) {
const path = this._dbPath();
const path = this.path;
return promisify('keepSynced', FirebaseDatabase)(path, bool);
}
@ -44,7 +44,7 @@ export default class Reference extends ReferenceBase {
* @returns {*}
*/
set(value: any) {
const path = this._dbPath();
const path = this.path;
const _value = this._serializeAnyType(value);
return promisify('set', FirebaseDatabase)(path, _value);
}
@ -55,7 +55,7 @@ export default class Reference extends ReferenceBase {
* @returns {*}
*/
update(val: Object) {
const path = this._dbPath();
const path = this.path;
const value = this._serializeObject(val);
return promisify('update', FirebaseDatabase)(path, value);
}
@ -65,7 +65,7 @@ export default class Reference extends ReferenceBase {
* @returns {*}
*/
remove() {
return promisify('remove', FirebaseDatabase)(this._dbPath());
return promisify('remove', FirebaseDatabase)(this.path);
}
/**
@ -76,15 +76,15 @@ export default class Reference extends ReferenceBase {
*/
push(value: any, onComplete: Function) {
if (value === null || value === undefined) {
return new Reference(this.db, `${this.path}/${generatePushID(this.db.serverTimeOffset)}`);
return new Reference(this.database, `${this.path}/${generatePushID(this.database.serverTimeOffset)}`);
}
const path = this._dbPath();
const path = this.path;
const _value = this._serializeAnyType(value);
return promisify('push', FirebaseDatabase)(path, _value)
.then(({ ref }) => {
const newRef = new Reference(this.db, ref);
const newRef = new Reference(this.database, ref);
if (isFunction(onComplete)) return onComplete(null, newRef);
return newRef;
}).catch((e) => {
@ -104,11 +104,12 @@ export default class Reference extends ReferenceBase {
on(eventType: string, successCallback: () => any, failureCallback: () => any) {
if (!isFunction(successCallback)) throw new Error('The specified callback must be a function');
if (failureCallback && !isFunction(failureCallback)) throw new Error('The specified error callback must be a function');
const path = this._dbPath();
const path = this.path;
const modifiers = this.query.getModifiers();
const modifiersString = this.query.getModifiersString();
this.log.debug('adding reference.on', path, modifiersString, eventType);
return this.db.on(path, modifiersString, modifiers, eventType, successCallback, failureCallback);
this.database.on(path, modifiersString, modifiers, eventType, successCallback, failureCallback);
return successCallback;
}
/**
@ -120,7 +121,7 @@ export default class Reference extends ReferenceBase {
* @returns {Promise.<TResult>}
*/
once(eventType: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: Error) => void) {
const path = this._dbPath();
const path = this.path;
const modifiers = this.query.getModifiers();
const modifiersString = this.query.getModifiersString();
return promisify('once', FirebaseDatabase)(path, modifiersString, modifiers, eventType)
@ -130,7 +131,7 @@ export default class Reference extends ReferenceBase {
return snapshot;
})
.catch((error) => {
const firebaseError = this.db._toFirebaseError(error);
const firebaseError = this.database._toFirebaseError(error);
if (isFunction(failureCallback)) return failureCallback(firebaseError);
return Promise.reject(firebaseError);
});
@ -143,10 +144,40 @@ export default class Reference extends ReferenceBase {
* @returns {*}
*/
off(eventType?: string = '', origCB?: () => any) {
const path = this._dbPath();
const path = this.path;
const modifiersString = this.query.getModifiersString();
this.log.debug('ref.off(): ', path, modifiersString, eventType);
return this.db.off(path, modifiersString, eventType, origCB);
return this.database.off(path, modifiersString, eventType, origCB);
}
/**
* Atomically modifies the data at this location.
* @url https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
* @param transactionUpdate
* @param onComplete
* @param applyLocally
*/
transaction(transactionUpdate, onComplete?: () => any, 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 (error) {
if (isFunction(onComplete)) onComplete(error, committed, null);
return reject(error);
}
const snapshot = new Snapshot(this, snapshotData);
if (isFunction(onComplete)) {
onComplete(null, committed, snapshot);
}
return resolve({ committed, snapshot });
};
this.database.transaction.add(this, transactionUpdate, onCompleteWrapper, applyLocally);
});
}
/**
@ -193,7 +224,7 @@ export default class Reference extends ReferenceBase {
* @returns {Reference}
*/
orderBy(name: string, key?: string): Reference {
const newRef = new Reference(this.db, this.path, this.query.getModifiers());
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.setOrderBy(name, key);
return newRef;
}
@ -227,7 +258,7 @@ export default class Reference extends ReferenceBase {
* @returns {Reference}
*/
limit(name: string, limit: number): Reference {
const newRef = new Reference(this.db, this.path, this.query.getModifiers());
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.setLimit(name, limit);
return newRef;
}
@ -274,7 +305,7 @@ export default class Reference extends ReferenceBase {
* @returns {Reference}
*/
filter(name: string, value: any, key?: string): Reference {
const newRef = new Reference(this.db, this.path, this.query.getModifiers());
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.setFilter(name, value, key);
return newRef;
}
@ -293,7 +324,7 @@ export default class Reference extends ReferenceBase {
* @returns {Reference}
*/
child(path: string) {
return new Reference(this.db, `${this.path}/${path}`);
return new Reference(this.database, `${this.path}/${path}`);
}
/**
@ -301,7 +332,7 @@ export default class Reference extends ReferenceBase {
* @returns {string}
*/
toString(): string {
return this._dbPath();
return this.path;
}
/**
@ -314,7 +345,7 @@ export default class Reference extends ReferenceBase {
*/
get parent(): Reference|null {
if (this.path === '/') return null;
return new Reference(this.db, this.path.substring(0, this.path.lastIndexOf('/')));
return new Reference(this.database, this.path.substring(0, this.path.lastIndexOf('/')));
}
@ -323,16 +354,13 @@ export default class Reference extends ReferenceBase {
* @returns {Reference}
*/
get root(): Reference {
return new Reference(this.db, '/');
return new Reference(this.database, '/');
}
/**
* INTERNALS
*/
_dbPath(): string {
return this.path;
}
/**
*

View File

@ -0,0 +1,145 @@
/**
* @flow
* Database representation wrapper
*/
import { NativeModules } from 'react-native';
import { Base } from './../base';
import { generatePushID } from './../../utils';
const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
/**
* @class Database
*/
export default class TransactionHandler extends Base {
constructor(firebase: Object, database: Object, FirebaseDatabaseEvt) {
super(firebase, {});
this.transactions = {};
this.database = database;
this.namespace = 'firebase:database:transaction';
this.transactionListener = FirebaseDatabaseEvt.addListener(
'database_transaction_event',
event => this._handleTransactionEvent(event)
);
}
/**
* Add a new transaction and begin starts it natively.
* @param reference
* @param transactionUpdater
* @param onComplete
* @param applyLocally
*/
add(reference, transactionUpdater, onComplete, applyLocally = false) {
const id = this._generateTransactionId();
this.transactions[id] = {
id,
reference,
transactionUpdater,
onComplete,
applyLocally,
completed: false,
started: true,
};
FirebaseDatabase.startTransaction(reference.path, id, applyLocally || false);
}
/**
* INTERNALS
*/
/**
* Uses the push id generator to create a transaction id
* @returns {string}
* @private
*/
_generateTransactionId() {
return generatePushID(this.database.serverTimeOffset);
}
/**
*
* @param event
* @returns {*}
* @private
*/
_handleTransactionEvent(event: Object = {}) {
switch (event.type) {
case 'update':
return this._handleUpdate(event);
case 'error':
return this._handleError(error);
case 'complete':
return this._handleComplete(event);
default:
this.log.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];
// todo handle when transaction no longer exists on js side?
newValue = transaction.transactionUpdater(value);
} finally {
let abort = false;
if (newValue === undefined) {
abort = true;
}
FirebaseDatabase.tryCommitTransaction(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(new Error(event.message, event.code), 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];
});
}
}
}
}

View File

@ -46,7 +46,7 @@ function finish(data) {
if (!this._finishCalled && this._completionHandlerId) {
let result = Object.assign({}, data);
let result = data;
this._finishCalled = true;

View File

@ -12,6 +12,11 @@ type StorageOptionsType = {
};
export default class Storage extends Base {
/**
*
* @param firebase
* @param options
*/
constructor(firebase: Object, options: StorageOptionsType = {}) {
super(firebase, options);
this.subscriptions = {};
@ -27,27 +32,58 @@ export default class Storage extends Base {
);
}
/**
* Returns a reference for the given path in the default bucket.
* @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#ref
* @param path
* @returns {StorageReference}
*/
ref(path: string): StorageRef {
return new StorageRef(this, path);
}
/**
* Returns a reference for the given absolute URL.
* @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#refFromURL
* @param url
* @returns {StorageReference}
*/
refFromURL(url: string): Promise<StorageRef> {
// TODO don't think this is correct?
return new StorageRef(this, `url::${url}`);
}
/**
* setMaxOperationRetryTime
* @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime
* @param time The new maximum operation retry time in milliseconds.
*/
setMaxOperationRetryTime(time: number) {
FirebaseStorage.setMaxOperationRetryTime(time);
}
/**
* setMaxUploadRetryTime
* @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime
* @param time The new maximum upload retry time in milliseconds.
*/
setMaxUploadRetryTime(time: number) {
FirebaseStorage.setMaxUploadRetryTime(time);
}
// additional methods compared to Web API
/**
* setMaxDownloadRetryTime
* @url N/A
* @param time The new maximum download retry time in milliseconds.
*/
setMaxDownloadRetryTime(time: number) {
FirebaseStorage.setMaxDownloadRetryTime(time);
}
/** **********
* INTERNALS
********** **/
_handleStorageEvent(event: Object) {
const { path, eventName } = event;
const body = event.body || {};
@ -91,18 +127,6 @@ export default class Storage extends Base {
}
}
static constants = {
MAIN_BUNDLE_PATH: FirebaseStorage.MAIN_BUNDLE_PATH,
CACHES_DIRECTORY_PATH: FirebaseStorage.CACHES_DIRECTORY_PATH,
DOCUMENT_DIRECTORY_PATH: FirebaseStorage.DOCUMENT_DIRECTORY_PATH,
EXTERNAL_DIRECTORY_PATH: FirebaseStorage.EXTERNAL_DIRECTORY_PATH,
EXTERNAL_STORAGE_DIRECTORY_PATH: FirebaseStorage.EXTERNAL_STORAGE_DIRECTORY_PATH,
TEMP_DIRECTORY_PATH: FirebaseStorage.TEMP_DIRECTORY_PATH,
LIBRARY_DIRECTORY_PATH: FirebaseStorage.LIBRARY_DIRECTORY_PATH,
FILETYPE_REGULAR: FirebaseStorage.FILETYPE_REGULAR,
FILETYPE_DIRECTORY: FirebaseStorage.FILETYPE_DIRECTORY,
};
get namespace(): string {
return 'firebase:storage';
}
@ -113,11 +137,22 @@ export const statics = {
STATE_CHANGED: 'state_changed',
},
TaskState: {
RUNNING: 'RUNNING',
PAUSED: 'PAUSED',
SUCCESS: 'SUCCESS',
CANCELED: 'CANCELED',
ERROR: 'ERROR',
RUNNING: 'running',
PAUSED: 'paused',
SUCCESS: 'success',
CANCELLED: 'cancelled',
ERROR: 'error',
},
Native: {
MAIN_BUNDLE_PATH: FirebaseStorage.MAIN_BUNDLE_PATH,
CACHES_DIRECTORY_PATH: FirebaseStorage.CACHES_DIRECTORY_PATH,
DOCUMENT_DIRECTORY_PATH: FirebaseStorage.DOCUMENT_DIRECTORY_PATH,
EXTERNAL_DIRECTORY_PATH: FirebaseStorage.EXTERNAL_DIRECTORY_PATH,
EXTERNAL_STORAGE_DIRECTORY_PATH: FirebaseStorage.EXTERNAL_STORAGE_DIRECTORY_PATH,
TEMP_DIRECTORY_PATH: FirebaseStorage.TEMP_DIRECTORY_PATH,
LIBRARY_DIRECTORY_PATH: FirebaseStorage.LIBRARY_DIRECTORY_PATH,
FILETYPE_REGULAR: FirebaseStorage.FILETYPE_REGULAR,
FILETYPE_DIRECTORY: FirebaseStorage.FILETYPE_DIRECTORY,
},
};

View File

@ -13,8 +13,8 @@ export default class StorageTask {
this.path = storageRef.path;
// 'proxy' original promise
this.then = promise.then;
this.catch = promise.catch;
this.then = promise.then.bind(promise);
this.catch = promise.catch.bind(promise);
}
/**

View File

@ -1,6 +1,6 @@
{
"name": "react-native-firebase",
"version": "1.0.0-alpha7",
"version": "1.0.0-alpha9",
"author": "Invertase <contact@invertase.io> (http://invertase.io)",
"description": "A react native firebase library supporting both android and ios native firebase SDK's",
"main": "index",