Merge branch 'master' of https://github.com/invertase/react-native-firebase into functions
This commit is contained in:
commit
e5233c9af3
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
name: ⚠️ Bug/Issue report
|
||||
about: Please provide as much detail as possible to help us with a bug or issue. Issues
|
||||
will be closed if they do not follow the template.
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
BEFORE YOU MAKE AN ISSUE
|
||||
|
||||
The issue list of this repo is exclusively for bug reports.
|
||||
|
||||
1) For feature requests, please use our Canny board: https://react-native-firebase.canny.io/feature-requests
|
||||
|
||||
2) For questions and support please use our Discord chat: https://discord.gg/C9aK28N or Stack Overflow: https://stackoverflow.com/questions/tagged/react-native-firebase
|
||||
|
||||
3) If this is a setup issue then please make sure you've correctly followed the setup guides, most setup issues such as 'duplicate dex files', 'default app has not been initialized' etc are all down to an incorrect setup as the guides haven't been correctly followed.
|
||||
-->
|
||||
|
||||
### Issue
|
||||
|
||||
<!--- Please write your issue here, provide as much detail as you can, code snippets, key files which will help us to debug such as your `Podfile` and/or `app/build.gradle` file). -->
|
||||
|
||||
### Environment
|
||||
|
||||
<!--- (e.g. iOS, Android, Both) --->
|
||||
|
||||
1. Application Target Platform:
|
||||
|
||||
<!--- (e.g. macOS Sierra, Windows 10) --->
|
||||
|
||||
2. Development Operating System:
|
||||
|
||||
<!--- (Xcode or Android Studio version, iOS or Android SDK version - if relevant) --->
|
||||
|
||||
3. Build Tools:
|
||||
|
||||
<!--- (e.g. 0.45.1) --->
|
||||
|
||||
4. React Native version:
|
||||
|
||||
<!--- (e.g. 2.1.3) --->
|
||||
|
||||
5. RNFirebase Version:
|
||||
|
||||
<!--- (e.g. database, auth, messaging, analytics etc - or N/A if not applicable) --->
|
||||
|
||||
6. Firebase Module:
|
||||
|
||||
<!-- Love react-native-firebase? Please consider supporting our collective:
|
||||
👉 https://opencollective.com/react-native-firebase/donate -->
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: 🎁 Feature request
|
||||
about: Please create feature requests on our canny board: [https://react-native-firebase.canny.io/feature-requests](https://react-native-firebase.canny.io/feature-requests)
|
||||
|
||||
---
|
||||
|
||||
[https://react-native-firebase.canny.io/feature-requests](https://react-native-firebase.canny.io/feature-requests)
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
name: 🤝 Support React Native Firebase
|
||||
about: Please help to keep RNFirebase supported and maintained 🤘
|
||||
|
||||
---
|
||||
## Supporting RNFirebase
|
||||
|
||||
RNFirebase is an Apache-2.0 licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [sponsors](#sponsors) and [backers](#backers). If you'd like to join them, please consider:
|
||||
|
||||
* [Become a backer or sponsor on Open Collective](https://opencollective.com/react-native-firebase).
|
||||
|
||||
### Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/react-native-firebase#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-firebase/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/9/avatar.svg"></a>
|
||||
|
||||
### Backers
|
||||
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/react-native-firebase#backer)]
|
||||
|
||||
<a href="https://opencollective.com/react-native-firebase#backers" target="_blank"><img src="https://opencollective.com/react-native-firebase/backers.svg?width=890"></a>
|
||||
|
||||
### Contributing
|
||||
|
||||
Please make sure to read the [Contributing Guide](CONTRIBUTING.md) before making a pull request.
|
||||
|
||||
Thank you to all the people who have already contributed to RNFirebase!
|
||||
|
||||
<a href="graphs/contributors"><img src="https://opencollective.com/react-native-firebase/contributors.svg?width=890" /></a>
|
||||
|
||||
<hr>
|
|
@ -1,5 +1,6 @@
|
|||
package io.invertase.firebase.firestore;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
|
@ -8,6 +9,7 @@ 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.firestore.Blob;
|
||||
import com.google.firebase.firestore.DocumentChange;
|
||||
import com.google.firebase.firestore.DocumentReference;
|
||||
import com.google.firebase.firestore.DocumentSnapshot;
|
||||
|
@ -209,6 +211,9 @@ public class FirestoreSerialize {
|
|||
} else if (value instanceof Date) {
|
||||
typeMap.putString("type", "date");
|
||||
typeMap.putDouble("value", ((Date) value).getTime());
|
||||
} else if (value instanceof Blob) {
|
||||
typeMap.putString("type", "blob");
|
||||
typeMap.putString("value", Base64.encodeToString(((Blob) value).toBytes(), Base64.NO_WRAP));
|
||||
} else {
|
||||
Log.e(TAG, "buildTypeMap: Cannot convert object of type " + value.getClass());
|
||||
typeMap.putString("type", "null");
|
||||
|
@ -261,6 +266,9 @@ public class FirestoreSerialize {
|
|||
} else if ("geopoint".equals(type)) {
|
||||
ReadableMap geoPoint = typeMap.getMap("value");
|
||||
return new GeoPoint(geoPoint.getDouble("latitude"), geoPoint.getDouble("longitude"));
|
||||
} else if ("blob".equals(type)) {
|
||||
String base64String = typeMap.getString("value");
|
||||
return Blob.fromBytes(Base64.decode(base64String, Base64.NO_WRAP));
|
||||
} else if ("date".equals(type)) {
|
||||
Double time = typeMap.getDouble("value");
|
||||
return new Date(time.longValue());
|
||||
|
|
|
@ -0,0 +1,407 @@
|
|||
package io.invertase.firebase.notifications;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
public class DisplayNotificationTask extends AsyncTask<Void, Void, Void> {
|
||||
private static final String TAG = "DisplayNotificationTask";
|
||||
|
||||
private final Context context;
|
||||
private final Bundle notification;
|
||||
private final NotificationManager notificationManager;
|
||||
private final Promise promise;
|
||||
private ReactApplicationContext reactContext;
|
||||
|
||||
public DisplayNotificationTask(Context context, ReactApplicationContext reactContext,
|
||||
NotificationManager notificationManager,
|
||||
Bundle notification, Promise promise) {
|
||||
this.context = context;
|
||||
this.notification = notification;
|
||||
this.notificationManager = notificationManager;
|
||||
this.promise = promise;
|
||||
this.reactContext = reactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
try {
|
||||
Class intentClass = getMainActivityClass();
|
||||
if (intentClass == null) {
|
||||
if (promise != null) {
|
||||
promise.reject("notification/display_notification_error", "Could not find main activity class");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 = RNFirebaseNotificationManager.getSound(context, 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("bigPicture")) {
|
||||
Bundle bigPicture = android.getBundle("bigPicture");
|
||||
|
||||
NotificationCompat.BigPictureStyle bp = new NotificationCompat.BigPictureStyle();
|
||||
Bitmap picture = getBitmap(bigPicture.getString("picture"));
|
||||
if (picture != null) {
|
||||
bp = bp.bigPicture(picture);
|
||||
}
|
||||
if (bigPicture.containsKey("largeIcon")) {
|
||||
Bitmap largeIcon = getBitmap(bigPicture.getString("largeIcon"));
|
||||
if (largeIcon != null) {
|
||||
bp = bp.bigLargeIcon(largeIcon);
|
||||
}
|
||||
}
|
||||
if (bigPicture.containsKey("contentTitle")) {
|
||||
bp = bp.setBigContentTitle(bigPicture.getString("contentTitle"));
|
||||
}
|
||||
if (bigPicture.containsKey("summaryText")) {
|
||||
bp = bp.setSummaryText(bigPicture.getString("summaryText"));
|
||||
}
|
||||
nb = nb.setStyle(bp);
|
||||
}
|
||||
if (android.containsKey("bigText")) {
|
||||
Bundle bigText = android.getBundle("bigText");
|
||||
|
||||
NotificationCompat.BigTextStyle bt = new NotificationCompat.BigTextStyle();
|
||||
bt.bigText(bigText.getString("text"));
|
||||
if (bigText.containsKey("contentTitle")) {
|
||||
bt = bt.setBigContentTitle(bigText.getString("contentTitle"));
|
||||
}
|
||||
if (bigText.containsKey("summaryText")) {
|
||||
bt = bt.setSummaryText(bigText.getString("summaryText"));
|
||||
}
|
||||
nb = nb.setStyle(bt);
|
||||
}
|
||||
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")) {
|
||||
List<String> people = android.getStringArrayList("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")) {
|
||||
ArrayList<Integer> vibrate = android.getIntegerArrayList("vibrate");
|
||||
if(vibrate != null) {
|
||||
long[] vibrateArray = new long[vibrate.size()];
|
||||
for (int i = 0; i < vibrate.size(); i++) {
|
||||
vibrateArray[i] = vibrate.get(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());
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Log.e(TAG, "Failed to send notification", e);
|
||||
if (promise != null) {
|
||||
promise.reject("notification/display_notification_error", "Could not send notification", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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")) {
|
||||
List<String> choices = remoteInput.getStringArrayList("choices");
|
||||
rb.setChoices(choices.toArray(new String[choices.size()]));
|
||||
}
|
||||
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 if (image.startsWith("file://")) {
|
||||
return BitmapFactory.decodeFile(image.replace("file://", ""));
|
||||
} else {
|
||||
int largeIconResId = RNFirebaseNotificationManager.getResourceId(context,"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 = RNFirebaseNotificationManager.getResourceId(context,"mipmap", icon);
|
||||
if (smallIconResourceId == 0) {
|
||||
smallIconResourceId = RNFirebaseNotificationManager.getResourceId(context,"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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -202,365 +202,14 @@ public class RNFirebaseNotificationManager {
|
|||
}
|
||||
|
||||
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("bigPicture")) {
|
||||
Bundle bigPicture = android.getBundle("bigPicture");
|
||||
|
||||
NotificationCompat.BigPictureStyle bp = new NotificationCompat.BigPictureStyle();
|
||||
Bitmap picture = getBitmap(bigPicture.getString("picture"));
|
||||
if (picture != null) {
|
||||
bp = bp.bigPicture(picture);
|
||||
}
|
||||
if (bigPicture.containsKey("largeIcon")) {
|
||||
Bitmap largeIcon = getBitmap(bigPicture.getString("largeIcon"));
|
||||
if (largeIcon != null) {
|
||||
bp = bp.bigLargeIcon(largeIcon);
|
||||
}
|
||||
}
|
||||
if (bigPicture.containsKey("contentTitle")) {
|
||||
bp = bp.setBigContentTitle(bigPicture.getString("contentTitle"));
|
||||
}
|
||||
if (bigPicture.containsKey("summaryText")) {
|
||||
bp = bp.setSummaryText(bigPicture.getString("summaryText"));
|
||||
}
|
||||
nb = nb.setStyle(bp);
|
||||
}
|
||||
if (android.containsKey("bigText")) {
|
||||
Bundle bigText = android.getBundle("bigText");
|
||||
|
||||
NotificationCompat.BigTextStyle bt = new NotificationCompat.BigTextStyle();
|
||||
bt.bigText(bigText.getString("text"));
|
||||
if (bigText.containsKey("contentTitle")) {
|
||||
bt = bt.setBigContentTitle(bigText.getString("contentTitle"));
|
||||
}
|
||||
if (bigText.containsKey("summaryText")) {
|
||||
bt = bt.setSummaryText(bigText.getString("summaryText"));
|
||||
}
|
||||
nb = nb.setStyle(bt);
|
||||
}
|
||||
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")) {
|
||||
List<String> people = android.getStringArrayList("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")) {
|
||||
ArrayList<Integer> vibrate = android.getIntegerArrayList("vibrate");
|
||||
if(vibrate != null) {
|
||||
long[] vibrateArray = new long[vibrate.size()];
|
||||
for (int i = 0; i < vibrate.size(); i++) {
|
||||
vibrateArray[i] = vibrate.get(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());
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Log.e(TAG, "Failed to send notification", e);
|
||||
if (promise != null) {
|
||||
promise.reject("notification/display_notification_error", "Could not send notification", e);
|
||||
}
|
||||
}
|
||||
new DisplayNotificationTask(context, reactContext, notificationManager, notification, promise).execute();
|
||||
}
|
||||
|
||||
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")) {
|
||||
List<String> choices = remoteInput.getStringArrayList("choices");
|
||||
rb.setChoices(choices.toArray(new String[choices.size()]));
|
||||
}
|
||||
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 if (image.startsWith("file://")) {
|
||||
return BitmapFactory.decodeFile(image.replace("file://", ""));
|
||||
} 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) {
|
||||
public static int getResourceId(Context context, String type, String image) {
|
||||
return context.getResources().getIdentifier(image, type, context.getPackageName());
|
||||
}
|
||||
|
||||
private Uri getSound(String sound) {
|
||||
public static Uri getSound(Context context, String sound) {
|
||||
if (sound == null) {
|
||||
return null;
|
||||
} else if (sound.contains("://")) {
|
||||
|
@ -568,9 +217,9 @@ public class RNFirebaseNotificationManager {
|
|||
} else if (sound.equalsIgnoreCase("default")) {
|
||||
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
} else {
|
||||
int soundResourceId = getResourceId("raw", sound);
|
||||
int soundResourceId = getResourceId(context,"raw", sound);
|
||||
if (soundResourceId == 0) {
|
||||
soundResourceId = getResourceId("raw", sound.substring(0, sound.lastIndexOf('.')));
|
||||
soundResourceId = getResourceId(context,"raw", sound.substring(0, sound.lastIndexOf('.')));
|
||||
}
|
||||
return Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId);
|
||||
}
|
||||
|
@ -616,7 +265,7 @@ public class RNFirebaseNotificationManager {
|
|||
channel.setShowBadge(channelMap.getBoolean("showBadge"));
|
||||
}
|
||||
if (channelMap.hasKey("sound")) {
|
||||
Uri sound = getSound(channelMap.getString("sound"));
|
||||
Uri sound = getSound(context, channelMap.getString("sound"));
|
||||
channel.setSound(sound, null);
|
||||
}
|
||||
if (channelMap.hasKey("vibrationEnabled")) {
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
const testObject = { hello: 'world', testRunId };
|
||||
const testString = JSON.stringify(testObject);
|
||||
const testBuffer = Buffer.from(testString);
|
||||
const testBase64 = testBuffer.toString('base64');
|
||||
|
||||
const testObjectLarge = new Array(5000).fill(testObject);
|
||||
const testStringLarge = JSON.stringify(testObjectLarge);
|
||||
const testBufferLarge = Buffer.from(testStringLarge);
|
||||
const testBase64Large = testBufferLarge.toString('base64');
|
||||
|
||||
/** ----------------
|
||||
* CLASS TESTS
|
||||
* -----------------*/
|
||||
describe('firestore', () => {
|
||||
it('should export Blob class on statics', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
should.exist(Blob);
|
||||
});
|
||||
|
||||
describe('Blob', () => {
|
||||
it('.constructor() -> returns new instance of Blob', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = new Blob(testStringLarge);
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
myBlob._binaryString.should.equal(testStringLarge);
|
||||
myBlob.toBase64().should.equal(testBase64Large);
|
||||
});
|
||||
|
||||
it('.fromBase64String() -> returns new instance of Blob', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromBase64String(testBase64);
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
myBlob._binaryString.should.equal(testString);
|
||||
should.deepEqual(
|
||||
JSON.parse(myBlob._binaryString),
|
||||
testObject,
|
||||
'Expected Blob _binaryString internals to serialize to json and match test object'
|
||||
);
|
||||
});
|
||||
|
||||
it('.fromBase64String() -> throws if arg not typeof string and length > 0', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromBase64String(testBase64);
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
(() => Blob.fromBase64String(1234)).should.throwError();
|
||||
(() => Blob.fromBase64String('')).should.throwError();
|
||||
});
|
||||
|
||||
it('.fromUint8Array() -> returns new instance of Blob', async () => {
|
||||
const testUInt8Array = new Uint8Array(testBuffer);
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromUint8Array(testUInt8Array);
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
const json = JSON.parse(myBlob._binaryString);
|
||||
json.hello.should.equal('world');
|
||||
});
|
||||
|
||||
it('.fromUint8Array() -> throws if arg not instanceof Uint8Array', async () => {
|
||||
const testUInt8Array = new Uint8Array(testBuffer);
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromUint8Array(testUInt8Array);
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
(() => Blob.fromUint8Array('derp')).should.throwError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Blob instance', () => {
|
||||
it('.toString() -> returns string representation of blob instance', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromBase64String(testBase64);
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
should.equal(
|
||||
myBlob.toString().includes(testBase64),
|
||||
true,
|
||||
'toString() should return a string that includes the base64'
|
||||
);
|
||||
});
|
||||
|
||||
it('.isEqual() -> returns true or false', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromBase64String(testBase64);
|
||||
const myBlob2 = Blob.fromBase64String(testBase64Large);
|
||||
myBlob.isEqual(myBlob).should.equal(true);
|
||||
myBlob2.isEqual(myBlob).should.equal(false);
|
||||
});
|
||||
|
||||
it('.isEqual() -> throws if arg not instanceof Blob', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromBase64String(testBase64);
|
||||
const myBlob2 = Blob.fromBase64String(testBase64Large);
|
||||
myBlob.isEqual(myBlob).should.equal(true);
|
||||
(() => myBlob2.isEqual('derp')).should.throwError();
|
||||
});
|
||||
|
||||
it('.toBase64() -> returns base64 string', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromBase64String(testBase64);
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
myBlob.toBase64().should.equal(testBase64);
|
||||
});
|
||||
|
||||
it('.toUint8Array() -> returns Uint8Array', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
const myBlob = Blob.fromBase64String(testBase64);
|
||||
const testUInt8Array = new Uint8Array(testBuffer);
|
||||
const testUInt8Array2 = new Uint8Array();
|
||||
|
||||
myBlob.should.be.instanceOf(Blob);
|
||||
should.deepEqual(myBlob.toUint8Array(), testUInt8Array);
|
||||
should.notDeepEqual(myBlob.toUint8Array(), testUInt8Array2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/** ----------------
|
||||
* USAGE TESTS
|
||||
* -----------------*/
|
||||
describe('firestore', () => {
|
||||
describe('Blob', () => {
|
||||
it('reads and writes small blobs', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
|
||||
await firebase
|
||||
.firestore()
|
||||
.doc('blob-tests/small')
|
||||
.set({ blobby: Blob.fromBase64String(testBase64) });
|
||||
|
||||
const snapshot = await firebase
|
||||
.firestore()
|
||||
.doc('blob-tests/small')
|
||||
.get();
|
||||
|
||||
const blob = snapshot.data().blobby;
|
||||
blob._binaryString.should.equal(testString);
|
||||
blob.toBase64().should.equal(testBase64);
|
||||
});
|
||||
|
||||
it('reads and writes large blobs', async () => {
|
||||
const { Blob } = firebase.firestore;
|
||||
|
||||
await firebase
|
||||
.firestore()
|
||||
.doc('blob-tests/large')
|
||||
.set({ blobby: Blob.fromBase64String(testBase64Large) });
|
||||
|
||||
const snapshot = await firebase
|
||||
.firestore()
|
||||
.doc('blob-tests/large')
|
||||
.get();
|
||||
|
||||
const blob = snapshot.data().blobby;
|
||||
blob._binaryString.should.equal(testStringLarge);
|
||||
blob.toBase64().should.equal(testBase64Large);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -9,6 +9,38 @@ Object.defineProperty(global, 'firebase', {
|
|||
},
|
||||
});
|
||||
|
||||
// TODO move as part of bridge
|
||||
const { Uint8Array } = global;
|
||||
Object.defineProperty(global, 'Uint8Array', {
|
||||
get() {
|
||||
const { stack } = new Error();
|
||||
if (
|
||||
(stack.includes('Context.it') || stack.includes('Context.beforeEach')) &&
|
||||
global.bridge &&
|
||||
global.bridge.context
|
||||
) {
|
||||
return bridge.context.window.Uint8Array;
|
||||
}
|
||||
return Uint8Array;
|
||||
},
|
||||
});
|
||||
|
||||
// TODO move as part of bridge
|
||||
const { Array } = global;
|
||||
Object.defineProperty(global, 'Array', {
|
||||
get() {
|
||||
const { stack } = new Error();
|
||||
if (
|
||||
(stack.includes('Context.it') || stack.includes('Context.beforeEach')) &&
|
||||
global.bridge &&
|
||||
global.bridge.context
|
||||
) {
|
||||
return bridge.context.window.Array;
|
||||
}
|
||||
return Array;
|
||||
},
|
||||
});
|
||||
|
||||
global.isObject = function isObject(item) {
|
||||
return item
|
||||
? typeof item === 'object' && !Array.isArray(item) && item !== null
|
||||
|
@ -30,11 +62,17 @@ global.randomString = (length, chars) => {
|
|||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
global.firebaseAdmin = require('firebase-admin');
|
||||
|
||||
global.testRunId = randomString(4, 'aA#');
|
||||
|
||||
/** ------------------
|
||||
* Init WEB SDK
|
||||
---------------------*/
|
||||
|
||||
/** ------------------
|
||||
* Init ADMIN SDK
|
||||
---------------------*/
|
||||
global.firebaseAdmin = require('firebase-admin');
|
||||
|
||||
firebaseAdmin.initializeApp({
|
||||
credential: firebaseAdmin.credential.cert(require('./service-account')),
|
||||
databaseURL: 'https://rnfirebase-b9ad4.firebaseio.com',
|
||||
|
|
|
@ -16,4 +16,5 @@ Pod::Spec.new do |s|
|
|||
s.platform = :ios, "9.0"
|
||||
s.source_files = 'RNFirebase/**/*.{h,m}'
|
||||
s.dependency 'React'
|
||||
s.dependency 'Firebase/Core'
|
||||
end
|
||||
|
|
|
@ -208,6 +208,10 @@ static NSMutableDictionary *_listeners;
|
|||
typeMap[@"type"] = @"number";
|
||||
}
|
||||
typeMap[@"value"] = value;
|
||||
} else if ([value isKindOfClass:[NSData class]]) {
|
||||
typeMap[@"type"] = @"blob";
|
||||
NSData *blob = (NSData *)value;
|
||||
typeMap[@"value"] = [blob base64EncodedStringWithOptions:0];
|
||||
} else {
|
||||
// TODO: Log an error
|
||||
typeMap[@"type"] = @"null";
|
||||
|
@ -248,6 +252,8 @@ static NSMutableDictionary *_listeners;
|
|||
return [RNFirebaseFirestoreDocumentReference parseJSMap:firestore jsMap:value];
|
||||
} else if ([type isEqualToString:@"reference"]) {
|
||||
return [firestore documentWithPath:value];
|
||||
} else if ([type isEqualToString:@"blob"]) {
|
||||
return [[NSData alloc] initWithBase64EncodedString:(NSString *) value options:0];
|
||||
} else if ([type isEqualToString:@"geopoint"]) {
|
||||
NSDictionary *geopoint = (NSDictionary*)value;
|
||||
NSNumber *latitude = geopoint[@"latitude"];
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,91 @@
|
|||
import Base64 from './../../utils/Base64';
|
||||
|
||||
export default class Blob {
|
||||
_binaryString: string;
|
||||
|
||||
constructor(binaryString: string) {
|
||||
this._binaryString = binaryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Blob from the given Base64 string
|
||||
*
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.firestore.Blob#.fromBase64String
|
||||
* @param base64 string
|
||||
*/
|
||||
static fromBase64String(base64: string): Blob {
|
||||
if (typeof base64 !== 'string' || base64.length < 1) {
|
||||
throw new Error(
|
||||
'firestore.Blob.fromBase64String expects a string of at least 1 character in length'
|
||||
);
|
||||
}
|
||||
|
||||
return new Blob(Base64.atob(base64));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Blob from the given Uint8Array.
|
||||
*
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.firestore.Blob#.fromUint8Array
|
||||
* @param array Array
|
||||
*/
|
||||
static fromUint8Array(array: Uint8Array): Blob {
|
||||
if (!(array instanceof Uint8Array)) {
|
||||
throw new Error(
|
||||
'firestore.Blob.fromUint8Array expects an instance of Uint8Array'
|
||||
);
|
||||
}
|
||||
|
||||
return new Blob(
|
||||
Array.prototype.map
|
||||
.call(array, (char: number) => String.fromCharCode(char))
|
||||
.join('')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 'true' if this Blob is equal to the provided one.
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.firestore.Blob#isEqual
|
||||
* @param {*} blob Blob The Blob to compare against. Value must not be null.
|
||||
* @returns boolean 'true' if this Blob is equal to the provided one.
|
||||
*/
|
||||
isEqual(blob: Blob): boolean {
|
||||
if (!(blob instanceof Blob)) {
|
||||
throw new Error('firestore.Blob.isEqual expects an instance of Blob');
|
||||
}
|
||||
|
||||
return this._binaryString === blob._binaryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytes of a Blob as a Base64-encoded string.
|
||||
*
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.firestore.Blob#toBase64
|
||||
* @returns string The Base64-encoded string created from the Blob object.
|
||||
*/
|
||||
toBase64(): string {
|
||||
return Base64.btoa(this._binaryString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytes of a Blob in a new Uint8Array.
|
||||
*
|
||||
* @url https://firebase.google.com/docs/reference/js/firebase.firestore.Blob#toUint8Array
|
||||
* @returns non-null Uint8Array The Uint8Array created from the Blob object.
|
||||
*/
|
||||
toUint8Array(): Uint8Array {
|
||||
return new Uint8Array(
|
||||
this._binaryString.split('').map(c => c.charCodeAt(0))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this blob instance
|
||||
*
|
||||
* @returns {string}
|
||||
* @memberof Blob
|
||||
*/
|
||||
toString(): string {
|
||||
return `firestore.Blob(base64: ${this.toBase64()})`;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import DocumentReference from './DocumentReference';
|
|||
import FieldPath from './FieldPath';
|
||||
import FieldValue from './FieldValue';
|
||||
import GeoPoint from './GeoPoint';
|
||||
import Blob from './Blob';
|
||||
import Path from './Path';
|
||||
import WriteBatch from './WriteBatch';
|
||||
import TransactionHandler from './TransactionHandler';
|
||||
|
@ -255,6 +256,7 @@ export default class Firestore extends ModuleBase {
|
|||
}
|
||||
|
||||
export const statics = {
|
||||
Blob,
|
||||
FieldPath,
|
||||
FieldValue,
|
||||
GeoPoint,
|
||||
|
|
|
@ -42,6 +42,7 @@ export type NativeTypeMap = {
|
|||
| 'array'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'blob'
|
||||
| 'documentid'
|
||||
| 'fieldvalue'
|
||||
| 'geopoint'
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import DocumentReference from '../DocumentReference';
|
||||
import Blob from '../Blob';
|
||||
import { DOCUMENT_ID } from '../FieldPath';
|
||||
import {
|
||||
DELETE_FIELD_VALUE,
|
||||
|
@ -98,6 +99,11 @@ export const buildTypeMap = (value: any): NativeTypeMap | null => {
|
|||
type: 'date',
|
||||
value: value.getTime(),
|
||||
};
|
||||
} else if (value instanceof Blob) {
|
||||
return {
|
||||
type: 'blob',
|
||||
value: value.toBase64(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'object',
|
||||
|
@ -156,6 +162,8 @@ const parseTypeMap = (firestore: Firestore, typeMap: NativeTypeMap): any => {
|
|||
return new GeoPoint(value.latitude, value.longitude);
|
||||
} else if (type === 'date') {
|
||||
return new Date(value);
|
||||
} else if (type === 'blob') {
|
||||
return Blob.fromBase64String(value);
|
||||
}
|
||||
console.warn(`Unknown data type received ${type}`);
|
||||
return value;
|
||||
|
|
|
@ -103,9 +103,9 @@ 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];
|
||||
if (nativeRemoteInput.allowedDataTypes) {
|
||||
for (let i = 0; i < nativeRemoteInput.allowedDataTypes.length; i++) {
|
||||
const allowDataType = nativeRemoteInput.allowedDataTypes[i];
|
||||
remoteInput.setAllowDataType(allowDataType.mimeType, allowDataType.allow);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
/* eslint-disable */
|
||||
|
||||
const CHARS =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* window.btoa
|
||||
*/
|
||||
btoa(input: string = ''): string {
|
||||
let map;
|
||||
let i = 0;
|
||||
let block = 0;
|
||||
let output = '';
|
||||
|
||||
// eslint-disable-next-line
|
||||
for (
|
||||
block = 0, i = 0, map = CHARS;
|
||||
input.charAt(i | 0) || ((map = '='), i % 1);
|
||||
output += map.charAt(63 & (block >> (8 - (i % 1) * 8)))
|
||||
) {
|
||||
const charCode = input.charCodeAt((i += 3 / 4));
|
||||
|
||||
if (charCode > 0xff) {
|
||||
throw new Error(
|
||||
"'RNFirebase.utils.btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
|
||||
);
|
||||
}
|
||||
|
||||
block = (block << 8) | charCode;
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
/**
|
||||
* window.atob
|
||||
*/
|
||||
atob(input: string = ''): string {
|
||||
let i = 0;
|
||||
let bc = 0;
|
||||
let bs = 0;
|
||||
let buffer;
|
||||
let output = '';
|
||||
|
||||
const str = input.replace(/=+$/, '');
|
||||
|
||||
if (str.length % 4 === 1) {
|
||||
throw new Error(
|
||||
"'RNFirebase.utils.atob' failed: The string to be decoded is not correctly encoded."
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
for (
|
||||
bc = 0, bs = 0, i = 0;
|
||||
(buffer = str.charAt(i++));
|
||||
~buffer && ((bs = bc % 4 ? bs * 64 + buffer : buffer), bc++ % 4)
|
||||
? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))
|
||||
: 0
|
||||
) {
|
||||
buffer = CHARS.indexOf(buffer);
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-native-firebase",
|
||||
"version": "4.0.6",
|
||||
"version": "4.0.7",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-native-firebase",
|
||||
"version": "4.0.6",
|
||||
"version": "4.0.7",
|
||||
"author": "Invertase <contact@invertase.io> (http://invertase.io)",
|
||||
"description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Messaging (FCM), Remote Config, Storage and Performance.",
|
||||
"main": "dist/index.js",
|
||||
|
|
|
@ -47,6 +47,7 @@ const notifications = async () => {
|
|||
RNfirebase.notifications().onNotificationDisplayed(notification => {
|
||||
console.log('onNotificationDisplayed: ', notification);
|
||||
});
|
||||
|
||||
// RNfirebase.iid().delete();
|
||||
const channel = new RNfirebase.notifications.Android.Channel(
|
||||
'test',
|
||||
|
@ -77,7 +78,13 @@ const notifications = async () => {
|
|||
.android.addAction(action)
|
||||
.android.setChannelId('test')
|
||||
.android.setClickAction('action')
|
||||
.android.setPriority(RNfirebase.notifications.Android.Priority.Max);
|
||||
.android.setPriority(RNfirebase.notifications.Android.Priority.Max)
|
||||
.android.setBigPicture(
|
||||
'https://image.winudf.com/v2/image/Y29tLmFsZmFwaXhlbC5tYWhhZGV2aGR3YWxscGFwZXJfc2NyZWVuXzFfbGtzbzJwZGc/screen-1.jpg?h=355&fakeurl=1&type=.jpg',
|
||||
'https://www.google.co.in/images/branding/product/ico/googleg_lodp.ico',
|
||||
'test title 1',
|
||||
'test title 2'
|
||||
);
|
||||
|
||||
const date = new Date();
|
||||
date.setMinutes(date.getMinutes() + 1);
|
||||
|
|
Loading…
Reference in New Issue