Merge branch 'master' of https://github.com/invertase/react-native-firebase into bridge-detox

This commit is contained in:
Salakar 2018-03-23 13:41:56 +00:00
commit a174c48cd5
127 changed files with 7749 additions and 2450 deletions

View File

@ -1,6 +1,6 @@
<p align="center">
<a href="https://rnfirebase.io">
<img src="http://i.imgur.com/01XQL0x.png"><br/>
<img src="https://i.imgur.com/eBNJlHd.png"><br/>
</a>
<h2 align="center">React Native Firebase</h2>
</p>

View File

@ -1 +1,32 @@
<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>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 183 197" style="enable-background:new 0 0 183 197;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E88634;}
.st1{fill:#F9CB52;}
.st2{fill:#F4A73E;}
</style>
<title>Slice 1</title>
<g>
<path class="st0" d="M1,98c0.6,20.7,40.7,37,90,37v-8c-39.7-0.2-81.3-12.4-82-29C9,98,1,98,1,98z"/>
<path class="st1" d="M135.5,21.8c-18.3-9.8-52.4,16.7-77,59.4l6.9,4c20-34.3,51.4-64.3,66.1-56.5L135.5,21.8L135.5,21.8z"/>
<path class="st0" d="M1,98c0.6-20.7,40.7-37,90-37v8C51.3,69.2,9.7,81.4,9,98C9,98,1,98,1,98z"/>
<path class="st2" d="M46,20c-17.6,10.9-11.7,53.7,13,96.4l6.9-4C46.3,78,36,35.8,50,26.9L46,20L46,20z"/>
<path class="st1" d="M46,176.4C28.4,165.5,34.3,122.7,59,80l6.9,4c-19.7,34.5-30,76.6-15.9,85.5L46,176.4L46,176.4z"/>
<path class="st2" d="M45,20.5c18.3-9.8,52.4,16.7,77,59.4l-6.9,4C95.1,49.7,63.7,19.7,49,27.4L45,20.5z"/>
<path class="st0" d="M181,98c-0.6-20.7-40.7-37-90-37v8c39.7,0.2,81.3,12.4,82,29H181z"/>
<path class="st1" d="M45.2,176.5c0.1,0.1,0.2,0.1,0.3,0.2c18.1,10.5,52.6-16.1,77.5-59.2l-6.9-4.1C96,148,64.2,178.2,49.6,169.8
c-0.1-0.1-0.3-0.2-0.4-0.3L45.2,176.5L45.2,176.5z"/>
<path class="st2" d="M136,175.9c-18.3,9.8-52.4-16.7-77-59.4l-0.3-0.4l6.8-4.2c20,34.5,51.6,64.9,66.4,57.1L136,175.9L136,175.9z"
/>
<path class="st0" d="M181,97c0,0.2,0,0.3,0,0.5c0,20.9-40.3,37.5-90,37.5v-8c40-0.2,82-12.6,82-29.4c0-0.2,0-0.4,0-0.6H181z"/>
<path class="st2" d="M135,176.4c17.6-10.9,11.7-53.7-13-96.4l-0.4-0.8l-7,3.8l0,0.1c20,34.6,30.5,77.2,16.5,86.3L135,176.4z"/>
<path class="st1" d="M135.5,21.3c17.6,10.9,11.7,53.7-13,96.4l-6.9-4c19.7-34.5,30-76.6,15.9-85.5
C131.5,28.2,135.5,21.3,135.5,21.3z"/>
<path class="st2" d="M81,103.1c0,5.5,4.5,10,10,10l0,0c5.5,0,10-4.5,10-10c0-5.6-2.8-10-0.9-13.9c0,0-3,0.6-4.7,5
c0,0-5.6-5.6-3.2-13.4c0,0-5.7,2.2-5.1,9.5c0.5,6.2-0.6,7.3-0.6,7.3c0-5-3.9-7.3-3.9-7.3C83.7,96.4,81,98.1,81,103.1z"/>
<path class="st1" d="M98.2,105.5c0,4.2-3.2,7.7-7.4,7.7c-3.9-0.1-6.9-3.1-6.5-7c0.1-1.7,1.5-4.6,3.6-7.4l1.4,0.9
c0-3.5,1.6-8.2,1.6-8.2C92.1,96.4,98.2,97.7,98.2,105.5z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -15,8 +15,8 @@ buildscript {
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
buildToolsVersion "25.0.3"
compileSdkVersion 27
buildToolsVersion "27.0.2"
defaultConfig {
minSdkVersion 16
targetSdkVersion 26
@ -82,7 +82,8 @@ rootProject.gradle.buildFinished { buildResult ->
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile "com.facebook.react:react-native:+" // From node_modules
compile 'me.leolin:ShortcutBadger:1.1.18@aar'
compile "com.android.support:support-v4:27.0.2"
compile 'me.leolin:ShortcutBadger:1.1.21@aar'
compile "com.google.android.gms:play-services-base:$firebaseVersion"
compile "com.google.firebase:firebase-core:$firebaseVersion"
compile "com.google.firebase:firebase-config:$firebaseVersion"

View File

@ -1,5 +1,7 @@
package io.invertase.firebase;
import android.app.ActivityManager;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
@ -522,4 +524,26 @@ public class Utils {
}
return deconstructedList;
}
public static boolean isAppInForeground(Context context) {
/**
We need to check if app is in foreground otherwise the app will crash.
http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
**/
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses =
activityManager.getRunningAppProcesses();
if (appProcesses == null) {
return false;
}
final String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance ==
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
}

View File

@ -35,9 +35,9 @@ import io.invertase.firebase.Utils;
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseDatabase";
private boolean enableLogging = false;
private static boolean enableLogging = false;
private HashMap<String, RNFirebaseDatabaseReference> references = new HashMap<>();
private HashMap<String, Boolean> loggingLevelSet = new HashMap<>();
private static HashMap<String, Boolean> loggingLevelSet = new HashMap<>();
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
RNFirebaseDatabase(ReactApplicationContext reactContext) {
@ -53,16 +53,16 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param appName
*/
@ReactMethod
public void goOnline(String appName) {
getDatabaseForApp(appName).goOnline();
public void goOnline(String appName, String dbURL) {
getDatabaseForApp(appName, dbURL).goOnline();
}
/**
* @param appName
*/
@ReactMethod
public void goOffline(String appName) {
getDatabaseForApp(appName).goOffline();
public void goOffline(String appName, String dbURL) {
getDatabaseForApp(appName, dbURL).goOffline();
}
/**
@ -70,8 +70,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param state
*/
@ReactMethod
public void setPersistence(String appName, Boolean state) {
getDatabaseForApp(appName).setPersistenceEnabled(state);
public void setPersistence(String appName, String dbURL, Boolean state) {
getDatabaseForApp(appName, dbURL).setPersistenceEnabled(state);
}
/**
@ -79,8 +79,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param size
*/
@ReactMethod
public void setPersistenceCacheSizeBytes(String appName, int size) {
getDatabaseForApp(appName).setPersistenceCacheSizeBytes((long) size);
public void setPersistenceCacheSizeBytes(String appName, String dbURL, int size) {
getDatabaseForApp(appName, dbURL).setPersistenceCacheSizeBytes((long) size);
}
@ -116,8 +116,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param state
*/
@ReactMethod
public void keepSynced(String appName, String key, String path, ReadableArray modifiers, Boolean state) {
getInternalReferenceForApp(appName, key, path, modifiers).getQuery().keepSynced(state);
public void keepSynced(String appName, String dbURL, String key, String path, ReadableArray modifiers, Boolean state) {
getInternalReferenceForApp(appName, dbURL, key, path, modifiers).getQuery().keepSynced(state);
}
@ -130,7 +130,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param updates
*/
@ReactMethod
public void transactionTryCommit(String appName, int transactionId, ReadableMap updates) {
public void transactionTryCommit(String appName, String dbURL, int transactionId, ReadableMap updates) {
RNFirebaseTransactionHandler handler = transactionHandlers.get(transactionId);
if (handler != null) {
@ -147,16 +147,16 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param applyLocally
*/
@ReactMethod
public void transactionStart(final String appName, final String path, final int transactionId, final Boolean applyLocally) {
public void transactionStart(final String appName, final String dbURL, final String path, final int transactionId, final Boolean applyLocally) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
DatabaseReference reference = getReferenceForAppPath(appName, path);
DatabaseReference reference = getReferenceForAppPath(appName, dbURL, path);
reference.runTransaction(new Transaction.Handler() {
@Override
public Transaction.Result doTransaction(MutableData mutableData) {
final RNFirebaseTransactionHandler transactionHandler = new RNFirebaseTransactionHandler(transactionId, appName);
final RNFirebaseTransactionHandler transactionHandler = new RNFirebaseTransactionHandler(transactionId, appName, dbURL);
transactionHandlers.put(transactionId, transactionHandler);
final WritableMap updatesMap = transactionHandler.createUpdateMap(mutableData);
@ -212,9 +212,9 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void onDisconnectSet(String appName, String path, ReadableMap props, final Promise promise) {
public void onDisconnectSet(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
String type = props.getString("type");
DatabaseReference ref = getReferenceForAppPath(appName, path);
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
OnDisconnect onDisconnect = ref.onDisconnect();
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@ -257,8 +257,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void onDisconnectUpdate(String appName, String path, ReadableMap props, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void onDisconnectUpdate(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
OnDisconnect ondDisconnect = ref.onDisconnect();
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props);
@ -279,8 +279,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void onDisconnectRemove(String appName, String path, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void onDisconnectRemove(String appName, String dbURL, String path, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
OnDisconnect onDisconnect = ref.onDisconnect();
onDisconnect.removeValue(new DatabaseReference.CompletionListener() {
@ -299,8 +299,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void onDisconnectCancel(String appName, String path, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void onDisconnectCancel(String appName, String dbURL, String path, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
OnDisconnect onDisconnect = ref.onDisconnect();
onDisconnect.cancel(new DatabaseReference.CompletionListener() {
@ -318,8 +318,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void set(String appName, String path, ReadableMap props, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void set(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
Object value = Utils.recursivelyDeconstructReadableMap(props).get("value");
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@ -339,8 +339,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void setPriority(String appName, String path, ReadableMap priority, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void setPriority(String appName, String dbURL, String path, ReadableMap priority, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
Object priorityValue = Utils.recursivelyDeconstructReadableMap(priority).get("value");
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@ -361,8 +361,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void setWithPriority(String appName, String path, ReadableMap data, ReadableMap priority, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void setWithPriority(String appName, String dbURL, String path, ReadableMap data, ReadableMap priority, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
Object dataValue = Utils.recursivelyDeconstructReadableMap(data).get("value");
Object priorityValue = Utils.recursivelyDeconstructReadableMap(priority).get("value");
@ -383,8 +383,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void update(String appName, String path, ReadableMap props, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void update(String appName, String dbURL, String path, ReadableMap props, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
Map<String, Object> updates = Utils.recursivelyDeconstructReadableMap(props);
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@ -403,8 +403,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void remove(String appName, String path, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, path);
public void remove(String appName, String dbURL, String path, final Promise promise) {
DatabaseReference ref = getReferenceForAppPath(appName, dbURL, path);
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
@Override
@ -428,8 +428,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param promise
*/
@ReactMethod
public void once(String appName, String key, String path, ReadableArray modifiers, String eventType, Promise promise) {
getInternalReferenceForApp(appName, key, path, modifiers).once(eventType, promise);
public void once(String appName, String dbURL, String key, String path, ReadableArray modifiers, String eventType, Promise promise) {
getInternalReferenceForApp(appName, dbURL, key, path, modifiers).once(eventType, promise);
}
/**
@ -439,8 +439,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param props ReadableMap
*/
@ReactMethod
public void on(String appName, ReadableMap props) {
getCachedInternalReferenceForApp(appName, props)
public void on(String appName, String dbURL, ReadableMap props) {
getCachedInternalReferenceForApp(appName, dbURL, props)
.on(
props.getString("eventType"),
props.getMap("registration")
@ -494,11 +494,18 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* Get a database instance for a specific firebase app instance
*
* @param appName
* @param dbURL
* @return
*/
private FirebaseDatabase getDatabaseForApp(String appName) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseDatabase firebaseDatabase = FirebaseDatabase.getInstance(firebaseApp);
public static FirebaseDatabase getDatabaseForApp(String appName, String dbURL) {
FirebaseDatabase firebaseDatabase;
if(dbURL != null && dbURL.length() > 0) {
firebaseDatabase = FirebaseDatabase.getInstance(dbURL);
} else {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
firebaseDatabase = FirebaseDatabase.getInstance(firebaseApp);
}
Boolean logLevel = loggingLevelSet.get(firebaseDatabase.getApp().getName());
if (enableLogging && (logLevel == null || !logLevel)) {
@ -535,8 +542,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param path
* @return
*/
private DatabaseReference getReferenceForAppPath(String appName, String path) {
return getDatabaseForApp(appName).getReference(path);
private DatabaseReference getReferenceForAppPath(String appName, String dbURL, String path) {
return getDatabaseForApp(appName, dbURL).getReference(path);
}
/**
@ -548,10 +555,11 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param modifiers
* @return
*/
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, String key, String path, ReadableArray modifiers) {
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, String dbURL, String key, String path, ReadableArray modifiers) {
return new RNFirebaseDatabaseReference(
getReactApplicationContext(),
appName,
dbURL,
key,
path,
modifiers
@ -565,7 +573,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param props
* @return
*/
private RNFirebaseDatabaseReference getCachedInternalReferenceForApp(String appName, ReadableMap props) {
private RNFirebaseDatabaseReference getCachedInternalReferenceForApp(String appName, String dbURL, ReadableMap props) {
String key = props.getString("key");
String path = props.getString("path");
ReadableArray modifiers = props.getArray("modifiers");
@ -573,7 +581,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
RNFirebaseDatabaseReference existingRef = references.get(key);
if (existingRef == null) {
existingRef = getInternalReferenceForApp(appName, key, path, modifiers);
existingRef = getInternalReferenceForApp(appName, dbURL, key, path, modifiers);
references.put(key, existingRef);
}

View File

@ -28,6 +28,7 @@ class RNFirebaseDatabaseReference {
private String key;
private Query query;
private String appName;
private String dbURL;
private ReactContext reactContext;
private static final String TAG = "RNFirebaseDBReference";
private HashMap<String, ChildEventListener> childEventListeners = new HashMap<>();
@ -43,10 +44,11 @@ class RNFirebaseDatabaseReference {
* @param refPath
* @param modifiersArray
*/
RNFirebaseDatabaseReference(ReactContext context, String app, String refKey, String refPath, ReadableArray modifiersArray) {
RNFirebaseDatabaseReference(ReactContext context, String app, String url, String refKey, String refPath, ReadableArray modifiersArray) {
key = refKey;
query = null;
appName = app;
dbURL = url;
reactContext = context;
buildDatabaseQueryAtPathAndModifiers(refPath, modifiersArray);
}
@ -346,9 +348,7 @@ class RNFirebaseDatabaseReference {
* @return
*/
private void buildDatabaseQueryAtPathAndModifiers(String path, ReadableArray modifiers) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseDatabase firebaseDatabase = FirebaseDatabase.getInstance(firebaseApp);
FirebaseDatabase firebaseDatabase = RNFirebaseDatabase.getDatabaseForApp(appName, dbURL);
query = firebaseDatabase.getReference(path);
List<Object> modifiersList = Utils.recursivelyDeconstructReadableArray(modifiers);

View File

@ -21,6 +21,7 @@ import io.invertase.firebase.Utils;
public class RNFirebaseTransactionHandler {
private int transactionId;
private String appName;
private String dbURL;
private final ReentrantLock lock;
private final Condition condition;
private Map<String, Object> data;
@ -31,8 +32,9 @@ public class RNFirebaseTransactionHandler {
boolean abort = false;
boolean timeout = false;
RNFirebaseTransactionHandler(int id, String app) {
RNFirebaseTransactionHandler(int id, String app, String url) {
appName = app;
dbURL = url;
transactionId = id;
lock = new ReentrantLock();
condition = lock.newCondition();
@ -107,6 +109,7 @@ public class RNFirebaseTransactionHandler {
// all events get distributed js side based on app name
updatesMap.putString("appName", appName);
updatesMap.putString("dbURL", dbURL);
if (!updatesData.hasChildren()) {
Utils.mapPutValue("value", updatesData.getValue(), updatesMap);
@ -129,6 +132,7 @@ public class RNFirebaseTransactionHandler {
resultMap.putInt("id", transactionId);
resultMap.putString("appName", appName);
resultMap.putString("dbURL", dbURL);
resultMap.putBoolean("timeout", timeout);
resultMap.putBoolean("committed", committed);

View File

@ -0,0 +1,45 @@
package io.invertase.firebase.instanceid;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.google.firebase.iid.FirebaseInstanceId;
import java.io.IOException;
public class RNFirebaseInstanceId extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseInstanceId";
public RNFirebaseInstanceId(ReactApplicationContext reactContext) {
super(reactContext);
Log.d(TAG, "New instance");
}
@Override
public String getName() {
return TAG;
}
@ReactMethod
public void delete(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("instance_id_error", e.getMessage());
}
}
@ReactMethod
public void get(Promise promise){
String id = FirebaseInstanceId.getInstance().getId();
promise.resolve(id);
}
}

View File

@ -0,0 +1,38 @@
package io.invertase.firebase.instanceid;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("unused")
public class RNFirebaseInstanceIdPackage implements ReactPackage {
public RNFirebaseInstanceIdPackage() {
}
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNFirebaseInstanceId(reactContext));
return modules;
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,236 @@
package io.invertase.firebase.invites;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.Log;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.appinvite.AppInviteInvitation;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.appinvite.FirebaseAppInvite;
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
import com.google.firebase.dynamiclinks.PendingDynamicLinkData;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import io.invertase.firebase.Utils;
import io.invertase.firebase.links.RNFirebaseLinks;
public class RNFirebaseInvites extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener {
private static final String TAG = "RNFirebaseInvites";
private static final int REQUEST_INVITE = 81283;
private boolean mInitialInvitationInitialized = false;
private String mInitialDeepLink = null;
private String mInitialInvitationId = null;
private Promise mPromise = null;
public RNFirebaseInvites(ReactApplicationContext context) {
super(context);
getReactApplicationContext().addActivityEventListener(this);
}
@Override
public String getName() {
return "RNFirebaseInvites";
}
@ReactMethod
public void getInitialInvitation(final Promise promise) {
if (mInitialInvitationInitialized) {
if (mInitialDeepLink != null || mInitialInvitationId != null) {
promise.resolve(buildInvitationMap(mInitialDeepLink, mInitialInvitationId));
} else {
promise.resolve(null);
}
} else {
if (getCurrentActivity() != null) {
FirebaseDynamicLinks.getInstance()
.getDynamicLink(getCurrentActivity().getIntent())
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
@Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
if (pendingDynamicLinkData != null) {
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(pendingDynamicLinkData);
if (invite == null) {
promise.resolve(null);
return;
}
mInitialDeepLink = pendingDynamicLinkData.getLink().toString();
mInitialInvitationId = invite.getInvitationId();
promise.resolve(buildInvitationMap(mInitialDeepLink, mInitialInvitationId));
} else {
promise.resolve(null);
}
mInitialInvitationInitialized = true;
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "getInitialInvitation: failed to resolve invitation", e);
promise.reject("invites/initial-invitation-error", e.getMessage(), e);
}
});
} else {
Log.d(TAG, "getInitialInvitation: activity is null");
promise.resolve(null);
}
}
}
@ReactMethod
public void sendInvitation(ReadableMap invitationMap, Promise promise) {
if (!invitationMap.hasKey("message")) {
promise.reject("invites/invalid-invitation", "The supplied invitation is missing a 'message' field");
return;
}
if (!invitationMap.hasKey("title")) {
promise.reject("invites/invalid-invitation", "The supplied invitation is missing a 'title' field");
return;
}
AppInviteInvitation.IntentBuilder ib = new AppInviteInvitation.IntentBuilder(invitationMap.getString("title"));
if (invitationMap.hasKey("androidMinimumVersionCode")) {
Double androidMinimumVersionCode = invitationMap.getDouble("androidMinimumVersionCode");
ib = ib.setAndroidMinimumVersionCode(androidMinimumVersionCode.intValue());
}
if (invitationMap.hasKey("callToActionText")) {
ib = ib.setCallToActionText(invitationMap.getString("callToActionText"));
}
if (invitationMap.hasKey("customImage")) {
ib = ib.setCustomImage(Uri.parse(invitationMap.getString("customImage")));
}
if (invitationMap.hasKey("deepLink")) {
ib = ib.setDeepLink(Uri.parse(invitationMap.getString("deepLink")));
}
if (invitationMap.hasKey("iosClientId")) {
ib = ib.setOtherPlatformsTargetApplication(
AppInviteInvitation.IntentBuilder.PlatformMode.PROJECT_PLATFORM_IOS,
invitationMap.getString("iosClientId"));
}
ib = ib.setMessage(invitationMap.getString("message"));
// Android specific properties
if (invitationMap.hasKey("android")) {
ReadableMap androidMap = invitationMap.getMap("android");
if (androidMap.hasKey("additionalReferralParameters")) {
Map<String, String> arpMap = new HashMap<>();
ReadableMap arpReadableMap = androidMap.getMap("additionalReferralParameters");
ReadableMapKeySetIterator iterator = arpReadableMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
arpMap.put(key, arpReadableMap.getString(key));
}
ib = ib.setAdditionalReferralParameters(arpMap);
}
if (androidMap.hasKey("emailHtmlContent")) {
ib = ib.setEmailHtmlContent(invitationMap.getString("emailHtmlContent"));
}
if (androidMap.hasKey("emailSubject")) {
ib = ib.setEmailSubject(invitationMap.getString("emailSubject"));
}
if (androidMap.hasKey("googleAnalyticsTrackingId")) {
ib = ib.setGoogleAnalyticsTrackingId(invitationMap.getString("googleAnalyticsTrackingId"));
}
}
Intent invitationIntent = ib.build();
// Save the promise for later
this.mPromise = promise;
// Start the intent
this.getCurrentActivity().startActivityForResult(invitationIntent, REQUEST_INVITE);
}
//////////////////////////////////////////////////////////////////////
// Start ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_INVITE) {
if (resultCode == Activity.RESULT_OK) {
String[] ids = AppInviteInvitation.getInvitationIds(resultCode, data);
mPromise.resolve(Arguments.fromList(Arrays.asList(ids)));
} else if (resultCode == Activity.RESULT_CANCELED) {
mPromise.reject("invites/invitation-cancelled", "Invitation cancelled");
} else {
mPromise.reject("invites/invitation-error", "Invitation failed to send");
}
// Clear the promise
mPromise = null;
}
}
@Override
public void onNewIntent(Intent intent) {
FirebaseDynamicLinks.getInstance()
.getDynamicLink(intent)
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
@Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
if (pendingDynamicLinkData != null) {
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(pendingDynamicLinkData);
if (invite == null) {
// this is a dynamic link, not an invitation
return;
}
String deepLink = pendingDynamicLinkData.getLink().toString();
String invitationId = invite.getInvitationId();
WritableMap invitationMap = buildInvitationMap(deepLink, invitationId);
Utils.sendEvent(getReactApplicationContext(), "invites_invitation_received", invitationMap);
}
}
});
}
//////////////////////////////////////////////////////////////////////
// End ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Start LifecycleEventListener methods
//////////////////////////////////////////////////////////////////////
@Override
public void onHostResume() {
// Not required for this module
}
@Override
public void onHostPause() {
// Not required for this module
}
@Override
public void onHostDestroy() {
mInitialDeepLink = null;
mInitialInvitationId = null;
mInitialInvitationInitialized = false;
}
//////////////////////////////////////////////////////////////////////
// End LifecycleEventListener methods
//////////////////////////////////////////////////////////////////////
private WritableMap buildInvitationMap(String deepLink, String invitationId) {
WritableMap invitationMap = Arguments.createMap();
invitationMap.putString("deepLink", deepLink);
invitationMap.putString("invitationId", invitationId);
return invitationMap;
}
}

View File

@ -0,0 +1,38 @@
package io.invertase.firebase.invites;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("unused")
public class RNFirebaseInvitesPackage implements ReactPackage {
public RNFirebaseInvitesPackage() {
}
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNFirebaseInvites(reactContext));
return modules;
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -6,8 +6,6 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.Log;
import java.util.Map;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
@ -16,6 +14,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.google.firebase.appinvite.FirebaseAppInvite;
import com.google.firebase.dynamiclinks.DynamicLink;
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
import com.google.firebase.dynamiclinks.ShortDynamicLink;
@ -33,14 +32,6 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
private String mInitialLink = null;
private boolean mInitialLinkInitialized = false;
private interface ResolveHandler {
void onResolved(String url);
}
private interface ErrorHandler {
void onError(Exception e);
}
public RNFirebaseLinks(ReactApplicationContext reactContext) {
super(reactContext);
getReactApplicationContext().addActivityEventListener(this);
@ -52,28 +43,49 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
return "RNFirebaseLinks";
}
@ReactMethod
public void createDynamicLink(final ReadableMap linkData, final Promise promise) {
try {
DynamicLink.Builder builder = getDynamicLinkBuilder(linkData);
String link = builder.buildDynamicLink().getUri().toString();
Log.d(TAG, "created dynamic link: " + link);
promise.resolve(link);
} catch (Exception ex) {
Log.e(TAG, "create dynamic link failure " + ex.getMessage());
promise.reject("links/failure", ex.getMessage(), ex);
}
}
private void resolveLink(Intent intent, final ResolveHandler resolveHandler, final ErrorHandler errorHandler) {
FirebaseDynamicLinks.getInstance()
.getDynamicLink(intent)
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
@Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
if (pendingDynamicLinkData != null) {
Uri deepLinkUri = pendingDynamicLinkData.getLink();
String url = deepLinkUri.toString();
resolveHandler.onResolved(url);
} else {
resolveHandler.onResolved(null);
}
@ReactMethod
public void createShortDynamicLink(final ReadableMap linkData, final String type, final Promise promise) {
try {
DynamicLink.Builder builder = getDynamicLinkBuilder(linkData);
Task<ShortDynamicLink> shortLinkTask;
if ("SHORT".equals(type)) {
shortLinkTask = builder.buildShortDynamicLink(ShortDynamicLink.Suffix.SHORT);
} else if ("UNGUESSABLE".equals(type)) {
shortLinkTask = builder.buildShortDynamicLink(ShortDynamicLink.Suffix.UNGUESSABLE);
} else {
shortLinkTask = builder.buildShortDynamicLink();
}
shortLinkTask.addOnCompleteListener(new OnCompleteListener<ShortDynamicLink>() {
@Override
public void onComplete(@NonNull Task<ShortDynamicLink> task) {
if (task.isSuccessful()) {
String shortLink = task.getResult().getShortLink().toString();
Log.d(TAG, "created short dynamic link: " + shortLink);
promise.resolve(shortLink);
} else {
Log.e(TAG, "create short dynamic link failure " + task.getException().getMessage());
promise.reject("links/failure", task.getException().getMessage(), task.getException());
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
errorHandler.onError(e);
}
});
} catch (Exception ex) {
Log.e(TAG, "create short dynamic link failure " + ex.getMessage());
promise.reject("links/failure", ex.getMessage(), ex);
}
}
@ReactMethod
@ -81,26 +93,29 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
if (mInitialLinkInitialized) {
promise.resolve(mInitialLink);
} else {
Activity activity = getCurrentActivity();
if (activity != null) {
resolveLink(activity.getIntent(), new ResolveHandler() {
@Override
public void onResolved(String url) {
if (url != null) {
mInitialLink = url;
Log.d(TAG, "getInitialLink received a new dynamic link from pendingDynamicLinkData");
if (getCurrentActivity() != null) {
FirebaseDynamicLinks.getInstance()
.getDynamicLink(getCurrentActivity().getIntent())
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
@Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
if (pendingDynamicLinkData != null
&& !isInvitation(pendingDynamicLinkData)) {
mInitialLink = pendingDynamicLinkData.getLink().toString();
}
Log.d(TAG, "getInitialLink: link is: " + mInitialLink);
mInitialLinkInitialized = true;
promise.resolve(mInitialLink);
}
Log.d(TAG, "initial link is: " + mInitialLink);
promise.resolve(mInitialLink);
mInitialLinkInitialized = true;
}
}, new ErrorHandler() {
@Override
public void onError(Exception e) {
Log.e(TAG, "getInitialLink: failed to resolve link", e);
promise.reject("links/getDynamicLink", e.getMessage(), e);
}
});
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "getInitialLink: failed to resolve link", e);
promise.reject("link/initial-link-error", e.getMessage(), e);
}
});
} else {
Log.d(TAG, "getInitialLink: activity is null");
promise.resolve(null);
@ -108,6 +123,9 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
}
}
//////////////////////////////////////////////////////////////////////
// Start ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
// Not required for this module
@ -115,22 +133,26 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
@Override
public void onNewIntent(Intent intent) {
resolveLink(intent, new ResolveHandler() {
@Override
public void onResolved(String url) {
if (url != null) {
Log.d(TAG, "handleLink: sending link: " + url);
Utils.sendEvent(getReactApplicationContext(), "dynamic_link_received", url);
FirebaseDynamicLinks.getInstance()
.getDynamicLink(intent)
.addOnSuccessListener(new OnSuccessListener<PendingDynamicLinkData>() {
@Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
if (pendingDynamicLinkData != null
&& !isInvitation(pendingDynamicLinkData)) {
String link = pendingDynamicLinkData.getLink().toString();
Utils.sendEvent(getReactApplicationContext(), "links_link_received", link);
}
}
}
}, new ErrorHandler() {
@Override
public void onError(Exception e) {
Log.e(TAG, "handleLink: failed to resolve link", e);
}
});
});
}
//////////////////////////////////////////////////////////////////////
// End ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Start LifecycleEventListener methods
//////////////////////////////////////////////////////////////////////
@Override
public void onHostResume() {
// Not required for this module
@ -146,139 +168,131 @@ public class RNFirebaseLinks extends ReactContextBaseJavaModule implements Activ
mInitialLink = null;
mInitialLinkInitialized = false;
}
//////////////////////////////////////////////////////////////////////
// End LifecycleEventListener methods
//////////////////////////////////////////////////////////////////////
@ReactMethod
public void createDynamicLink(final ReadableMap parameters, final Promise promise) {
try {
Map<String, Object> metaData = Utils.recursivelyDeconstructReadableMap(parameters);
DynamicLink.Builder builder = getDynamicLinkBuilderFromMap(metaData);
Uri link = builder.buildDynamicLink().getUri();
Log.d(TAG, "created dynamic link: " + link.toString());
promise.resolve(link.toString());
} catch (Exception ex) {
Log.e(TAG, "create dynamic link failure " + ex.getMessage());
promise.reject("links/failure", ex.getMessage(), ex);
}
// Looks at the internals of the link data to detect whether it's an invitation or not
private boolean isInvitation(PendingDynamicLinkData pendingDynamicLinkData) {
return FirebaseAppInvite.getInvitation(pendingDynamicLinkData) != null;
}
@ReactMethod
public void createShortDynamicLink(final ReadableMap parameters, final Promise promise) {
private DynamicLink.Builder getDynamicLinkBuilder(final ReadableMap linkData) {
DynamicLink.Builder builder = FirebaseDynamicLinks.getInstance().createDynamicLink();
try {
Map<String, Object> metaData = Utils.recursivelyDeconstructReadableMap(parameters);
DynamicLink.Builder builder = getDynamicLinkBuilderFromMap(metaData);
Task<ShortDynamicLink> shortLinkTask = getShortDynamicLinkTask(builder, metaData)
.addOnCompleteListener(new OnCompleteListener<ShortDynamicLink>() {
@Override
public void onComplete(@NonNull Task<ShortDynamicLink> task) {
if (task.isSuccessful()) {
Uri shortLink = task.getResult().getShortLink();
Log.d(TAG, "created short dynamic link: " + shortLink.toString());
promise.resolve(shortLink.toString());
} else {
Log.e(TAG, "create short dynamic link failure " + task.getException().getMessage());
promise.reject("links/failure", task.getException().getMessage(), task.getException());
}
}
});
} catch (Exception ex) {
Log.e(TAG, "create short dynamic link failure " + ex.getMessage());
promise.reject("links/failure", ex.getMessage(), ex);
}
}
private DynamicLink.Builder getDynamicLinkBuilderFromMap(final Map<String, Object> metaData) {
DynamicLink.Builder parametersBuilder = FirebaseDynamicLinks.getInstance().createDynamicLink();
try {
parametersBuilder.setLink(Uri.parse((String) metaData.get("link")));
parametersBuilder.setDynamicLinkDomain((String) metaData.get("dynamicLinkDomain"));
setAndroidParameters(metaData, parametersBuilder);
setIosParameters(metaData, parametersBuilder);
setSocialMetaTagParameters(metaData, parametersBuilder);
builder.setLink(Uri.parse(linkData.getString("link")));
builder.setDynamicLinkDomain(linkData.getString("dynamicLinkDomain"));
setAnalyticsParameters(linkData.getMap("analytics"), builder);
setAndroidParameters(linkData.getMap("android"), builder);
setIosParameters(linkData.getMap("ios"), builder);
setITunesParameters(linkData.getMap("itunes"), builder);
setNavigationParameters(linkData.getMap("navigation"), builder);
setSocialParameters(linkData.getMap("social"), builder);
} catch (Exception e) {
Log.e(TAG, "error while building parameters " + e.getMessage());
throw e;
}
return parametersBuilder;
return builder;
}
private Task<ShortDynamicLink> getShortDynamicLinkTask(final DynamicLink.Builder builder, final Map<String, Object> metaData) {
Map<String, Object> suffix = (Map<String, Object>) metaData.get("suffix");
if (suffix != null) {
String option = (String) suffix.get("option");
if ("SHORT".equals(option)) {
return builder.buildShortDynamicLink(ShortDynamicLink.Suffix.SHORT);
} else if ("UNGUESSABLE".equals(option)) {
return builder.buildShortDynamicLink(ShortDynamicLink.Suffix.UNGUESSABLE);
}
private void setAnalyticsParameters(final ReadableMap analyticsData, final DynamicLink.Builder builder) {
DynamicLink.GoogleAnalyticsParameters.Builder analyticsParameters = new DynamicLink.GoogleAnalyticsParameters.Builder();
if (analyticsData.hasKey("campaign")) {
analyticsParameters.setCampaign(analyticsData.getString("campaign"));
}
return builder.buildShortDynamicLink();
if (analyticsData.hasKey("content")) {
analyticsParameters.setContent(analyticsData.getString("content"));
}
if (analyticsData.hasKey("medium")) {
analyticsParameters.setMedium(analyticsData.getString("medium"));
}
if (analyticsData.hasKey("source")) {
analyticsParameters.setSource(analyticsData.getString("source"));
}
if (analyticsData.hasKey("term")) {
analyticsParameters.setTerm(analyticsData.getString("term"));
}
builder.setGoogleAnalyticsParameters(analyticsParameters.build());
}
private void setAndroidParameters(final ReadableMap androidData, final DynamicLink.Builder builder) {
if (androidData.hasKey("packageName")) {
DynamicLink.AndroidParameters.Builder androidParameters = new DynamicLink.AndroidParameters.Builder(androidData.getString("packageName"));
private void setAndroidParameters(final Map<String, Object> metaData, final DynamicLink.Builder parametersBuilder) {
Map<String, Object> androidParameters = (Map<String, Object>) metaData.get("androidInfo");
if (androidParameters != null) {
DynamicLink.AndroidParameters.Builder androidParametersBuilder =
new DynamicLink.AndroidParameters.Builder((String) androidParameters.get("androidPackageName"));
if (androidParameters.containsKey("androidFallbackLink")) {
androidParametersBuilder.setFallbackUrl(Uri.parse((String) androidParameters.get("androidFallbackLink")));
if (androidData.hasKey("fallbackUrl")) {
androidParameters.setFallbackUrl(Uri.parse(androidData.getString("fallbackUrl")));
}
if (androidParameters.containsKey("androidMinPackageVersionCode")) {
androidParametersBuilder.setMinimumVersion(Integer.parseInt((String) androidParameters.get("androidMinPackageVersionCode")));
if (androidData.hasKey("minimumVersion")) {
androidParameters.setMinimumVersion(Integer.parseInt(androidData.getString("minimumVersion")));
}
parametersBuilder.setAndroidParameters(androidParametersBuilder.build());
builder.setAndroidParameters(androidParameters.build());
}
}
private void setIosParameters(final Map<String, Object> metaData, final DynamicLink.Builder parametersBuilder) {
Map<String, Object> iosParameters = (Map<String, Object>) metaData.get("iosInfo");
if (iosParameters != null) {
DynamicLink.IosParameters.Builder iosParametersBuilder =
new DynamicLink.IosParameters.Builder((String) iosParameters.get("iosBundleId"));
private void setIosParameters(final ReadableMap iosData, final DynamicLink.Builder builder) {
if (iosData.hasKey("bundleId")) {
DynamicLink.IosParameters.Builder iosParameters =
new DynamicLink.IosParameters.Builder(iosData.getString("bundleId"));
if (iosParameters.containsKey("iosAppStoreId")) {
iosParametersBuilder.setAppStoreId((String) iosParameters.get("iosAppStoreId"));
if (iosData.hasKey("appStoreId")) {
iosParameters.setAppStoreId(iosData.getString("appStoreId"));
}
if (iosParameters.containsKey("iosCustomScheme")) {
iosParametersBuilder.setCustomScheme((String) iosParameters.get("iosCustomScheme"));
if (iosData.hasKey("customScheme")) {
iosParameters.setCustomScheme(iosData.getString("customScheme"));
}
if (iosParameters.containsKey("iosFallbackLink")) {
iosParametersBuilder.setFallbackUrl(Uri.parse((String) iosParameters.get("iosFallbackLink")));
if (iosData.hasKey("fallbackUrl")) {
iosParameters.setFallbackUrl(Uri.parse(iosData.getString("fallbackUrl")));
}
if (iosParameters.containsKey("iosIpadBundleId")) {
iosParametersBuilder.setIpadBundleId((String) iosParameters.get("iosIpadBundleId"));
if (iosData.hasKey("iPadBundleId")) {
iosParameters.setIpadBundleId(iosData.getString("iPadBundleId"));
}
if (iosParameters.containsKey("iosIpadFallbackLink")) {
iosParametersBuilder.setIpadFallbackUrl(Uri.parse((String) iosParameters.get("iosIpadFallbackLink")));
if (iosData.hasKey("iPadFallbackUrl")) {
iosParameters.setIpadFallbackUrl(Uri.parse(iosData.getString("iPadFallbackUrl")));
}
if (iosParameters.containsKey("iosMinPackageVersionCode")) {
iosParametersBuilder.setMinimumVersion((String) iosParameters.get("iosMinPackageVersionCode"));
if (iosData.hasKey("minimumVersion")) {
iosParameters.setMinimumVersion(iosData.getString("minimumVersion"));
}
parametersBuilder.setIosParameters(iosParametersBuilder.build());
builder.setIosParameters(iosParameters.build());
}
}
private void setSocialMetaTagParameters(final Map<String, Object> metaData, final DynamicLink.Builder parametersBuilder) {
Map<String, Object> socialMetaTagParameters = (Map<String, Object>) metaData.get("socialMetaTagInfo");
if (socialMetaTagParameters != null) {
DynamicLink.SocialMetaTagParameters.Builder socialMetaTagParametersBuilder =
new DynamicLink.SocialMetaTagParameters.Builder();
private void setITunesParameters(final ReadableMap itunesData, final DynamicLink.Builder builder) {
DynamicLink.ItunesConnectAnalyticsParameters.Builder itunesParameters = new DynamicLink.ItunesConnectAnalyticsParameters.Builder();
if (socialMetaTagParameters.containsKey("socialDescription")) {
socialMetaTagParametersBuilder.setDescription((String) socialMetaTagParameters.get("socialDescription"));
}
if (socialMetaTagParameters.containsKey("socialImageLink")) {
socialMetaTagParametersBuilder.setImageUrl(Uri.parse((String) socialMetaTagParameters.get("socialImageLink")));
}
if (socialMetaTagParameters.containsKey("socialTitle")) {
socialMetaTagParametersBuilder.setTitle((String) socialMetaTagParameters.get("socialTitle"));
}
parametersBuilder.setSocialMetaTagParameters(socialMetaTagParametersBuilder.build());
if (itunesData.hasKey("affiliateToken")) {
itunesParameters.setAffiliateToken(itunesData.getString("affiliateToken"));
}
if (itunesData.hasKey("campaignToken")) {
itunesParameters.setCampaignToken(itunesData.getString("campaignToken"));
}
if (itunesData.hasKey("providerToken")) {
itunesParameters.setProviderToken(itunesData.getString("providerToken"));
}
builder.setItunesConnectAnalyticsParameters(itunesParameters.build());
}
private void setNavigationParameters(final ReadableMap navigationData, final DynamicLink.Builder builder) {
DynamicLink.NavigationInfoParameters.Builder navigationParameters = new DynamicLink.NavigationInfoParameters.Builder();
if (navigationData.hasKey("forcedRedirectEnabled")) {
navigationParameters.setForcedRedirectEnabled(navigationData.getBoolean("forcedRedirectEnabled"));
}
builder.setNavigationInfoParameters(navigationParameters.build());
}
private void setSocialParameters(final ReadableMap socialData, final DynamicLink.Builder builder) {
DynamicLink.SocialMetaTagParameters.Builder socialParameters = new DynamicLink.SocialMetaTagParameters.Builder();
if (socialData.hasKey("descriptionText")) {
socialParameters.setDescription(socialData.getString("descriptionText"));
}
if (socialData.hasKey("imageUrl")) {
socialParameters.setImageUrl(Uri.parse(socialData.getString("imageUrl")));
}
if (socialData.hasKey("title")) {
socialParameters.setTitle(socialData.getString("title"));
}
builder.setSocialMetaTagParameters(socialParameters.build());
}
}

View File

@ -1,43 +0,0 @@
package io.invertase.firebase.messaging;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import me.leolin.shortcutbadger.ShortcutBadger;
public class BadgeHelper {
private static final String TAG = "BadgeHelper";
private static final String PREFERENCES_FILE = "BadgeCountFile";
private static final String BADGE_COUNT_KEY = "BadgeCount";
private Context mContext;
private SharedPreferences sharedPreferences = null;
public BadgeHelper(Context context) {
mContext = context;
sharedPreferences = (SharedPreferences) mContext.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
}
public int getBadgeCount() {
return sharedPreferences.getInt(BADGE_COUNT_KEY, 0);
}
public void setBadgeCount(int badgeCount) {
storeBadgeCount(badgeCount);
if (badgeCount == 0) {
ShortcutBadger.removeCount(mContext);
Log.d(TAG, "Remove count");
} else {
ShortcutBadger.applyCount(mContext, badgeCount);
Log.d(TAG, "Apply count: " + badgeCount);
}
}
private void storeBadgeCount(int badgeCount) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(BADGE_COUNT_KEY, badgeCount);
editor.apply();
}
}

View File

@ -110,23 +110,28 @@ public class BundleJSONConverter {
SETTERS.put(JSONArray.class, new Setter() {
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
JSONArray jsonArray = (JSONArray) value;
ArrayList<String> stringArrayList = new ArrayList<String>();
// Empty list, can't even figure out the type, assume an ArrayList<String>
if (jsonArray.length() == 0) {
bundle.putStringArrayList(key, stringArrayList);
bundle.putStringArrayList(key, new ArrayList<String>());
return;
}
// Only strings are supported for now
for (int i = 0; i < jsonArray.length(); i++) {
Object current = jsonArray.get(i);
if (current instanceof String) {
stringArrayList.add((String) current);
} else {
throw new IllegalArgumentException("Unexpected type in an array: " + current.getClass());
if (jsonArray.get(0) instanceof String) {
ArrayList<String> stringArrayList = new ArrayList<String>();
for (int i = 0; i < jsonArray.length(); i++) {
stringArrayList.add((String) jsonArray.get(i));
}
bundle.putStringArrayList(key, stringArrayList);
} else if (jsonArray.get(0) instanceof JSONObject) {
ArrayList<Bundle> bundleArrayList = new ArrayList<>();
for (int i =0; i < jsonArray.length(); i++) {
bundleArrayList.add(convertToBundle((JSONObject) jsonArray.get(i)));
}
bundle.putSerializable(key, bundleArrayList);
} else {
throw new IllegalArgumentException("Unexpected type in an array: " + jsonArray.get(0).getClass());
}
bundle.putStringArrayList(key, stringArrayList);
}
@Override
@ -152,13 +157,18 @@ public class BundleJSONConverter {
continue;
}
// Special case List<String> as getClass would not work, since List is an interface
// Special case List<?> as getClass would not work, since List is an interface
if (value instanceof List<?>) {
JSONArray jsonArray = new JSONArray();
@SuppressWarnings("unchecked")
List<String> listValue = (List<String>) value;
for (String stringValue : listValue) {
jsonArray.put(stringValue);
List<Object> listValue = (List<Object>) value;
for (Object objValue : listValue) {
if (objValue instanceof String) {
jsonArray.put(objValue);
} else if (objValue instanceof Bundle) {
jsonArray.put(convertToJSON((Bundle) objValue));
} else {
throw new IllegalArgumentException("Unsupported type: " + objValue.getClass());
}
}
json.put(key, jsonArray);
continue;

View File

@ -1,32 +0,0 @@
package io.invertase.firebase.messaging;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
public class InstanceIdService extends FirebaseInstanceIdService {
private static final String TAG = "InstanceIdService";
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. This call is initiated by the
* InstanceID provider.
*/
@Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// Broadcast refreshed token
Intent i = new Intent("io.invertase.firebase.messaging.FCMRefreshToken");
Bundle bundle = new Bundle();
bundle.putString("token", refreshedToken);
i.putExtras(bundle);
sendBroadcast(i);
}
}

View File

@ -0,0 +1,43 @@
package io.invertase.firebase.messaging;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.google.firebase.messaging.RemoteMessage;
import java.util.Map;
public class MessagingSerializer {
public static WritableMap parseRemoteMessage(RemoteMessage message) {
WritableMap messageMap = Arguments.createMap();
WritableMap dataMap = Arguments.createMap();
if (message.getCollapseKey() != null) {
messageMap.putString("collapseKey", message.getCollapseKey());
}
if (message.getData() != null) {
for (Map.Entry<String, String> e : message.getData().entrySet()) {
dataMap.putString(e.getKey(), e.getValue());
}
}
messageMap.putMap("data", dataMap);
if (message.getFrom() != null) {
messageMap.putString("from", message.getFrom());
}
if (message.getMessageId() != null) {
messageMap.putString("messageId", message.getMessageId());
}
if (message.getMessageType() != null) {
messageMap.putString("messageType", message.getMessageType());
}
messageMap.putDouble("sentTime", message.getSentTime());
if (message.getTo() != null) {
messageMap.putString("to", message.getTo());
}
messageMap.putDouble("ttl", message.getTtl());
return messageMap;
}
}

View File

@ -1,64 +0,0 @@
package io.invertase.firebase.messaging;
import java.util.Map;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONException;
import org.json.JSONObject;
public class MessagingService extends FirebaseMessagingService {
private static final String TAG = "MessagingService";
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "Remote message received");
Intent i = new Intent("io.invertase.firebase.messaging.ReceiveNotification");
i.putExtra("data", remoteMessage);
handleBadge(remoteMessage);
buildLocalNotification(remoteMessage);
sendOrderedBroadcast(i, null);
}
private void handleBadge(RemoteMessage remoteMessage) {
BadgeHelper badgeHelper = new BadgeHelper(this);
if (remoteMessage.getData() == null) {
return;
}
Map data = remoteMessage.getData();
if (data.get("badge") == null) {
return;
}
try {
int badgeCount = Integer.parseInt((String)data.get("badge"));
badgeHelper.setBadgeCount(badgeCount);
} catch (Exception e) {
Log.e(TAG, "Badge count needs to be an integer", e);
}
}
private void buildLocalNotification(RemoteMessage remoteMessage) {
if(remoteMessage.getData() == null){
return;
}
Map<String, String> data = remoteMessage.getData();
String customNotification = data.get("custom_notification");
if(customNotification != null){
try {
Bundle bundle = BundleJSONConverter.convertToBundle(new JSONObject(customNotification));
RNFirebaseLocalMessagingHelper helper = new RNFirebaseLocalMessagingHelper(this.getApplication());
helper.sendNotification(bundle);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,30 @@
package io.invertase.firebase.messaging;
import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import com.google.firebase.messaging.RemoteMessage;
import javax.annotation.Nullable;
public class RNFirebaseBackgroundMessagingService extends HeadlessJsTaskService {
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
RemoteMessage message = intent.getParcelableExtra("message");
WritableMap messageMap = MessagingSerializer.parseRemoteMessage(message);
return new HeadlessJsTaskConfig(
"RNFirebaseBackgroundMessage",
messageMap,
60000,
false
);
}
return null;
}
}

View File

@ -0,0 +1,24 @@
package io.invertase.firebase.messaging;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.google.firebase.iid.FirebaseInstanceIdService;
public class RNFirebaseInstanceIdService extends FirebaseInstanceIdService {
private static final String TAG = "RNFInstanceIdService";
public static final String TOKEN_REFRESH_EVENT = "messaging-token-refresh";
@Override
public void onTokenRefresh() {
Log.d(TAG, "onTokenRefresh event received");
// Build an Intent to pass the token to the RN Application
Intent tokenRefreshEvent = new Intent(TOKEN_REFRESH_EVENT);
// Broadcast it so it is only available to the RN Application
LocalBroadcastManager.getInstance(this).sendBroadcast(tokenRefreshEvent);
}
}

View File

@ -1,336 +0,0 @@
package io.invertase.firebase.messaging;
import android.app.AlarmManager;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.content.SharedPreferences;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.HttpURLConnection;
public class RNFirebaseLocalMessagingHelper {
private static final long DEFAULT_VIBRATION = 300L;
private static final String TAG = RNFirebaseLocalMessagingHelper.class.getSimpleName();
private final static String PREFERENCES_KEY = "ReactNativeSystemNotification";
private static boolean mIsForeground = false; //this is a hack
private Context mContext;
private SharedPreferences sharedPreferences = null;
public RNFirebaseLocalMessagingHelper(Application context) {
mContext = context;
sharedPreferences = mContext.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE);
}
public void sendNotification(Bundle bundle) {
try {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
return;
}
if (bundle.getString("body") == null) {
return;
}
Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
String title = bundle.getString("title");
if (title == null) {
ApplicationInfo appInfo = mContext.getApplicationInfo();
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
}
NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext)
.setContentTitle(title)
.setContentText(bundle.getString("body"))
.setTicker(bundle.getString("ticker"))
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setAutoCancel(bundle.getBoolean("auto_cancel", true))
.setNumber(bundle.getInt("number"))
.setSubText(bundle.getString("sub_text"))
.setGroup(bundle.getString("group"))
.setVibrate(new long[]{0, DEFAULT_VIBRATION})
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setExtras(bundle.getBundle("data"));
//priority
String priority = bundle.getString("priority", "");
switch(priority) {
case "min":
notification.setPriority(NotificationCompat.PRIORITY_MIN);
break;
case "high":
notification.setPriority(NotificationCompat.PRIORITY_HIGH);
break;
case "max":
notification.setPriority(NotificationCompat.PRIORITY_MAX);
break;
default:
notification.setPriority(NotificationCompat.PRIORITY_DEFAULT);
}
//icon
String smallIcon = bundle.getString("icon", "ic_launcher");
int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
notification.setSmallIcon(smallIconResId);
//large icon
String largeIcon = bundle.getString("large_icon");
if(largeIcon != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
if (largeIcon.startsWith("http://") || largeIcon.startsWith("https://")) {
Bitmap bitmap = getBitmapFromURL(largeIcon);
notification.setLargeIcon(bitmap);
} else {
int largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
if (largeIconResId != 0) {
notification.setLargeIcon(largeIconBitmap);
}
}
}
//big text
String bigText = bundle.getString("big_text");
if(bigText != null){
notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
}
//sound
String soundName = bundle.getString("sound", "default");
if (!soundName.equalsIgnoreCase("default")) {
int soundResourceId = res.getIdentifier(soundName, "raw", packageName);
if(soundResourceId == 0){
soundName = soundName.substring(0, soundName.lastIndexOf('.'));
soundResourceId = res.getIdentifier(soundName, "raw", packageName);
}
notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId));
}
//color
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setCategory(NotificationCompat.CATEGORY_CALL);
String color = bundle.getString("color");
if (color != null) {
notification.setColor(Color.parseColor(color));
}
}
//vibrate
if(bundle.containsKey("vibrate")){
long vibrate = bundle.getLong("vibrate", Math.round(bundle.getDouble("vibrate", bundle.getInt("vibrate"))));
if(vibrate > 0){
notification.setVibrate(new long[]{0, vibrate});
}else{
notification.setVibrate(null);
}
}
//lights
if (bundle.getBoolean("lights")) {
notification.setDefaults(NotificationCompat.DEFAULT_LIGHTS);
}
Log.d(TAG, "broadcast intent before showing notification");
Intent i = new Intent("io.invertase.firebase.messaging.ReceiveLocalNotification");
i.putExtras(bundle);
mContext.sendOrderedBroadcast(i, null);
if(!mIsForeground || bundle.getBoolean("show_in_foreground")){
Intent intent = new Intent(mContext, intentClass);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtras(bundle);
intent.setAction(bundle.getString("click_action"));
int notificationID = bundle.containsKey("id") ? bundle.getString("id", "").hashCode() : (int) System.currentTimeMillis();
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, notificationID, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notification.setContentIntent(pendingIntent);
Notification info = notification.build();
if (bundle.containsKey("tag")) {
String tag = bundle.getString("tag");
notificationManager.notify(tag, notificationID, info);
} else {
notificationManager.notify(notificationID, info);
}
}
//clear out one time scheduled notification once fired
if(!bundle.containsKey("repeat_interval") && bundle.containsKey("fire_date")) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(bundle.getString("id"));
editor.apply();
}
} catch (Exception e) {
Log.e(TAG, "failed to send local notification", e);
}
}
public void sendNotificationScheduled(Bundle bundle) {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
return;
}
String notificationId = bundle.getString("id");
if(notificationId == null){
Log.e(TAG, "failed to schedule notification because id is missing");
return;
}
Long fireDate = Math.round(bundle.getDouble("fire_date"));
if (fireDate == 0) {
Log.e(TAG, "failed to schedule notification because fire date is missing");
return;
}
Intent notificationIntent = new Intent(mContext, RNFirebaseLocalMessagingPublisher.class);
notificationIntent.putExtras(bundle);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Long interval = null;
switch (bundle.getString("repeat_interval", "")) {
case "minute":
interval = (long) 60000;
break;
case "hour":
interval = AlarmManager.INTERVAL_HOUR;
break;
case "day":
interval = AlarmManager.INTERVAL_DAY;
break;
case "week":
interval = AlarmManager.INTERVAL_DAY * 7;
break;
}
if(interval != null){
getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent);
} else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
getAlarmManager().setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}else {
getAlarmManager().set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}
//store intent
SharedPreferences.Editor editor = sharedPreferences.edit();
try {
JSONObject json = BundleJSONConverter.convertToJSON(bundle);
editor.putString(notificationId, json.toString());
editor.apply();
} catch (JSONException e) {
e.printStackTrace();
}
}
public void cancelLocalNotification(String notificationId) {
cancelAlarm(notificationId);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(notificationId);
editor.apply();
}
public void cancelAllLocalNotifications() {
java.util.Map<String, ?> keyMap = sharedPreferences.getAll();
SharedPreferences.Editor editor = sharedPreferences.edit();
for(java.util.Map.Entry<String, ?> entry:keyMap.entrySet()){
cancelAlarm(entry.getKey());
}
editor.clear();
editor.apply();
}
public void removeDeliveredNotification(String notificationId){
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId.hashCode());
}
public void removeAllDeliveredNotifications(){
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
}
public ArrayList<Bundle> getScheduledLocalNotifications(){
ArrayList<Bundle> array = new ArrayList<Bundle>();
java.util.Map<String, ?> keyMap = sharedPreferences.getAll();
for(java.util.Map.Entry<String, ?> entry:keyMap.entrySet()){
try {
JSONObject json = new JSONObject((String)entry.getValue());
Bundle bundle = BundleJSONConverter.convertToBundle(json);
array.add(bundle);
} catch (JSONException e) {
e.printStackTrace();
}
}
return array;
}
public void setApplicationForeground(boolean foreground){
mIsForeground = foreground;
}
private Class getMainActivityClass() {
String packageName = mContext.getPackageName();
Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
private AlarmManager getAlarmManager() {
return (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
}
private void cancelAlarm(String notificationId) {
Intent notificationIntent = new Intent(mContext, RNFirebaseLocalMessagingPublisher.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
getAlarmManager().cancel(pendingIntent);
}
private Bitmap getBitmapFromURL(String strURL) {
try {
URL url = new URL(strURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -1,14 +0,0 @@
package io.invertase.firebase.messaging;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class RNFirebaseLocalMessagingPublisher extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
new RNFirebaseLocalMessagingHelper((Application) context.getApplicationContext()).sendNotification(intent.getExtras());
}
}

View File

@ -1,51 +1,39 @@
package io.invertase.firebase.messaging;
import android.app.Activity;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.RemoteMessage;
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;
public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseMessaging";
public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements LifecycleEventListener, ActivityEventListener {
private final static String TAG = RNFirebaseMessaging.class.getCanonicalName();
private RNFirebaseLocalMessagingHelper mRNFirebaseLocalMessagingHelper;
private BadgeHelper mBadgeHelper;
public RNFirebaseMessaging(ReactApplicationContext context) {
super(context);
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
public RNFirebaseMessaging(ReactApplicationContext reactContext) {
super(reactContext);
mRNFirebaseLocalMessagingHelper = new RNFirebaseLocalMessagingHelper((Application) reactContext.getApplicationContext());
mBadgeHelper = new BadgeHelper(reactContext.getApplicationContext());
getReactApplicationContext().addLifecycleEventListener(this);
getReactApplicationContext().addActivityEventListener(this);
registerTokenRefreshHandler();
registerMessageHandler();
registerLocalMessageHandler();
// Subscribe to message events
localBroadcastManager.registerReceiver(new MessageReceiver(),
new IntentFilter(RNFirebaseMessagingService.MESSAGE_EVENT));
// Subscribe to token refresh events
localBroadcastManager.registerReceiver(new RefreshTokenReceiver(),
new IntentFilter(RNFirebaseInstanceIdService.TOKEN_REFRESH_EVENT));
}
@Override
@ -54,73 +42,57 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements L
}
@ReactMethod
public void getInitialNotification(Promise promise) {
Activity activity = getCurrentActivity();
if (activity == null) {
promise.resolve(null);
public void getToken(Promise promise) {
String token = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Firebase token: " + token);
promise.resolve(token);
}
@ReactMethod
public void requestPermission(Promise promise) {
promise.resolve(null);
}
// Non Web SDK methods
@ReactMethod
public void hasPermission(Promise promise) {
promise.resolve(true);
}
@ReactMethod
public void sendMessage(ReadableMap messageMap, Promise promise) {
if (!messageMap.hasKey("to")) {
promise.reject("messaging/invalid-message", "The supplied message is missing a 'to' field");
return;
}
promise.resolve(parseIntent(getCurrentActivity().getIntent()));
}
@ReactMethod
public void requestPermissions() {
}
RemoteMessage.Builder mb = new RemoteMessage.Builder(messageMap.getString("to"));
@ReactMethod
public void getToken(Promise promise) {
Log.d(TAG, "Firebase token: " + FirebaseInstanceId.getInstance().getToken());
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());
if (messageMap.hasKey("collapseKey")) {
mb = mb.setCollapseKey(messageMap.getString("collapseKey"));
}
}
@ReactMethod
public void createLocalNotification(ReadableMap details) {
Bundle bundle = Arguments.toBundle(details);
mRNFirebaseLocalMessagingHelper.sendNotification(bundle);
}
@ReactMethod
public void scheduleLocalNotification(ReadableMap details) {
Bundle bundle = Arguments.toBundle(details);
mRNFirebaseLocalMessagingHelper.sendNotificationScheduled(bundle);
}
@ReactMethod
public void cancelLocalNotification(String notificationID) {
mRNFirebaseLocalMessagingHelper.cancelLocalNotification(notificationID);
}
@ReactMethod
public void cancelAllLocalNotifications() {
try {
mRNFirebaseLocalMessagingHelper.cancelAllLocalNotifications();
} catch (SecurityException e) {
// In some devices/situations cancelAllLocalNotifications will throw a SecurityException
// We can safely ignore this error for now as the UX impact of this not working is minor.
Log.w(TAG, e.getMessage());
if (messageMap.hasKey("messageId")) {
mb = mb.setMessageId(messageMap.getString("messageId"));
}
if (messageMap.hasKey("messageType")) {
mb = mb.setMessageType(messageMap.getString("messageType"));
}
if (messageMap.hasKey("ttl")) {
mb = mb.setTtl(messageMap.getInt("ttl"));
}
if (messageMap.hasKey("data")) {
ReadableMap dataMap = messageMap.getMap("data");
ReadableMapKeySetIterator iterator = dataMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
mb = mb.addData(key, dataMap.getString(key));
}
}
}
@ReactMethod
public void removeDeliveredNotification(String notificationID) {
mRNFirebaseLocalMessagingHelper.removeDeliveredNotification(notificationID);
}
FirebaseMessaging.getInstance().send(mb.build());
@ReactMethod
public void removeAllDeliveredNotifications() {
mRNFirebaseLocalMessagingHelper.removeAllDeliveredNotifications();
// TODO: Listen to onMessageSent and onSendError for better feedback?
promise.resolve(null);
}
@ReactMethod
@ -133,159 +105,29 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements L
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
}
@ReactMethod
public void getScheduledLocalNotifications(Promise promise) {
ArrayList<Bundle> bundles = mRNFirebaseLocalMessagingHelper.getScheduledLocalNotifications();
WritableArray array = Arguments.createArray();
for (Bundle bundle : bundles) {
array.pushMap(Arguments.fromBundle(bundle));
}
promise.resolve(array);
}
private class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
Log.d(TAG, "Received new message");
@ReactMethod
public void setBadgeNumber(int badgeNumber) {
mBadgeHelper.setBadgeCount(badgeNumber);
}
RemoteMessage message = intent.getParcelableExtra("message");
WritableMap messageMap = MessagingSerializer.parseRemoteMessage(message);
@ReactMethod
public void getBadgeNumber(Promise promise) {
promise.resolve(mBadgeHelper.getBadgeCount());
}
private void registerTokenRefreshHandler() {
IntentFilter intentFilter = new IntentFilter("io.invertase.firebase.messaging.FCMRefreshToken");
getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
String token = intent.getStringExtra("token");
Utils.sendEvent(getReactApplicationContext(), "messaging_token_refreshed", token);
}
Utils.sendEvent(getReactApplicationContext(), "messaging_message_received", messageMap);
}
}, intentFilter);
}
@ReactMethod
public void send(ReadableMap remoteMessage) {
FirebaseMessaging fm = FirebaseMessaging.getInstance();
RemoteMessage.Builder message = new RemoteMessage.Builder(remoteMessage.getString("sender"));
message.setTtl(remoteMessage.getInt("ttl"));
message.setMessageId(remoteMessage.getString("id"));
message.setMessageType(remoteMessage.getString("type"));
if (remoteMessage.hasKey("collapseKey")) {
message.setCollapseKey(remoteMessage.getString("collapseKey"));
}
}
// get data keys and values and add to builder
// js side ensures all data values are strings
// so no need to check types
ReadableMap data = remoteMessage.getMap("data");
ReadableMapKeySetIterator iterator = data.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
String value = data.getString(key);
message.addData(key, value);
private class RefreshTokenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
String token = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Received new FCM token: " + token);
Utils.sendEvent(getReactApplicationContext(), "messaging_token_refreshed", token);
}
}
fm.send(message.build());
}
private void registerMessageHandler() {
IntentFilter intentFilter = new IntentFilter("io.invertase.firebase.messaging.ReceiveNotification");
getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
RemoteMessage message = intent.getParcelableExtra("data");
WritableMap params = Arguments.createMap();
WritableMap fcmData = Arguments.createMap();
if (message.getNotification() != null) {
Notification notification = message.getNotification();
fcmData.putString("title", notification.getTitle());
fcmData.putString("body", notification.getBody());
fcmData.putString("color", notification.getColor());
fcmData.putString("icon", notification.getIcon());
fcmData.putString("tag", notification.getTag());
fcmData.putString("action", notification.getClickAction());
}
params.putMap("fcm", fcmData);
if (message.getData() != null) {
Map<String, String> data = message.getData();
Set<String> keysIterator = data.keySet();
for (String key : keysIterator) {
params.putString(key, data.get(key));
}
}
Utils.sendEvent(getReactApplicationContext(), "messaging_notification_received", params);
}
}
}, intentFilter);
}
private void registerLocalMessageHandler() {
IntentFilter intentFilter = new IntentFilter("io.invertase.firebase.messaging.ReceiveLocalNotification");
getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
Utils.sendEvent(getReactApplicationContext(), "messaging_notification_received", Arguments.fromBundle(intent.getExtras()));
}
}
}, intentFilter);
}
private WritableMap parseIntent(Intent intent) {
WritableMap params;
Bundle extras = intent.getExtras();
if (extras != null) {
try {
params = Arguments.fromBundle(extras);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
params = Arguments.createMap();
}
} else {
params = Arguments.createMap();
}
WritableMap fcm = Arguments.createMap();
fcm.putString("action", intent.getAction());
params.putMap("fcm", fcm);
params.putBoolean("opened_from_tray", true);
return params;
}
@Override
public void onHostResume() {
mRNFirebaseLocalMessagingHelper.setApplicationForeground(true);
}
@Override
public void onHostPause() {
mRNFirebaseLocalMessagingHelper.setApplicationForeground(false);
}
@Override
public void onHostDestroy() {
}
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
// todo hmm?
}
@Override
public void onNewIntent(Intent intent) {
// todo hmm?
Utils.sendEvent(getReactApplicationContext(), "messaging_notification_received", parseIntent(intent));
}
}

View File

@ -1,7 +1,6 @@
package io.invertase.firebase.messaging;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIManagerModule;

View File

@ -0,0 +1,46 @@
package io.invertase.firebase.messaging;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.facebook.react.HeadlessJsTaskService;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import io.invertase.firebase.Utils;
public class RNFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "RNFMessagingService";
public static final String MESSAGE_EVENT = "messaging-message";
public static final String REMOTE_NOTIFICATION_EVENT = "notifications-remote-notification";
@Override
public void onMessageReceived(RemoteMessage message) {
Log.d(TAG, "onMessageReceived event received");
if (message.getNotification() != null) {
// It's a notification, pass to the Notifications module
Intent notificationEvent = new Intent(REMOTE_NOTIFICATION_EVENT);
notificationEvent.putExtra("notification", message);
// Broadcast it to the (foreground) RN Application
LocalBroadcastManager.getInstance(this).sendBroadcast(notificationEvent);
} else {
// It's a data message
// If the app is in the foreground we send it to the Messaging module
if (Utils.isAppInForeground(this.getApplicationContext())) {
Intent messagingEvent = new Intent(MESSAGE_EVENT);
messagingEvent.putExtra("message", message);
// Broadcast it so it is only available to the RN Application
LocalBroadcastManager.getInstance(this).sendBroadcast(messagingEvent);
} else {
// If the app is in the background we send it to the Headless JS Service
Intent headlessIntent = new Intent(this.getApplicationContext(), RNFirebaseBackgroundMessagingService.class);
headlessIntent.putExtra("message", message);
this.getApplicationContext().startService(headlessIntent);
HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext());
}
}
}
}

View File

@ -1,27 +0,0 @@
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);
}
}
}

View File

@ -0,0 +1,673 @@
package io.invertase.firebase.notifications;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.invertase.firebase.Utils;
import io.invertase.firebase.messaging.BundleJSONConverter;
public class RNFirebaseNotificationManager {
private static final String PREFERENCES_KEY = "RNFNotifications";
public static final String SCHEDULED_NOTIFICATION_EVENT = "notifications-scheduled-notification";
private static final String TAG = "RNFNotificationManager";
private AlarmManager alarmManager;
private Context context;
private ReactApplicationContext reactContext;
private NotificationManager notificationManager;
private SharedPreferences preferences;
public RNFirebaseNotificationManager(ReactApplicationContext reactContext) {
this(reactContext.getApplicationContext());
this.reactContext = reactContext;
}
public RNFirebaseNotificationManager(Context context) {
this.alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
this.context = context;
this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
this.preferences = context.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE);
}
public void cancelAllNotifications() {
try {
Map<String, ?> notifications = preferences.getAll();
for(String notificationId : notifications.keySet()){
cancelAlarm(notificationId);
}
preferences.edit().clear().apply();
} catch (SecurityException e) {
// TODO: Identify what these situations are
// In some devices/situations cancelAllLocalNotifications can throw a SecurityException.
Log.e(TAG, e.getMessage());
}
}
public void cancelNotification(String notificationId) {
cancelAlarm(notificationId);
preferences.edit().remove(notificationId).apply();
}
public void createChannel(ReadableMap channelMap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = parseChannelMap(channelMap);
notificationManager.createNotificationChannel(channel);
}
}
public void createChannelGroup(ReadableMap channelGroupMap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannelGroup channelGroup = parseChannelGroupMap(channelGroupMap);
notificationManager.createNotificationChannelGroup(channelGroup);
}
}
public void createChannelGroups(ReadableArray channelGroupsArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
List<NotificationChannelGroup> channelGroups = new ArrayList<>();
for (int i = 0; i < channelGroupsArray.size(); i++) {
NotificationChannelGroup channelGroup = parseChannelGroupMap(channelGroupsArray.getMap(i));
channelGroups.add(channelGroup);
}
notificationManager.createNotificationChannelGroups(channelGroups);
}
}
public void createChannels(ReadableArray channelsArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
List<NotificationChannel> channels = new ArrayList<>();
for (int i = 0; i < channelsArray.size(); i++) {
NotificationChannel channel = parseChannelMap(channelsArray.getMap(i));
channels.add(channel);
}
notificationManager.createNotificationChannels(channels);
}
}
public void displayNotification(ReadableMap notification, Promise promise) {
Bundle notificationBundle = Arguments.toBundle(notification);
displayNotification(notificationBundle, promise);
}
public void displayScheduledNotification(Bundle notification) {
// If this isn't a repeated notification, clear it from the scheduled notifications list
if (!notification.getBundle("schedule").containsKey("repeated")
|| !notification.getBundle("schedule").getBoolean("repeated")) {
String notificationId = notification.getString("notificationId");
preferences.edit().remove(notificationId).apply();;
}
if (Utils.isAppInForeground(context)) {
// If the app is in the foregound, broadcast the notification to the RN Application
// It is up to the JS to decide whether to display the notification
Intent scheduledNotificationEvent = new Intent(SCHEDULED_NOTIFICATION_EVENT);
scheduledNotificationEvent.putExtra("notification", notification);
LocalBroadcastManager.getInstance(context).sendBroadcast(scheduledNotificationEvent);
} else {
// If the app is in the background, then we display it automatically
displayNotification(notification, null);
}
}
public ArrayList<Bundle> getScheduledNotifications(){
ArrayList<Bundle> array = new ArrayList<>();
Map<String, ?> notifications = preferences.getAll();
for(String notificationId : notifications.keySet()){
try {
JSONObject json = new JSONObject((String)notifications.get(notificationId));
Bundle bundle = BundleJSONConverter.convertToBundle(json);
array.add(bundle);
} catch (JSONException e) {
Log.e(TAG, e.getMessage());
}
}
return array;
}
public void removeAllDeliveredNotifications() {
notificationManager.cancelAll();
}
public void removeDeliveredNotification(String notificationId) {
notificationManager.cancel(notificationId.hashCode());
}
public void rescheduleNotifications() {
ArrayList<Bundle> bundles = getScheduledNotifications();
for(Bundle bundle: bundles){
scheduleNotification(bundle, null);
}
}
public void scheduleNotification(ReadableMap notification, Promise promise) {
Bundle notificationBundle = Arguments.toBundle(notification);
scheduleNotification(notificationBundle, promise);
}
private void cancelAlarm(String notificationId) {
Intent notificationIntent = new Intent(context, RNFirebaseNotificationManager.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(pendingIntent);
}
private void displayNotification(Bundle notification, Promise promise) {
try {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
if (promise != null) {
promise.reject("notification/display_notification_error", "Could not find main activity class");
}
return;
}
Bundle android = notification.getBundle("android");
String channelId = android.getString("channelId");
String notificationId = notification.getString("notificationId");
NotificationCompat.Builder nb;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
nb = new NotificationCompat.Builder(context, channelId);
} else {
nb = new NotificationCompat.Builder(context);
}
if (notification.containsKey("body")) {
nb = nb.setContentText(notification.getString("body"));
}
if (notification.containsKey("data")) {
nb = nb.setExtras(notification.getBundle("data"));
}
if (notification.containsKey("sound")) {
Uri sound = getSound(notification.getString("sound"));
nb = nb.setSound(sound);
}
if (notification.containsKey("subtitle")) {
nb = nb.setSubText(notification.getString("subtitle"));
}
if (notification.containsKey("title")) {
nb = nb.setContentTitle(notification.getString("title"));
}
if (android.containsKey("autoCancel")) {
nb = nb.setAutoCancel(android.getBoolean("autoCancel"));
}
if (android.containsKey("badgeIconType")) {
Double badgeIconType = android.getDouble("badgeIconType");
nb = nb.setBadgeIconType(badgeIconType.intValue());
}
if (android.containsKey("category")) {
nb = nb.setCategory(android.getString("category"));
}
if (android.containsKey("color")) {
String color = android.getString("color");
nb = nb.setColor(Color.parseColor(color));
}
if (android.containsKey("colorized")) {
nb = nb.setColorized(android.getBoolean("colorized"));
}
if (android.containsKey("contentInfo")) {
nb = nb.setContentInfo(android.getString("contentInfo"));
}
if (notification.containsKey("defaults")) {
double[] defaultsArray = android.getDoubleArray("defaults");
int defaults = 0;
for (Double d : defaultsArray) {
defaults |= d.intValue();
}
nb = nb.setDefaults(defaults);
}
if (android.containsKey("group")) {
nb = nb.setGroup(android.getString("group"));
}
if (android.containsKey("groupAlertBehaviour")) {
Double groupAlertBehaviour = android.getDouble("groupAlertBehaviour");
nb = nb.setGroupAlertBehavior(groupAlertBehaviour.intValue());
}
if (android.containsKey("groupSummary")) {
nb = nb.setGroupSummary(android.getBoolean("groupSummary"));
}
if (android.containsKey("largeIcon")) {
Bitmap largeIcon = getBitmap(android.getString("largeIcon"));
if (largeIcon != null) {
nb = nb.setLargeIcon(largeIcon);
}
}
if (android.containsKey("lights")) {
Bundle lights = android.getBundle("lights");
Double argb = lights.getDouble("argb");
Double onMs = lights.getDouble("onMs");
Double offMs = lights.getDouble("offMs");
nb = nb.setLights(argb.intValue(), onMs.intValue(), offMs.intValue());
}
if (android.containsKey("localOnly")) {
nb = nb.setLocalOnly(android.getBoolean("localOnly"));
}
if (android.containsKey("number")) {
Double number = android.getDouble("number");
nb = nb.setNumber(number.intValue());
}
if (android.containsKey("ongoing")) {
nb = nb.setOngoing(android.getBoolean("ongoing"));
}
if (android.containsKey("onlyAlertOnce")) {
nb = nb.setOngoing(android.getBoolean("onlyAlertOnce"));
}
if (android.containsKey("people")) {
String[] people = android.getStringArray("people");
if (people != null) {
for (String person : people) {
nb = nb.addPerson(person);
}
}
}
if (android.containsKey("priority")) {
Double priority = android.getDouble("priority");
nb = nb.setPriority(priority.intValue());
}
if (android.containsKey("progress")) {
Bundle progress = android.getBundle("lights");
Double max = progress.getDouble("max");
Double progressI = progress.getDouble("progress");
nb = nb.setProgress(max.intValue(), progressI.intValue(), progress.getBoolean("indeterminate"));
}
// TODO: Public version of notification
/* if (android.containsKey("publicVersion")) {
nb = nb.setPublicVersion();
} */
if (android.containsKey("remoteInputHistory")) {
nb = nb.setRemoteInputHistory(android.getStringArray("remoteInputHistory"));
}
if (android.containsKey("shortcutId")) {
nb = nb.setShortcutId(android.getString("shortcutId"));
}
if (android.containsKey("showWhen")) {
nb = nb.setShowWhen(android.getBoolean("showWhen"));
}
if (android.containsKey("smallIcon")) {
Bundle smallIcon = android.getBundle("smallIcon");
int smallIconResourceId = getIcon(smallIcon.getString("icon"));
if (smallIconResourceId != 0) {
if (smallIcon.containsKey("level")) {
Double level = smallIcon.getDouble("level");
nb = nb.setSmallIcon(smallIconResourceId, level.intValue());
} else {
nb = nb.setSmallIcon(smallIconResourceId);
}
}
}
if (android.containsKey("sortKey")) {
nb = nb.setSortKey(android.getString("sortKey"));
}
if (android.containsKey("ticker")) {
nb = nb.setTicker(android.getString("ticker"));
}
if (android.containsKey("timeoutAfter")) {
Double timeoutAfter = android.getDouble("timeoutAfter");
nb = nb.setTimeoutAfter(timeoutAfter.longValue());
}
if (android.containsKey("usesChronometer")) {
nb = nb.setUsesChronometer(android.getBoolean("usesChronometer"));
}
if (android.containsKey("vibrate")) {
double[] vibrate = android.getDoubleArray("vibrate");
long[] vibrateArray = new long[vibrate.length];
for (int i = 0; i < vibrate.length; i++) {
vibrateArray[i] = ((Double)vibrate[i]).longValue();
}
nb = nb.setVibrate(vibrateArray);
}
if (android.containsKey("visibility")) {
Double visibility = android.getDouble("visibility");
nb = nb.setVisibility(visibility.intValue());
}
if (android.containsKey("when")) {
Double when = android.getDouble("when");
nb = nb.setWhen(when.longValue());
}
// TODO: Big text / Big picture
/* String bigText = bundle.getString("big_text");
if(bigText != null){
notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
}
String picture = bundle.getString("picture");
if(picture!=null){
NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle();
Bitmap pictureBitmap = getBitmap(picture);
if (pictureBitmap != null) {
bigPicture.bigPicture(pictureBitmap);
}
bigPicture.setBigContentTitle(title);
bigPicture.setSummaryText(bundle.getString("body"));
notification.setStyle(bigPicture);
} */
// Build any actions
if (android.containsKey("actions")) {
List<Bundle> actions = (List) android.getSerializable("actions");
for (Bundle a : actions) {
NotificationCompat.Action action = createAction(a, intentClass, notification);
nb = nb.addAction(action);
}
}
// Create the notification intent
PendingIntent contentIntent = createIntent(intentClass, notification, android.getString("clickAction"));
nb = nb.setContentIntent(contentIntent);
// Build the notification and send it
Notification builtNotification = nb.build();
notificationManager.notify(notificationId.hashCode(), builtNotification);
if (reactContext != null) {
Utils.sendEvent(reactContext, "notifications_notification_displayed", Arguments.fromBundle(notification));
}
} catch (Exception e) {
if (promise == null) {
Log.e(TAG, "Failed to send notification", e);
} else {
promise.reject("notification/display_notification_error", "Could not send notification", e);
}
}
}
private NotificationCompat.Action createAction(Bundle action, Class intentClass, Bundle notification) {
String actionKey = action.getString("action");
PendingIntent actionIntent = createIntent(intentClass, notification, actionKey);
int icon = getIcon(action.getString("icon"));
String title = action.getString("title");
NotificationCompat.Action.Builder ab = new NotificationCompat.Action.Builder(icon, title, actionIntent);
if (action.containsKey("allowGeneratedReplies")) {
ab = ab.setAllowGeneratedReplies(action.getBoolean("allowGeneratedReplies"));
}
if (action.containsKey("remoteInputs")) {
List<Bundle> remoteInputs = (List) action.getSerializable("remoteInputs");
for (Bundle ri : remoteInputs) {
RemoteInput remoteInput = createRemoteInput(ri);
ab = ab.addRemoteInput(remoteInput);
}
}
// TODO: SemanticAction and ShowsUserInterface only available on v28?
// if (action.containsKey("semanticAction")) {
// Double semanticAction = action.getDouble("semanticAction");
// ab = ab.setSemanticAction(semanticAction.intValue());
// }
// if (action.containsKey("showsUserInterface")) {
// ab = ab.setShowsUserInterface(action.getBoolean("showsUserInterface"));
// }
return ab.build();
}
private PendingIntent createIntent(Class intentClass, Bundle notification, String action) {
Intent intent = new Intent(context, intentClass);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtras(notification);
if (action != null) {
intent.setAction(action);
}
String notificationId = notification.getString("notificationId");
return PendingIntent.getActivity(context, notificationId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private RemoteInput createRemoteInput(Bundle remoteInput) {
String resultKey = remoteInput.getString("resultKey");
RemoteInput.Builder rb = new RemoteInput.Builder(resultKey);
if (remoteInput.containsKey("allowedDataTypes")) {
List<Bundle> allowedDataTypes = (List) remoteInput.getSerializable("allowedDataTypes");
for (Bundle adt : allowedDataTypes) {
rb.setAllowDataType(adt.getString("mimeType"), adt.getBoolean("allow"));
}
}
if (remoteInput.containsKey("allowFreeFormInput")) {
rb.setAllowFreeFormInput(remoteInput.getBoolean("allowFreeFormInput"));
}
if (remoteInput.containsKey("choices")) {
rb.setChoices(remoteInput.getStringArray("choices"));
}
if (remoteInput.containsKey("label")) {
rb.setLabel(remoteInput.getString("label"));
}
return rb.build();
}
private Bitmap getBitmap(String image) {
if (image.startsWith("http://") || image.startsWith("https://")) {
return getBitmapFromUrl(image);
} else {
int largeIconResId = getResourceId("mipmap", image);
return BitmapFactory.decodeResource(context.getResources(), largeIconResId);
}
}
private Bitmap getBitmapFromUrl(String imageUrl) {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection();
connection.setDoInput(true);
connection.connect();
return BitmapFactory.decodeStream(connection.getInputStream());
} catch (IOException e) {
Log.e(TAG, "Failed to get bitmap for url: " + imageUrl, e);
return null;
}
}
private int getIcon(String icon) {
int smallIconResourceId = getResourceId("mipmap", icon);
if (smallIconResourceId == 0) {
smallIconResourceId = getResourceId("drawable", icon);
}
return smallIconResourceId;
}
private Class getMainActivityClass() {
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
try {
return Class.forName(launchIntent.getComponent().getClassName());
} catch (ClassNotFoundException e) {
Log.e(TAG, "Failed to get main activity class", e);
return null;
}
}
private int getResourceId(String type, String image) {
return context.getResources().getIdentifier(image, type, context.getPackageName());
}
private Uri getSound(String sound) {
if (sound.equalsIgnoreCase("default")) {
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
} else {
int soundResourceId = getResourceId("raw", sound);
if (soundResourceId == 0) {
soundResourceId = getResourceId("raw", sound.substring(0, sound.lastIndexOf('.')));
}
return Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId);
}
}
private NotificationChannelGroup parseChannelGroupMap(ReadableMap channelGroupMap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String groupId = channelGroupMap.getString("groupId");
String name = channelGroupMap.getString("name");
return new NotificationChannelGroup(groupId, name);
}
return null;
}
private NotificationChannel parseChannelMap(ReadableMap channelMap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = channelMap.getString("channelId");
String name = channelMap.getString("name");
int importance = channelMap.getInt("importance");
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
if (channelMap.hasKey("bypassDnd")) {
channel.setBypassDnd(channelMap.getBoolean("bypassDnd"));
}
if (channelMap.hasKey("description")) {
channel.setDescription(channelMap.getString("description"));
}
if (channelMap.hasKey("group")) {
channel.setGroup(channelMap.getString("group"));
}
if (channelMap.hasKey("lightColor")) {
String lightColor = channelMap.getString("lightColor");
channel.setLightColor(Color.parseColor(lightColor));
}
if (channelMap.hasKey("lockScreenVisibility")) {
channel.setLockscreenVisibility(channelMap.getInt("lockScreenVisibility"));
}
if (channelMap.hasKey("showBadge")) {
channel.setShowBadge(channelMap.getBoolean("showBadge"));
}
if (channelMap.hasKey("sound")) {
Uri sound = getSound(channelMap.getString("sound"));
channel.setSound(sound, null);
}
if (channelMap.hasKey("vibrationPattern")) {
ReadableArray vibrationArray = channelMap.getArray("vibrationPattern");
long[] vibration = new long[]{};
for (int i = 0; i < vibrationArray.size(); i++) {
vibration[i] = (long) vibrationArray.getDouble(i);
}
channel.setVibrationPattern(vibration);
}
return channel;
}
return null;
}
private void scheduleNotification(Bundle notification, Promise promise) {
if (!notification.containsKey("notificationId")) {
if (promise == null) {
Log.e(TAG, "Missing notificationId");
} else {
promise.reject("notification/schedule_notification_error", "Missing notificationId");
}
return;
}
if (!notification.containsKey("schedule")) {
if (promise == null) {
Log.e(TAG, "Missing schedule information");
} else {
promise.reject("notification/schedule_notification_error", "Missing schedule information");
}
return;
}
String notificationId = notification.getString("notificationId");
Bundle schedule = notification.getBundle("schedule");
Double fireDate = schedule.getDouble("fireDate");
// Scheduled alarms are cleared on restart
// We store them so that they can be re-scheduled when the phone restarts in RNFirebaseNotificationsRebootReceiver
try {
JSONObject json = BundleJSONConverter.convertToJSON(notification);
preferences.edit().putString(notificationId, json.toString()).apply();
} catch (JSONException e) {
promise.reject("notification/schedule_notification_error", "Failed to store notification", e);
return;
}
Intent notificationIntent = new Intent(context, RNFirebaseNotificationReceiver.class);
notificationIntent.putExtras(notification);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(),
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
if (schedule.containsKey("repeatInterval")) {
Long interval = null;
switch (schedule.getString("repeatInterval")) {
case "minute":
interval = 60000L;
break;
case "hour":
interval = AlarmManager.INTERVAL_HOUR;
break;
case "day":
interval = AlarmManager.INTERVAL_DAY;
break;
case "week":
interval = AlarmManager.INTERVAL_DAY * 7;
break;
default:
Log.e(TAG, "Invalid interval: " + schedule.getString("interval"));
break;
}
if (interval == null) {
promise.reject("notification/schedule_notification_error", "Invalid interval");
return;
}
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, fireDate.longValue(), interval, pendingIntent);
} else {
if (schedule.containsKey("exact")
&& schedule.getBoolean("exact")
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate.longValue(), pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate.longValue(), pendingIntent);
}
}
promise.resolve(null);
}
}

View File

@ -0,0 +1,15 @@
package io.invertase.firebase.notifications;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/*
* This is invoked by the Alarm Manager when it is time to display a scheduled notification.
*/
public class RNFirebaseNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
new RNFirebaseNotificationManager(context).displayScheduledNotification(intent.getExtras());
}
}

View File

@ -0,0 +1,319 @@
package io.invertase.firebase.notifications;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.RemoteInput;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.firebase.messaging.RemoteMessage;
import java.util.ArrayList;
import java.util.Map;
import io.invertase.firebase.Utils;
import io.invertase.firebase.messaging.RNFirebaseMessagingService;
import me.leolin.shortcutbadger.ShortcutBadger;
public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements ActivityEventListener {
private static final String BADGE_FILE = "BadgeCountFile";
private static final String BADGE_KEY = "BadgeCount";
private static final String TAG = "RNFirebaseNotifications";
private SharedPreferences sharedPreferences = null;
private RNFirebaseNotificationManager notificationManager;
public RNFirebaseNotifications(ReactApplicationContext context) {
super(context);
context.addActivityEventListener(this);
notificationManager = new RNFirebaseNotificationManager(context);
sharedPreferences = context.getSharedPreferences(BADGE_FILE, Context.MODE_PRIVATE);
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
// Subscribe to remote notification events
localBroadcastManager.registerReceiver(new RemoteNotificationReceiver(),
new IntentFilter(RNFirebaseMessagingService.REMOTE_NOTIFICATION_EVENT));
// Subscribe to scheduled notification events
localBroadcastManager.registerReceiver(new ScheduledNotificationReceiver(),
new IntentFilter(RNFirebaseNotificationManager.SCHEDULED_NOTIFICATION_EVENT));
}
@Override
public String getName() {
return "RNFirebaseNotifications";
}
@ReactMethod
public void cancelAllNotifications() {
notificationManager.cancelAllNotifications();
}
@ReactMethod
public void cancelNotification(String notificationId) {
notificationManager.cancelNotification(notificationId);
}
@ReactMethod
public void displayNotification(ReadableMap notification, Promise promise) {
notificationManager.displayNotification(notification, promise);
}
@ReactMethod
public void getBadge(Promise promise) {
int badge = sharedPreferences.getInt(BADGE_KEY, 0);
Log.d(TAG, "Got badge count: " + badge);
promise.resolve(badge);
}
@ReactMethod
public void getInitialNotification(Promise promise) {
WritableMap notificationOpenMap = null;
if (getCurrentActivity() != null) {
notificationOpenMap = parseIntentForNotification(getCurrentActivity().getIntent());
}
promise.resolve(notificationOpenMap);
}
@ReactMethod
public void getScheduledNotifications(Promise promise) {
ArrayList<Bundle> bundles = notificationManager.getScheduledNotifications();
WritableArray array = Arguments.createArray();
for (Bundle bundle : bundles) {
array.pushMap(parseNotificationBundle(bundle));
}
promise.resolve(array);
}
@ReactMethod
public void removeAllDeliveredNotifications() {
notificationManager.removeAllDeliveredNotifications();
}
@ReactMethod
public void removeDeliveredNotification(String notificationId) {
notificationManager.removeDeliveredNotification(notificationId);
}
@ReactMethod
public void setBadge(int badge) {
// Store the badge count for later retrieval
sharedPreferences.edit().putInt(BADGE_KEY, badge).apply();
if (badge == 0) {
Log.d(TAG, "Remove badge count");
ShortcutBadger.removeCount(this.getReactApplicationContext());
} else {
Log.d(TAG, "Apply badge count: " + badge);
ShortcutBadger.applyCount(this.getReactApplicationContext(), badge);
}
}
@ReactMethod
public void scheduleNotification(ReadableMap notification, Promise promise) {
notificationManager.scheduleNotification(notification, promise);
}
//////////////////////////////////////////////////////////////////////
// Start Android specific methods
//////////////////////////////////////////////////////////////////////
@ReactMethod
public void createChannel(ReadableMap channelMap, Promise promise) {
notificationManager.createChannel(channelMap);
promise.resolve(null);
}
@ReactMethod
public void createChannelGroup(ReadableMap channelGroupMap, Promise promise) {
notificationManager.createChannelGroup(channelGroupMap);
promise.resolve(null);
}
@ReactMethod
public void createChannelGroup(ReadableArray channelGroupsArray, Promise promise) {
notificationManager.createChannelGroups(channelGroupsArray);
promise.resolve(null);
}
@ReactMethod
public void createChannels(ReadableArray channelsArray, Promise promise) {
notificationManager.createChannels(channelsArray);
promise.resolve(null);
}
//////////////////////////////////////////////////////////////////////
// End Android specific methods
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Start ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
// FCM functionality does not need this function
}
@Override
public void onNewIntent(Intent intent) {
WritableMap notificationOpenMap = parseIntentForNotification(intent);
if (notificationOpenMap != null) {
Utils.sendEvent(getReactApplicationContext(), "notifications_notification_opened", notificationOpenMap);
}
}
//////////////////////////////////////////////////////////////////////
// End ActivityEventListener methods
//////////////////////////////////////////////////////////////////////
private WritableMap parseIntentForNotification(Intent intent) {
WritableMap notificationOpenMap = parseIntentForRemoteNotification(intent);
if (notificationOpenMap == null) {
notificationOpenMap = parseIntentForLocalNotification(intent);
}
return notificationOpenMap;
}
private WritableMap parseIntentForLocalNotification(Intent intent) {
if (intent.getExtras() == null || !intent.hasExtra("notificationId")) {
return null;
}
WritableMap notificationMap = Arguments.makeNativeMap(intent.getExtras());
WritableMap notificationOpenMap = Arguments.createMap();
notificationOpenMap.putString("action", intent.getAction());
notificationOpenMap.putMap("notification", notificationMap);
// Check for remote input results
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
notificationOpenMap.putMap("results", Arguments.makeNativeMap(remoteInput));
}
return notificationOpenMap;
}
private WritableMap parseIntentForRemoteNotification(Intent intent) {
// Check if FCM data exists
if (intent.getExtras() == null || !intent.hasExtra("google.message_id")) {
return null;
}
Bundle extras = intent.getExtras();
WritableMap notificationMap = Arguments.createMap();
WritableMap dataMap = Arguments.createMap();
for (String key : extras.keySet()) {
if (key.equals("google.message_id")) {
notificationMap.putString("notificationId", extras.getString(key));
} else if (key.equals("collapse_key")
|| key.equals("from")
|| key.equals("google.sent_time")
|| key.equals("google.ttl")
|| key.equals("_fbSourceApplicationHasBeenSet")) {
// ignore known unneeded fields
} else {
dataMap.putString(key, extras.getString(key));
}
}
notificationMap.putMap("data", dataMap);
WritableMap notificationOpenMap = Arguments.createMap();
notificationOpenMap.putString("action", intent.getAction());
notificationOpenMap.putMap("notification", notificationMap);
return notificationOpenMap;
}
private WritableMap parseNotificationBundle(Bundle notification) {
return Arguments.makeNativeMap(notification);
}
private WritableMap parseRemoteMessage(RemoteMessage message) {
RemoteMessage.Notification notification = message.getNotification();
WritableMap notificationMap = Arguments.createMap();
WritableMap dataMap = Arguments.createMap();
// Cross platform notification properties
notificationMap.putString("body", notification.getBody());
if (message.getData() != null) {
for (Map.Entry<String, String> e : message.getData().entrySet()) {
dataMap.putString(e.getKey(), e.getValue());
}
}
notificationMap.putMap("data", dataMap);
if (message.getMessageId() != null) {
notificationMap.putString("notificationId", message.getMessageId());
}
if (notification.getSound() != null) {
notificationMap.putString("sound", notification.getSound());
}
if (notification.getTitle() != null) {
notificationMap.putString("title", notification.getTitle());
}
// Android specific notification properties
WritableMap androidMap = Arguments.createMap();
if (notification.getClickAction() != null) {
androidMap.putString("clickAction", notification.getClickAction());
}
if (notification.getColor() != null) {
androidMap.putString("color", notification.getColor());
}
if (notification.getIcon() != null) {
WritableMap iconMap = Arguments.createMap();
iconMap.putString("icon", notification.getIcon());
androidMap.putMap("smallIcon", iconMap);
}
if (notification.getTag() != null) {
androidMap.putString("group", notification.getTag());
}
notificationMap.putMap("android", androidMap);
return notificationMap;
}
private class RemoteNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
Log.d(TAG, "Received new remote notification");
RemoteMessage message = intent.getParcelableExtra("notification");
WritableMap messageMap = parseRemoteMessage(message);
Utils.sendEvent(getReactApplicationContext(), "notifications_notification_received", messageMap);
}
}
}
private class ScheduledNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
Log.d(TAG, "Received new scheduled notification");
Bundle notification = intent.getBundleExtra("notification");
WritableMap messageMap = parseNotificationBundle(notification);
Utils.sendEvent(getReactApplicationContext(), "notifications_notification_received", messageMap);
}
}
}
}

View File

@ -0,0 +1,37 @@
package io.invertase.firebase.notifications;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RNFirebaseNotificationsPackage implements ReactPackage {
public RNFirebaseNotificationsPackage() {
}
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNFirebaseNotifications(reactContext));
return modules;
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,18 @@
package io.invertase.firebase.notifications;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/*
* This is invoked when the phone restarts to ensure that all notifications are rescheduled
* correctly, as Android removes all scheduled alarms when the phone shuts down.
*/
public class RNFirebaseNotificationsRebootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("RNFNotifRebootReceiver", "Received reboot event");
new RNFirebaseNotificationManager(context).rescheduleNotifications();
}
}

View File

@ -17,6 +17,9 @@
8376F7141F7C149100D45A85 /* RNFirebaseFirestoreDocumentReference.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F70E1F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.m */; };
8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F7101F7C149000D45A85 /* RNFirebaseFirestore.m */; };
8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */ = {isa = PBXBuildFile; fileRef = 8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */; };
838E36FE201B9169004DCD3A /* RNFirebaseMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 838E36FD201B9169004DCD3A /* RNFirebaseMessaging.m */; };
838E372320231DF0004DCD3A /* RNFirebaseInstanceId.m in Sources */ = {isa = PBXBuildFile; fileRef = 838E372220231DF0004DCD3A /* RNFirebaseInstanceId.m */; };
838E372720231E15004DCD3A /* RNFirebaseNotifications.m in Sources */ = {isa = PBXBuildFile; fileRef = 838E372520231E15004DCD3A /* RNFirebaseNotifications.m */; };
839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D914F1EF3E20A0077C7C8 /* RNFirebaseAdMob.m */; };
839D916D1EF3E20B0077C7C8 /* RNFirebaseAdMobInterstitial.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91511EF3E20A0077C7C8 /* RNFirebaseAdMobInterstitial.m */; };
839D916E1EF3E20B0077C7C8 /* RNFirebaseAdMobRewardedVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91531EF3E20A0077C7C8 /* RNFirebaseAdMobRewardedVideo.m */; };
@ -25,8 +28,8 @@
839D91711EF3E20B0077C7C8 /* RNFirebaseRemoteConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D915C1EF3E20A0077C7C8 /* RNFirebaseRemoteConfig.m */; };
839D91721EF3E20B0077C7C8 /* RNFirebaseCrash.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D915F1EF3E20A0077C7C8 /* RNFirebaseCrash.m */; };
839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91621EF3E20A0077C7C8 /* RNFirebaseDatabase.m */; };
839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */; };
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91681EF3E20A0077C7C8 /* RNFirebasePerformance.m */; };
83AAA0792063DEC2007EC5F7 /* RNFirebaseInvites.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AAA0772063DEC2007EC5F7 /* RNFirebaseInvites.m */; };
83C3EEEE1FA1EACC00B64D3C /* RNFirebaseUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3EEEC1FA1EACC00B64D3C /* RNFirebaseUtil.m */; };
BA84AE571FA9E59800E79390 /* RNFirebaseStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = BA84AE561FA9E59800E79390 /* RNFirebaseStorage.m */; };
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */ = {isa = PBXBuildFile; fileRef = D950369D1D19C77400F7094D /* RNFirebase.m */; };
@ -66,6 +69,12 @@
8376F7111F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseFirestoreCollectionReference.m; sourceTree = "<group>"; };
8376F7121F7C149000D45A85 /* RNFirebaseFirestoreDocumentReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestoreDocumentReference.h; sourceTree = "<group>"; };
8376F7131F7C149000D45A85 /* RNFirebaseFirestoreCollectionReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseFirestoreCollectionReference.h; sourceTree = "<group>"; };
838E36FC201B9169004DCD3A /* RNFirebaseMessaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseMessaging.h; sourceTree = "<group>"; };
838E36FD201B9169004DCD3A /* RNFirebaseMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseMessaging.m; sourceTree = "<group>"; };
838E372120231DF0004DCD3A /* RNFirebaseInstanceId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseInstanceId.h; sourceTree = "<group>"; };
838E372220231DF0004DCD3A /* RNFirebaseInstanceId.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseInstanceId.m; sourceTree = "<group>"; };
838E372520231E15004DCD3A /* RNFirebaseNotifications.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseNotifications.m; sourceTree = "<group>"; };
838E372620231E15004DCD3A /* RNFirebaseNotifications.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseNotifications.h; sourceTree = "<group>"; };
839D914E1EF3E20A0077C7C8 /* RNFirebaseAdMob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMob.h; sourceTree = "<group>"; };
839D914F1EF3E20A0077C7C8 /* RNFirebaseAdMob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseAdMob.m; sourceTree = "<group>"; };
839D91501EF3E20A0077C7C8 /* RNFirebaseAdMobInterstitial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseAdMobInterstitial.h; sourceTree = "<group>"; };
@ -82,11 +91,11 @@
839D915F1EF3E20A0077C7C8 /* RNFirebaseCrash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseCrash.m; sourceTree = "<group>"; };
839D91611EF3E20A0077C7C8 /* RNFirebaseDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseDatabase.h; sourceTree = "<group>"; };
839D91621EF3E20A0077C7C8 /* RNFirebaseDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseDatabase.m; sourceTree = "<group>"; };
839D91641EF3E20A0077C7C8 /* RNFirebaseMessaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseMessaging.h; sourceTree = "<group>"; };
839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseMessaging.m; sourceTree = "<group>"; };
839D91671EF3E20A0077C7C8 /* RNFirebasePerformance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebasePerformance.h; sourceTree = "<group>"; };
839D91681EF3E20A0077C7C8 /* RNFirebasePerformance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebasePerformance.m; sourceTree = "<group>"; };
839D91771EF3E22F0077C7C8 /* RNFirebaseEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseEvents.h; path = RNFirebase/RNFirebaseEvents.h; sourceTree = "<group>"; };
83AAA0772063DEC2007EC5F7 /* RNFirebaseInvites.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseInvites.m; sourceTree = "<group>"; };
83AAA0782063DEC2007EC5F7 /* RNFirebaseInvites.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseInvites.h; sourceTree = "<group>"; };
83C3EEEC1FA1EACC00B64D3C /* RNFirebaseUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseUtil.m; path = RNFirebase/RNFirebaseUtil.m; sourceTree = "<group>"; };
83C3EEED1FA1EACC00B64D3C /* RNFirebaseUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseUtil.h; path = RNFirebase/RNFirebaseUtil.h; sourceTree = "<group>"; };
BA84AE551FA9E59800E79390 /* RNFirebaseStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseStorage.h; sourceTree = "<group>"; };
@ -127,6 +136,9 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
83AAA0762063DEC2007EC5F7 /* invites */,
838E372420231E15004DCD3A /* notifications */,
838E372020231DF0004DCD3A /* instanceid */,
8336930F1FD80DE800AA806B /* fabric */,
BA84AE541FA9E59800E79390 /* storage */,
17AF4F681F59CDBF00C02336 /* links */,
@ -179,6 +191,26 @@
path = RNFirebase/firestore;
sourceTree = "<group>";
};
838E372020231DF0004DCD3A /* instanceid */ = {
isa = PBXGroup;
children = (
838E372120231DF0004DCD3A /* RNFirebaseInstanceId.h */,
838E372220231DF0004DCD3A /* RNFirebaseInstanceId.m */,
);
name = instanceid;
path = RNFirebase/instanceid;
sourceTree = "<group>";
};
838E372420231E15004DCD3A /* notifications */ = {
isa = PBXGroup;
children = (
838E372520231E15004DCD3A /* RNFirebaseNotifications.m */,
838E372620231E15004DCD3A /* RNFirebaseNotifications.h */,
);
name = notifications;
path = RNFirebase/notifications;
sourceTree = "<group>";
};
839D914D1EF3E20A0077C7C8 /* admob */ = {
isa = PBXGroup;
children = (
@ -256,8 +288,8 @@
839D91631EF3E20A0077C7C8 /* messaging */ = {
isa = PBXGroup;
children = (
839D91641EF3E20A0077C7C8 /* RNFirebaseMessaging.h */,
839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */,
838E36FC201B9169004DCD3A /* RNFirebaseMessaging.h */,
838E36FD201B9169004DCD3A /* RNFirebaseMessaging.m */,
);
name = messaging;
path = RNFirebase/messaging;
@ -273,6 +305,16 @@
path = RNFirebase/perf;
sourceTree = "<group>";
};
83AAA0762063DEC2007EC5F7 /* invites */ = {
isa = PBXGroup;
children = (
83AAA0772063DEC2007EC5F7 /* RNFirebaseInvites.m */,
83AAA0782063DEC2007EC5F7 /* RNFirebaseInvites.h */,
);
name = invites;
path = RNFirebase/invites;
sourceTree = "<group>";
};
BA84AE541FA9E59800E79390 /* storage */ = {
isa = PBXGroup;
children = (
@ -339,10 +381,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
838E372320231DF0004DCD3A /* RNFirebaseInstanceId.m in Sources */,
839D916E1EF3E20B0077C7C8 /* RNFirebaseAdMobRewardedVideo.m in Sources */,
839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */,
17AF4F6B1F59CDBF00C02336 /* RNFirebaseLinks.m in Sources */,
8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */,
838E36FE201B9169004DCD3A /* RNFirebaseMessaging.m in Sources */,
8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */,
839D91701EF3E20B0077C7C8 /* RNFirebaseAuth.m in Sources */,
8323CF091F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m in Sources */,
@ -352,11 +396,12 @@
833693131FD824EF00AA806B /* RNFirebaseCrashlytics.m in Sources */,
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */,
839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */,
838E372720231E15004DCD3A /* RNFirebaseNotifications.m in Sources */,
BA84AE571FA9E59800E79390 /* RNFirebaseStorage.m in Sources */,
83AAA0792063DEC2007EC5F7 /* RNFirebaseInvites.m in Sources */,
8323CF071F6FBD870071420B /* NativeExpressComponent.m in Sources */,
83C3EEEE1FA1EACC00B64D3C /* RNFirebaseUtil.m in Sources */,
839D91721EF3E20B0077C7C8 /* RNFirebaseCrash.m in Sources */,
839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */,
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.m in Sources */,
8323CF061F6FBD870071420B /* BannerComponent.m in Sources */,
839D916D1EF3E20B0077C7C8 /* RNFirebaseAdMobInterstitial.m in Sources */,

View File

@ -33,14 +33,22 @@ static NSString *const STORAGE_DOWNLOAD_SUCCESS = @"download_success";
static NSString *const STORAGE_DOWNLOAD_FAILURE = @"download_failure";
// Messaging
static NSString *const MESSAGING_MESSAGE_RECEIVED = @"messaging_message_received";
static NSString *const MESSAGING_TOKEN_REFRESHED = @"messaging_token_refreshed";
static NSString *const MESSAGING_NOTIFICATION_RECEIVED = @"messaging_notification_received";
// Notifications
static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed";
static NSString *const NOTIFICATIONS_NOTIFICATION_OPENED = @"notifications_notification_opened";
static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received";
// AdMob
static NSString *const ADMOB_INTERSTITIAL_EVENT = @"interstitial_event";
static NSString *const ADMOB_REWARDED_VIDEO_EVENT = @"rewarded_video_event";
// Links
static NSString *const LINKS_DYNAMIC_LINK_RECEIVED = @"dynamic_link_received";
static NSString *const LINKS_LINK_RECEIVED = @"links_link_received";
// Invites
static NSString *const INVITES_INVITATION_RECEIVED = @"invites_invitation_received";
#endif

View File

@ -10,8 +10,8 @@
+ (FIRApp *)getApp:(NSString *)appDisplayName;
+ (NSString *)getAppName:(NSString *)appDisplayName;
+ (NSString *)getAppDisplayName:(NSString *)appName;
+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(NSDictionary *)body;
+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(NSDictionary *)body;
+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body;
+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(id)body;
@end

View File

@ -24,7 +24,7 @@ static NSString *const DEFAULT_APP_NAME = @"__FIRAPP_DEFAULT";
return appName;
}
+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(NSDictionary *)body {
+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body {
@try {
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
@ -36,7 +36,7 @@ static NSString *const DEFAULT_APP_NAME = @"__FIRAPP_DEFAULT";
}
}
+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(NSDictionary *)body {
+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(id)body {
// Add the appName to the body
NSMutableDictionary *newBody = [body mutableCopy];
newBody[@"appName"] = [RNFirebaseUtil getAppDisplayName:app.name];

View File

@ -17,6 +17,7 @@
+ (void)handlePromise:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject databaseError:(NSError *)databaseError;
+ (FIRDatabase *)getDatabaseForApp:(NSString *)appDisplayName;
+ (FIRDatabase *)getDatabaseForApp:(NSString *)appDisplayName URL:(NSString *)url;
+ (NSDictionary *)getJSError:(NSError *)nativeError;

View File

@ -22,22 +22,26 @@ RCT_EXPORT_MODULE();
return self;
}
RCT_EXPORT_METHOD(goOnline:(NSString *)appDisplayName) {
[[RNFirebaseDatabase getDatabaseForApp:appDisplayName] goOnline];
RCT_EXPORT_METHOD(goOnline:(NSString *)appDisplayName
dbURL:(NSString *)dbURL) {
[[RNFirebaseDatabase getDatabaseForApp:appDisplayName URL:dbURL] goOnline];
}
RCT_EXPORT_METHOD(goOffline:(NSString *)appDisplayName) {
[[RNFirebaseDatabase getDatabaseForApp:appDisplayName] goOffline];
RCT_EXPORT_METHOD(goOffline:(NSString *)appDisplayName
dbURL:(NSString *)dbURL) {
[[RNFirebaseDatabase getDatabaseForApp:appDisplayName URL:dbURL] goOffline];
}
RCT_EXPORT_METHOD(setPersistence:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
state:(BOOL)state) {
[RNFirebaseDatabase getDatabaseForApp:appDisplayName].persistenceEnabled = state;
[RNFirebaseDatabase getDatabaseForApp:appDisplayName URL:dbURL].persistenceEnabled = state;
}
RCT_EXPORT_METHOD(setPersistenceCacheSizeBytes:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
size:(NSInteger *)size) {
[RNFirebaseDatabase getDatabaseForApp:appDisplayName].persistenceCacheSizeBytes = (NSUInteger)size;
[RNFirebaseDatabase getDatabaseForApp:appDisplayName URL:dbURL].persistenceCacheSizeBytes = (NSUInteger)size;
}
RCT_EXPORT_METHOD(enableLogging:(BOOL)enabled) {
@ -45,15 +49,17 @@ RCT_EXPORT_METHOD(enableLogging:(BOOL)enabled) {
}
RCT_EXPORT_METHOD(keepSynced:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
key:(NSString *)key
path:(NSString *)path
modifiers:(NSArray *)modifiers
state:(BOOL)state) {
FIRDatabaseQuery *query = [self getInternalReferenceForApp:appDisplayName key:key path:path modifiers:modifiers].query;
FIRDatabaseQuery *query = [self getInternalReferenceForApp:appDisplayName dbURL:dbURL key:key path:path modifiers:modifiers].query;
[query keepSynced:state];
}
RCT_EXPORT_METHOD(transactionTryCommit:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
transactionId:(nonnull NSNumber *)transactionId
updates:(NSDictionary *)updates) {
__block NSMutableDictionary *transactionState;
@ -83,6 +89,7 @@ RCT_EXPORT_METHOD(transactionTryCommit:(NSString *)appDisplayName
RCT_EXPORT_METHOD(transactionStart:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
transactionId:(nonnull NSNumber *)transactionId
applyLocally:(BOOL)applyLocally) {
@ -90,12 +97,12 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *)appDisplayName
NSMutableDictionary *transactionState = [NSMutableDictionary new];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
transactionState[@"semaphore"] = sema;
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref runTransactionBlock:^FIRTransactionResult *_Nonnull (FIRMutableData *_Nonnull currentData) {
dispatch_barrier_async(_transactionQueue, ^{
[_transactions setValue:transactionState forKey:[transactionId stringValue]];
NSDictionary *updateMap = [self createTransactionUpdateMap:appDisplayName transactionId:transactionId updatesData:currentData];
NSDictionary *updateMap = [self createTransactionUpdateMap:appDisplayName dbURL:dbURL transactionId:transactionId updatesData:currentData];
[RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:updateMap];
});
@ -120,123 +127,134 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *)appDisplayName
return [FIRTransactionResult successWithValue:currentData];
}
} andCompletionBlock:^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) {
NSDictionary *resultMap = [self createTransactionResultMap:appDisplayName transactionId:transactionId error:databaseError committed:committed snapshot:snapshot];
NSDictionary *resultMap = [self createTransactionResultMap:appDisplayName dbURL:dbURL transactionId:transactionId error:databaseError committed:committed snapshot:snapshot];
[RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:resultMap];
} withLocalEvents:applyLocally];
});
}
RCT_EXPORT_METHOD(onDisconnectSet:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
props:(NSDictionary *)props
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref onDisconnectSetValue:props[@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(onDisconnectUpdate:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
props:(NSDictionary *)props
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref onDisconnectUpdateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref onDisconnectRemoveValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref cancelDisconnectOperationsWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(set:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
props:(NSDictionary *)props
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref setValue:[props valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(setPriority:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
priority:(NSDictionary *)priority
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref setPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(setWithPriority:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
data:(NSDictionary *)data
priority:(NSDictionary *)priority
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref setValue:[data valueForKey:@"value"] andPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(update:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
props:(NSDictionary *)props
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref updateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(remove:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
path:(NSString *)path
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName dbURL:dbURL path:path];
[ref removeValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
}];
}
RCT_EXPORT_METHOD(once:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
key:(NSString *)key
path:(NSString *)path
modifiers:(NSArray *)modifiers
eventName:(NSString *)eventName
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appDisplayName key:key path:path modifiers:modifiers];
RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appDisplayName dbURL:dbURL key:key path:path modifiers:modifiers];
[ref once:eventName resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(on:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
props:(NSDictionary *)props) {
RNFirebaseDatabaseReference *ref = [self getCachedInternalReferenceForApp:appDisplayName props:props];
RNFirebaseDatabaseReference *ref = [self getCachedInternalReferenceForApp:appDisplayName dbURL:dbURL props:props];
[ref on:props[@"eventType"] registration:props[@"registration"]];
}
@ -271,23 +289,31 @@ RCT_EXPORT_METHOD(off:(NSString *)key
return [FIRDatabase databaseForApp:app];
}
- (FIRDatabaseReference *)getReferenceForAppPath:(NSString *)appDisplayName path:(NSString *)path {
return [[RNFirebaseDatabase getDatabaseForApp:appDisplayName] referenceWithPath:path];
+ (FIRDatabase *)getDatabaseForApp:(NSString *)appDisplayName URL:(NSString *)url {
if (url == nil) {
return [self getDatabaseForApp:appDisplayName];
}
FIRApp *app = [RNFirebaseUtil getApp:appDisplayName];
return [FIRDatabase databaseForApp:app URL:url];
}
- (RNFirebaseDatabaseReference *)getInternalReferenceForApp:(NSString *)appDisplayName key:(NSString *)key path:(NSString *)path modifiers:(NSArray *)modifiers {
return [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self appDisplayName:appDisplayName key:key refPath:path modifiers:modifiers];
- (FIRDatabaseReference *)getReferenceForAppPath:(NSString *)appDisplayName dbURL:(NSString *)dbURL path:(NSString *)path {
return [[RNFirebaseDatabase getDatabaseForApp:appDisplayName URL:dbURL] referenceWithPath:path];
}
- (RNFirebaseDatabaseReference *)getCachedInternalReferenceForApp:(NSString *)appDisplayName props:(NSDictionary *)props {
- (RNFirebaseDatabaseReference *)getInternalReferenceForApp:(NSString *)appDisplayName dbURL:(NSString *)dbURL key:(NSString *)key path:(NSString *)path modifiers:(NSArray *)modifiers {
return [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self appDisplayName:appDisplayName dbURL:dbURL key:key refPath:path modifiers:modifiers];
}
- (RNFirebaseDatabaseReference *)getCachedInternalReferenceForApp:(NSString *)appDisplayName dbURL:(NSString *)dbURL props:(NSDictionary *)props {
NSString *key = props[@"key"];
NSString *path = props[@"path"];
NSDictionary *modifiers = props[@"modifiers"];
NSArray *modifiers = props[@"modifiers"];
RNFirebaseDatabaseReference *ref = _dbReferences[key];
if (ref == nil) {
ref = [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self appDisplayName:appDisplayName key:key refPath:path modifiers:modifiers];
ref = [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self appDisplayName:appDisplayName dbURL:dbURL key:key refPath:path modifiers:modifiers];
_dbReferences[key] = ref;
}
return ref;
@ -375,20 +401,22 @@ RCT_EXPORT_METHOD(off:(NSString *)key
return errorMap;
}
- (NSDictionary *)createTransactionUpdateMap:(NSString *)appDisplayName transactionId:(NSNumber *)transactionId updatesData:(FIRMutableData *)updatesData {
- (NSDictionary *)createTransactionUpdateMap:(NSString *)appDisplayName dbURL:(NSString *)dbURL transactionId:(NSNumber *)transactionId updatesData:(FIRMutableData *)updatesData {
NSMutableDictionary *updatesMap = [[NSMutableDictionary alloc] init];
[updatesMap setValue:transactionId forKey:@"id"];
[updatesMap setValue:@"update" forKey:@"type"];
[updatesMap setValue:appDisplayName forKey:@"appName"];
[updatesMap setValue:dbURL forKey:@"dbURL"];
[updatesMap setValue:updatesData.value forKey:@"value"];
return updatesMap;
}
- (NSDictionary *)createTransactionResultMap:(NSString *)appDisplayName transactionId:(NSNumber *)transactionId error:(NSError *)error committed:(BOOL)committed snapshot:(FIRDataSnapshot *)snapshot {
- (NSDictionary *)createTransactionResultMap:(NSString *)appDisplayName dbURL:(NSString *)dbURL transactionId:(NSNumber *)transactionId error:(NSError *)error committed:(BOOL)committed snapshot:(FIRDataSnapshot *)snapshot {
NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];
[resultMap setValue:transactionId forKey:@"id"];
[resultMap setValue:appDisplayName forKey:@"appName"];
[resultMap setValue:dbURL forKey:@"dbURL"];
// TODO: no timeout on iOS
[resultMap setValue:@(committed) forKey:@"committed"];
// TODO: no interrupted on iOS

View File

@ -13,11 +13,12 @@
@property RCTEventEmitter *emitter;
@property FIRDatabaseQuery *query;
@property NSString *appDisplayName;
@property NSString *dbURL;
@property NSString *key;
@property NSString *path;
@property NSMutableDictionary *listeners;
- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter appDisplayName:(NSString *)appDisplayName key:(NSString *)key refPath:(NSString *)refPath modifiers:(NSArray *)modifiers;
- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter appDisplayName:(NSString *)appDisplayName dbURL:(NSString *)dbURL key:(NSString *)key refPath:(NSString *)refPath modifiers:(NSArray *)modifiers;
- (void)on:(NSString *) eventName registration:(NSDictionary *) registration;
- (void)once:(NSString *) eventType resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (void)removeEventListener:(NSString *)eventRegistrationKey;

View File

@ -6,6 +6,7 @@
- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter
appDisplayName:(NSString *)appDisplayName
dbURL:(NSString *)dbURL
key:(NSString *)key
refPath:(NSString *)refPath
modifiers:(NSArray *)modifiers {
@ -13,6 +14,7 @@
if (self) {
_emitter = emitter;
_appDisplayName = appDisplayName;
_dbURL = dbURL;
_key = key;
_path = refPath;
_listeners = [[NSMutableDictionary alloc] init];
@ -123,7 +125,7 @@
- (FIRDatabaseQuery *)buildQueryAtPathWithModifiers:(NSString *)path
modifiers:(NSArray *)modifiers {
FIRDatabase *firebaseDatabase = [RNFirebaseDatabase getDatabaseForApp:_appDisplayName];
FIRDatabase *firebaseDatabase = [RNFirebaseDatabase getDatabaseForApp:_appDisplayName URL:_dbURL];
FIRDatabaseQuery *query = [[firebaseDatabase reference] child:path];
for (NSDictionary *modifier in modifiers) {

View File

@ -0,0 +1,19 @@
#ifndef RNFirebaseInstanceId_h
#define RNFirebaseInstanceId_h
#import <Foundation/Foundation.h>
#if __has_include(<FirebaseInstanceID/FirebaseInstanceID.h>)
#import <React/RCTBridgeModule.h>
@interface RNFirebaseInstanceId : NSObject <RCTBridgeModule> {
}
@end
#else
@interface RNFirebaseInstanceId : NSObject
@end
#endif
#endif

View File

@ -0,0 +1,35 @@
#import "RNFirebaseInstanceId.h"
#if __has_include(<FirebaseInstanceID/FIRInstanceID.h>)
#import <FirebaseInstanceID/FIRInstanceID.h>
@implementation RNFirebaseInstanceId
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(delete:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
[[FIRInstanceID instanceID] deleteIDWithHandler:^(NSError * _Nullable error) {
if (error) {
reject(@"instance_id_error", @"Failed to delete instance id", error);
} else {
resolve(nil);
}
}];
}
RCT_EXPORT_METHOD(get:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
[[FIRInstanceID instanceID] getIDWithHandler:^(NSString * _Nullable identity, NSError * _Nullable error) {
if (error) {
reject(@"instance_id_error", @"Failed to get instance id", error);
} else {
resolve(identity);
}
}];
}
@end
#else
@implementation RNFirebaseInstanceId
@end
#endif

View File

@ -0,0 +1,27 @@
#ifndef RNFirebaseInvites_h
#define RNFirebaseInvites_h
#import <Foundation/Foundation.h>
#if __has_include(<FirebaseInvites/FirebaseInvites.h>)
#import <FirebaseInvites/FirebaseInvites.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RNFirebaseInvites : RCTEventEmitter<RCTBridgeModule, FIRInviteDelegate>
+ (_Nonnull instancetype)instance;
@property _Nullable RCTPromiseRejectBlock invitationsRejecter;
@property _Nullable RCTPromiseResolveBlock invitationsResolver;
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler;
@end
#else
@interface RNFirebaseInvites : NSObject
@end
#endif
#endif

View File

@ -0,0 +1,186 @@
#import "RNFirebaseInvites.h"
#if __has_include(<FirebaseInvites/FirebaseInvites.h>)
#import "RNFirebaseEvents.h"
#import "RNFirebaseLinks.h"
#import "RNFirebaseUtil.h"
#import <FirebaseInvites/FirebaseInvites.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTConvert.h>
#import <React/RCTUtils.h>
@implementation RNFirebaseInvites
static RNFirebaseInvites *theRNFirebaseInvites = nil;
+ (nonnull instancetype)instance {
return theRNFirebaseInvites;
}
RCT_EXPORT_MODULE()
- (id)init {
self = [super init];
if (self != nil) {
NSLog(@"Setting up RNFirebaseInvites instance");
// Set static instance for use from AppDelegate
theRNFirebaseInvites = self;
}
return self;
}
// *******************************************************
// ** Start AppDelegate methods
// *******************************************************
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [self handleUrl:url];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *))restorationHandler {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
return [self handleUrl:userActivity.webpageURL];
}
return NO;
}
// *******************************************************
// ** Finish AppDelegate methods
// *******************************************************
// *******************************************************
// ** Start FIRInviteDelegate methods
// *******************************************************
// Listen for invitation response
- (void)inviteFinishedWithInvitations:(NSArray *)invitationIds error:(NSError *)error {
if (error) {
if (error.code == -402) {
_invitationsRejecter(@"invites/invitation-cancelled", @"Invitation cancelled", nil);
} else if (error.code == -404) {
_invitationsRejecter(@"invites/invitation-error", @"User must be signed in with GoogleSignIn", nil);
} else {
_invitationsRejecter(@"invites/invitation-error", @"Invitation failed to send", error);
}
} else {
_invitationsResolver(invitationIds);
}
_invitationsRejecter = nil;
_invitationsResolver = nil;
}
// *******************************************************
// ** Finish FIRInviteDelegate methods
// *******************************************************
// ** Start React Module methods **
RCT_EXPORT_METHOD(getInitialInvitation:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
NSURL* url = nil;
if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
url = (NSURL*)self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
} else if (self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
NSDictionary *dictionary = self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
if ([dictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) {
NSUserActivity* userActivity = (NSUserActivity*) dictionary[@"UIApplicationLaunchOptionsUserActivityKey"];
url = userActivity.webpageURL;
}
}
if (url) {
[FIRInvites handleUniversalLink:url completion:^(FIRReceivedInvite * _Nullable receivedInvite, NSError * _Nullable error) {
if (error) {
NSLog(@"Failed to handle universal link: %@", [error localizedDescription]);
reject(@"invites/initial-invitation-error", @"Failed to handle invitation", error);
} else if (receivedInvite && receivedInvite.inviteId) {
resolve(@{
@"deepLink": receivedInvite.deepLink,
@"invitationId": receivedInvite.inviteId,
});
} else {
resolve(nil);
}
}];
} else {
resolve(nil);
}
}
RCT_EXPORT_METHOD(sendInvitation:(NSDictionary *) invitation
resolve:(RCTPromiseResolveBlock) resolve
reject:(RCTPromiseRejectBlock) reject) {
if (!invitation[@"message"]) {
reject(@"invites/invalid-invitation", @"The supplied invitation is missing a 'message' field", nil);
}
if (!invitation[@"title"]) {
reject(@"invites/invalid-invitation", @"The supplied invitation is missing a 'title' field", nil);
}
id<FIRInviteBuilder> inviteDialog = [FIRInvites inviteDialog];
[inviteDialog setInviteDelegate: self];
[inviteDialog setMessage:invitation[@"message"]];
[inviteDialog setTitle:invitation[@"title"]];
if (invitation[@"androidClientId"]) {
FIRInvitesTargetApplication *targetApplication = [[FIRInvitesTargetApplication alloc] init];
targetApplication.androidClientID = invitation[@"androidClientId"];
[inviteDialog setOtherPlatformsTargetApplication:targetApplication];
}
if (invitation[@"androidMinimumVersionCode"]) {
[inviteDialog setAndroidMinimumVersionCode:invitation[@"androidMinimumVersionCode"]];
}
if (invitation[@"callToActionText"]) {
[inviteDialog setCallToActionText:invitation[@"callToActionText"]];
}
if (invitation[@"customImage"]) {
[inviteDialog setCustomImage:invitation[@"customImage"]];
}
if (invitation[@"deepLink"]) {
[inviteDialog setDeepLink:invitation[@"deepLink"]];
}
// Save the promise details for later
_invitationsRejecter = reject;
_invitationsResolver = resolve;
// Open the invitation dialog
dispatch_async(dispatch_get_main_queue(), ^{
[inviteDialog open];
});
}
// ** Start internals **
- (BOOL)handleUrl:(NSURL *)url {
return [FIRInvites handleUniversalLink:url completion:^(FIRReceivedInvite * _Nullable receivedInvite, NSError * _Nullable error) {
if (error) {
NSLog(@"Failed to handle invitation: %@", [error localizedDescription]);
} else if (receivedInvite && receivedInvite.inviteId) {
[RNFirebaseUtil sendJSEvent:self name:INVITES_INVITATION_RECEIVED body:@{
@"deepLink": receivedInvite.deepLink,
@"invitationId": receivedInvite.inviteId,
}];
} else {
[[RNFirebaseLinks instance] sendLink:receivedInvite.deepLink];
}
}];
}
- (NSArray<NSString *> *)supportedEvents {
return @[INVITES_INVITATION_RECEIVED];
}
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@end
#else
@implementation RNFirebaseInvites
@end
#endif

View File

@ -7,21 +7,13 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RNFirebaseLinks : RCTEventEmitter<RCTBridgeModule> {
}
+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;
@interface RNFirebaseLinks : RCTEventEmitter<RCTBridgeModule>
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation;
+ (_Nonnull instancetype)instance;
+ (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *))restorationHandler;
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler;
- (void)sendLink:(NSString *)link;
@end
@ -31,3 +23,4 @@ continueUserActivity:(NSUserActivity *)userActivity
#endif
#endif

View File

@ -3,147 +3,86 @@
#if __has_include(<FirebaseDynamicLinks/FirebaseDynamicLinks.h>)
#import <Firebase.h>
#import "RNFirebaseEvents.h"
static void sendDynamicLink(NSURL *url, id sender) {
if (url) {
[[NSNotificationCenter defaultCenter] postNotificationName:LINKS_DYNAMIC_LINK_RECEIVED
object:sender
userInfo:@{@"url": url.absoluteString}];
NSLog(@"sendDynamicLink Success: %@", url.absoluteString);
}
}
#import "RNFirebaseUtil.h"
@implementation RNFirebaseLinks
static RNFirebaseLinks *theRNFirebaseLinks = nil;
+ (nonnull instancetype)instance {
return theRNFirebaseLinks;
}
RCT_EXPORT_MODULE();
- (id)init {
self = [super init];
if (self != nil) {
NSLog(@"Setting up RNFirebaseLinks instance");
[self initialiseLinks];
// Set static instance for use from AppDelegate
theRNFirebaseLinks = self;
}
return self;
}
- (void)initialiseLinks {
// Set up internal listener to send notification over bridge
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(sendDynamicLinkEvent:)
name:LINKS_DYNAMIC_LINK_RECEIVED
object:nil];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
+ (BOOL)application:(UIApplication *)app
// *******************************************************
// ** Start AppDelegate methods
// *******************************************************
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [self handleLinkFromCustomSchemeURL:url];
}
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
return [self handleLinkFromCustomSchemeURL:url];
}
+ (BOOL)handleLinkFromCustomSchemeURL:(NSURL *)url {
FIRDynamicLink *dynamicLink =
[[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
if (dynamicLink && dynamicLink.url) {
NSURL* dynamicLinkUrl = dynamicLink.url;
sendDynamicLink(dynamicLinkUrl, self);
NSURL* url = dynamicLink.url;
[RNFirebaseUtil sendJSEvent:self name:LINKS_LINK_RECEIVED body:url.absoluteString];
return YES;
}
return NO;
}
+ (BOOL)application:(UIApplication *)application
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *))restorationHandler {
BOOL handled = [[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink, NSError * _Nullable error) {
if (error != nil){
NSLog(@"Failed to handle universal link: %@", [error localizedDescription]);
}
else {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
NSURL* url = dynamicLink ? dynamicLink.url : userActivity.webpageURL;
sendDynamicLink(url, self);
}
}
}];
return handled;
}
- (NSArray<NSString *> *)supportedEvents {
return @[LINKS_DYNAMIC_LINK_RECEIVED];
}
- (void)sendDynamicLinkEvent:(NSNotification *)notification {
[self sendEventWithName:LINKS_DYNAMIC_LINK_RECEIVED body:notification.userInfo[@"url"]];
}
-(void)handleInitialLinkFromCustomSchemeURL:(NSURL*)url resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
FIRDynamicLink *dynamicLink =
[[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
NSString* urlString = dynamicLink ? dynamicLink.url.absoluteString : (id)kCFNull;
NSLog(@"initial link is: %@", urlString);
resolve(urlString);
}
-(void)handleInitialLinkFromUniversalLinkURL:(NSDictionary *)userActivityDictionary resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
NSUserActivity* userActivity = (NSUserActivity*) userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"];
if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb])
{
[[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink, NSError * _Nullable error) {
if (error != nil){
NSLog(@"Failed to handle universal link: %@", [error localizedDescription]);
reject(@"links/failure", @"Failed to handle universal link", error);
}
else {
NSString* urlString = dynamicLink ? dynamicLink.url.absoluteString : userActivity.webpageURL.absoluteString;
NSLog(@"initial link is: %@", urlString);
resolve(urlString);
}
}];
}
else {
NSLog(@"no initial link");
resolve((id)kCFNull);
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
return [[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink, NSError * _Nullable error) {
if (error != nil){
NSLog(@"Failed to handle universal link: %@", [error localizedDescription]);
} else {
NSURL* url = dynamicLink ? dynamicLink.url : userActivity.webpageURL;
[RNFirebaseUtil sendJSEvent:self name:LINKS_LINK_RECEIVED body:url.absoluteString];
}
}];
}
return NO;
}
// *******************************************************
// ** Finish AppDelegate methods
// *******************************************************
- (void)sendLink:(NSString *)link {
[RNFirebaseUtil sendJSEvent:self name:LINKS_LINK_RECEIVED body:link];
}
RCT_EXPORT_METHOD(getInitialLink:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
NSURL* url = (NSURL*)self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
[self handleInitialLinkFromCustomSchemeURL:url resolver:resolve rejecter:reject];
} else {
NSDictionary *userActivityDictionary =
self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
[self handleInitialLinkFromUniversalLinkURL:userActivityDictionary resolver:resolve rejecter:reject];
}
}
RCT_EXPORT_METHOD(createDynamicLink: (NSDictionary *) metadata resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
// ** Start React Module methods **
RCT_EXPORT_METHOD(createDynamicLink:(NSDictionary *)linkData
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
@try {
FIRDynamicLinkComponents *components = [self getDynamicLinkComponentsFromMetadata:metadata];
FIRDynamicLinkComponents *dynamicLink = [self buildDynamicLink:linkData];
if (components == nil) {
if (dynamicLink == nil) {
reject(@"links/failure", @"Failed to create Dynamic Link", nil);
} else {
NSURL *longLink = components.url;
NSLog(@"created long dynamic link: %@", longLink.absoluteString);
resolve(longLink.absoluteString);
NSString *longLink = dynamicLink.url.absoluteString;
NSLog(@"created long dynamic link: %@", longLink);
resolve(longLink);
}
}
@catch(NSException * e) {
@ -152,21 +91,29 @@ RCT_EXPORT_METHOD(createDynamicLink: (NSDictionary *) metadata resolver:(RCTProm
}
}
RCT_EXPORT_METHOD(createShortDynamicLink: (NSDictionary *) metadata resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
RCT_EXPORT_METHOD(createShortDynamicLink:(NSDictionary *)linkData
type:(NSString *)type
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
@try {
FIRDynamicLinkComponents *components = [self getDynamicLinkComponentsFromMetadata:metadata];
[self setSuffixParameters:metadata components:components];
[components shortenWithCompletion:^(NSURL *_Nullable shortURL,
NSArray *_Nullable warnings,
NSError *_Nullable error) {
FIRDynamicLinkComponents *components = [self buildDynamicLink:linkData];
if (type) {
FIRDynamicLinkComponentsOptions *options = [FIRDynamicLinkComponentsOptions options];
if ([type isEqual: @"SHORT"]) {
options.pathLength = FIRShortDynamicLinkPathLengthShort;
} else if ([type isEqual: @"UNGUESSABLE"]) {
options.pathLength = FIRShortDynamicLinkPathLengthUnguessable;
}
components.options = options;
}
[components shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings, NSError *_Nullable error) {
if (error) {
NSLog(@"create short dynamic link failure %@", [error localizedDescription]);
reject(@"links/failure", @"Failed to create Short Dynamic Link", error);
}
else {
NSURL *shortLink = shortURL;
NSLog(@"created short dynamic link: %@", shortLink.absoluteString);
resolve(shortLink.absoluteString);
} else {
NSString *shortLink = shortURL.absoluteString;
NSLog(@"created short dynamic link: %@", shortLink);
resolve(shortLink);
}
}];
}
@ -176,15 +123,43 @@ RCT_EXPORT_METHOD(createShortDynamicLink: (NSDictionary *) metadata resolver:(RC
}
}
- (FIRDynamicLinkComponents *)getDynamicLinkComponentsFromMetadata:(NSDictionary *)metadata {
RCT_EXPORT_METHOD(getInitialLink:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
NSURL* url = (NSURL*)self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
resolve(dynamicLink ? dynamicLink.url.absoluteString : nil);
} else if (self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]
&& [self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey][UIApplicationLaunchOptionsUserActivityTypeKey] isEqualToString:NSUserActivityTypeBrowsingWeb]) {
NSDictionary *dictionary = self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
NSUserActivity* userActivity = (NSUserActivity*) dictionary[@"UIApplicationLaunchOptionsUserActivityKey"];
[[FIRDynamicLinks dynamicLinks] handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink, NSError * _Nullable error) {
if (error != nil){
NSLog(@"Failed to handle universal link: %@", [error localizedDescription]);
reject(@"links/failure", @"Failed to handle universal link", error);
} else {
NSString* urlString = dynamicLink ? dynamicLink.url.absoluteString : userActivity.webpageURL.absoluteString;
NSLog(@"initial link is: %@", urlString);
resolve(urlString);
}
}];
} else {
resolve(nil);
}
}
// ** Start internals **
- (FIRDynamicLinkComponents *)buildDynamicLink:(NSDictionary *)linkData {
@try {
NSURL *link = [NSURL URLWithString:metadata[@"link"]];
FIRDynamicLinkComponents *components =
[FIRDynamicLinkComponents componentsWithLink:link domain:metadata[@"dynamicLinkDomain"]];
NSURL *link = [NSURL URLWithString:linkData[@"link"]];
FIRDynamicLinkComponents *components = [FIRDynamicLinkComponents componentsWithLink:link domain:linkData[@"dynamicLinkDomain"]];
[self setAndroidParameters:metadata components:components];
[self setIosParameters:metadata components:components];
[self setSocialMetaTagParameters:metadata components:components];
[self setAnalyticsParameters:linkData[@"analytics"] components:components];
[self setAndroidParameters:linkData[@"android"] components:components];
[self setIosParameters:linkData[@"ios"] components:components];
[self setITunesParameters:linkData[@"itunes"] components:components];
[self setNavigationParameters:linkData[@"navigation"] components:components];
[self setSocialParameters:linkData[@"social"] components:components];
return components;
}
@ -194,86 +169,113 @@ RCT_EXPORT_METHOD(createShortDynamicLink: (NSDictionary *) metadata resolver:(RC
}
}
- (void)setAndroidParameters:(NSDictionary *)metadata
- (void)setAnalyticsParameters:(NSDictionary *)analyticsData
components:(FIRDynamicLinkComponents *)components {
FIRDynamicLinkGoogleAnalyticsParameters *analyticsParams = [FIRDynamicLinkGoogleAnalyticsParameters parameters];
if (analyticsData[@"campaign"]) {
analyticsParams.campaign = analyticsData[@"campaign"];
}
if (analyticsData[@"content"]) {
analyticsParams.content = analyticsData[@"content"];
}
if (analyticsData[@"medium"]) {
analyticsParams.medium = analyticsData[@"medium"];
}
if (analyticsData[@"source"]) {
analyticsParams.source = analyticsData[@"source"];
}
if (analyticsData[@"term"]) {
analyticsParams.term = analyticsData[@"term"];
}
components.analyticsParameters = analyticsParams;
}
- (void)setAndroidParameters:(NSDictionary *)androidData
components:(FIRDynamicLinkComponents *)components {
NSDictionary *androidParametersDict = metadata[@"androidInfo"];
if (androidParametersDict) {
FIRDynamicLinkAndroidParameters *androidParams = [FIRDynamicLinkAndroidParameters
parametersWithPackageName: androidParametersDict[@"androidPackageName"]];
if (androidData[@"packageName"]) {
FIRDynamicLinkAndroidParameters *androidParams = [FIRDynamicLinkAndroidParameters parametersWithPackageName: androidData[@"packageName"]];
if (androidParametersDict[@"androidFallbackLink"]) {
androidParams.fallbackURL = [NSURL URLWithString:androidParametersDict[@"androidFallbackLink"]];
if (androidData[@"fallbackUrl"]) {
androidParams.fallbackURL = [NSURL URLWithString:androidData[@"fallbackUrl"]];
}
if (androidParametersDict[@"androidMinPackageVersionCode"]) {
androidParams.minimumVersion = [androidParametersDict[@"androidMinPackageVersionCode"] integerValue];
if (androidData[@"minimumVersion"]) {
androidParams.minimumVersion = [androidData[@"minimumVersion"] integerValue];
}
components.androidParameters = androidParams;
}
}
- (void)setIosParameters:(NSDictionary *)metadata
- (void)setIosParameters:(NSDictionary *)iosData
components:(FIRDynamicLinkComponents *)components {
NSDictionary *iosParametersDict = metadata[@"iosInfo"];
if (iosParametersDict) {
FIRDynamicLinkIOSParameters *iOSParams = [FIRDynamicLinkIOSParameters
parametersWithBundleID:iosParametersDict[@"iosBundleId"]];
if (iosParametersDict[@"iosAppStoreId"]) {
iOSParams.appStoreID = iosParametersDict[@"iosAppStoreId"];
if (iosData[@"bundleId"]) {
FIRDynamicLinkIOSParameters *iOSParams = [FIRDynamicLinkIOSParameters parametersWithBundleID:iosData[@"bundleId"]];
if (iosData[@"appStoreId"]) {
iOSParams.appStoreID = iosData[@"appStoreId"];
}
if (iosParametersDict[@"iosCustomScheme"]) {
iOSParams.customScheme = iosParametersDict[@"iosCustomScheme"];
if (iosData[@"customScheme"]) {
iOSParams.customScheme = iosData[@"customScheme"];
}
if (iosParametersDict[@"iosFallbackLink"]) {
iOSParams.fallbackURL = [NSURL URLWithString:iosParametersDict[@"iosFallbackLink"]];
if (iosData[@"fallbackUrl"]) {
iOSParams.fallbackURL = [NSURL URLWithString:iosData[@"fallbackUrl"]];
}
if (iosParametersDict[@"iosIpadBundleId"]) {
iOSParams.iPadBundleID = iosParametersDict[@"iosIpadBundleId"];
if (iosData[@"iPadBundleId"]) {
iOSParams.iPadBundleID = iosData[@"iPadBundleId"];
}
if (iosParametersDict[@"iosIpadFallbackLink"]) {
iOSParams.iPadFallbackURL = [NSURL URLWithString:iosParametersDict[@"iosIpadFallbackLink"]];
if (iosData[@"iPadFallbackUrl"]) {
iOSParams.iPadFallbackURL = [NSURL URLWithString:iosData[@"iPadFallbackUrl"]];
}
if (iosParametersDict[@"iosMinPackageVersionCode"]) {
iOSParams.minimumAppVersion = iosParametersDict[@"iosMinPackageVersionCode"];
if (iosData[@"minimumVersion"]) {
iOSParams.minimumAppVersion = iosData[@"minimumVersion"];
}
components.iOSParameters = iOSParams;
}
}
- (void)setSocialMetaTagParameters:(NSDictionary *)metadata
components:(FIRDynamicLinkComponents *)components {
NSDictionary *socialParamsDict = metadata[@"socialMetaTagInfo"];
if (socialParamsDict) {
FIRDynamicLinkSocialMetaTagParameters *socialParams = [FIRDynamicLinkSocialMetaTagParameters parameters];
if (socialParamsDict[@"socialTitle"]) {
socialParams.title = socialParamsDict[@"socialTitle"];
}
if (socialParamsDict[@"socialDescription"]) {
socialParams.descriptionText = socialParamsDict[@"socialDescription"];
}
if (socialParamsDict[@"socialImageLink"]) {
socialParams.imageURL = [NSURL URLWithString:socialParamsDict[@"socialImageLink"]];
}
components.socialMetaTagParameters = socialParams;
}
}
- (void)setSuffixParameters:(NSDictionary *)metadata
- (void)setITunesParameters:(NSDictionary *)itunesData
components:(FIRDynamicLinkComponents *)components {
NSDictionary *suffixParametersDict = metadata[@"suffix"];
if (suffixParametersDict) {
FIRDynamicLinkComponentsOptions *options = [FIRDynamicLinkComponentsOptions options];
if ([suffixParametersDict[@"option"] isEqual: @"SHORT"]) {
options.pathLength = FIRShortDynamicLinkPathLengthShort;
}
else if ([suffixParametersDict[@"option"] isEqual: @"UNGUESSABLE"]) {
options.pathLength = FIRShortDynamicLinkPathLengthUnguessable;
}
components.options = options;
FIRDynamicLinkItunesConnectAnalyticsParameters *itunesParams = [FIRDynamicLinkItunesConnectAnalyticsParameters parameters];
if (itunesData[@"affiliateToken"]) {
itunesParams.affiliateToken = itunesData[@"affiliateToken"];
}
if (itunesData[@"campaignToken"]) {
itunesParams.campaignToken = itunesData[@"campaignToken"];
}
if (itunesData[@"providerToken"]) {
itunesParams.providerToken = itunesData[@"providerToken"];
}
components.iTunesConnectParameters = itunesParams;
}
+ (BOOL)requiresMainQueueSetup
{
- (void)setNavigationParameters:(NSDictionary *)navigationData
components:(FIRDynamicLinkComponents *)components {
FIRDynamicLinkNavigationInfoParameters *navigationParams = [FIRDynamicLinkNavigationInfoParameters parameters];
if (navigationData[@"forcedRedirectEnabled"]) {
navigationParams.forcedRedirectEnabled = navigationData[@"forcedRedirectEnabled"];
}
components.navigationInfoParameters = navigationParams;
}
- (void)setSocialParameters:(NSDictionary *)socialData
components:(FIRDynamicLinkComponents *)components {
FIRDynamicLinkSocialMetaTagParameters *socialParams = [FIRDynamicLinkSocialMetaTagParameters parameters];
if (socialData[@"descriptionText"]) {
socialParams.descriptionText = socialData[@"descriptionText"];
}
if (socialData[@"imageUrl"]) {
socialParams.imageURL = [NSURL URLWithString:socialData[@"imageUrl"]];
}
if (socialData[@"title"]) {
socialParams.title = socialData[@"title"];
}
components.socialMetaTagParameters = socialParams;
}
- (NSArray<NSString *> *)supportedEvents {
return @[LINKS_LINK_RECEIVED];
}
+ (BOOL)requiresMainQueueSetup {
return YES;
}
@ -284,4 +286,3 @@ RCT_EXPORT_METHOD(createShortDynamicLink: (NSDictionary *) metadata resolver:(RC
@end
#endif

View File

@ -7,22 +7,16 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@import UserNotifications;
@interface RNFirebaseMessaging : RCTEventEmitter<RCTBridgeModule, FIRMessagingDelegate>
typedef void (^RCTRemoteNotificationCallback)(UIBackgroundFetchResult result);
typedef void (^RCTWillPresentNotificationCallback)(UNNotificationPresentationOptions result);
typedef void (^RCTNotificationResponseCallback)();
+ (_Nonnull instancetype)instance;
@property (nonatomic, assign) bool connectedToFCM;
@property _Nullable RCTPromiseRejectBlock permissionRejecter;
@property _Nullable RCTPromiseResolveBlock permissionResolver;
#if !TARGET_OS_TV
+ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo;
+ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull RCTRemoteNotificationCallback)completionHandler;
+ (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification;
+ (void)didReceiveNotificationResponse:(nonnull UNNotificationResponse *)response withCompletionHandler:(nonnull RCTNotificationResponseCallback)completionHandler;
+ (void)willPresentNotification:(nonnull UNNotification *)notification withCompletionHandler:(nonnull RCTWillPresentNotificationCallback)completionHandler;
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo;
- (void)didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings;
#endif
@end
@ -33,3 +27,4 @@ typedef void (^RCTNotificationResponseCallback)();
#endif
#endif

View File

@ -1,7 +1,7 @@
#import "RNFirebaseMessaging.h"
@import UserNotifications;
#if __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
@import UserNotifications;
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import <FirebaseMessaging/FirebaseMessaging.h>
@ -11,297 +11,130 @@
#import <React/RCTConvert.h>
#import <React/RCTUtils.h>
@implementation RCTConvert (NSCalendarUnit)
RCT_ENUM_CONVERTER(NSCalendarUnit,
(@{
@"year": @(NSCalendarUnitYear),
@"month": @(NSCalendarUnitMonth),
@"week": @(NSCalendarUnitWeekOfYear),
@"day": @(NSCalendarUnitDay),
@"hour": @(NSCalendarUnitHour),
@"minute": @(NSCalendarUnitMinute)
}),
0,
integerValue)
@end
@implementation RCTConvert (UNNotificationRequest)
+ (UNNotificationRequest *)UNNotificationRequest:(id)json
{
NSDictionary<NSString *, id> *details = [self NSDictionary:json];
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.title =[RCTConvert NSString:details[@"title"]];
content.body =[RCTConvert NSString:details[@"body"]];
NSString* sound = [RCTConvert NSString:details[@"sound"]];
if(sound != nil){
content.sound = [UNNotificationSound soundNamed:sound];
}else{
content.sound = [UNNotificationSound defaultSound];
}
content.categoryIdentifier = [RCTConvert NSString:details[@"click_action"]];
content.userInfo = details;
content.badge = [RCTConvert NSNumber:details[@"badge"]];
if([details objectForKey:@"show_in_foreground"] != nil) {
if([(NSNumber *)details[@"show_in_foreground"] boolValue] == YES) {
[content setValue:@YES forKeyPath:@"shouldAlwaysAlertWhileAppIsForeground"];
}
}
NSDate *fireDate = [RCTConvert NSDate:details[@"fire_date"]];
if(fireDate == nil){
return [UNNotificationRequest requestWithIdentifier:[RCTConvert NSString:details[@"id"]] content:content trigger:nil];
}
NSCalendarUnit interval = [RCTConvert NSCalendarUnit:details[@"repeat_interval"]];
NSCalendarUnit unitFlags;
switch (interval) {
case NSCalendarUnitMinute: {
unitFlags = NSCalendarUnitSecond;
break;
}
case NSCalendarUnitHour: {
unitFlags = NSCalendarUnitMinute | NSCalendarUnitSecond;
break;
}
case NSCalendarUnitDay: {
unitFlags = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
break;
}
case NSCalendarUnitWeekOfYear: {
unitFlags = NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
break;
}
case NSCalendarUnitMonth:{
unitFlags = NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
}
case NSCalendarUnitYear:{
unitFlags = NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
}
default:
unitFlags = NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
break;
}
NSDateComponents *components = [[NSCalendar currentCalendar] components:unitFlags fromDate:fireDate];
UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:interval != 0];
return [UNNotificationRequest requestWithIdentifier:[RCTConvert NSString:details[@"id"]] content:content trigger:trigger];
}
@end
@implementation RCTConvert (UILocalNotification)
+ (UILocalNotification *)UILocalNotification:(id)json
{
NSDictionary<NSString *, id> *details = [self NSDictionary:json];
UILocalNotification *notification = [UILocalNotification new];
notification.fireDate = [RCTConvert NSDate:details[@"fire_date"]] ?: [NSDate date];
if([notification respondsToSelector:@selector(setAlertTitle:)]){
[notification setAlertTitle:[RCTConvert NSString:details[@"title"]]];
}
notification.alertBody = [RCTConvert NSString:details[@"body"]];
notification.alertAction = [RCTConvert NSString:details[@"alert_action"]];
notification.soundName = [RCTConvert NSString:details[@"sound"]] ?: UILocalNotificationDefaultSoundName;
notification.userInfo = details;
notification.category = [RCTConvert NSString:details[@"click_action"]];
notification.repeatInterval = [RCTConvert NSCalendarUnit:details[@"repeat_interval"]];
notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"badge"]];
return notification;
}
RCT_ENUM_CONVERTER(UIBackgroundFetchResult, (@{
@"UIBackgroundFetchResultNewData": @(UIBackgroundFetchResultNewData),
@"UIBackgroundFetchResultNoData": @(UIBackgroundFetchResultNoData),
@"UIBackgroundFetchResultFailed": @(UIBackgroundFetchResultFailed),
}), UIBackgroundFetchResultNoData, integerValue)
RCT_ENUM_CONVERTER(UNNotificationPresentationOptions, (@{
@"UNNotificationPresentationOptionAll": @(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound),
@"UNNotificationPresentationOptionNone": @(UNNotificationPresentationOptionNone)}), UIBackgroundFetchResultNoData, integerValue)
@end
@interface RNFirebaseMessaging ()
@property (nonatomic, strong) NSMutableDictionary *notificationCallbacks;
@end
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@import UserNotifications;
#endif
@implementation RNFirebaseMessaging
static RNFirebaseMessaging *theRNFirebaseMessaging = nil;
+ (nonnull instancetype)instance {
return theRNFirebaseMessaging;
}
RCT_EXPORT_MODULE()
+ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: userInfo];
[data setValue:@"remote_notification" forKey:@"_notificationType"];
[data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"];
[[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_NOTIFICATION_RECEIVED object:self userInfo:@{@"data": data}];
}
+ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull RCTRemoteNotificationCallback)completionHandler {
NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: userInfo];
[data setValue:@"remote_notification" forKey:@"_notificationType"];
[data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"];
[[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_NOTIFICATION_RECEIVED object:self userInfo:@{@"data": data, @"completionHandler": completionHandler}];
}
+ (void)didReceiveLocalNotification:(UILocalNotification *)notification {
NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: notification.userInfo];
[data setValue:@"local_notification" forKey:@"_notificationType"];
[data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"];
[[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_NOTIFICATION_RECEIVED object:self userInfo:@{@"data": data}];
}
+ (void)willPresentNotification:(UNNotification *)notification withCompletionHandler:(nonnull RCTWillPresentNotificationCallback)completionHandler
{
NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: notification.request.content.userInfo];
[data setValue:@"will_present_notification" forKey:@"_notificationType"];
[[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_NOTIFICATION_RECEIVED object:self userInfo:@{@"data": data, @"completionHandler": completionHandler}];
}
+ (void)didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(nonnull RCTNotificationResponseCallback)completionHandler
{
NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: response.notification.request.content.userInfo];
[data setValue:@"notification_response" forKey:@"_notificationType"];
[data setValue:@YES forKey:@"opened_from_tray"];
if (response.actionIdentifier) {
[data setValue:response.actionIdentifier forKey:@"_actionIdentifier"];
}
[[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_NOTIFICATION_RECEIVED object:self userInfo:@{@"data": data, @"completionHandler": completionHandler}];
}
- (id)init {
self = [super init];
if (self != nil) {
NSLog(@"Setting up RNFirebaseMessaging instance");
[self initialiseMessaging];
[self configure];
}
return self;
}
- (void)initialiseMessaging {
- (void)configure {
// Set as delegate for FIRMessaging
[FIRMessaging messaging].delegate = self;
// Establish Firebase managed data channel
[FIRMessaging messaging].shouldEstablishDirectChannel = YES;
// Set up listeners for data messages
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(sendDataMessageFailure:)
name:FIRMessagingSendErrorNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(sendDataMessageSuccess:)
name:FIRMessagingSendSuccessNotification
object:nil];
// Set up internal listener to send notification over bridge
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNotificationReceived:)
name:MESSAGING_NOTIFICATION_RECEIVED
object:nil];
// Set this as a delegate for FIRMessaging
dispatch_async(dispatch_get_main_queue(), ^{
[FIRMessaging messaging].delegate = self;
});
// Set static instance for use from AppDelegate
theRNFirebaseMessaging = self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// *******************************************************
// ** Start AppDelegate methods
// ** iOS 8/9 Only
// *******************************************************
- (void)handleNotificationReceived:(NSNotification *)notification {
id completionHandler = notification.userInfo[@"completionHandler"];
NSMutableDictionary* data = notification.userInfo[@"data"];
if(completionHandler != nil){
NSString *completionHandlerId = [[NSUUID UUID] UUIDString];
if (!self.notificationCallbacks) {
// Lazy initialization
self.notificationCallbacks = [NSMutableDictionary dictionary];
// Listen for permission response
- (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
if (notificationSettings.types == UIUserNotificationTypeNone) {
if (_permissionRejecter) {
_permissionRejecter(@"messaging/permission_error", @"Failed to grant permission", nil);
}
self.notificationCallbacks[completionHandlerId] = completionHandler;
data[@"_completionHandlerId"] = completionHandlerId;
} else if (_permissionResolver) {
_permissionResolver(nil);
}
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_NOTIFICATION_RECEIVED body:data];
_permissionRejecter = nil;
_permissionResolver = nil;
}
- (void)sendDataMessageFailure:(NSNotification *)notification {
NSString *messageID = (NSString *)notification.userInfo[@"messageID"];
NSLog(@"sendDataMessageFailure: %@", messageID);
// Listen for FCM data messages that arrive as a remote notification
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
NSDictionary *message = [self parseUserInfo:userInfo];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
- (void)sendDataMessageSuccess:(NSNotification *)notification {
NSString *messageID = (NSString *)notification.userInfo[@"messageID"];
NSLog(@"sendDataMessageSuccess: %@", messageID);
}
// *******************************************************
// ** Finish AppDelegate methods
// *******************************************************
// ** Start FIRMessagingDelegate methods **
// Handle data messages in the background
- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_NOTIFICATION_RECEIVED body:[remoteMessage appData]];
}
// Listen for token refreshes
- (void)messaging:(nonnull FIRMessaging *)messaging didRefreshRegistrationToken:(nonnull NSString *)fcmToken {
NSLog(@"FCM registration token: %@", fcmToken);
// *******************************************************
// ** Start FIRMessagingDelegate methods
// ** iOS 8+
// *******************************************************
// Listen for FCM tokens
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken {
NSLog(@"Received new FCM token: %@", fcmToken);
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_TOKEN_REFRESHED body:fcmToken];
}
// ** End FIRMessagingDelegate methods **
// Listen for data messages in the foreground
- (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
// To enable direct data messages, you can set [Messaging messaging].shouldEstablishDirectChannel to YES.
- (void)messaging:(nonnull FIRMessaging *)messaging
didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// *******************************************************
// ** Finish FIRMessagingDelegate methods
// *******************************************************
// ** Start React Module methods **
RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
UILocalNotification *localUserInfo = [self bridge].launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
if (localUserInfo) {
resolve([[localUserInfo userInfo] copy]);
} else {
resolve([[self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]);
}
}
RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
resolve([FIRMessaging messaging].FCMToken);
resolve([[FIRInstanceID instanceID] token]);
}
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) {
RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (RCTRunningInAppExtension()) {
reject(@"messaging/request-permission-unavailable", @"requestPermission is not supported in App Extensions", nil);
return;
}
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
UIUserNotificationType allNotificationTypes =
(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
[RCTSharedApplication() registerUserNotificationSettings:settings];
// Unfortunately on iOS 9 or below, there's no way to tell whether the user accepted or
// rejected the permissions popup
resolve(@{@"status":@"unknown"});
UIUserNotificationType types = (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
dispatch_async(dispatch_get_main_queue(), ^{
[RCTSharedApplication() registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:types categories:nil]];
// We set the promise for usage by the AppDelegate callback which listens
// for the result of the permission request
_permissionRejecter = reject;
_permissionResolver = resolve;
});
} else {
// iOS 10 or later
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
// For iOS 10 display notification (sent via APNS)
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
UNAuthorizationOptions authOptions =
UNAuthorizationOptionAlert
| UNAuthorizationOptionSound
| UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
resolve(@{@"granted":@(granted)});
}];
#endif
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
// For iOS 10 display notification (sent via APNS)
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
resolve(nil);
} else {
reject(@"messaging/permission_error", @"Failed to grant permission", error);
}
}];
#endif
}
dispatch_async(dispatch_get_main_queue(), ^{
@ -309,6 +142,39 @@ RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(R
});
}
// Non Web SDK methods
RCT_EXPORT_METHOD(hasPermission: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
dispatch_async(dispatch_get_main_queue(), ^{
resolve(@([RCTSharedApplication() currentUserNotificationSettings].types != UIUserNotificationTypeNone));
});
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
resolve(@(settings.alertSetting == UNNotificationSettingEnabled));
}];
#endif
}
}
RCT_EXPORT_METHOD(sendMessage: (NSDictionary *) message
resolve:(RCTPromiseResolveBlock) resolve
reject:(RCTPromiseRejectBlock) reject) {
if (!message[@"to"]) {
reject(@"messaging/invalid-message", @"The supplied message is missing a 'to' field", nil);
}
NSString *to = message[@"to"];
NSString *messageId = message[@"messageId"];
NSNumber *ttl = message[@"ttl"];
NSDictionary *data = message[@"data"];
[[FIRMessaging messaging] sendMessage:data to:to withMessageID:messageId timeToLive:[ttl intValue]];
// TODO: Listen for send success / errors
resolve(nil);
}
RCT_EXPORT_METHOD(subscribeToTopic: (NSString*) topic) {
[[FIRMessaging messaging] subscribeToTopic:topic];
}
@ -317,144 +183,61 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) {
[[FIRMessaging messaging] unsubscribeFromTopic:topic];
}
RCT_EXPORT_METHOD(createLocalNotification:(id)data resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if([UNUserNotificationCenter currentNotificationCenter] != nil){
UNNotificationRequest* request = [RCTConvert UNNotificationRequest:data];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
resolve(nil);
}else{
reject(@"notification_error", @"Failed to present local notificaton", error);
}
}];
}else{
UILocalNotification* notif = [RCTConvert UILocalNotification:data];
[RCTSharedApplication() presentLocalNotificationNow:notif];
resolve(nil);
}
}
// ** Start internals **
RCT_EXPORT_METHOD(scheduleLocalNotification:(id)data resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if([UNUserNotificationCenter currentNotificationCenter] != nil){
UNNotificationRequest* request = [RCTConvert UNNotificationRequest:data];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
resolve(nil);
}else{
reject(@"notification_error", @"Failed to present local notificaton", error);
}
}];
}else{
UILocalNotification* notif = [RCTConvert UILocalNotification:data];
[RCTSharedApplication() scheduleLocalNotification:notif];
resolve(nil);
}
}
- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *appData = remoteMessage.appData;
RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId) {
if([UNUserNotificationCenter currentNotificationCenter] != nil){
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[notificationId]];
}
}
RCT_EXPORT_METHOD(removeAllDeliveredNotifications) {
if([UNUserNotificationCenter currentNotificationCenter] != nil){
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
} else {
[RCTSharedApplication() setApplicationIconBadgeNumber: 0];
}
}
RCT_EXPORT_METHOD(cancelAllLocalNotifications) {
if([UNUserNotificationCenter currentNotificationCenter] != nil){
[[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
} else {
[RCTSharedApplication() cancelAllLocalNotifications];
}
}
RCT_EXPORT_METHOD(cancelLocalNotification:(NSString*) notificationId) {
if([UNUserNotificationCenter currentNotificationCenter] != nil){
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[notificationId]];
} else {
for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) {
NSDictionary<NSString *, id> *notificationInfo = notification.userInfo;
if([notificationId isEqualToString:[notificationInfo valueForKey:@"id"]]){
[RCTSharedApplication() cancelLocalNotification:notification];
}
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
for (id k1 in appData) {
if ([k1 isEqualToString:@"collapse_key"]) {
message[@"collapseKey"] = appData[@"collapse_key"];
} else if ([k1 isEqualToString:@"from"]) {
message[@"from"] = appData[k1];
} else if ([k1 isEqualToString:@"notification"]) {
// Ignore for messages
} else {
// Assume custom data key
data[k1] = appData[k1];
}
}
message[@"data"] = data;
return message;
}
RCT_EXPORT_METHOD(getScheduledLocalNotifications:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if([UNUserNotificationCenter currentNotificationCenter] != nil){
[[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:^(NSArray<UNNotificationRequest *> * _Nonnull requests) {
NSMutableArray* list = [[NSMutableArray alloc] init];
for(UNNotificationRequest * notif in requests){
UNMutableNotificationContent *content = notif.content;
[list addObject:content.userInfo];
}
resolve(list);
}];
} else {
NSMutableArray* list = [[NSMutableArray alloc] init];
for(UILocalNotification * notif in [RCTSharedApplication() scheduledLocalNotifications]){
[list addObject:notif.userInfo];
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo {
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
for (id k1 in userInfo) {
if ([k1 isEqualToString:@"aps"]) {
// Ignore notification section
} else if ([k1 isEqualToString:@"gcm.message_id"]) {
message[@"messageId"] = userInfo[k1];
} else if ([k1 isEqualToString:@"google.c.a.ts"]) {
message[@"sentTime"] = userInfo[k1];
} else if ([k1 isEqualToString:@"gcm.n.e"]
|| [k1 isEqualToString:@"gcm.notification.sound2"]
|| [k1 isEqualToString:@"google.c.a.c_id"]
|| [k1 isEqualToString:@"google.c.a.c_l"]
|| [k1 isEqualToString:@"google.c.a.e"]
|| [k1 isEqualToString:@"google.c.a.udt"]) {
// Ignore known keys
} else {
// Assume custom data
data[k1] = userInfo[k1];
}
resolve(list);
}
}
RCT_EXPORT_METHOD(setBadgeNumber: (NSInteger*) number) {
dispatch_async(dispatch_get_main_queue(), ^{
[RCTSharedApplication() setApplicationIconBadgeNumber:number];
});
}
RCT_EXPORT_METHOD(getBadgeNumber: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
resolve(@([RCTSharedApplication() applicationIconBadgeNumber]));
}
RCT_EXPORT_METHOD(send:(NSDictionary *)remoteMessage) {
int64_t ttl = @([[remoteMessage valueForKey:@"ttl"] intValue]).doubleValue;
NSString * mId = [remoteMessage valueForKey:@"id"];
NSString * receiver = [remoteMessage valueForKey:@"sender"];
NSDictionary * data = [remoteMessage valueForKey:@"data"];
[[FIRMessaging messaging] sendMessage:data to:receiver withMessageID:mId timeToLive:ttl];
}
RCT_EXPORT_METHOD(finishRemoteNotification: (NSString *)completionHandlerId fetchResult:(UIBackgroundFetchResult)result) {
RCTRemoteNotificationCallback completionHandler = self.notificationCallbacks[completionHandlerId];
if (!completionHandler) {
RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId);
return;
}
completionHandler(result);
[self.notificationCallbacks removeObjectForKey:completionHandlerId];
}
RCT_EXPORT_METHOD(finishWillPresentNotification: (NSString *)completionHandlerId fetchResult:(UNNotificationPresentationOptions)result) {
RCTWillPresentNotificationCallback completionHandler = self.notificationCallbacks[completionHandlerId];
if (!completionHandler) {
RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId);
return;
}
completionHandler(result);
[self.notificationCallbacks removeObjectForKey:completionHandlerId];
}
RCT_EXPORT_METHOD(finishNotificationResponse: (NSString *)completionHandlerId) {
RCTNotificationResponseCallback completionHandler = self.notificationCallbacks[completionHandlerId];
if (!completionHandler) {
RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId);
return;
}
completionHandler();
[self.notificationCallbacks removeObjectForKey:completionHandlerId];
message[@"data"] = data;
return message;
}
- (NSArray<NSString *> *)supportedEvents {
return @[MESSAGING_TOKEN_REFRESHED, MESSAGING_NOTIFICATION_RECEIVED];
return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED];
}
+ (BOOL)requiresMainQueueSetup
@ -468,3 +251,4 @@ RCT_EXPORT_METHOD(finishNotificationResponse: (NSString *)completionHandlerId) {
@implementation RNFirebaseMessaging
@end
#endif

View File

@ -0,0 +1,27 @@
#ifndef RNFirebaseNotifications_h
#define RNFirebaseNotifications_h
#import <Foundation/Foundation.h>
#if __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RNFirebaseNotifications : RCTEventEmitter<RCTBridgeModule>
+ (void)configure;
+ (_Nonnull instancetype)instance;
#if !TARGET_OS_TV
- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification;
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler;
#endif
@end
#else
@interface RNFirebaseNotifications : NSObject
@end
#endif
#endif

View File

@ -0,0 +1,725 @@
#import "RNFirebaseNotifications.h"
#if __has_include(<FirebaseMessaging/FIRMessaging.h>)
#import "RNFirebaseEvents.h"
#import "RNFirebaseMessaging.h"
#import "RNFirebaseUtil.h"
#import <React/RCTUtils.h>
// For iOS 10 we need to implement UNUserNotificationCenterDelegate to receive display
// notifications via APNS
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@import UserNotifications;
@interface RNFirebaseNotifications () <UNUserNotificationCenterDelegate>
#else
@interface RNFirebaseNotifications ()
#endif
@end
@implementation RNFirebaseNotifications
static RNFirebaseNotifications *theRNFirebaseNotifications = nil;
// PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side
// static NSMutableArray *pendingEvents = nil;
static NSDictionary *initialNotification = nil;
+ (nonnull instancetype)instance {
return theRNFirebaseNotifications;
}
+ (void)configure {
// PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side
// pendingEvents = [[NSMutableArray alloc] init];
theRNFirebaseNotifications = [[RNFirebaseNotifications alloc] init];
}
RCT_EXPORT_MODULE();
- (id)init {
self = [super init];
if (self != nil) {
NSLog(@"Setting up RNFirebaseNotifications instance");
[self initialise];
}
return self;
}
- (void)initialise {
// If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
#endif
// Set static instance for use from AppDelegate
theRNFirebaseNotifications = self;
}
// PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side
// The bridge is initialised after the module is created
// When the bridge is set, check if we have any pending events to send, and send them
/* - (void)setValue:(nullable id)value forKey:(NSString *)key {
[super setValue:value forKey:key];
if ([key isEqualToString:@"bridge"] && value) {
for (NSDictionary* event in pendingEvents) {
[RNFirebaseUtil sendJSEvent:self name:event[@"name"] body:event[@"body"]];
}
[pendingEvents removeAllObjects];
}
} */
// *******************************************************
// ** Start AppDelegate methods
// ** iOS 8/9 Only
// *******************************************************
- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)localNotification {
if ([self isIOS89]) {
NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
event = NOTIFICATIONS_NOTIFICATION_OPENED;
} else {
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
NSDictionary *notification = [self parseUILocalNotification:localNotification];
if (event == NOTIFICATIONS_NOTIFICATION_OPENED) {
notification = @{
@"action": UNNotificationDefaultActionIdentifier,
@"notification": notification
};
}
[self sendJSEvent:self name:event body:notification];
}
}
// Listen for background messages
- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// FCM Data messages come through here if they specify content-available=true
// Pass them over to the RNFirebaseMessaging handler instead
if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) {
[[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo];
return;
}
NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if ([self isIOS89]) {
if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
event = NOTIFICATIONS_NOTIFICATION_OPENED;
} else {
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
} else {
// On IOS 10:
// - foreground notifications also go through willPresentNotification
// - background notification presses also go through didReceiveNotificationResponse
// This prevents duplicate messages from hitting the JS app
return;
}
NSDictionary *notification = [self parseUserInfo:userInfo];
// For onOpened events, we set the default action name as iOS 8/9 has no concept of actions
if (event == NOTIFICATIONS_NOTIFICATION_OPENED) {
notification = @{
@"action": UNNotificationDefaultActionIdentifier,
@"notification": notification
};
}
[self sendJSEvent:self name:event body:notification];
completionHandler(UIBackgroundFetchResultNoData);
}
// *******************************************************
// ** Finish AppDelegate methods
// *******************************************************
// *******************************************************
// ** Start UNUserNotificationCenterDelegate methods
// ** iOS 10+
// *******************************************************
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
// Handle incoming notification messages while app is in the foreground.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
UNNotificationTrigger *trigger = notification.request.trigger;
BOOL isFcm = trigger && [notification.request.trigger class] == [UNPushNotificationTrigger class];
BOOL isScheduled = trigger && [notification.request.trigger class] == [UNCalendarNotificationTrigger class];
NSString *event;
UNNotificationPresentationOptions options;
NSDictionary *message = [self parseUNNotification:notification];
if (isFcm || isScheduled) {
// If app is in the background
if (RCTSharedApplication().applicationState == UIApplicationStateBackground
|| RCTSharedApplication().applicationState == UIApplicationStateInactive) {
// display the notification
options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else {
// don't show notification
options = UNNotificationPresentationOptionNone;
// notification_received
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
} else {
// Triggered by `notifications().displayNotification(notification)`
// Display the notification
options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
}
[self sendJSEvent:self name:event body:message];
completionHandler(options);
}
// Handle notification messages after display notification is tapped by the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
#if defined(__IPHONE_11_0)
withCompletionHandler:(void(^)(void))completionHandler {
#else
withCompletionHandler:(void(^)())completionHandler {
#endif
NSDictionary *message = [self parseUNNotificationResponse:response];
[self sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_OPENED body:message];
completionHandler();
}
#endif
// *******************************************************
// ** Finish UNUserNotificationCenterDelegate methods
// *******************************************************
RCT_EXPORT_METHOD(cancelAllNotifications) {
if ([self isIOS89]) {
[RCTSharedApplication() cancelAllLocalNotifications];
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
if (notificationCenter != nil) {
[[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
}
#endif
}
}
RCT_EXPORT_METHOD(cancelNotification:(NSString*) notificationId) {
if ([self isIOS89]) {
for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) {
NSDictionary *notificationInfo = notification.userInfo;
if ([notificationId isEqualToString:[notificationInfo valueForKey:@"notificationId"]]) {
[RCTSharedApplication() cancelLocalNotification:notification];
}
}
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
if (notificationCenter != nil) {
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[notificationId]];
}
#endif
}
}
RCT_EXPORT_METHOD(displayNotification:(NSDictionary*) notification
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
if ([self isIOS89]) {
UILocalNotification* notif = [self buildUILocalNotification:notification withSchedule:false];
[RCTSharedApplication() presentLocalNotificationNow:notif];
resolve(nil);
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNNotificationRequest* request = [self buildUNNotificationRequest:notification withSchedule:false];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
resolve(nil);
} else{
reject(@"notifications/display_notification_error", @"Failed to display notificaton", error);
}
}];
#endif
}
}
RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
dispatch_async(dispatch_get_main_queue(), ^{
resolve(@([RCTSharedApplication() applicationIconBadgeNumber]));
});
}
RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if ([self isIOS89]) {
// With iOS 8/9 we rely on the launch options
UILocalNotification *localNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
NSDictionary *notification;
if (localNotification) {
notification = [self parseUILocalNotification:localNotification];
} else {
NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotification) {
notification = [self parseUserInfo:remoteNotification];
}
}
if (notification) {
resolve(@{@"action": UNNotificationDefaultActionIdentifier, @"notification": notification});
} else {
resolve(nil);
}
} else {
// With iOS 10+ we are able to intercept the didReceiveNotificationResponse message
// to get both the notification and the action
resolve(initialNotification);
}
}
RCT_EXPORT_METHOD(getScheduledNotifications:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
if ([self isIOS89]) {
NSMutableArray* notifications = [[NSMutableArray alloc] init];
for (UILocalNotification *notif in [RCTSharedApplication() scheduledLocalNotifications]){
NSDictionary *notification = [self parseUILocalNotification:notif];
[notifications addObject:notification];
}
resolve(notifications);
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
[[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:^(NSArray<UNNotificationRequest *> * _Nonnull requests) {
NSMutableArray* notifications = [[NSMutableArray alloc] init];
for (UNNotificationRequest *notif in requests){
NSDictionary *notification = [self parseUNNotificationRequest:notif];
[notifications addObject:notification];
}
resolve(notifications);
}];
#endif
}
}
RCT_EXPORT_METHOD(removeAllDeliveredNotifications) {
if ([self isIOS89]) {
// No such functionality on iOS 8/9
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
if (notificationCenter != nil) {
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
}
#endif
}
}
RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId) {
if ([self isIOS89]) {
// No such functionality on iOS 8/9
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
if (notificationCenter != nil) {
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[notificationId]];
}
#endif
}
}
RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
if ([self isIOS89]) {
UILocalNotification* notif = [self buildUILocalNotification:notification withSchedule:true];
[RCTSharedApplication() scheduleLocalNotification:notif];
resolve(nil);
} else {
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
UNNotificationRequest* request = [self buildUNNotificationRequest:notification withSchedule:true];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
resolve(nil);
} else{
reject(@"notification/schedule_notification_error", @"Failed to schedule notificaton", error);
}
}];
#endif
}
}
RCT_EXPORT_METHOD(setBadge: (NSInteger) number) {
dispatch_async(dispatch_get_main_queue(), ^{
[RCTSharedApplication() setApplicationIconBadgeNumber:number];
});
}
// Because of the time delay between the app starting and the bridge being initialised
// we create a temporary instance of RNFirebaseNotifications.
// With this temporary instance, we cache any events to be sent as soon as the bridge is set on the module
- (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body {
if (emitter.bridge) {
[RNFirebaseUtil sendJSEvent:emitter name:name body:body];
} else {
if ([name isEqualToString:NOTIFICATIONS_NOTIFICATION_OPENED] && !initialNotification) {
initialNotification = body;
}
// PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side
// [pendingEvents addObject:@{@"name":name, @"body":body}];
}
}
- (BOOL)isIOS89 {
return floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;
}
- (UILocalNotification*) buildUILocalNotification:(NSDictionary *) notification
withSchedule:(BOOL) withSchedule {
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
if (notification[@"body"]) {
localNotification.alertBody = notification[@"body"];
}
if (notification[@"data"]) {
localNotification.userInfo = notification[@"data"];
}
if (notification[@"sound"]) {
localNotification.soundName = notification[@"sound"];
}
if (notification[@"title"]) {
localNotification.alertTitle = notification[@"title"];
}
if (notification[@"ios"]) {
NSDictionary *ios = notification[@"ios"];
if (ios[@"alertAction"]) {
localNotification.alertAction = ios[@"alertAction"];
}
if (ios[@"badge"]) {
NSNumber *badge = ios[@"badge"];
localNotification.applicationIconBadgeNumber = badge.integerValue;
}
if (ios[@"category"]) {
localNotification.category = ios[@"category"];
}
if (ios[@"hasAction"]) {
localNotification.hasAction = ios[@"hasAction"];
}
if (ios[@"launchImage"]) {
localNotification.alertLaunchImage = ios[@"launchImage"];
}
}
if (withSchedule) {
NSDictionary *schedule = notification[@"schedule"];
NSNumber *fireDateNumber = schedule[@"fireDate"];
NSDate *fireDate = [NSDate dateWithTimeIntervalSince1970:([fireDateNumber doubleValue] / 1000.0)];
localNotification.fireDate = fireDate;
NSString *interval = schedule[@"repeatInterval"];
if (interval) {
if ([interval isEqualToString:@"minute"]) {
localNotification.repeatInterval = NSCalendarUnitMinute;
} else if ([interval isEqualToString:@"hour"]) {
localNotification.repeatInterval = NSCalendarUnitHour;
} else if ([interval isEqualToString:@"day"]) {
localNotification.repeatInterval = NSCalendarUnitDay;
} else if ([interval isEqualToString:@"week"]) {
localNotification.repeatInterval = NSCalendarUnitWeekday;
}
}
}
return localNotification;
}
- (UNNotificationRequest*) buildUNNotificationRequest:(NSDictionary *) notification
withSchedule:(BOOL) withSchedule {
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
if (notification[@"body"]) {
content.body = notification[@"body"];
}
if (notification[@"data"]) {
content.userInfo = notification[@"data"];
}
if (notification[@"sound"]) {
content.sound = notification[@"sound"];
}
if (notification[@"subtitle"]) {
content.subtitle = notification[@"subtitle"];
}
if (notification[@"title"]) {
content.title = notification[@"title"];
}
if (notification[@"ios"]) {
NSDictionary *ios = notification[@"ios"];
if (ios[@"attachments"]) {
NSMutableArray *attachments = [[NSMutableArray alloc] init];
for (NSDictionary *a in ios[@"attachments"]) {
NSString *identifier = a[@"identifier"];
NSURL *url = [NSURL URLWithString:a[@"url"]];
NSMutableDictionary *attachmentOptions = nil;
if (a[@"options"]) {
NSDictionary *options = a[@"options"];
attachmentOptions = [[NSMutableDictionary alloc] init];
for (id key in options) {
if ([key isEqualToString:@"typeHint"]) {
attachmentOptions[UNNotificationAttachmentOptionsTypeHintKey] = options[key];
} else if ([key isEqualToString:@"thumbnailHidden"]) {
attachmentOptions[UNNotificationAttachmentOptionsThumbnailHiddenKey] = options[key];
} else if ([key isEqualToString:@"thumbnailClippingRect"]) {
attachmentOptions[UNNotificationAttachmentOptionsThumbnailClippingRectKey] = options[key];
} else if ([key isEqualToString:@"thumbnailTime"]) {
attachmentOptions[UNNotificationAttachmentOptionsThumbnailTimeKey] = options[key];
}
}
}
NSError *error;
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:identifier URL:url options:attachmentOptions error:&error];
if (attachment) {
[attachments addObject:attachment];
} else {
NSLog(@"Failed to create attachment: %@", error);
}
}
content.attachments = attachments;
}
if (ios[@"badge"]) {
content.badge = ios[@"badge"];
}
if (ios[@"category"]) {
content.categoryIdentifier = ios[@"category"];
}
if (ios[@"launchImage"]) {
content.launchImageName = ios[@"launchImage"];
}
if (ios[@"threadIdentifier"]) {
content.threadIdentifier = ios[@"threadIdentifier"];
}
}
if (withSchedule) {
NSDictionary *schedule = notification[@"schedule"];
NSNumber *fireDateNumber = schedule[@"fireDate"];
NSString *interval = schedule[@"repeatInterval"];
NSDate *fireDate = [NSDate dateWithTimeIntervalSince1970:([fireDateNumber doubleValue] / 1000.0)];
NSCalendarUnit calendarUnit;
if (interval) {
if ([interval isEqualToString:@"minute"]) {
calendarUnit = NSCalendarUnitSecond;
} else if ([interval isEqualToString:@"hour"]) {
calendarUnit = NSCalendarUnitMinute | NSCalendarUnitSecond;
} else if ([interval isEqualToString:@"day"]) {
calendarUnit = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
} else if ([interval isEqualToString:@"week"]) {
calendarUnit = NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
}
} else {
// Needs to match exactly to the secpmd
calendarUnit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
}
NSDateComponents *components = [[NSCalendar currentCalendar] components:calendarUnit fromDate:fireDate];
UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:interval];
return [UNNotificationRequest requestWithIdentifier:notification[@"notificationId"] content:content trigger:trigger];
} else {
return [UNNotificationRequest requestWithIdentifier:notification[@"notificationId"] content:content trigger:nil];
}
}
- (NSDictionary*) parseUILocalNotification:(UILocalNotification *) localNotification {
NSMutableDictionary *notification = [[NSMutableDictionary alloc] init];
if (localNotification.alertBody) {
notification[@"body"] = localNotification.alertBody;
}
if (localNotification.userInfo) {
notification[@"data"] = localNotification.userInfo;
}
if (localNotification.soundName) {
notification[@"sound"] = localNotification.soundName;
}
if (localNotification.alertTitle) {
notification[@"title"] = localNotification.alertTitle;
}
NSMutableDictionary *ios = [[NSMutableDictionary alloc] init];
if (localNotification.alertAction) {
ios[@"alertAction"] = localNotification.alertAction;
}
if (localNotification.applicationIconBadgeNumber) {
ios[@"badge"] = @(localNotification.applicationIconBadgeNumber);
}
if (localNotification.category) {
ios[@"category"] = localNotification.category;
}
if (localNotification.hasAction) {
ios[@"hasAction"] = @(localNotification.hasAction);
}
if (localNotification.alertLaunchImage) {
ios[@"launchImage"] = localNotification.alertLaunchImage;
}
notification[@"ios"] = ios;
return notification;
}
- (NSDictionary*)parseUNNotificationResponse:(UNNotificationResponse *)response {
NSMutableDictionary *notificationResponse = [[NSMutableDictionary alloc] init];
NSDictionary *notification = [self parseUNNotification:response.notification];
notificationResponse[@"notification"] = notification;
notificationResponse[@"action"] = response.actionIdentifier;
return notificationResponse;
}
- (NSDictionary*)parseUNNotification:(UNNotification *)notification {
return [self parseUNNotificationRequest:notification.request];
}
- (NSDictionary*) parseUNNotificationRequest:(UNNotificationRequest *) notificationRequest {
NSMutableDictionary *notification = [[NSMutableDictionary alloc] init];
notification[@"notificationId"] = notificationRequest.identifier;
if (notificationRequest.content.body) {
notification[@"body"] = notificationRequest.content.body;
}
if (notificationRequest.content.userInfo) {
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
for (id k in notificationRequest.content.userInfo) {
if ([k isEqualToString:@"aps"]
|| [k isEqualToString:@"gcm.message_id"]) {
// ignore as these are handled by the OS
} else {
data[k] = notificationRequest.content.userInfo[k];
}
}
notification[@"data"] = data;
}
if (notificationRequest.content.sound) {
notification[@"sound"] = notificationRequest.content.sound;
}
if (notificationRequest.content.subtitle) {
notification[@"subtitle"] = notificationRequest.content.subtitle;
}
if (notificationRequest.content.title) {
notification[@"title"] = notificationRequest.content.title;
}
NSMutableDictionary *ios = [[NSMutableDictionary alloc] init];
if (notificationRequest.content.attachments) {
NSMutableArray *attachments = [[NSMutableArray alloc] init];
for (UNNotificationAttachment *a in notificationRequest.content.attachments) {
NSMutableDictionary *attachment = [[NSMutableDictionary alloc] init];
attachment[@"identifier"] = a.identifier;
attachment[@"type"] = a.type;
attachment[@"url"] = [a.URL absoluteString];
[attachments addObject:attachment];
}
ios[@"attachments"] = attachments;
}
if (notificationRequest.content.badge) {
ios[@"badge"] = notificationRequest.content.badge;
}
if (notificationRequest.content.categoryIdentifier) {
ios[@"category"] = notificationRequest.content.categoryIdentifier;
}
if (notificationRequest.content.launchImageName) {
ios[@"launchImage"] = notificationRequest.content.launchImageName;
}
if (notificationRequest.content.threadIdentifier) {
ios[@"threadIdentifier"] = notificationRequest.content.threadIdentifier;
}
notification[@"ios"] = ios;
return notification;
}
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo {
NSMutableDictionary *notification = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
NSMutableDictionary *ios = [[NSMutableDictionary alloc] init];
for (id k1 in userInfo) {
if ([k1 isEqualToString:@"aps"]) {
NSDictionary *aps = userInfo[k1];
for (id k2 in aps) {
if ([k2 isEqualToString:@"alert"]) {
NSDictionary *alert = aps[k2];
for (id k3 in alert) {
if ([k3 isEqualToString:@"body"]) {
notification[@"body"] = alert[k3];
} else if ([k3 isEqualToString:@"subtitle"]) {
notification[@"subtitle"] = alert[k3];
} else if ([k3 isEqualToString:@"title"]) {
notification[@"title"] = alert[k3];
} else if ([k3 isEqualToString:@"loc-args"]
|| [k3 isEqualToString:@"loc-key"]
|| [k3 isEqualToString:@"title-loc-args"]
|| [k3 isEqualToString:@"title-loc-key"]) {
// Ignore known keys
} else {
NSLog(@"Unknown alert key: %@", k2);
}
}
} else if ([k2 isEqualToString:@"badge"]) {
ios[@"badge"] = aps[k2];
} else if ([k2 isEqualToString:@"category"]) {
ios[@"category"] = aps[k2];
} else if ([k2 isEqualToString:@"sound"]) {
notification[@"sound"] = aps[k2];
} else {
NSLog(@"Unknown aps key: %@", k2);
}
}
} else if ([k1 isEqualToString:@"gcm.message_id"]) {
notification[@"notificationId"] = userInfo[k1];
} else if ([k1 isEqualToString:@"gcm.n.e"]
|| [k1 isEqualToString:@"gcm.notification.sound2"]
|| [k1 isEqualToString:@"google.c.a.c_id"]
|| [k1 isEqualToString:@"google.c.a.c_l"]
|| [k1 isEqualToString:@"google.c.a.e"]
|| [k1 isEqualToString:@"google.c.a.udt"]
|| [k1 isEqualToString:@"google.c.a.ts"]) {
// Ignore known keys
} else {
// Assume custom data
data[k1] = userInfo[k1];
}
}
notification[@"data"] = data;
notification[@"ios"] = ios;
return notification;
}
- (NSArray<NSString *> *)supportedEvents {
return @[NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_OPENED, NOTIFICATIONS_NOTIFICATION_RECEIVED];
}
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@end
#else
@implementation RNFirebaseNotifications
@end
#endif

4
lib/index.d.ts vendored
View File

@ -954,7 +954,7 @@ declare module "react-native-firebase" {
* IOS
* Requests app notification permissions in an Alert dialog.
*/
requestPermissions(): void
requestPermissions(): Promise<{ granted: boolean }>;
/**
* Sets the badge number on the iOS app icon.
@ -971,7 +971,7 @@ declare module "react-native-firebase" {
* @param senderId
* @param payload
*/
send(senderId: string, payload: RemoteMessage): any
sendMessage(senderId: string, payload: RemoteMessage): any
NOTIFICATION_TYPE: Object
REMOTE_NOTIFICATION_RESULT: Object

View File

@ -24,7 +24,7 @@ export type {
} from './modules/auth/types';
export type {
default as ConfirmationResult,
} from './modules/auth/ConfirmationResult';
} from './modules/auth/phone/ConfirmationResult';
export type { default as User } from './modules/auth/User';
/*
@ -64,3 +64,17 @@ export type {
default as QuerySnapshot,
} from './modules/firestore/QuerySnapshot';
export type { default as WriteBatch } from './modules/firestore/WriteBatch';
/*
* Export Messaging types
*/
export type {
default as RemoteMessage,
} from './modules/messaging/RemoteMessage';
/*
* Export Notifications types
*/
export type {
default as Notification,
} from './modules/notifications/Notification';

View File

@ -41,6 +41,7 @@ export default class AdMob extends ModuleBase {
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});

View File

@ -34,6 +34,7 @@ export default class Analytics extends ModuleBase {
super(app, {
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
}
@ -122,7 +123,12 @@ export default class Analytics extends ModuleBase {
* Sets the user ID property.
* @param id
*/
setUserId(id: string): void {
setUserId(id: string | null): void {
if (id !== null && !isString(id)) {
throw new Error(
'analytics.setUserId(): The supplied userId must be a string value or null.'
);
}
getNativeModule(this).setUserId(id);
}
@ -131,17 +137,28 @@ export default class Analytics extends ModuleBase {
* @param name
* @param value
*/
setUserProperty(name: string, value: string): void {
setUserProperty(name: string, value: string | null): void {
if (value !== null && !isString(value)) {
throw new Error(
'analytics.setUserProperty(): The supplied property must be a string value or null.'
);
}
getNativeModule(this).setUserProperty(name, value);
}
/**
* Sets a user property to a given value.
* Sets multiple user properties to the supplied values.
* @RNFirebaseSpecific
* @param object
*/
setUserProperties(object: Object): void {
Object.keys(object).forEach(property => {
const value = object[property];
if (value !== null && !isString(value)) {
throw new Error(
`analytics.setUserProperties(): The property with name '${property}' must be a string value or null.`
);
}
getNativeModule(this).setUserProperty(property, object[property]);
});
}

View File

@ -8,7 +8,8 @@ import { getAppEventName, SharedEventEmitter } from '../../utils/events';
import { getLogger } from '../../utils/log';
import { getNativeModule } from '../../utils/native';
import INTERNALS from '../../utils/internals';
import ConfirmationResult from './ConfirmationResult';
import ConfirmationResult from './phone/ConfirmationResult';
import PhoneAuthListener from './phone/PhoneAuthListener';
// providers
import EmailAuthProvider from './providers/EmailAuthProvider';
@ -19,8 +20,6 @@ import OAuthProvider from './providers/OAuthProvider';
import TwitterAuthProvider from './providers/TwitterAuthProvider';
import FacebookAuthProvider from './providers/FacebookAuthProvider';
import PhoneAuthListener from './PhoneAuthListener';
import type {
ActionCodeInfo,
ActionCodeSettings,
@ -54,6 +53,7 @@ export default class Auth extends ModuleBase {
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: true,
hasShards: false,
namespace: NAMESPACE,
});
this._user = null;
@ -251,7 +251,7 @@ export default class Auth extends ModuleBase {
createUserAndRetrieveDataWithEmailAndPassword(
email: string,
password: string
): Promise<User> {
): Promise<UserCredential> {
return getNativeModule(this)
.createUserAndRetrieveDataWithEmailAndPassword(email, password)
.then(userCredential => this._setUserCredential(userCredential));

View File

@ -2,9 +2,9 @@
* @flow
* ConfirmationResult representation wrapper
*/
import { getNativeModule } from '../../utils/native';
import type Auth from './';
import type User from './User';
import { getNativeModule } from '../../../utils/native';
import type Auth from '../';
import type User from '../User';
export default class ConfirmationResult {
_auth: Auth;

View File

@ -1,6 +1,6 @@
// @flow
import INTERNALS from '../../utils/internals';
import { SharedEventEmitter } from '../../utils/events';
import INTERNALS from '../../../utils/internals';
import { SharedEventEmitter } from '../../../utils/events';
import {
generatePushID,
isFunction,
@ -8,10 +8,10 @@ import {
isIOS,
isString,
nativeToJSError,
} from '../../utils';
import { getNativeModule } from '../../utils/native';
} from '../../../utils';
import { getNativeModule } from '../../../utils/native';
import type Auth from './';
import type Auth from '../';
type PhoneAuthSnapshot = {
state: 'sent' | 'timeout' | 'verified' | 'error',

View File

@ -32,6 +32,7 @@ export default class RemoteConfig extends ModuleBase {
super(app, {
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
this._developerModeEnabled = false;
@ -143,11 +144,11 @@ export default class RemoteConfig extends ModuleBase {
* "source" : OneOf<String>(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic)
* }
*/
getValues(keys: Array<String>) {
getValues(keys: Array<string>) {
return getNativeModule(this)
.getValues(keys || [])
.then(nativeValues => {
const values: { [String]: Object } = {};
const values: { [string]: Object } = {};
for (let i = 0, len = keys.length; i < len; i++) {
values[keys[i]] = this._nativeValueToJS(nativeValues[i]);
}

View File

@ -18,8 +18,13 @@ import Crashlytics, {
} from '../fabric/crashlytics';
import Database, { NAMESPACE as DatabaseNamespace } from '../database';
import Firestore, { NAMESPACE as FirestoreNamespace } from '../firestore';
import InstanceId, { NAMESPACE as InstanceIdNamespace } from '../instanceid';
import Invites, { NAMESPACE as InvitesNamespace } from '../invites';
import Links, { NAMESPACE as LinksNamespace } from '../links';
import Messaging, { NAMESPACE as MessagingNamespace } from '../messaging';
import Notifications, {
NAMESPACE as NotificationsNamespace,
} from '../notifications';
import Performance, { NAMESPACE as PerfNamespace } from '../perf';
import Storage, { NAMESPACE as StorageNamespace } from '../storage';
import Utils, { NAMESPACE as UtilsNamespace } from '../utils';
@ -44,8 +49,11 @@ export default class App {
crashlytics: () => Crashlytics,
};
firestore: () => Firestore;
instanceid: () => InstanceId;
invites: () => Invites;
links: () => Links;
messaging: () => Messaging;
notifications: () => Notifications;
perf: () => Performance;
storage: () => Storage;
utils: () => Utils;
@ -83,8 +91,15 @@ export default class App {
crashlytics: APPS.appModule(this, CrashlyticsNamespace, Crashlytics),
};
this.firestore = APPS.appModule(this, FirestoreNamespace, Firestore);
this.instanceid = APPS.appModule(this, InstanceIdNamespace, InstanceId);
this.invites = APPS.appModule(this, InvitesNamespace, Invites);
this.links = APPS.appModule(this, LinksNamespace, Links);
this.messaging = APPS.appModule(this, MessagingNamespace, Messaging);
this.notifications = APPS.appModule(
this,
NotificationsNamespace,
Notifications
);
this.perf = APPS.appModule(this, PerfNamespace, Performance);
this.storage = APPS.appModule(this, StorageNamespace, Storage);
this.utils = APPS.appModule(this, UtilsNamespace, Utils);

View File

@ -38,6 +38,14 @@ import {
statics as FirestoreStatics,
MODULE_NAME as FirestoreModuleName,
} from '../firestore';
import {
statics as InstanceIdStatics,
MODULE_NAME as InstanceIdModuleName,
} from '../instanceid';
import {
statics as InvitesStatics,
MODULE_NAME as InvitesModuleName,
} from '../invites';
import {
statics as LinksStatics,
MODULE_NAME as LinksModuleName,
@ -46,6 +54,10 @@ import {
statics as MessagingStatics,
MODULE_NAME as MessagingModuleName,
} from '../messaging';
import {
statics as NotificationsStatics,
MODULE_NAME as NotificationsModuleName,
} from '../notifications';
import {
statics as PerformanceStatics,
MODULE_NAME as PerfModuleName,
@ -69,8 +81,11 @@ import type {
FabricModule,
FirebaseOptions,
FirestoreModule,
InstanceIdModule,
InvitesModule,
LinksModule,
MessagingModule,
NotificationsModule,
PerformanceModule,
StorageModule,
UtilsModule,
@ -87,8 +102,11 @@ class Firebase {
database: DatabaseModule;
fabric: FabricModule;
firestore: FirestoreModule;
instanceid: InstanceIdModule;
invites: InvitesModule;
links: LinksModule;
messaging: MessagingModule;
notifications: NotificationsModule;
perf: PerformanceModule;
storage: StorageModule;
utils: UtilsModule;
@ -130,12 +148,27 @@ class Firebase {
FirestoreStatics,
FirestoreModuleName
);
this.instanceid = APPS.moduleAndStatics(
'instanceid',
InstanceIdStatics,
InstanceIdModuleName
);
this.invites = APPS.moduleAndStatics(
'invites',
InvitesStatics,
InvitesModuleName
);
this.links = APPS.moduleAndStatics('links', LinksStatics, LinksModuleName);
this.messaging = APPS.moduleAndStatics(
'messaging',
MessagingStatics,
MessagingModuleName
);
this.notifications = APPS.moduleAndStatics(
'notifications',
NotificationsStatics,
NotificationsModuleName
);
this.perf = APPS.moduleAndStatics(
'perf',
PerformanceStatics,

View File

@ -16,6 +16,7 @@ export default class Crash extends ModuleBase {
super(app, {
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
}

View File

@ -289,7 +289,7 @@ export default class Reference extends ReferenceBase {
})
.catch(error => {
if (isFunction(cancelOrContext)) return cancelOrContext(error);
return error;
throw error;
});
}
@ -497,7 +497,7 @@ export default class Reference extends ReferenceBase {
* @returns {string}
*/
toString(): string {
return `${this._database.app.options.databaseURL}/${this.path}`;
return `${this._database.databaseUrl}/${this.path}`;
}
/**
@ -606,7 +606,7 @@ export default class Reference extends ReferenceBase {
* @return {string}
*/
_getRegistrationKey(eventType: string): string {
return `$${this._database.app.name}$/${
return `$${this._database.databaseUrl}$/${
this.path
}$${this._query.queryIdentifier()}$${listeners}$${eventType}`;
}
@ -618,8 +618,8 @@ export default class Reference extends ReferenceBase {
* @return {string}
* @private
*/
_getRefKey(): string {
return `$${this._database.app.name}$/${
_getRefKey() {
return `$${this._database.databaseUrl}$/${
this.path
}$${this._query.queryIdentifier()}`;
}
@ -758,6 +758,7 @@ export default class Reference extends ReferenceBase {
path: this.path,
key: this._getRefKey(),
appName: this._database.app.name,
dbURL: this._database.databaseUrl,
eventRegistrationKey,
};
@ -776,6 +777,7 @@ export default class Reference extends ReferenceBase {
path: this.path,
key: this._getRefKey(),
appName: this._database.app.name,
dbURL: this._database.databaseUrl,
eventType: `${eventType}$cancelled`,
eventRegistrationKey: registrationCancellationKey,
listener: _context

View File

@ -10,6 +10,7 @@ import ModuleBase from '../../utils/ModuleBase';
import { getNativeModule } from '../../utils/native';
import type App from '../core/app';
import firebase from '../core/firebase';
const NATIVE_EVENTS = [
'database_transaction_event',
@ -26,14 +27,32 @@ export default class Database extends ModuleBase {
_offsetRef: Reference;
_serverTimeOffset: number;
_transactionHandler: TransactionHandler;
_serviceUrl: string;
constructor(app: App, options: Object = {}) {
super(app, {
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: true,
namespace: NAMESPACE,
});
constructor(appOrUrl: App | string, options: Object = {}) {
let app;
let serviceUrl;
if (typeof appOrUrl === 'string') {
app = firebase.app();
serviceUrl = appOrUrl.endsWith('/') ? appOrUrl : `${appOrUrl}/`;
} else {
app = appOrUrl;
serviceUrl = app.options.databaseURL;
}
super(
app,
{
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: true,
hasShards: true,
namespace: NAMESPACE,
},
serviceUrl
);
this._serviceUrl = serviceUrl;
this._transactionHandler = new TransactionHandler(this);
if (options.persistence) {
@ -83,6 +102,14 @@ export default class Database extends ModuleBase {
ref(path: string): Reference {
return new Reference(this, path);
}
/**
* Returns the database url
* @returns {string}
*/
get databaseUrl(): string {
return this._serviceUrl;
}
}
export const statics = {

View File

@ -15,6 +15,7 @@ export default class Crashlytics extends ModuleBase {
super(app, {
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
}

View File

@ -4,12 +4,11 @@
*/
import CollectionReference from './CollectionReference';
import DocumentSnapshot from './DocumentSnapshot';
import FieldPath from './FieldPath';
import { mergeFieldPathData } from './utils';
import { parseUpdateArgs } from './utils';
import { buildNativeMap } from './utils/serialize';
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
import { getLogger } from '../../utils/log';
import { firestoreAutoId, isFunction, isObject, isString } from '../../utils';
import { firestoreAutoId, isFunction, isObject } from '../../utils';
import { getNativeModule } from '../../utils/native';
import type Firestore from './';
@ -50,9 +49,7 @@ export default class DocumentReference {
get parent(): CollectionReference {
const parentPath = this._documentPath.parent();
if (!parentPath) {
throw new Error('Invalid document path');
}
// $FlowExpectedError: parentPath can never be null
return new CollectionReference(this._firestore, parentPath);
}
@ -232,34 +229,7 @@ export default class DocumentReference {
}
update(...args: any[]): Promise<void> {
let data = {};
if (args.length === 1) {
if (!isObject(args[0])) {
throw new Error(
'DocumentReference.update failed: If using a single argument, it must be an object.'
);
}
// eslint-disable-next-line prefer-destructuring
data = args[0];
} else if (args.length % 2 === 1) {
throw new Error(
'DocumentReference.update failed: Must have either a single object argument, or equal numbers of key/value pairs.'
);
} else {
for (let i = 0; i < args.length; i += 2) {
const key = args[i];
const value = args[i + 1];
if (isString(key)) {
data[key] = value;
} else if (key instanceof FieldPath) {
data = mergeFieldPathData(data, key._segments, value);
} else {
throw new Error(
`DocumentReference.update failed: Argument at index ${i} must be a string or FieldPath`
);
}
}
}
const data = parseUpdateArgs(args, 'DocumentReference.update');
const nativeData = buildNativeMap(data);
return getNativeModule(this._firestore).documentUpdate(
this.path,

View File

@ -14,10 +14,7 @@ export default class Path {
}
get id(): string | null {
if (this._parts.length > 0) {
return this._parts[this._parts.length - 1];
}
return null;
return this._parts.length > 0 ? this._parts[this._parts.length - 1] : null;
}
get isDocument(): boolean {
@ -37,11 +34,9 @@ export default class Path {
}
parent(): Path | null {
if (this._parts.length === 0) {
return null;
}
return new Path(this._parts.slice(0, this._parts.length - 1));
return this._parts.length > 0
? new Path(this._parts.slice(0, this._parts.length - 1))
: null;
}
/**
@ -50,10 +45,6 @@ export default class Path {
*/
static fromName(name: string): Path {
const parts = name.split('/');
if (parts.length === 0) {
return new Path([]);
}
return new Path(parts);
return parts.length === 0 ? new Path([]) : new Path(parts);
}
}

View File

@ -2,22 +2,20 @@
* @flow
* Firestore Transaction representation wrapper
*/
import { mergeFieldPathData } from './utils';
import { parseUpdateArgs } from './utils';
import { buildNativeMap } from './utils/serialize';
import type Firestore from './';
import type { TransactionMeta } from './TransactionHandler';
import type DocumentReference from './DocumentReference';
import DocumentSnapshot from './DocumentSnapshot';
import { isObject, isString } from '../../utils';
import FieldPath from './FieldPath';
import { getNativeModule } from '../../utils/native';
type Command = {
type: 'set' | 'update' | 'delete',
path: string,
data: ?{ [string]: any },
options: ?{ merge: boolean },
data?: { [string]: any },
options?: SetOptions | {},
};
type SetOptions = {
@ -124,35 +122,7 @@ export default class Transaction {
*/
update(documentRef: DocumentReference, ...args: Array<any>): Transaction {
// todo validate doc ref
let data = {};
if (args.length === 1) {
if (!isObject(args[0])) {
throw new Error(
'Transaction.update failed: If using a single data argument, it must be an object.'
);
}
[data] = args;
} else if (args.length % 2 === 1) {
throw new Error(
'Transaction.update failed: Must have either a single object data argument, or equal numbers of data key/value pairs.'
);
} else {
for (let i = 0; i < args.length; i += 2) {
const key = args[i];
const value = args[i + 1];
if (isString(key)) {
data[key] = value;
} else if (key instanceof FieldPath) {
data = mergeFieldPathData(data, key._segments, value);
} else {
throw new Error(
`Transaction.update failed: Argument at index ${i} must be a string or FieldPath`
);
}
}
}
const data = parseUpdateArgs(args, 'Transaction.update');
this._commandBuffer.push({
type: 'update',
path: documentRef.path,

View File

@ -3,7 +3,6 @@
* Firestore Transaction representation wrapper
*/
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
import { getLogger } from '../../utils/log';
import { getNativeModule } from '../../utils/native';
import Transaction from './Transaction';
import type Firestore from './';
@ -19,9 +18,9 @@ const generateTransactionId = (): number => transactionId++;
export type TransactionMeta = {
id: number,
stack: Array<string>,
reject: null | Function,
resolve: null | Function,
stack: string[],
reject?: Function,
resolve?: Function,
transaction: Transaction,
updateFunction: (transaction: Transaction) => Promise<any>,
};
@ -37,7 +36,12 @@ type TransactionEvent = {
*/
export default class TransactionHandler {
_firestore: Firestore;
_pending: { [number]: TransactionMeta };
_pending: {
[number]: {
meta: TransactionMeta,
transaction: Transaction,
},
};
constructor(firestore: Firestore) {
this._pending = {};
@ -62,10 +66,9 @@ export default class TransactionHandler {
updateFunction: (transaction: Transaction) => Promise<any>
): Promise<any> {
const id = generateTransactionId();
const meta = {
// $FlowExpectedError: Transaction has to be populated
const meta: TransactionMeta = {
id,
reject: null,
resolve: null,
updateFunction,
stack: new Error().stack
.split('\n')
@ -73,8 +76,10 @@ export default class TransactionHandler {
.join('\n'),
};
meta.transaction = new Transaction(this._firestore, meta);
this._pending[id] = meta;
this._pending[id] = {
meta,
transaction: new Transaction(this._firestore, meta),
};
// deferred promise
return new Promise((resolve, reject) => {
@ -97,9 +102,7 @@ export default class TransactionHandler {
* @private
*/
_remove(id) {
// todo confirm pending arg no longer needed
getNativeModule(this._firestore).transactionDispose(id);
// TODO may need delaying to next event loop
delete this._pending[id];
}
@ -118,19 +121,17 @@ export default class TransactionHandler {
* @private
*/
_handleTransactionEvent(event: TransactionEvent) {
// eslint-disable-next-line default-case
switch (event.type) {
case 'update':
return this._handleUpdate(event);
this._handleUpdate(event);
break;
case 'error':
return this._handleError(event);
this._handleError(event);
break;
case 'complete':
return this._handleComplete(event);
default:
getLogger(this._firestore).warn(
`Unknown transaction event type: '${event.type}'`,
event
);
return undefined;
this._handleComplete(event);
break;
}
}
@ -145,7 +146,8 @@ export default class TransactionHandler {
// abort if no longer exists js side
if (!this._pending[id]) return this._remove(id);
const { updateFunction, transaction, reject } = this._pending[id];
const { meta, transaction } = this._pending[id];
const { updateFunction, reject } = meta;
// clear any saved state from previous transaction runs
transaction._prepare();
@ -178,6 +180,7 @@ export default class TransactionHandler {
// update is failed when either the users updateFunction
// throws an error or rejects a promise
if (updateFailed) {
// $FlowExpectedError: Reject will always be present
return reject(finalError);
}
@ -201,17 +204,20 @@ export default class TransactionHandler {
*/
_handleError(event: TransactionEvent) {
const { id, error } = event;
const meta = this._pending[id];
const { meta } = this._pending[id];
if (meta) {
if (meta && error) {
const { code, message } = error;
// build a JS error and replace its stack
// with the captured one at start of transaction
// so it's actually relevant to the user
const errorWithStack = new Error(message);
// $FlowExpectedError: code is needed for Firebase errors
errorWithStack.code = code;
// $FlowExpectedError: stack should be a stack trace
errorWithStack.stack = meta.stack;
// $FlowExpectedError: Reject will always be present
meta.reject(errorWithStack);
}
}
@ -224,10 +230,11 @@ export default class TransactionHandler {
*/
_handleComplete(event: TransactionEvent) {
const { id } = event;
const meta = this._pending[id];
const { meta, transaction } = this._pending[id];
if (meta) {
const pendingResult = meta.transaction._pendingResult;
const pendingResult = transaction._pendingResult;
// $FlowExpectedError: Resolve will always be present
meta.resolve(pendingResult);
}
}

View File

@ -2,10 +2,8 @@
* @flow
* WriteBatch representation wrapper
*/
import FieldPath from './FieldPath';
import { mergeFieldPathData } from './utils';
import { parseUpdateArgs } from './utils';
import { buildNativeMap } from './utils/serialize';
import { isObject, isString } from '../../utils';
import { getNativeModule } from '../../utils/native';
import type DocumentReference from './DocumentReference';
@ -66,38 +64,9 @@ export default class WriteBatch {
update(docRef: DocumentReference, ...args: any[]): WriteBatch {
// TODO: Validation
// validate.isDocumentReference('docRef', docRef);
let data = {};
if (args.length === 1) {
if (!isObject(args[0])) {
throw new Error(
'WriteBatch.update failed: If using two arguments, the second must be an object.'
);
}
// eslint-disable-next-line prefer-destructuring
data = args[0];
} else if (args.length % 2 === 1) {
throw new Error(
'WriteBatch.update failed: Must have a document reference, followed by either a single object argument, or equal numbers of key/value pairs.'
);
} else {
for (let i = 0; i < args.length; i += 2) {
const key = args[i];
const value = args[i + 1];
if (isString(key)) {
data[key] = value;
} else if (key instanceof FieldPath) {
data = mergeFieldPathData(data, key._segments, value);
} else {
throw new Error(
`WriteBatch.update failed: Argument at index ${i} must be a string or FieldPath`
);
}
}
}
const nativeData = buildNativeMap(data);
const data = parseUpdateArgs(args, 'WriteBatch.update');
this._writes.push({
data: nativeData,
data: buildNativeMap(data),
path: docRef.path,
type: 'UPDATE',
});

View File

@ -58,6 +58,7 @@ export default class Firestore extends ModuleBase {
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: true,
hasShards: false,
namespace: NAMESPACE,
});

View File

@ -1,6 +1,8 @@
/**
* @flow
*/
import FieldPath from '../FieldPath';
import { isObject, isString } from '../../../utils';
const buildFieldPathData = (segments: string[], value: any): Object => {
if (segments.length === 1) {
@ -39,3 +41,34 @@ export const mergeFieldPathData = (
[segments[0]]: buildFieldPathData(segments.slice(1), value),
};
};
export const parseUpdateArgs = (args: any[], methodName: string) => {
let data = {};
if (args.length === 1) {
if (!isObject(args[0])) {
throw new Error(
`${methodName} failed: If using a single update argument, it must be an object.`
);
}
[data] = args;
} else if (args.length % 2 === 1) {
throw new Error(
`${methodName} failed: The update arguments must be either a single object argument, or equal numbers of key/value pairs.`
);
} else {
for (let i = 0; i < args.length; i += 2) {
const key = args[i];
const value = args[i + 1];
if (isString(key)) {
data[key] = value;
} else if (key instanceof FieldPath) {
data = mergeFieldPathData(data, key._segments, value);
} else {
throw new Error(
`${methodName} failed: Argument at index ${i} must be a string or FieldPath`
);
}
}
}
return data;
};

View File

@ -0,0 +1,32 @@
/**
* @flow
* Instance ID representation wrapper
*/
import ModuleBase from '../../utils/ModuleBase';
import { getNativeModule } from '../../utils/native';
import type App from '../core/app';
export const MODULE_NAME = 'RNFirebaseInstanceId';
export const NAMESPACE = 'instanceid';
export default class InstanceId extends ModuleBase {
constructor(app: App) {
super(app, {
hasShards: false,
moduleName: MODULE_NAME,
multiApp: false,
namespace: NAMESPACE,
});
}
delete(): Promise<void> {
return getNativeModule(this).delete();
}
get(): Promise<string> {
return getNativeModule(this).get();
}
}
export const statics = {};

View File

@ -0,0 +1,69 @@
/**
* @flow
* AndroidInvitation representation wrapper
*/
import type Invitation from './Invitation';
import type { NativeAndroidInvitation } from './types';
export default class AndroidInvitation {
_additionalReferralParameters: { [string]: string } | void;
_emailHtmlContent: string | void;
_emailSubject: string | void;
_googleAnalyticsTrackingId: string | void;
_invitation: Invitation;
constructor(invitation: Invitation) {
this._invitation = invitation;
}
/**
*
* @param additionalReferralParameters
* @returns {Invitation}
*/
setAdditionalReferralParameters(additionalReferralParameters: {
[string]: string,
}): Invitation {
this._additionalReferralParameters = additionalReferralParameters;
return this._invitation;
}
/**
*
* @param emailHtmlContent
* @returns {Invitation}
*/
setEmailHtmlContent(emailHtmlContent: string): Invitation {
this._emailHtmlContent = emailHtmlContent;
return this._invitation;
}
/**
*
* @param emailSubject
* @returns {Invitation}
*/
setEmailSubject(emailSubject: string): Invitation {
this._emailSubject = emailSubject;
return this._invitation;
}
/**
*
* @param googleAnalyticsTrackingId
* @returns {Invitation}
*/
setGoogleAnalyticsTrackingId(googleAnalyticsTrackingId: string): Invitation {
this._googleAnalyticsTrackingId = googleAnalyticsTrackingId;
return this._invitation;
}
build(): NativeAndroidInvitation {
return {
additionalReferralParameters: this._additionalReferralParameters,
emailHtmlContent: this._emailHtmlContent,
emailSubject: this._emailSubject,
googleAnalyticsTrackingId: this._googleAnalyticsTrackingId,
};
}
}

View File

@ -0,0 +1,110 @@
/**
* @flow
* Invitation representation wrapper
*/
import { Platform } from 'react-native';
import AndroidInvitation from './AndroidInvitation';
import type { NativeInvitation } from './types';
export default class Invitation {
_android: AndroidInvitation;
_androidClientId: string | void;
_androidMinimumVersionCode: number | void;
_callToActionText: string | void;
_customImage: string | void;
_deepLink: string | void;
_iosClientId: string | void;
_message: string;
_title: string;
constructor(title: string, message: string) {
this._android = new AndroidInvitation(this);
this._message = message;
this._title = title;
}
get android(): AndroidInvitation {
return this._android;
}
/**
*
* @param androidClientId
* @returns {Invitation}
*/
setAndroidClientId(androidClientId: string): Invitation {
this._androidClientId = androidClientId;
return this;
}
/**
*
* @param androidMinimumVersionCode
* @returns {Invitation}
*/
setAndroidMinimumVersionCode(androidMinimumVersionCode: number): Invitation {
this._androidMinimumVersionCode = androidMinimumVersionCode;
return this;
}
/**
*
* @param callToActionText
* @returns {Invitation}
*/
setCallToActionText(callToActionText: string): Invitation {
this._callToActionText = callToActionText;
return this;
}
/**
*
* @param customImage
* @returns {Invitation}
*/
setCustomImage(customImage: string): Invitation {
this._customImage = customImage;
return this;
}
/**
*
* @param deepLink
* @returns {Invitation}
*/
setDeepLink(deepLink: string): Invitation {
this._deepLink = deepLink;
return this;
}
/**
*
* @param iosClientId
* @returns {Invitation}
*/
setIOSClientId(iosClientId: string): Invitation {
this._iosClientId = iosClientId;
return this;
}
build(): NativeInvitation {
if (!this._message) {
throw new Error('Invitation: Missing required `message` property');
} else if (!this._title) {
throw new Error('Invitation: Missing required `title` property');
}
return {
android: Platform.OS === 'android' ? this._android.build() : undefined,
androidClientId: this._androidClientId,
androidMinimumVersionCode: this._androidMinimumVersionCode,
callToActionText: this._callToActionText,
customImage: this._customImage,
deepLink: this._deepLink,
iosClientId: this._iosClientId,
message: this._message,
title: this._title,
};
}
}

View File

@ -0,0 +1,78 @@
/**
* @flow
* Invites representation wrapper
*/
import { SharedEventEmitter } from '../../utils/events';
import { getLogger } from '../../utils/log';
import ModuleBase from '../../utils/ModuleBase';
import { getNativeModule } from '../../utils/native';
import Invitation from './Invitation';
import type App from '../core/app';
export const MODULE_NAME = 'RNFirebaseInvites';
export const NAMESPACE = 'invites';
const NATIVE_EVENTS = ['invites_invitation_received'];
type InvitationOpen = {
deepLink: string,
invitationId: string,
};
export default class Invites extends ModuleBase {
constructor(app: App) {
super(app, {
events: NATIVE_EVENTS,
hasShards: false,
moduleName: MODULE_NAME,
multiApp: false,
namespace: NAMESPACE,
});
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onMessage
'invites_invitation_received',
(invitation: InvitationOpen) => {
SharedEventEmitter.emit('onInvitation', invitation);
}
);
}
/**
* Returns the invitation that triggered application open
* @returns {Promise.<Object>}
*/
getInitialInvitation(): Promise<string> {
return getNativeModule(this).getInitialInvitation();
}
/**
* Subscribe to invites
* @param listener
* @returns {Function}
*/
onInvitation(listener: InvitationOpen => any) {
getLogger(this).info('Creating onInvitation listener');
SharedEventEmitter.addListener('onInvitation', listener);
return () => {
getLogger(this).info('Removing onInvitation listener');
SharedEventEmitter.removeListener('onInvitation', listener);
};
}
sendInvitation(invitation: Invitation): Promise<string[]> {
if (!(invitation instanceof Invitation)) {
throw new Error(
`Invites:sendInvitation expects an 'Invitation' but got type ${typeof invitation}`
);
}
return getNativeModule(this).sendInvitation(invitation.build());
}
}
export const statics = {
Invitation,
};

View File

@ -0,0 +1,21 @@
/**
* @flow
*/
export type NativeAndroidInvitation = {|
additionalReferralParameters?: { [string]: string },
emailHtmlContent?: string,
emailSubject?: string,
googleAnalyticsTrackingId?: string,
|};
export type NativeInvitation = {|
android?: NativeAndroidInvitation,
androidClientId?: string,
androidMinimumVersionCode?: number,
callToActionText?: string,
customImage?: string,
deepLink?: string,
iosClientId?: string,
message: string,
title: string,
|};

View File

@ -0,0 +1,79 @@
/**
* @flow
* AnalyticsParameters representation wrapper
*/
import type DynamicLink from './DynamicLink';
import type { NativeAnalyticsParameters } from './types';
export default class AnalyticsParameters {
_campaign: string | void;
_content: string | void;
_link: DynamicLink;
_medium: string | void;
_source: string | void;
_term: string | void;
constructor(link: DynamicLink) {
this._link = link;
}
/**
*
* @param campaign
* @returns {DynamicLink}
*/
setCampaign(campaign: string): DynamicLink {
this._campaign = campaign;
return this._link;
}
/**
*
* @param content
* @returns {DynamicLink}
*/
setContent(content: string): DynamicLink {
this._content = content;
return this._link;
}
/**
*
* @param medium
* @returns {DynamicLink}
*/
setMedium(medium: string): DynamicLink {
this._medium = medium;
return this._link;
}
/**
*
* @param source
* @returns {DynamicLink}
*/
setSource(source: string): DynamicLink {
this._source = source;
return this._link;
}
/**
*
* @param term
* @returns {DynamicLink}
*/
setTerm(term: string): DynamicLink {
this._term = term;
return this._link;
}
build(): NativeAnalyticsParameters {
return {
campaign: this._campaign,
content: this._content,
medium: this._medium,
source: this._source,
term: this._term,
};
}
}

View File

@ -0,0 +1,60 @@
/**
* @flow
* AndroidParameters representation wrapper
*/
import type DynamicLink from './DynamicLink';
import type { NativeAndroidParameters } from './types';
export default class AndroidParameters {
_fallbackUrl: string | void;
_link: DynamicLink;
_minimumVersion: number | void;
_packageName: string | void;
constructor(link: DynamicLink) {
this._link = link;
}
/**
*
* @param fallbackUrl
* @returns {DynamicLink}
*/
setFallbackUrl(fallbackUrl: string): DynamicLink {
this._fallbackUrl = fallbackUrl;
return this._link;
}
/**
*
* @param minimumVersion
* @returns {DynamicLink}
*/
setMinimumVersion(minimumVersion: number): DynamicLink {
this._minimumVersion = minimumVersion;
return this._link;
}
/**
*
* @param packageName
* @returns {DynamicLink}
*/
setPackageName(packageName: string): DynamicLink {
this._packageName = packageName;
return this._link;
}
build(): NativeAndroidParameters {
if ((this._fallbackUrl || this._minimumVersion) && !this._packageName) {
throw new Error(
'AndroidParameters: Missing required `packageName` property'
);
}
return {
fallbackUrl: this._fallbackUrl,
minimumVersion: this._minimumVersion,
packageName: this._packageName,
};
}
}

View File

@ -0,0 +1,79 @@
/**
* @flow
* DynamicLink representation wrapper
*/
import AnalyticsParameters from './AnalyticsParameters';
import AndroidParameters from './AndroidParameters';
import IOSParameters from './IOSParameters';
import ITunesParameters from './ITunesParameters';
import NavigationParameters from './NavigationParameters';
import SocialParameters from './SocialParameters';
import type { NativeDynamicLink } from './types';
export default class DynamicLink {
_analytics: AnalyticsParameters;
_android: AndroidParameters;
_dynamicLinkDomain: string;
_ios: IOSParameters;
_itunes: ITunesParameters;
_link: string;
_navigation: NavigationParameters;
_social: SocialParameters;
constructor(link: string, dynamicLinkDomain: string) {
this._analytics = new AnalyticsParameters(this);
this._android = new AndroidParameters(this);
this._dynamicLinkDomain = dynamicLinkDomain;
this._ios = new IOSParameters(this);
this._itunes = new ITunesParameters(this);
this._link = link;
this._navigation = new NavigationParameters(this);
this._social = new SocialParameters(this);
}
get analytics(): AnalyticsParameters {
return this._analytics;
}
get android(): AndroidParameters {
return this._android;
}
get ios(): IOSParameters {
return this._ios;
}
get itunes(): ITunesParameters {
return this._itunes;
}
get navigation(): NavigationParameters {
return this._navigation;
}
get social(): SocialParameters {
return this._social;
}
build(): NativeDynamicLink {
if (!this._link) {
throw new Error('DynamicLink: Missing required `link` property');
} else if (!this._dynamicLinkDomain) {
throw new Error(
'DynamicLink: Missing required `dynamicLinkDomain` property'
);
}
return {
analytics: this._analytics.build(),
android: this._android.build(),
dynamicLinkDomain: this._dynamicLinkDomain,
ios: this._ios.build(),
itunes: this._itunes.build(),
link: this._link,
navigation: this._navigation.build(),
social: this._social.build(),
};
}
}

View File

@ -0,0 +1,114 @@
/**
* @flow
* IOSParameters representation wrapper
*/
import type DynamicLink from './DynamicLink';
import type { NativeIOSParameters } from './types';
export default class IOSParameters {
_appStoreId: string | void;
_bundleId: string | void;
_customScheme: string | void;
_fallbackUrl: string | void;
_iPadBundleId: string | void;
_iPadFallbackUrl: string | void;
_link: DynamicLink;
_minimumVersion: string | void;
constructor(link: DynamicLink) {
this._link = link;
}
/**
*
* @param appStoreId
* @returns {DynamicLink}
*/
setAppStoreId(appStoreId: string): DynamicLink {
this._appStoreId = appStoreId;
return this._link;
}
/**
*
* @param bundleId
* @returns {DynamicLink}
*/
setBundleId(bundleId: string): DynamicLink {
this._bundleId = bundleId;
return this._link;
}
/**
*
* @param customScheme
* @returns {DynamicLink}
*/
setCustomScheme(customScheme: string): DynamicLink {
this._customScheme = customScheme;
return this._link;
}
/**
*
* @param fallbackUrl
* @returns {DynamicLink}
*/
setFallbackUrl(fallbackUrl: string): DynamicLink {
this._fallbackUrl = fallbackUrl;
return this._link;
}
/**
*
* @param iPadBundleId
* @returns {DynamicLink}
*/
setIPadBundleId(iPadBundleId: string): DynamicLink {
this._iPadBundleId = iPadBundleId;
return this._link;
}
/**
*
* @param iPadFallbackUrl
* @returns {DynamicLink}
*/
setIPadFallbackUrl(iPadFallbackUrl: string): DynamicLink {
this._iPadFallbackUrl = iPadFallbackUrl;
return this._link;
}
/**
*
* @param minimumVersion
* @returns {DynamicLink}
*/
setMinimumVersion(minimumVersion: string): DynamicLink {
this._minimumVersion = minimumVersion;
return this._link;
}
build(): NativeIOSParameters {
if (
(this._appStoreId ||
this._customScheme ||
this._fallbackUrl ||
this._iPadBundleId ||
this._iPadFallbackUrl ||
this._minimumVersion) &&
!this._bundleId
) {
throw new Error('IOSParameters: Missing required `bundleId` property');
}
return {
appStoreId: this._appStoreId,
bundleId: this._bundleId,
customScheme: this._customScheme,
fallbackUrl: this._fallbackUrl,
iPadBundleId: this._iPadBundleId,
iPadFallbackUrl: this._iPadFallbackUrl,
minimumVersion: this._minimumVersion,
};
}
}

View File

@ -0,0 +1,55 @@
/**
* @flow
* ITunesParameters representation wrapper
*/
import type DynamicLink from './DynamicLink';
import type { NativeITunesParameters } from './types';
export default class ITunesParameters {
_affiliateToken: string | void;
_campaignToken: string | void;
_link: DynamicLink;
_providerToken: string | void;
constructor(link: DynamicLink) {
this._link = link;
}
/**
*
* @param affiliateToken
* @returns {DynamicLink}
*/
setAffiliateToken(affiliateToken: string): DynamicLink {
this._affiliateToken = affiliateToken;
return this._link;
}
/**
*
* @param campaignToken
* @returns {DynamicLink}
*/
setCampaignToken(campaignToken: string): DynamicLink {
this._campaignToken = campaignToken;
return this._link;
}
/**
*
* @param providerToken
* @returns {DynamicLink}
*/
setProviderToken(providerToken: string): DynamicLink {
this._providerToken = providerToken;
return this._link;
}
build(): NativeITunesParameters {
return {
affiliateToken: this._affiliateToken,
campaignToken: this._campaignToken,
providerToken: this._providerToken,
};
}
}

View File

@ -0,0 +1,31 @@
/**
* @flow
* NavigationParameters representation wrapper
*/
import type DynamicLink from './DynamicLink';
import type { NativeNavigationParameters } from './types';
export default class NavigationParameters {
_forcedRedirectEnabled: string | void;
_link: DynamicLink;
constructor(link: DynamicLink) {
this._link = link;
}
/**
*
* @param forcedRedirectEnabled
* @returns {DynamicLink}
*/
setForcedRedirectEnabled(forcedRedirectEnabled: string): DynamicLink {
this._forcedRedirectEnabled = forcedRedirectEnabled;
return this._link;
}
build(): NativeNavigationParameters {
return {
forcedRedirectEnabled: this._forcedRedirectEnabled,
};
}
}

View File

@ -0,0 +1,55 @@
/**
* @flow
* SocialParameters representation wrapper
*/
import type DynamicLink from './DynamicLink';
import type { NativeSocialParameters } from './types';
export default class SocialParameters {
_descriptionText: string | void;
_imageUrl: string | void;
_link: DynamicLink;
_title: string | void;
constructor(link: DynamicLink) {
this._link = link;
}
/**
*
* @param descriptionText
* @returns {DynamicLink}
*/
setDescriptionText(descriptionText: string): DynamicLink {
this._descriptionText = descriptionText;
return this._link;
}
/**
*
* @param imageUrl
* @returns {DynamicLink}
*/
setImageUrl(imageUrl: string): DynamicLink {
this._imageUrl = imageUrl;
return this._link;
}
/**
*
* @param title
* @returns {DynamicLink}
*/
setTitle(title: string): DynamicLink {
this._title = title;
return this._link;
}
build(): NativeSocialParameters {
return {
descriptionText: this._descriptionText,
imageUrl: this._imageUrl,
title: this._title,
};
}
}

View File

@ -2,75 +2,19 @@
* @flow
* Dynamic Links representation wrapper
*/
import DynamicLink from './DynamicLink';
import { SharedEventEmitter } from '../../utils/events';
import { getLogger } from '../../utils/log';
import ModuleBase from '../../utils/ModuleBase';
import { areObjectKeysContainedInOther, isObject, isString } from '../../utils';
import { getNativeModule } from '../../utils/native';
import type App from '../core/app';
const EVENT_TYPE = {
Link: 'dynamic_link_received',
};
const NATIVE_EVENTS = [EVENT_TYPE.Link];
const NATIVE_EVENTS = ['links_link_received'];
export const MODULE_NAME = 'RNFirebaseLinks';
export const NAMESPACE = 'links';
function validateParameters(parameters: Object): void {
const suportedParametersObject = {
dynamicLinkDomain: 'string',
link: 'string',
androidInfo: {
androidPackageName: 'string',
androidFallbackLink: 'string',
androidMinPackageVersionCode: 'string',
androidLink: 'string',
},
iosInfo: {
iosBundleId: 'string',
iosFallbackLink: 'string',
iosCustomScheme: 'string',
iosIpadFallbackLink: 'string',
iosIpadBundleId: 'string',
iosAppStoreId: 'string',
},
socialMetaTagInfo: {
socialTitle: 'string',
socialDescription: 'string',
socialImageLink: 'string',
},
suffix: {
option: 'string',
},
};
if (!areObjectKeysContainedInOther(parameters, suportedParametersObject)) {
throw new Error('Invalid Parameters.');
}
}
function checkForMandatoryParameters(parameters: Object): void {
if (!isString(parameters.dynamicLinkDomain)) {
throw new Error('No dynamicLinkDomain was specified.');
}
if (!isString(parameters.link)) {
throw new Error('No link was specified.');
}
if (
isObject(parameters.androidInfo) &&
!isString(parameters.androidInfo.androidPackageName)
) {
throw new Error('No androidPackageName was specified.');
}
if (
isObject(parameters.iosInfo) &&
!isString(parameters.iosInfo.iosBundleId)
) {
throw new Error('No iosBundleId was specified.');
}
}
/**
* @class Links
*/
@ -80,12 +24,49 @@ export default class Links extends ModuleBase {
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onMessage
'links_link_received',
(link: string) => {
SharedEventEmitter.emit('onLink', link);
}
);
}
get EVENT_TYPE(): Object {
return EVENT_TYPE;
/**
* Create long Dynamic Link from parameters
* @param parameters
* @returns {Promise.<String>}
*/
createDynamicLink(link: DynamicLink): Promise<string> {
if (!(link instanceof DynamicLink)) {
throw new Error(
`Links:createDynamicLink expects a 'DynamicLink' but got type ${typeof link}`
);
}
return getNativeModule(this).createDynamicLink(link.build());
}
/**
* Create short Dynamic Link from parameters
* @param parameters
* @returns {Promise.<String>}
*/
createShortDynamicLink(
link: DynamicLink,
type?: 'SHORT' | 'UNGUESSABLE'
): Promise<String> {
if (!(link instanceof DynamicLink)) {
throw new Error(
`Links:createShortDynamicLink expects a 'DynamicLink' but got type ${typeof link}`
);
}
return getNativeModule(this).createShortDynamicLink(link.build(), type);
}
/**
@ -101,45 +82,16 @@ export default class Links extends ModuleBase {
* @param listener
* @returns {Function}
*/
onLink(listener: Function): () => any {
const rnListener = SharedEventEmitter.addListener(
EVENT_TYPE.Link,
listener
);
return () => rnListener.remove();
}
onLink(listener: string => any): () => any {
getLogger(this).info('Creating onLink listener');
/**
* Create long Dynamic Link from parameters
* @param parameters
* @returns {Promise.<String>}
*/
createDynamicLink(parameters: Object = {}): Promise<string> {
try {
checkForMandatoryParameters(parameters);
validateParameters(parameters);
return getNativeModule(this).createDynamicLink(parameters);
} catch (error) {
return Promise.reject(error);
}
}
SharedEventEmitter.addListener('onLink', listener);
/**
* Create short Dynamic Link from parameters
* @param parameters
* @returns {Promise.<String>}
*/
createShortDynamicLink(parameters: Object = {}): Promise<String> {
try {
checkForMandatoryParameters(parameters);
validateParameters(parameters);
return getNativeModule(this).createShortDynamicLink(parameters);
} catch (error) {
return Promise.reject(error);
}
return () => {
getLogger(this).info('Removing onLink listener');
SharedEventEmitter.removeListener('onLink', listener);
};
}
}
export const statics = {
EVENT_TYPE,
};
export const statics = {};

View File

@ -0,0 +1,53 @@
/**
* @flow
*/
export type NativeAnalyticsParameters = {|
campaign?: string,
content?: string,
medium?: string,
source?: string,
term?: string,
|};
export type NativeAndroidParameters = {|
fallbackUrl?: string,
minimumVersion?: number,
packageName?: string,
|};
export type NativeIOSParameters = {|
appStoreId?: string,
bundleId?: string,
customScheme?: string,
fallbackUrl?: string,
iPadBundleId?: string,
iPadFallbackUrl?: string,
minimumVersion?: string,
|};
export type NativeITunesParameters = {|
affiliateToken?: string,
campaignToken?: string,
providerToken?: string,
|};
export type NativeNavigationParameters = {|
forcedRedirectEnabled?: string,
|};
export type NativeSocialParameters = {|
descriptionText?: string,
imageUrl?: string,
title?: string,
|};
export type NativeDynamicLink = {|
analytics: NativeAnalyticsParameters,
android: NativeAndroidParameters,
dynamicLinkDomain: string,
ios: NativeIOSParameters,
itunes: NativeITunesParameters,
link: string,
navigation: NativeNavigationParameters,
social: NativeSocialParameters,
|};

View File

@ -1,61 +1,79 @@
/**
* @flow
* Remote message representation wrapper
* RemoteMessage representation wrapper
*/
import { isObject, generatePushID } from './../../utils';
import type {
NativeInboundRemoteMessage,
NativeOutboundRemoteMessage,
} from './types';
export default class RemoteMessage {
properties: Object;
_collapseKey: string | void;
_data: { [string]: string };
_from: string | void;
_messageId: string;
_messageType: string | void;
_sentTime: number | void;
_to: string;
_ttl: number;
constructor(sender: string) {
this.properties = {
id: generatePushID(),
ttl: 3600,
// add the googleapis sender id part if not already added.
sender: `${sender}`.includes('@')
? sender
: `${sender}@gcm.googleapis.com`,
type: 'remote',
data: {},
};
constructor(inboundMessage?: NativeInboundRemoteMessage) {
if (inboundMessage) {
this._collapseKey = inboundMessage.collapseKey;
this._data = inboundMessage.data;
this._from = inboundMessage.from;
this._messageId = inboundMessage.messageId;
this._messageType = inboundMessage.messageType;
this._sentTime = inboundMessage.sentTime;
}
// defaults
this._data = this._data || {};
// TODO: Is this the best way to generate an ID?
this._messageId = this._messageId || generatePushID();
this._ttl = 3600;
}
get collapseKey(): ?string {
return this._collapseKey;
}
get data(): { [string]: string } {
return this._data;
}
get from(): ?string {
return this._from;
}
get messageId(): ?string {
return this._messageId;
}
get messageType(): ?string {
return this._messageType;
}
get sentTime(): ?number {
return this._sentTime;
}
get to(): ?string {
return this._to;
}
get ttl(): ?number {
return this._ttl;
}
/**
*
* @param ttl
* @param collapseKey
* @returns {RemoteMessage}
*/
setTtl(ttl: number): RemoteMessage {
this.properties.ttl = ttl;
return this;
}
/**
*
* @param id
*/
setId(id: string): RemoteMessage {
this.properties.id = `${id}`;
return this;
}
/**
*
* @param type
* @returns {RemoteMessage}
*/
setType(type: string): RemoteMessage {
this.properties.type = `${type}`;
return this;
}
/**
*
* @param key
* @returns {RemoteMessage}
*/
setCollapseKey(key: string): RemoteMessage {
this.properties.collapseKey = `${key}`;
setCollapseKey(collapseKey: string): RemoteMessage {
this._collapseKey = collapseKey;
return this;
}
@ -64,26 +82,74 @@ export default class RemoteMessage {
* @param data
* @returns {RemoteMessage}
*/
setData(data: Object = {}) {
setData(data: { [string]: string } = {}) {
if (!isObject(data)) {
throw new Error(
`RemoteMessage:setData expects an object as the first parameter but got type '${typeof data}'.`
`RemoteMessage:setData expects an object but got type '${typeof data}'.`
);
}
const props = Object.keys(data);
// coerce all property values to strings as
// remote message data only supports strings
for (let i = 0, len = props.length; i < len; i++) {
const prop = props[i];
this.properties.data[prop] = `${data[prop]}`;
}
this._data = data;
return this;
}
toJSON(): Object {
return Object.assign({}, this.properties);
/**
*
* @param messageId
* @returns {RemoteMessage}
*/
setMessageId(messageId: string): RemoteMessage {
this._messageId = messageId;
return this;
}
/**
*
* @param messageType
* @returns {RemoteMessage}
*/
setMessageType(messageType: string): RemoteMessage {
this._messageType = messageType;
return this;
}
/**
*
* @param to
* @returns {RemoteMessage}
*/
setTo(to: string): RemoteMessage {
this._to = to;
return this;
}
/**
*
* @param ttl
* @returns {RemoteMessage}
*/
setTtl(ttl: number): RemoteMessage {
this._ttl = ttl;
return this;
}
build(): NativeOutboundRemoteMessage {
if (!this._data) {
throw new Error('RemoteMessage: Missing required `data` property');
} else if (!this._messageId) {
throw new Error('RemoteMessage: Missing required `messageId` property');
} else if (!this._to) {
throw new Error('RemoteMessage: Missing required `to` property');
} else if (!this._ttl) {
throw new Error('RemoteMessage: Missing required `ttl` property');
}
return {
collapseKey: this._collapseKey,
data: this._data,
messageId: this._messageId,
messageType: this._messageType,
to: this._to,
ttl: this._ttl,
};
}
}

View File

@ -1,94 +1,34 @@
/**
* @flow
* Messaging representation wrapper
* Messaging (FCM) representation wrapper
*/
import { Platform, NativeModules } from 'react-native';
import { SharedEventEmitter } from '../../utils/events';
import INTERNALS from '../../utils/internals';
import { getLogger } from '../../utils/log';
import ModuleBase from '../../utils/ModuleBase';
import RemoteMessage from './RemoteMessage';
import { getNativeModule } from '../../utils/native';
import { isFunction, isObject } from '../../utils';
import RemoteMessage from './RemoteMessage';
import type App from '../core/app';
import type { NativeInboundRemoteMessage } from './types';
const EVENT_TYPE = {
RefreshToken: 'messaging_token_refreshed',
Notification: 'messaging_notification_received',
type OnMessage = RemoteMessage => any;
type OnMessageObserver = {
next: OnMessage,
};
const NOTIFICATION_TYPE = {
Remote: 'remote_notification',
NotificationResponse: 'notification_response',
WillPresent: 'will_present_notification',
Local: 'local_notification',
type OnTokenRefresh = String => any;
type OnTokenRefreshObserver = {
next: OnTokenRefresh,
};
const REMOTE_NOTIFICATION_RESULT = {
NewData: 'UIBackgroundFetchResultNewData',
NoData: 'UIBackgroundFetchResultNoData',
ResultFailed: 'UIBackgroundFetchResultFailed',
};
const WILL_PRESENT_RESULT = {
All: 'UNNotificationPresentationOptionAll',
None: 'UNNotificationPresentationOptionNone',
};
const NATIVE_EVENTS = [EVENT_TYPE.RefreshToken, EVENT_TYPE.Notification];
const FirebaseMessaging = NativeModules.RNFirebaseMessaging;
/**
* IOS only finish function
* @param data
*/
function finish(data) {
if (Platform.OS !== 'ios') {
return;
}
if (!this._finishCalled && this._completionHandlerId) {
let result = data;
this._finishCalled = true;
switch (this._notificationType) {
case NOTIFICATION_TYPE.Remote:
result = result || REMOTE_NOTIFICATION_RESULT.NoData;
if (!Object.values(REMOTE_NOTIFICATION_RESULT).includes(result)) {
throw new Error(
'Invalid REMOTE_NOTIFICATION_RESULT value, use messaging().REMOTE_NOTIFICATION_RESULT'
);
}
FirebaseMessaging.finishRemoteNotification(
this._completionHandlerId,
result
);
return;
case NOTIFICATION_TYPE.NotificationResponse:
FirebaseMessaging.finishNotificationResponse(this._completionHandlerId);
return;
case NOTIFICATION_TYPE.WillPresent:
result =
result ||
(this.show_in_foreground
? WILL_PRESENT_RESULT.All
: WILL_PRESENT_RESULT.None);
if (!Object.values(WILL_PRESENT_RESULT).includes(result)) {
throw new Error(
'Invalid WILL_PRESENT_RESULT value, use messaging().WILL_PRESENT_RESULT'
);
}
FirebaseMessaging.finishWillPresentNotification(
this._completionHandlerId,
result
);
break;
default:
}
}
}
const NATIVE_EVENTS = [
'messaging_message_received',
'messaging_token_refreshed',
];
export const MODULE_NAME = 'RNFirebaseMessaging';
export const NAMESPACE = 'messaging';
@ -102,206 +42,140 @@ export default class Messaging extends ModuleBase {
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onMessage
'messaging_message_received',
(message: NativeInboundRemoteMessage) => {
SharedEventEmitter.emit('onMessage', new RemoteMessage(message));
}
);
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onMessage
'messaging_token_refreshed',
(token: string) => {
SharedEventEmitter.emit('onTokenRefresh', token);
}
);
}
get EVENT_TYPE(): Object {
return EVENT_TYPE;
}
get NOTIFICATION_TYPE(): Object {
return NOTIFICATION_TYPE;
}
get REMOTE_NOTIFICATION_RESULT(): Object {
return REMOTE_NOTIFICATION_RESULT;
}
get WILL_PRESENT_RESULT(): Object {
return WILL_PRESENT_RESULT;
}
/**
* Returns the notification that triggered application open
* @returns {*}
*/
getInitialNotification(): Promise<Object> {
return getNativeModule(this).getInitialNotification();
}
/**
* Returns the fcm token for the current device
* @returns {*|Promise.<String>}
*/
getToken(): Promise<string> {
return getNativeModule(this).getToken();
}
/**
* Reset Instance ID and revokes all tokens.
* @returns {*|Promise.<*>}
*/
deleteInstanceId(): Promise<void> {
return getNativeModule(this).deleteInstanceId();
}
/**
* Create and display a local notification
* @param notification
* @returns {*}
*/
createLocalNotification(notification: Object): Promise<void> {
const _notification = Object.assign({}, notification);
_notification.id = _notification.id || new Date().getTime().toString();
_notification.local_notification = true;
return getNativeModule(this).createLocalNotification(_notification);
}
/**
*
* @param notification
* @returns {*}
*/
scheduleLocalNotification(notification: Object): Promise<void> {
const _notification = Object.assign({}, notification);
if (!notification.id)
return Promise.reject(
new Error('An id is required to schedule a local notification.')
onMessage(nextOrObserver: OnMessage | OnMessageObserver): () => any {
let listener: RemoteMessage => any;
if (isFunction(nextOrObserver)) {
// $FlowExpectedError: Not coping with the overloaded method signature
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Messaging.onMessage failed: First argument must be a function or observer object with a `next` function.'
);
_notification.local_notification = true;
return getNativeModule(this).scheduleLocalNotification(_notification);
}
getLogger(this).info('Creating onMessage listener');
SharedEventEmitter.addListener('onMessage', listener);
return () => {
getLogger(this).info('Removing onMessage listener');
SharedEventEmitter.removeListener('onMessage', listener);
};
}
onTokenRefresh(
nextOrObserver: OnTokenRefresh | OnTokenRefreshObserver
): () => any {
let listener: String => any;
if (isFunction(nextOrObserver)) {
// $FlowExpectedError: Not coping with the overloaded method signature
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Messaging.OnTokenRefresh failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onTokenRefresh listener');
SharedEventEmitter.addListener('onTokenRefresh', listener);
return () => {
getLogger(this).info('Removing onTokenRefresh listener');
SharedEventEmitter.removeListener('onTokenRefresh', listener);
};
}
requestPermission(): Promise<void> {
return getNativeModule(this).requestPermission();
}
/**
* Returns an array of all scheduled notifications
* @returns {Promise.<Array>}
* NON WEB-SDK METHODS
*/
getScheduledLocalNotifications(): Promise<Object[]> {
return getNativeModule(this).getScheduledLocalNotifications();
hasPermission(): Promise<boolean> {
return getNativeModule(this).hasPermission();
}
/**
* Cancel a local notification by id - using '*' will cancel
* all local notifications.
* @param id
* @returns {*}
*/
cancelLocalNotification(id: string): Promise<void> {
if (!id) return Promise.reject(new Error('Missing notification id'));
if (id === '*') return getNativeModule(this).cancelAllLocalNotifications();
return getNativeModule(this).cancelLocalNotification(id);
sendMessage(remoteMessage: RemoteMessage): Promise<void> {
if (!(remoteMessage instanceof RemoteMessage)) {
throw new Error(
`Messaging:sendMessage expects a 'RemoteMessage' but got type ${typeof remoteMessage}`
);
}
return getNativeModule(this).sendMessage(remoteMessage.build());
}
/**
* Remove a delivered notification - using '*' will remove
* all delivered notifications.
* @param id
* @returns {*}
*/
removeDeliveredNotification(id: string): Promise<void> {
if (!id) return Promise.reject(new Error('Missing notification id'));
if (id === '*')
return getNativeModule(this).removeAllDeliveredNotifications();
return getNativeModule(this).removeDeliveredNotification(id);
}
/**
* Request notification permission
* @platforms ios
* @returns {*|Promise.<*>}
*/
requestPermissions(): Promise<void> {
return getNativeModule(this).requestPermissions();
}
/**
* Set notification count badge number
* @param n
*/
setBadgeNumber(n: number): void {
getNativeModule(this).setBadgeNumber(n);
}
/**
* set notification count badge number
* @returns {Promise.<Number>}
*/
getBadgeNumber(): Promise<number> {
return getNativeModule(this).getBadgeNumber();
}
/**
* Subscribe to messages / notifications
* @param listener
* @returns {*}
*/
onMessage(listener: Object => any): () => any {
const rnListener = SharedEventEmitter.addListener(
EVENT_TYPE.Notification,
async event => {
const data = {
...event,
finish,
};
await listener(data);
if (!data._finishCalled) {
data.finish();
}
}
);
return () => rnListener.remove();
}
/**
* Subscribe to token refresh events
* @param listener
* @returns {*}
*/
onTokenRefresh(listener: string => any): () => any {
const rnListener = SharedEventEmitter.addListener(
EVENT_TYPE.RefreshToken,
listener
);
return () => rnListener.remove();
}
/**
* Subscribe to a topic
* @param topic
*/
subscribeToTopic(topic: string): void {
getNativeModule(this).subscribeToTopic(topic);
}
/**
* Unsubscribe from a topic
* @param topic
*/
unsubscribeFromTopic(topic: string): void {
getNativeModule(this).unsubscribeFromTopic(topic);
}
/**
* Send an upstream message
* @param remoteMessage
* KNOWN UNSUPPORTED METHODS
*/
send(remoteMessage: RemoteMessage): Promise<void> {
if (!(remoteMessage instanceof RemoteMessage)) {
throw new Error(
'messaging().send requires an instance of RemoteMessage as the first argument.'
);
}
return getNativeModule(this).send(remoteMessage.toJSON());
deleteToken() {
throw new Error(
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
'messaging',
'deleteToken'
)
);
}
setBackgroundMessageHandler() {
throw new Error(
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
'messaging',
'setBackgroundMessageHandler'
)
);
}
useServiceWorker() {
throw new Error(
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
'messaging',
'useServiceWorker'
)
);
}
}
export const statics = {
EVENT_TYPE,
NOTIFICATION_TYPE,
REMOTE_NOTIFICATION_RESULT,
WILL_PRESENT_RESULT,
RemoteMessage,
};

View File

@ -0,0 +1,38 @@
/**
* @flow
*/
export type Notification = {
body: string,
bodyLocalizationArgs?: string[],
bodyLocalizationKey?: string,
clickAction?: string,
color?: string,
icon?: string,
link?: string,
sound: string,
subtitle?: string,
tag?: string,
title: string,
titleLocalizationArgs?: string[],
titleLocalizationKey?: string,
};
export type NativeInboundRemoteMessage = {
collapseKey?: string,
data: { [string]: string },
from?: string,
messageId: string,
messageType?: string,
sentTime?: number,
to?: string,
ttl?: number,
};
export type NativeOutboundRemoteMessage = {
collapseKey?: string,
data: { [string]: string },
messageId: string,
messageType?: string,
to: string,
ttl: number,
};

View File

@ -0,0 +1,150 @@
/**
* @flow
* AndroidAction representation wrapper
*/
import RemoteInput, {
fromNativeAndroidRemoteInput,
} from './AndroidRemoteInput';
import { SemanticAction } from './types';
import type { NativeAndroidAction, SemanticActionType } from './types';
export default class AndroidAction {
_action: string;
_allowGeneratedReplies: boolean | void;
_icon: string;
_remoteInputs: RemoteInput[];
_semanticAction: SemanticActionType | void;
_showUserInterface: boolean | void;
_title: string;
constructor(action: string, icon: string, title: string) {
this._action = action;
this._icon = icon;
this._remoteInputs = [];
this._title = title;
}
get action(): string {
return this._action;
}
get allowGeneratedReplies(): ?boolean {
return this._allowGeneratedReplies;
}
get icon(): string {
return this._icon;
}
get remoteInputs(): RemoteInput[] {
return this._remoteInputs;
}
get semanticAction(): ?SemanticActionType {
return this._semanticAction;
}
get showUserInterface(): ?boolean {
return this._showUserInterface;
}
get title(): string {
return this._title;
}
/**
*
* @param remoteInput
* @returns {AndroidAction}
*/
addRemoteInput(remoteInput: RemoteInput): AndroidAction {
if (!(remoteInput instanceof RemoteInput)) {
throw new Error(
`AndroidAction:addRemoteInput expects an 'RemoteInput' but got type ${typeof remoteInput}`
);
}
this._remoteInputs.push(remoteInput);
return this;
}
/**
*
* @param allowGeneratedReplies
* @returns {AndroidAction}
*/
setAllowGenerateReplies(allowGeneratedReplies: boolean): AndroidAction {
this._allowGeneratedReplies = allowGeneratedReplies;
return this;
}
/**
*
* @param semanticAction
* @returns {AndroidAction}
*/
setSemanticAction(semanticAction: SemanticActionType): AndroidAction {
if (!Object.values(SemanticAction).includes(semanticAction)) {
throw new Error(
`AndroidAction:setSemanticAction Invalid Semantic Action: ${semanticAction}`
);
}
this._semanticAction = semanticAction;
return this;
}
/**
*
* @param showUserInterface
* @returns {AndroidAction}
*/
setShowUserInterface(showUserInterface: boolean): AndroidAction {
this._showUserInterface = showUserInterface;
return this;
}
build(): NativeAndroidAction {
if (!this._action) {
throw new Error('AndroidAction: Missing required `action` property');
} else if (!this._icon) {
throw new Error('AndroidAction: Missing required `icon` property');
} else if (!this._title) {
throw new Error('AndroidAction: Missing required `title` property');
}
return {
action: this._action,
allowGeneratedReplies: this._allowGeneratedReplies,
icon: this._icon,
remoteInputs: this._remoteInputs.map(remoteInput => remoteInput.build()),
semanticAction: this._semanticAction,
showUserInterface: this._showUserInterface,
title: this._title,
};
}
}
export const fromNativeAndroidAction = (
nativeAction: NativeAndroidAction
): AndroidAction => {
const action = new AndroidAction(
nativeAction.action,
nativeAction.icon,
nativeAction.title
);
if (nativeAction.allowGeneratedReplies) {
action.setAllowGenerateReplies(nativeAction.allowGeneratedReplies);
}
if (nativeAction.remoteInputs) {
nativeAction.remoteInputs.forEach(remoteInput => {
action.addRemoteInput(fromNativeAndroidRemoteInput(remoteInput));
});
}
if (nativeAction.semanticAction) {
action.setSemanticAction(nativeAction.semanticAction);
}
if (nativeAction.showUserInterface) {
action.setShowUserInterface(nativeAction.showUserInterface);
}
return action;
};

View File

@ -0,0 +1,198 @@
/**
* @flow
* AndroidChannel representation wrapper
*/
import { Importance, Visibility } from './types';
import type { ImportanceType, VisibilityType } from './types';
type NativeAndroidChannel = {|
bypassDnd?: boolean,
channelId: string,
description?: string,
group?: string,
importance: ImportanceType,
lightColor?: string,
lockScreenVisibility?: VisibilityType,
name: string,
showBadge?: boolean,
sound?: string,
vibrationPattern?: number[],
|};
export default class AndroidChannel {
_bypassDnd: boolean | void;
_channelId: string;
_description: string | void;
_group: string | void;
_importance: ImportanceType;
_lightColor: string | void;
_lockScreenVisibility: VisibilityType;
_name: string;
_showBadge: boolean | void;
_sound: string | void;
_vibrationPattern: number[] | void;
constructor(channelId: string, name: string, importance: ImportanceType) {
if (!Object.values(Importance).includes(importance)) {
throw new Error(`AndroidChannel() Invalid Importance: ${importance}`);
}
this._channelId = channelId;
this._name = name;
this._importance = importance;
}
get bypassDnd(): ?boolean {
return this._bypassDnd;
}
get channelId(): string {
return this._channelId;
}
get description(): ?string {
return this._description;
}
get group(): ?string {
return this._group;
}
get importance(): ImportanceType {
return this._importance;
}
get lightColor(): ?string {
return this._lightColor;
}
get lockScreenVisibility(): ?VisibilityType {
return this._lockScreenVisibility;
}
get name(): string {
return this._name;
}
get showBadge(): ?boolean {
return this._showBadge;
}
get sound(): ?string {
return this._sound;
}
get vibrationPattern(): ?(number[]) {
return this._vibrationPattern;
}
/**
*
* @param bypassDnd
* @returns {AndroidChannel}
*/
setBypassDnd(bypassDnd: boolean): AndroidChannel {
this._bypassDnd = bypassDnd;
return this;
}
/**
*
* @param description
* @returns {AndroidChannel}
*/
setDescription(description: string): AndroidChannel {
this._description = description;
return this;
}
/**
*
* @param group
* @returns {AndroidChannel}
*/
setGroup(groupId: string): AndroidChannel {
this._group = groupId;
return this;
}
/**
*
* @param lightColor
* @returns {AndroidChannel}
*/
setLightColor(lightColor: string): AndroidChannel {
this._lightColor = lightColor;
return this;
}
/**
*
* @param lockScreenVisibility
* @returns {AndroidChannel}
*/
setLockScreenVisibility(
lockScreenVisibility: VisibilityType
): AndroidChannel {
if (!Object.values(Visibility).includes(lockScreenVisibility)) {
throw new Error(
`AndroidChannel:setLockScreenVisibility Invalid Visibility: ${lockScreenVisibility}`
);
}
this._lockScreenVisibility = lockScreenVisibility;
return this;
}
/**
*
* @param showBadge
* @returns {AndroidChannel}
*/
setShowBadge(showBadge: boolean): AndroidChannel {
this._showBadge = showBadge;
return this;
}
/**
*
* @param sound
* @returns {AndroidChannel}
*/
setSound(sound: string): AndroidChannel {
this._sound = sound;
return this;
}
/**
*
* @param vibrationPattern
* @returns {AndroidChannel}
*/
setVibrationPattern(vibrationPattern: number[]): AndroidChannel {
this._vibrationPattern = vibrationPattern;
return this;
}
build(): NativeAndroidChannel {
if (!this._channelId) {
throw new Error('AndroidChannel: Missing required `channelId` property');
} else if (!this._importance) {
throw new Error('AndroidChannel: Missing required `importance` property');
} else if (!this._name) {
throw new Error('AndroidChannel: Missing required `name` property');
}
return {
bypassDnd: this._bypassDnd,
channelId: this._channelId,
description: this._description,
group: this._group,
importance: this._importance,
lightColor: this._lightColor,
lockScreenVisibility: this._lockScreenVisibility,
name: this._name,
showBadge: this._showBadge,
sound: this._sound,
vibrationPattern: this._vibrationPattern,
};
}
}

View File

@ -0,0 +1,42 @@
/**
* @flow
* AndroidChannelGroup representation wrapper
*/
type NativeAndroidChannelGroup = {|
groupId: string,
name: string,
|};
export default class AndroidChannelGroup {
_groupId: string;
_name: string;
constructor(groupId: string, name: string) {
this._groupId = groupId;
this._name = name;
}
get groupId(): string {
return this._groupId;
}
get name(): string {
return this._name;
}
build(): NativeAndroidChannelGroup {
if (!this._groupId) {
throw new Error(
'AndroidChannelGroup: Missing required `groupId` property'
);
} else if (!this._name) {
throw new Error('AndroidChannelGroup: Missing required `name` property');
}
return {
groupId: this._groupId,
name: this._name,
};
}
}

View File

@ -0,0 +1,676 @@
/**
* @flow
* AndroidNotification representation wrapper
*/
import AndroidAction, { fromNativeAndroidAction } from './AndroidAction';
import { BadgeIconType, Category, GroupAlert, Priority } from './types';
import type Notification from './Notification';
import type {
BadgeIconTypeType,
CategoryType,
DefaultsType,
GroupAlertType,
Lights,
NativeAndroidNotification,
PriorityType,
Progress,
SmallIcon,
VisibilityType,
} from './types';
export default class AndroidNotification {
_actions: AndroidAction[];
_autoCancel: boolean | void;
_badgeIconType: BadgeIconTypeType | void;
_category: CategoryType | void;
_channelId: string;
_clickAction: string | void;
_color: string | void;
_colorized: boolean | void;
_contentInfo: string | void;
_defaults: DefaultsType[] | void;
_group: string | void;
_groupAlertBehaviour: GroupAlertType | void;
_groupSummary: boolean | void;
_largeIcon: string | void;
_lights: Lights | void;
_localOnly: boolean | void;
_notification: Notification;
_number: number | void;
_ongoing: boolean | void;
_onlyAlertOnce: boolean | void;
_people: string[];
_priority: PriorityType | void;
_progress: Progress | void;
// _publicVersion: Notification;
_remoteInputHistory: string[] | void;
_shortcutId: string | void;
_showWhen: boolean | void;
_smallIcon: SmallIcon;
_sortKey: string | void;
// TODO: style: Style; // Need to figure out if this can work
_ticker: string | void;
_timeoutAfter: number | void;
_usesChronometer: boolean | void;
_vibrate: number[] | void;
_visibility: VisibilityType | void;
_when: number | void;
// android unsupported
// content: RemoteViews
// contentIntent: PendingIntent - need to look at what this is
// customBigContentView: RemoteViews
// customContentView: RemoteViews
// customHeadsUpContentView: RemoteViews
// deleteIntent: PendingIntent
// fullScreenIntent: PendingIntent
// sound.streamType
constructor(notification: Notification, data?: NativeAndroidNotification) {
this._notification = notification;
if (data) {
this._actions = data.actions
? data.actions.map(action => fromNativeAndroidAction(action))
: [];
this._autoCancel = data.autoCancel;
this._badgeIconType = data.badgeIconType;
this._category = data.category;
this._channelId = data.channelId;
this._clickAction = data.clickAction;
this._color = data.color;
this._colorized = data.colorized;
this._contentInfo = data.contentInfo;
this._defaults = data.defaults;
this._group = data.group;
this._groupAlertBehaviour = data.groupAlertBehaviour;
this._groupSummary = data.groupSummary;
this._largeIcon = data.largeIcon;
this._lights = data.lights;
this._localOnly = data.localOnly;
this._number = data.number;
this._ongoing = data.ongoing;
this._onlyAlertOnce = data.onlyAlertOnce;
this._people = data.people;
this._priority = data.priority;
this._progress = data.progress;
// _publicVersion: Notification;
this._remoteInputHistory = data.remoteInputHistory;
this._shortcutId = data.shortcutId;
this._showWhen = data.showWhen;
this._smallIcon = data.smallIcon;
this._sortKey = data.sortKey;
this._ticker = data.ticker;
this._timeoutAfter = data.timeoutAfter;
this._usesChronometer = data.usesChronometer;
this._vibrate = data.vibrate;
this._visibility = data.visibility;
this._when = data.when;
}
// Defaults
this._actions = this._actions || [];
this._people = this._people || [];
this._smallIcon = this._smallIcon || {
icon: 'ic_launcher',
};
}
get actions(): AndroidAction[] {
return this._actions;
}
get autoCancel(): ?boolean {
return this._autoCancel;
}
get badgeIconType(): ?BadgeIconTypeType {
return this._badgeIconType;
}
get category(): ?CategoryType {
return this._category;
}
get channelId(): string {
return this._channelId;
}
get clickAction(): ?string {
return this._clickAction;
}
get color(): ?string {
return this._color;
}
get colorized(): ?boolean {
return this._colorized;
}
get contentInfo(): ?string {
return this._contentInfo;
}
get defaults(): ?(DefaultsType[]) {
return this._defaults;
}
get group(): ?string {
return this._group;
}
get groupAlertBehaviour(): ?GroupAlertType {
return this._groupAlertBehaviour;
}
get groupSummary(): ?boolean {
return this._groupSummary;
}
get largeIcon(): ?string {
return this._largeIcon;
}
get lights(): ?Lights {
return this._lights;
}
get localOnly(): ?boolean {
return this._localOnly;
}
get number(): ?number {
return this._number;
}
get ongoing(): ?boolean {
return this._ongoing;
}
get onlyAlertOnce(): ?boolean {
return this._onlyAlertOnce;
}
get people(): string[] {
return this._people;
}
get priority(): ?PriorityType {
return this._priority;
}
get progress(): ?Progress {
return this._progress;
}
get remoteInputHistory(): ?(string[]) {
return this._remoteInputHistory;
}
get shortcutId(): ?string {
return this._shortcutId;
}
get showWhen(): ?boolean {
return this._showWhen;
}
get smallIcon(): SmallIcon {
return this._smallIcon;
}
get sortKey(): ?string {
return this._sortKey;
}
get ticker(): ?string {
return this._ticker;
}
get timeoutAfter(): ?number {
return this._timeoutAfter;
}
get usesChronometer(): ?boolean {
return this._usesChronometer;
}
get vibrate(): ?(number[]) {
return this._vibrate;
}
get visibility(): ?VisibilityType {
return this._visibility;
}
get when(): ?number {
return this._when;
}
/**
*
* @param action
* @returns {Notification}
*/
addAction(action: AndroidAction): Notification {
if (!(action instanceof AndroidAction)) {
throw new Error(
`AndroidNotification:addAction expects an 'AndroidAction' but got type ${typeof action}`
);
}
this._actions.push(action);
return this._notification;
}
/**
*
* @param person
* @returns {Notification}
*/
addPerson(person: string): Notification {
this._people.push(person);
return this._notification;
}
/**
*
* @param autoCancel
* @returns {Notification}
*/
setAutoCancel(autoCancel: boolean): Notification {
this._autoCancel = autoCancel;
return this._notification;
}
/**
*
* @param badgeIconType
* @returns {Notification}
*/
setBadgeIconType(badgeIconType: BadgeIconTypeType): Notification {
if (!Object.values(BadgeIconType).includes(badgeIconType)) {
throw new Error(
`AndroidNotification:setBadgeIconType Invalid BadgeIconType: ${badgeIconType}`
);
}
this._badgeIconType = badgeIconType;
return this._notification;
}
/**
*
* @param category
* @returns {Notification}
*/
setCategory(category: CategoryType): Notification {
if (!Object.values(Category).includes(category)) {
throw new Error(
`AndroidNotification:setCategory Invalid Category: ${category}`
);
}
this._category = category;
return this._notification;
}
/**
*
* @param channelId
* @returns {Notification}
*/
setChannelId(channelId: string): Notification {
this._channelId = channelId;
return this._notification;
}
/**
*
* @param clickAction
* @returns {Notification}
*/
setClickAction(clickAction: string): Notification {
this._clickAction = clickAction;
return this._notification;
}
/**
*
* @param color
* @returns {Notification}
*/
setColor(color: string): Notification {
this._color = color;
return this._notification;
}
/**
*
* @param colorized
* @returns {Notification}
*/
setColorized(colorized: boolean): Notification {
this._colorized = colorized;
return this._notification;
}
/**
*
* @param contentInfo
* @returns {Notification}
*/
setContentInfo(contentInfo: string): Notification {
this._contentInfo = contentInfo;
return this._notification;
}
/**
*
* @param defaults
* @returns {Notification}
*/
setDefaults(defaults: DefaultsType[]): Notification {
this._defaults = defaults;
return this._notification;
}
/**
*
* @param group
* @returns {Notification}
*/
setGroup(group: string): Notification {
this._group = group;
return this._notification;
}
/**
*
* @param groupAlertBehaviour
* @returns {Notification}
*/
setGroupAlertBehaviour(groupAlertBehaviour: GroupAlertType): Notification {
if (!Object.values(GroupAlert).includes(groupAlertBehaviour)) {
throw new Error(
`AndroidNotification:setGroupAlertBehaviour Invalid GroupAlert: ${groupAlertBehaviour}`
);
}
this._groupAlertBehaviour = groupAlertBehaviour;
return this._notification;
}
/**
*
* @param groupSummary
* @returns {Notification}
*/
setGroupSummary(groupSummary: boolean): Notification {
this._groupSummary = groupSummary;
return this._notification;
}
/**
*
* @param largeIcon
* @returns {Notification}
*/
setLargeIcon(largeIcon: string): Notification {
this._largeIcon = largeIcon;
return this._notification;
}
/**
*
* @param argb
* @param onMs
* @param offMs
* @returns {Notification}
*/
setLights(argb: number, onMs: number, offMs: number): Notification {
this._lights = {
argb,
onMs,
offMs,
};
return this._notification;
}
/**
*
* @param localOnly
* @returns {Notification}
*/
setLocalOnly(localOnly: boolean): Notification {
this._localOnly = localOnly;
return this._notification;
}
/**
*
* @param number
* @returns {Notification}
*/
setNumber(number: number): Notification {
this._number = number;
return this._notification;
}
/**
*
* @param ongoing
* @returns {Notification}
*/
setOngoing(ongoing: boolean): Notification {
this._ongoing = ongoing;
return this._notification;
}
/**
*
* @param onlyAlertOnce
* @returns {Notification}
*/
setOnlyAlertOnce(onlyAlertOnce: boolean): Notification {
this._onlyAlertOnce = onlyAlertOnce;
return this._notification;
}
/**
*
* @param priority
* @returns {Notification}
*/
setPriority(priority: PriorityType): Notification {
if (!Object.values(Priority).includes(priority)) {
throw new Error(
`AndroidNotification:setPriority Invalid Priority: ${priority}`
);
}
this._priority = priority;
return this._notification;
}
/**
*
* @param max
* @param progress
* @param indeterminate
* @returns {Notification}
*/
setProgress(
max: number,
progress: number,
indeterminate: boolean
): Notification {
this._progress = {
max,
progress,
indeterminate,
};
return this._notification;
}
/**
*
* @param publicVersion
* @returns {Notification}
*/
/* setPublicVersion(publicVersion: Notification): Notification {
this._publicVersion = publicVersion;
return this._notification;
} */
/**
*
* @param remoteInputHistory
* @returns {Notification}
*/
setRemoteInputHistory(remoteInputHistory: string[]): Notification {
this._remoteInputHistory = remoteInputHistory;
return this._notification;
}
/**
*
* @param shortcutId
* @returns {Notification}
*/
setShortcutId(shortcutId: string): Notification {
this._shortcutId = shortcutId;
return this._notification;
}
/**
*
* @param showWhen
* @returns {Notification}
*/
setShowWhen(showWhen: boolean): Notification {
this._showWhen = showWhen;
return this._notification;
}
/**
*
* @param icon
* @param level
* @returns {Notification}
*/
setSmallIcon(icon: string, level?: number): Notification {
this._smallIcon = {
icon,
level,
};
return this._notification;
}
/**
*
* @param sortKey
* @returns {Notification}
*/
setSortKey(sortKey: string): Notification {
this._sortKey = sortKey;
return this._notification;
}
/**
*
* @param ticker
* @returns {Notification}
*/
setTicker(ticker: string): Notification {
this._ticker = ticker;
return this._notification;
}
/**
*
* @param timeoutAfter
* @returns {Notification}
*/
setTimeoutAfter(timeoutAfter: number): Notification {
this._timeoutAfter = timeoutAfter;
return this._notification;
}
/**
*
* @param usesChronometer
* @returns {Notification}
*/
setUsesChronometer(usesChronometer: boolean): Notification {
this._usesChronometer = usesChronometer;
return this._notification;
}
/**
*
* @param vibrate
* @returns {Notification}
*/
setVibrate(vibrate: number[]): Notification {
this._vibrate = vibrate;
return this._notification;
}
/**
*
* @param when
* @returns {Notification}
*/
setWhen(when: number): Notification {
this._when = when;
return this._notification;
}
build(): NativeAndroidNotification {
// TODO: Validation of required fields
if (!this._channelId) {
throw new Error(
'AndroidNotification: Missing required `channelId` property'
);
} else if (!this._smallIcon) {
throw new Error(
'AndroidNotification: Missing required `smallIcon` property'
);
}
return {
actions: this._actions.map(action => action.build()),
autoCancel: this._autoCancel,
badgeIconType: this._badgeIconType,
category: this._category,
channelId: this._channelId,
clickAction: this._clickAction,
color: this._color,
colorized: this._colorized,
contentInfo: this._contentInfo,
defaults: this._defaults,
group: this._group,
groupAlertBehaviour: this._groupAlertBehaviour,
groupSummary: this._groupSummary,
largeIcon: this._largeIcon,
lights: this._lights,
localOnly: this._localOnly,
number: this._number,
ongoing: this._ongoing,
onlyAlertOnce: this._onlyAlertOnce,
people: this._people,
priority: this._priority,
progress: this._progress,
// publicVersion: this._publicVersion,
remoteInputHistory: this._remoteInputHistory,
shortcutId: this._shortcutId,
showWhen: this._showWhen,
smallIcon: this._smallIcon,
sortKey: this._sortKey,
// TODO: style: Style,
ticker: this._ticker,
timeoutAfter: this._timeoutAfter,
usesChronometer: this._usesChronometer,
vibrate: this._vibrate,
visibility: this._visibility,
when: this._when,
};
}
}

View File

@ -0,0 +1,94 @@
/**
* @flow
* AndroidNotifications representation wrapper
*/
import { Platform } from 'react-native';
import AndroidChannel from './AndroidChannel';
import AndroidChannelGroup from './AndroidChannelGroup';
import { getNativeModule } from '../../utils/native';
import type Notifications from './';
export default class AndroidNotifications {
_notifications: Notifications;
constructor(notifications: Notifications) {
this._notifications = notifications;
}
createChannel(channel: AndroidChannel): Promise<void> {
if (Platform.OS === 'android') {
if (!(channel instanceof AndroidChannel)) {
throw new Error(
`AndroidNotifications:createChannel expects an 'AndroidChannel' but got type ${typeof channel}`
);
}
return getNativeModule(this._notifications).createChannel(
channel.build()
);
}
return Promise.resolve();
}
createChannelGroup(channelGroup: AndroidChannelGroup): Promise<void> {
if (Platform.OS === 'android') {
if (!(channelGroup instanceof AndroidChannelGroup)) {
throw new Error(
`AndroidNotifications:createChannelGroup expects an 'AndroidChannelGroup' but got type ${typeof channelGroup}`
);
}
return getNativeModule(this._notifications).createChannelGroup(
channelGroup.build()
);
}
return Promise.resolve();
}
createChannelGroups(channelGroups: AndroidChannelGroup[]): Promise<void> {
if (Platform.OS === 'android') {
if (!Array.isArray(channelGroups)) {
throw new Error(
`AndroidNotifications:createChannelGroups expects an 'Array' but got type ${typeof channelGroups}`
);
}
const nativeChannelGroups = [];
for (let i = 0; i < channelGroups.length; i++) {
const channelGroup = channelGroups[i];
if (!(channelGroup instanceof AndroidChannelGroup)) {
throw new Error(
`AndroidNotifications:createChannelGroups expects array items of type 'AndroidChannelGroup' but got type ${typeof channelGroup}`
);
}
nativeChannelGroups.push(channelGroup.build());
}
return getNativeModule(this._notifications).createChannelGroups(
nativeChannelGroups
);
}
return Promise.resolve();
}
createChannels(channels: AndroidChannel[]): Promise<void> {
if (Platform.OS === 'android') {
if (!Array.isArray(channels)) {
throw new Error(
`AndroidNotifications:createChannels expects an 'Array' but got type ${typeof channels}`
);
}
const nativeChannels = [];
for (let i = 0; i < channels.length; i++) {
const channel = channels[i];
if (!(channel instanceof AndroidChannel)) {
throw new Error(
`AndroidNotifications:createChannels expects array items of type 'AndroidChannel' but got type ${typeof channel}`
);
}
nativeChannels.push(channel.build());
}
return getNativeModule(this._notifications).createChannels(
nativeChannels
);
}
return Promise.resolve();
}
}

View File

@ -0,0 +1,123 @@
/**
* @flow
* AndroidRemoteInput representation wrapper
*/
import type { AndroidAllowDataType, NativeAndroidRemoteInput } from './types';
export default class AndroidRemoteInput {
_allowedDataTypes: AndroidAllowDataType[];
_allowFreeFormInput: boolean | void;
_choices: string[];
_label: string | void;
_resultKey: string;
constructor(resultKey: string) {
this._allowedDataTypes = [];
this._choices = [];
this._resultKey = resultKey;
}
get allowedDataTypes(): AndroidAllowDataType[] {
return this._allowedDataTypes;
}
get allowFreeFormInput(): ?boolean {
return this._allowFreeFormInput;
}
get choices(): string[] {
return this._choices;
}
get label(): ?string {
return this._label;
}
get resultKey(): string {
return this._resultKey;
}
/**
*
* @param mimeType
* @param allow
* @returns {AndroidRemoteInput}
*/
setAllowDataType(mimeType: string, allow: boolean): AndroidRemoteInput {
this._allowedDataTypes.push({
allow,
mimeType,
});
return this;
}
/**
*
* @param allowFreeFormInput
* @returns {AndroidRemoteInput}
*/
setAllowFreeFormInput(allowFreeFormInput: boolean): AndroidRemoteInput {
this._allowFreeFormInput = allowFreeFormInput;
return this;
}
/**
*
* @param choices
* @returns {AndroidRemoteInput}
*/
setChoices(choices: string[]): AndroidRemoteInput {
this._choices = choices;
return this;
}
/**
*
* @param label
* @returns {AndroidRemoteInput}
*/
setLabel(label: string): AndroidRemoteInput {
this._label = label;
return this;
}
build(): NativeAndroidRemoteInput {
if (!this._resultKey) {
throw new Error(
'AndroidRemoteInput: Missing required `resultKey` property'
);
}
return {
allowedDataTypes: this._allowedDataTypes,
allowFreeFormInput: this._allowFreeFormInput,
choices: this._choices,
label: this._label,
resultKey: this._resultKey,
};
}
}
export const fromNativeAndroidRemoteInput = (
nativeRemoteInput: NativeAndroidRemoteInput
): AndroidRemoteInput => {
const remoteInput = new AndroidRemoteInput(nativeRemoteInput.resultKey);
if (nativeRemoteInput.allowDataType) {
for (let i = 0; i < nativeRemoteInput.allowDataType.length; i++) {
const allowDataType = nativeRemoteInput.allowDataType[i];
remoteInput.setAllowDataType(allowDataType.mimeType, allowDataType.allow);
}
}
if (nativeRemoteInput.allowFreeFormInput) {
remoteInput.setAllowFreeFormInput(nativeRemoteInput.allowFreeFormInput);
}
if (nativeRemoteInput.choices) {
remoteInput.setChoices(nativeRemoteInput.choices);
}
if (nativeRemoteInput.label) {
remoteInput.setLabel(nativeRemoteInput.label);
}
return remoteInput;
};

View File

@ -0,0 +1,160 @@
/**
* @flow
* IOSNotification representation wrapper
*/
import type Notification from './Notification';
import type {
IOSAttachment,
IOSAttachmentOptions,
NativeIOSNotification,
} from './types';
export default class IOSNotification {
_alertAction: string | void; // alertAction | N/A
_attachments: IOSAttachment[]; // N/A | attachments
_badge: number | void; // applicationIconBadgeNumber | badge
_category: string | void;
_hasAction: boolean | void; // hasAction | N/A
_launchImage: string | void; // alertLaunchImage | launchImageName
_notification: Notification;
_threadIdentifier: string | void; // N/A | threadIdentifier
constructor(notification: Notification, data?: NativeIOSNotification) {
this._notification = notification;
if (data) {
this._alertAction = data.alertAction;
this._attachments = data.attachments;
this._badge = data.badge;
this._category = data.category;
this._hasAction = data.hasAction;
this._launchImage = data.launchImage;
this._threadIdentifier = data.threadIdentifier;
}
// Defaults
this._attachments = this._attachments || [];
}
get alertAction(): ?string {
return this._alertAction;
}
get attachments(): IOSAttachment[] {
return this._attachments;
}
get badge(): ?number {
return this._badge;
}
get category(): ?string {
return this._category;
}
get hasAction(): ?boolean {
return this._hasAction;
}
get launchImage(): ?string {
return this._launchImage;
}
get threadIdentifier(): ?string {
return this._threadIdentifier;
}
/**
*
* @param identifier
* @param url
* @param options
* @returns {Notification}
*/
addAttachment(
identifier: string,
url: string,
options?: IOSAttachmentOptions
): Notification {
this._attachments.push({
identifier,
options,
url,
});
return this._notification;
}
/**
*
* @param alertAction
* @returns {Notification}
*/
setAlertAction(alertAction: string): Notification {
this._alertAction = alertAction;
return this._notification;
}
/**
*
* @param badge
* @returns {Notification}
*/
setBadge(badge: number): Notification {
this._badge = badge;
return this._notification;
}
/**
*
* @param category
* @returns {Notification}
*/
setCategory(category: string): Notification {
this._category = category;
return this._notification;
}
/**
*
* @param hasAction
* @returns {Notification}
*/
setHasAction(hasAction: boolean): Notification {
this._hasAction = hasAction;
return this._notification;
}
/**
*
* @param launchImage
* @returns {Notification}
*/
setLaunchImage(launchImage: string): Notification {
this._launchImage = launchImage;
return this._notification;
}
/**
*
* @param threadIdentifier
* @returns {Notification}
*/
setThreadIdentifier(threadIdentifier: string): Notification {
this._threadIdentifier = threadIdentifier;
return this._notification;
}
build(): NativeIOSNotification {
// TODO: Validation of required fields
return {
alertAction: this._alertAction,
attachments: this._attachments,
badge: this._badge,
category: this._category,
hasAction: this._hasAction,
launchImage: this._launchImage,
threadIdentifier: this._threadIdentifier,
};
}
}

View File

@ -0,0 +1,169 @@
/**
* @flow
* Notification representation wrapper
*/
import { Platform } from 'react-native';
import AndroidNotification from './AndroidNotification';
import IOSNotification from './IOSNotification';
import { generatePushID, isObject } from '../../utils';
import type { NativeNotification } from './types';
export type NotificationOpen = {|
action: string,
notification: Notification,
results?: { [string]: string },
|};
export default class Notification {
// iOS 8/9 | 10+ | Android
_android: AndroidNotification;
_body: string; // alertBody | body | contentText
_data: { [string]: string }; // userInfo | userInfo | extras
_ios: IOSNotification;
_notificationId: string;
_sound: string | void; // soundName | sound | sound
_subtitle: string | void; // N/A | subtitle | subText
_title: string; // alertTitle | title | contentTitle
constructor(data?: NativeNotification) {
this._android = new AndroidNotification(this, data && data.android);
this._ios = new IOSNotification(this, data && data.ios);
if (data) {
this._body = data.body;
this._data = data.data;
this._notificationId = data.notificationId;
this._sound = data.sound;
this._subtitle = data.subtitle;
this._title = data.title;
}
// Defaults
this._data = this._data || {};
// TODO: Is this the best way to generate an ID?
this._notificationId = this._notificationId || generatePushID();
}
get android(): AndroidNotification {
return this._android;
}
get body(): string {
return this._body;
}
get data(): { [string]: string } {
return this._data;
}
get ios(): IOSNotification {
return this._ios;
}
get notificationId(): string {
return this._notificationId;
}
get sound(): ?string {
return this._sound;
}
get subtitle(): ?string {
return this._subtitle;
}
get title(): string {
return this._title;
}
/**
*
* @param body
* @returns {Notification}
*/
setBody(body: string): Notification {
this._body = body;
return this;
}
/**
*
* @param data
* @returns {Notification}
*/
setData(data: Object = {}): Notification {
if (!isObject(data)) {
throw new Error(
`Notification:withData expects an object but got type '${typeof data}'.`
);
}
this._data = data;
return this;
}
/**
*
* @param notificationId
* @returns {Notification}
*/
setNotificationId(notificationId: string): Notification {
this._notificationId = notificationId;
return this;
}
/**
*
* @param sound
* @returns {Notification}
*/
setSound(sound: string): Notification {
this._sound = sound;
return this;
}
/**
*
* @param subtitle
* @returns {Notification}
*/
setSubtitle(subtitle: string): Notification {
this._subtitle = subtitle;
return this;
}
/**
*
* @param title
* @returns {Notification}
*/
setTitle(title: string): Notification {
this._title = title;
return this;
}
build(): NativeNotification {
// Android required fields: body, title, smallicon
// iOS required fields: TODO
if (!this._body) {
throw new Error('Notification: Missing required `body` property');
} else if (!this._notificationId) {
throw new Error(
'Notification: Missing required `notificationId` property'
);
} else if (!this._title) {
throw new Error('Notification: Missing required `title` property');
}
return {
android: Platform.OS === 'android' ? this._android.build() : undefined,
body: this._body,
data: this._data,
ios: Platform.OS === 'ios' ? this._ios.build() : undefined,
notificationId: this._notificationId,
sound: this._sound,
subtitle: this._subtitle,
title: this._title,
};
}
}

View File

@ -0,0 +1,318 @@
/**
* @flow
* Notifications representation wrapper
*/
import { SharedEventEmitter } from '../../utils/events';
import { getLogger } from '../../utils/log';
import ModuleBase from '../../utils/ModuleBase';
import { getNativeModule } from '../../utils/native';
import { isFunction, isObject } from '../../utils';
import AndroidAction from './AndroidAction';
import AndroidChannel from './AndroidChannel';
import AndroidChannelGroup from './AndroidChannelGroup';
import AndroidNotifications from './AndroidNotifications';
import AndroidRemoteInput from './AndroidRemoteInput';
import Notification from './Notification';
import {
BadgeIconType,
Category,
Defaults,
GroupAlert,
Importance,
Priority,
SemanticAction,
Visibility,
} from './types';
import type App from '../core/app';
import type { NotificationOpen } from './Notification';
import type {
NativeNotification,
NativeNotificationOpen,
Schedule,
} from './types';
type OnNotification = Notification => any;
type OnNotificationObserver = {
next: OnNotification,
};
type OnNotificationOpened = NotificationOpen => any;
type OnNotificationOpenedObserver = {
next: NotificationOpen,
};
const NATIVE_EVENTS = [
'notifications_notification_displayed',
'notifications_notification_opened',
'notifications_notification_received',
];
export const MODULE_NAME = 'RNFirebaseNotifications';
export const NAMESPACE = 'notifications';
// iOS 8/9 scheduling
// fireDate: Date;
// timeZone: TimeZone;
// repeatInterval: NSCalendar.Unit;
// repeatCalendar: Calendar;
// region: CLRegion;
// regionTriggersOnce: boolean;
// iOS 10 scheduling
// TODO
// Android scheduling
// TODO
/**
* @class Notifications
*/
export default class Notifications extends ModuleBase {
_android: AndroidNotifications;
constructor(app: App) {
super(app, {
events: NATIVE_EVENTS,
hasShards: false,
moduleName: MODULE_NAME,
multiApp: false,
namespace: NAMESPACE,
});
this._android = new AndroidNotifications(this);
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onNotificationDisplayed
'notifications_notification_displayed',
(notification: NativeNotification) => {
SharedEventEmitter.emit(
'onNotificationDisplayed',
new Notification(notification)
);
}
);
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onNotificationOpened
'notifications_notification_opened',
(notificationOpen: NativeNotificationOpen) => {
SharedEventEmitter.emit('onNotificationOpened', {
action: notificationOpen.action,
notification: new Notification(notificationOpen.notification),
results: notificationOpen.results,
});
}
);
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onNotification
'notifications_notification_received',
(notification: NativeNotification) => {
SharedEventEmitter.emit(
'onNotification',
new Notification(notification)
);
}
);
}
get android(): AndroidNotifications {
return this._android;
}
/**
* Cancel all notifications
*/
cancelAllNotifications(): void {
getNativeModule(this).cancelAllNotifications();
}
/**
* Cancel a notification by id.
* @param notificationId
*/
cancelNotification(notificationId: string): void {
if (!notificationId) {
throw new Error(
'Notifications: cancelNotification expects a `notificationId`'
);
}
getNativeModule(this).cancelNotification(notificationId);
}
/**
* Display a notification
* @param notification
* @returns {*}
*/
displayNotification(notification: Notification): Promise<void> {
if (!(notification instanceof Notification)) {
throw new Error(
`Notifications:displayNotification expects a 'Notification' but got type ${typeof notification}`
);
}
return getNativeModule(this).displayNotification(notification.build());
}
getBadge(): Promise<number> {
return getNativeModule(this).getBadge();
}
getInitialNotification(): Promise<NotificationOpen> {
return getNativeModule(this)
.getInitialNotification()
.then((notificationOpen: NativeNotificationOpen) => {
if (notificationOpen) {
return {
action: notificationOpen.action,
notification: new Notification(notificationOpen.notification),
results: notificationOpen.results,
};
}
return null;
});
}
/**
* Returns an array of all scheduled notifications
* @returns {Promise.<Array>}
*/
getScheduledNotifications(): Promise<Notification[]> {
return getNativeModule(this).getScheduledNotifications();
}
onNotification(
nextOrObserver: OnNotification | OnNotificationObserver
): () => any {
let listener;
if (isFunction(nextOrObserver)) {
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Notifications.onNotification failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onNotification listener');
SharedEventEmitter.addListener('onNotification', listener);
return () => {
getLogger(this).info('Removing onNotification listener');
SharedEventEmitter.removeListener('onNotification', listener);
};
}
onNotificationDisplayed(
nextOrObserver: OnNotification | OnNotificationObserver
): () => any {
let listener;
if (isFunction(nextOrObserver)) {
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Notifications.onNotificationDisplayed failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onNotificationDisplayed listener');
SharedEventEmitter.addListener('onNotificationDisplayed', listener);
return () => {
getLogger(this).info('Removing onNotificationDisplayed listener');
SharedEventEmitter.removeListener('onNotificationDisplayed', listener);
};
}
onNotificationOpened(
nextOrObserver: OnNotificationOpened | OnNotificationOpenedObserver
): () => any {
let listener;
if (isFunction(nextOrObserver)) {
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Notifications.onNotificationOpened failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onNotificationOpened listener');
SharedEventEmitter.addListener('onNotificationOpened', listener);
return () => {
getLogger(this).info('Removing onNotificationOpened listener');
SharedEventEmitter.removeListener('onNotificationOpened', listener);
};
}
/**
* Remove all delivered notifications.
*/
removeAllDeliveredNotifications(): void {
getNativeModule(this).removeAllDeliveredNotifications();
}
/**
* Remove a delivered notification.
* @param notificationId
*/
removeDeliveredNotification(notificationId: string): void {
if (!notificationId) {
throw new Error(
'Notifications: removeDeliveredNotification expects a `notificationId`'
);
}
getNativeModule(this).removeDeliveredNotification(notificationId);
}
/**
* Schedule a notification
* @param notification
* @returns {*}
*/
scheduleNotification(
notification: Notification,
schedule: Schedule
): Promise<void> {
if (!(notification instanceof Notification)) {
throw new Error(
`Notifications:scheduleNotification expects a 'Notification' but got type ${typeof notification}`
);
}
const nativeNotification = notification.build();
nativeNotification.schedule = schedule;
return getNativeModule(this).scheduleNotification(nativeNotification);
}
setBadge(badge: number): void {
getNativeModule(this).setBadge(badge);
}
}
export const statics = {
Android: {
Action: AndroidAction,
BadgeIconType,
Category,
Channel: AndroidChannel,
ChannelGroup: AndroidChannelGroup,
Defaults,
GroupAlert,
Importance,
Priority,
RemoteInput: AndroidRemoteInput,
SemanticAction,
Visibility,
},
Notification,
};

View File

@ -0,0 +1,216 @@
/**
* @flow
*/
export const BadgeIconType = {
Large: 2,
None: 0,
Small: 1,
};
export const Category = {
Alarm: 'alarm',
Call: 'call',
Email: 'email',
Error: 'err',
Event: 'event',
Message: 'msg',
Progress: 'progress',
Promo: 'promo',
Recommendation: 'recommendation',
Reminder: 'reminder',
Service: 'service',
Social: 'social',
Status: 'status',
System: 'system',
Transport: 'transport',
};
export const Defaults = {
All: -1,
Lights: 4,
Sound: 1,
Vibrate: 2,
};
export const GroupAlert = {
All: 0,
Children: 2,
Summary: 1,
};
export const Importance = {
Default: 3,
High: 4,
Low: 2,
Max: 5,
Min: 1,
None: 3,
Unspecified: -1000,
};
export const Priority = {
Default: 0,
High: 1,
Low: -1,
Max: 2,
Min: -2,
};
export const SemanticAction = {
Archive: 5,
Call: 10,
Delete: 4,
MarkAsRead: 2,
MarkAsUnread: 3,
Mute: 6,
None: 0,
Reply: 1,
ThumbsDown: 9,
ThumbsUp: 8,
Unmute: 7,
};
export const Visibility = {
Private: 0,
Public: 1,
Secret: -1,
};
export type BadgeIconTypeType = $Values<typeof BadgeIconType>;
export type CategoryType = $Values<typeof Category>;
export type DefaultsType = $Values<typeof Defaults>;
export type GroupAlertType = $Values<typeof GroupAlert>;
export type ImportanceType = $Values<typeof Importance>;
export type PriorityType = $Values<typeof Priority>;
export type SemanticActionType = $Values<typeof SemanticAction>;
export type VisibilityType = $Values<typeof Visibility>;
export type Lights = {|
argb: number,
onMs: number,
offMs: number,
|};
export type Progress = {|
max: number,
progress: number,
indeterminate: boolean,
|};
export type SmallIcon = {|
icon: string,
level?: number,
|};
export type AndroidAllowDataType = {
allow: boolean,
mimeType: string,
};
export type NativeAndroidRemoteInput = {|
allowedDataTypes: AndroidAllowDataType[],
allowFreeFormInput?: boolean,
choices: string[],
label?: string,
resultKey: string,
|};
export type NativeAndroidAction = {|
action: string,
allowGeneratedReplies?: boolean,
icon: string,
remoteInputs: NativeAndroidRemoteInput[],
semanticAction?: SemanticActionType,
showUserInterface?: boolean,
title: string,
|};
export type NativeAndroidNotification = {|
actions?: NativeAndroidAction[],
autoCancel?: boolean,
badgeIconType?: BadgeIconTypeType,
category?: CategoryType,
channelId: string,
clickAction?: string,
color?: string,
colorized?: boolean,
contentInfo?: string,
defaults?: DefaultsType[],
group?: string,
groupAlertBehaviour?: GroupAlertType,
groupSummary?: boolean,
largeIcon?: string,
lights?: Lights,
localOnly?: boolean,
number?: number,
ongoing?: boolean,
onlyAlertOnce?: boolean,
people: string[],
priority?: PriorityType,
progress?: Progress,
// publicVersion: Notification,
remoteInputHistory?: string[],
shortcutId?: string,
showWhen?: boolean,
smallIcon: SmallIcon,
sortKey?: string,
// TODO: style: Style,
ticker?: string,
timeoutAfter?: number,
usesChronometer?: boolean,
vibrate?: number[],
visibility?: VisibilityType,
when?: number,
|};
export type IOSAttachmentOptions = {|
typeHint: string,
thumbnailHidden: boolean,
thumbnailClippingRect: {
height: number,
width: number,
x: number,
y: number,
},
thumbnailTime: number,
|};
export type IOSAttachment = {|
identifier: string,
options?: IOSAttachmentOptions,
url: string,
|};
export type NativeIOSNotification = {|
alertAction?: string,
attachments: IOSAttachment[],
badge?: number,
category?: string,
hasAction?: boolean,
launchImage?: string,
threadIdentifier?: string,
|};
export type Schedule = {|
exact?: boolean,
fireDate: number,
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
|};
export type NativeNotification = {|
android?: NativeAndroidNotification,
body: string,
data: { [string]: string },
ios?: NativeIOSNotification,
notificationId: string,
schedule?: Schedule,
sound?: string,
subtitle?: string,
title: string,
|};
export type NativeNotificationOpen = {|
action: string,
notification: NativeNotification,
results?: { [string]: string },
|};

View File

@ -16,6 +16,7 @@ export default class PerformanceMonitoring extends ModuleBase {
super(app, {
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
}

View File

@ -30,6 +30,7 @@ export default class Storage extends ModuleBase {
events: NATIVE_EVENTS,
moduleName: MODULE_NAME,
multiApp: true,
hasShards: false,
namespace: NAMESPACE,
});

View File

@ -23,6 +23,7 @@ export default class RNFirebaseUtils extends ModuleBase {
super(app, {
moduleName: MODULE_NAME,
multiApp: false,
hasShards: false,
namespace: NAMESPACE,
});
}

View File

@ -15,10 +15,16 @@ import type Database from '../modules/database';
import { typeof statics as DatabaseStatics } from '../modules/database';
import type Firestore from '../modules/firestore';
import { typeof statics as FirestoreStatics } from '../modules/firestore';
import type InstanceId from '../modules/instanceid';
import { typeof statics as InstanceIdStatics } from '../modules/instanceid';
import type Invites from '../modules/invites';
import { typeof statics as InvitesStatics } from '../modules/invites';
import type Links from '../modules/links';
import { typeof statics as LinksStatics } from '../modules/links';
import type Messaging from '../modules/messaging';
import { typeof statics as MessagingStatics } from '../modules/messaging';
import type Notifications from '../modules/notifications';
import { typeof statics as NotificationsStatics } from '../modules/notifications';
import type ModuleBase from '../utils/ModuleBase';
import type Performance from '../modules/perf';
import { typeof statics as PerformanceStatics } from '../modules/perf';
@ -44,6 +50,7 @@ export type FirebaseModuleConfig = {
events?: string[],
moduleName: FirebaseModuleName,
multiApp: boolean,
hasShards: boolean,
namespace: FirebaseNamespace,
};
@ -56,8 +63,11 @@ export type FirebaseModuleName =
| 'RNFirebaseCrashlytics'
| 'RNFirebaseDatabase'
| 'RNFirebaseFirestore'
| 'RNFirebaseInstanceId'
| 'RNFirebaseInvites'
| 'RNFirebaseLinks'
| 'RNFirebaseMessaging'
| 'RNFirebaseNotifications'
| 'RNFirebasePerformance'
| 'RNFirebaseStorage'
| 'RNFirebaseUtils';
@ -71,8 +81,11 @@ export type FirebaseNamespace =
| 'crashlytics'
| 'database'
| 'firestore'
| 'instanceid'
| 'invites'
| 'links'
| 'messaging'
| 'notifications'
| 'perf'
| 'storage'
| 'utils';
@ -162,6 +175,20 @@ export type FirestoreModule = {
nativeModuleExists: boolean,
} & FirestoreStatics;
/* InstanceId types */
export type InstanceIdModule = {
(): InstanceId,
nativeModuleExists: boolean,
} & InstanceIdStatics;
/* Invites types */
export type InvitesModule = {
(): Invites,
nativeModuleExists: boolean,
} & InvitesStatics;
/* Links types */
export type LinksModule = {
@ -176,6 +203,13 @@ export type MessagingModule = {
nativeModuleExists: boolean,
} & MessagingStatics;
/* Notifications types */
export type NotificationsModule = {
(): Notifications,
nativeModuleExists: boolean,
} & NotificationsStatics;
/* Performance types */
export type PerformanceModule = {

Some files were not shown because too many files have changed in this diff Show More