diff --git a/android/build.gradle b/android/build.gradle index af712657..460a890b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -43,7 +43,9 @@ allprojects { // END dependencies { - compile 'com.facebook.react:react-native:0.20.+' + compile fileTree(include: ['*.jar'], dir: 'libs') + compile 'com.facebook.react:react-native:+' + compile 'me.leolin:ShortcutBadger:1.1.10@aar' compile 'com.google.android.gms:play-services-base:10.2.0' compile 'com.google.firebase:firebase-core:10.2.0' compile 'com.google.firebase:firebase-config:10.2.0' diff --git a/android/src/main/java/io/invertase/firebase/RNFirebaseInstanceIdService.java b/android/src/main/java/io/invertase/firebase/RNFirebaseInstanceIdService.java deleted file mode 100644 index 4a92b9af..00000000 --- a/android/src/main/java/io/invertase/firebase/RNFirebaseInstanceIdService.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.invertase.firebase; - -import android.util.Log; -import android.os.Bundle; -import android.content.Intent; - -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.iid.FirebaseInstanceIdService; - -import io.invertase.firebase.messaging.RNFirebaseMessaging; - -public class RNFirebaseInstanceIdService extends FirebaseInstanceIdService { - - private static final String TAG = "FSInstanceIdService"; - - /** - * - */ - @Override - public void onTokenRefresh() { - String refreshedToken = FirebaseInstanceId.getInstance().getToken(); - Log.d(TAG, "Refreshed token: " + refreshedToken); - Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_TOKEN); - Bundle bundle = new Bundle(); - bundle.putString("token", refreshedToken); - i.putExtras(bundle); - sendBroadcast(i); - } -} diff --git a/android/src/main/java/io/invertase/firebase/RNFirebaseMessagingService.java b/android/src/main/java/io/invertase/firebase/RNFirebaseMessagingService.java deleted file mode 100644 index c315b212..00000000 --- a/android/src/main/java/io/invertase/firebase/RNFirebaseMessagingService.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.invertase.firebase; - -import android.content.Intent; -import android.util.Log; - -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; -import com.google.firebase.messaging.SendException; - -import io.invertase.firebase.messaging.RNFirebaseMessaging; - -public class RNFirebaseMessagingService extends FirebaseMessagingService { - - private static final String TAG = "FSMessagingService"; - - @Override - public void onMessageReceived(RemoteMessage remoteMessage) { - Log.d(TAG, "Remote message received"); - // debug - Log.d(TAG, "From: " + remoteMessage.getFrom()); - - if (remoteMessage.getData().size() > 0) { - Log.d(TAG, "Message data payload: " + remoteMessage.getData()); - } - - if (remoteMessage.getNotification() != null) { - Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); - } - - Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_NOTIFICATION); - i.putExtra("data", remoteMessage); - sendOrderedBroadcast(i, null); - - } - - @Override - public void onMessageSent(String msgId) { - // Called when an upstream message has been successfully sent to the GCM connection server. - Log.d(TAG, "upstream message has been successfully sent"); - Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_SEND); - i.putExtra("msgId", msgId); - sendOrderedBroadcast(i, null); - } - - @Override - public void onSendError(String msgId, Exception exception) { - // Called when there was an error sending an upstream message. - Log.d(TAG, "error sending an upstream message"); - Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_SEND); - i.putExtra("msgId", msgId); - i.putExtra("hasError", true); - SendException sendException = (SendException) exception; - i.putExtra("errorCode", sendException.getErrorCode()); - switch (sendException.getErrorCode()) { - case SendException.ERROR_INVALID_PARAMETERS: - i.putExtra("errorMessage", "Message was sent with invalid parameters."); - break; - case SendException.ERROR_SIZE: - i.putExtra("errorMessage", "Message exceeded the maximum payload size."); - break; - case SendException.ERROR_TOO_MANY_MESSAGES: - i.putExtra("errorMessage", "App has too many pending messages so this one was dropped."); - break; - case SendException.ERROR_TTL_EXCEEDED: - i.putExtra("errorMessage", "Message time to live (TTL) was exceeded before the message could be sent."); - break; - case SendException.ERROR_UNKNOWN: - default: - i.putExtra("errorMessage", "Unknown error."); - break; - } - sendOrderedBroadcast(i, null); - } -} diff --git a/android/src/main/java/io/invertase/firebase/messaging/BadgeHelper.java b/android/src/main/java/io/invertase/firebase/messaging/BadgeHelper.java new file mode 100644 index 00000000..ba88bc7e --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/messaging/BadgeHelper.java @@ -0,0 +1,43 @@ +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(); + } +} diff --git a/android/src/main/java/io/invertase/firebase/messaging/BundleJSONConverter.java b/android/src/main/java/io/invertase/firebase/messaging/BundleJSONConverter.java new file mode 100644 index 00000000..70d31515 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/messaging/BundleJSONConverter.java @@ -0,0 +1,206 @@ +package io.invertase.firebase.messaging; + +// taken from https://github.com/facebook/facebook-android-sdk/blob/master/facebook/src/main/java/com/facebook/internal/BundleJSONConverter.java + +/* + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + *

+ * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Facebook. + *

+ * As with any software that integrates with the Facebook platform, your use of + * this software is subject to the Facebook Developer Principles and Policies + * [http://developers.facebook.com/policy/]. This copyright notice shall be + * included in all copies or substantial portions of the software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +import android.os.Bundle; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.*; + +/** + * com.facebook.internal is solely for the use of other packages within the Facebook SDK for + * Android. Use of any of the classes in this package is unsupported, and they may be modified or + * removed without warning at any time. + * + * A helper class that can round trip between JSON and Bundle objects that contains the types: + * Boolean, Integer, Long, Double, String + * If other types are found, an IllegalArgumentException is thrown. + */ +public class BundleJSONConverter { + private static final Map, Setter> SETTERS = new HashMap, Setter>(); + + static { + SETTERS.put(Boolean.class, new Setter() { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { + bundle.putBoolean(key, (Boolean) value); + } + + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { + json.put(key, value); + } + }); + SETTERS.put(Integer.class, new Setter() { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { + bundle.putInt(key, (Integer) value); + } + + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { + json.put(key, value); + } + }); + SETTERS.put(Long.class, new Setter() { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { + bundle.putLong(key, (Long) value); + } + + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { + json.put(key, value); + } + }); + SETTERS.put(Double.class, new Setter() { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { + bundle.putDouble(key, (Double) value); + } + + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { + json.put(key, value); + } + }); + SETTERS.put(String.class, new Setter() { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { + bundle.putString(key, (String) value); + } + + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { + json.put(key, value); + } + }); + SETTERS.put(String[].class, new Setter() { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { + throw new IllegalArgumentException("Unexpected type from JSON"); + } + + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { + JSONArray jsonArray = new JSONArray(); + for (String stringValue : (String[]) value) { + jsonArray.put(stringValue); + } + json.put(key, jsonArray); + } + }); + + SETTERS.put(JSONArray.class, new Setter() { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { + JSONArray jsonArray = (JSONArray) value; + ArrayList stringArrayList = new ArrayList(); + // Empty list, can't even figure out the type, assume an ArrayList + if (jsonArray.length() == 0) { + bundle.putStringArrayList(key, stringArrayList); + 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()); + } + } + bundle.putStringArrayList(key, stringArrayList); + } + + @Override + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { + throw new IllegalArgumentException("JSONArray's are not supported in bundles."); + } + }); + } + + public interface Setter { + public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException; + + public void setOnJSON(JSONObject json, String key, Object value) throws JSONException; + } + + public static JSONObject convertToJSON(Bundle bundle) throws JSONException { + JSONObject json = new JSONObject(); + + for (String key : bundle.keySet()) { + Object value = bundle.get(key); + if (value == null) { + // Null is not supported. + continue; + } + + // Special case List as getClass would not work, since List is an interface + if (value instanceof List) { + JSONArray jsonArray = new JSONArray(); + @SuppressWarnings("unchecked") + List listValue = (List) value; + for (String stringValue : listValue) { + jsonArray.put(stringValue); + } + json.put(key, jsonArray); + continue; + } + + // Special case Bundle as it's one way, on the return it will be JSONObject + if (value instanceof Bundle) { + json.put(key, convertToJSON((Bundle) value)); + continue; + } + + Setter setter = SETTERS.get(value.getClass()); + if (setter == null) { + throw new IllegalArgumentException("Unsupported type: " + value.getClass()); + } + setter.setOnJSON(json, key, value); + } + + return json; + } + + public static Bundle convertToBundle(JSONObject jsonObject) throws JSONException { + Bundle bundle = new Bundle(); + @SuppressWarnings("unchecked") + Iterator jsonIterator = jsonObject.keys(); + while (jsonIterator.hasNext()) { + String key = jsonIterator.next(); + Object value = jsonObject.get(key); + if (value == null || value == JSONObject.NULL) { + // Null is not supported. + continue; + } + + // Special case JSONObject as it's one way, on the return it would be Bundle. + if (value instanceof JSONObject) { + bundle.putBundle(key, convertToBundle((JSONObject) value)); + continue; + } + + Setter setter = SETTERS.get(value.getClass()); + if (setter == null) { + throw new IllegalArgumentException("Unsupported type: " + value.getClass()); + } + setter.setOnBundle(bundle, key, value); + } + + return bundle; + } +} diff --git a/android/src/main/java/io/invertase/firebase/messaging/InstanceIdService.java b/android/src/main/java/io/invertase/firebase/messaging/InstanceIdService.java new file mode 100644 index 00000000..1b09d540 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/messaging/InstanceIdService.java @@ -0,0 +1,34 @@ +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. + */ + // [START refresh_token] + @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); + } +} diff --git a/android/src/main/java/io/invertase/firebase/messaging/MessagingService.java b/android/src/main/java/io/invertase/firebase/messaging/MessagingService.java new file mode 100644 index 00000000..cd5af92a --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/messaging/MessagingService.java @@ -0,0 +1,64 @@ +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); + } + + public 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); + } + } + + public void buildLocalNotification(RemoteMessage remoteMessage) { + if(remoteMessage.getData() == null){ + return; + } + Map 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(); + } + + } + } +} diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java new file mode 100644 index 00000000..642ef6e7 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingHelper.java @@ -0,0 +1,332 @@ +package io.invertase.firebase.messaging; + +import android.app.*; +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.util.Patterns; +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 = (SharedPreferences) mContext.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE); + } + + public 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); + } + + 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 = bundle.getLong("fire_date", 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 keyMap = sharedPreferences.getAll(); + SharedPreferences.Editor editor = sharedPreferences.edit(); + for(java.util.Map.Entry 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 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); + } + + public ArrayList getScheduledLocalNotifications(){ + ArrayList array = new ArrayList(); + java.util.Map keyMap = sharedPreferences.getAll(); + for(java.util.Map.Entry 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; + } + + public 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; + } + } +} diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingPublisher.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingPublisher.java new file mode 100644 index 00000000..103b3168 --- /dev/null +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseLocalMessagingPublisher.java @@ -0,0 +1,14 @@ +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()); + } +} diff --git a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java index eeae6740..148b0e7d 100644 --- a/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java +++ b/android/src/main/java/io/invertase/firebase/messaging/RNFirebaseMessaging.java @@ -1,222 +1,285 @@ package io.invertase.firebase.messaging; -import java.util.Map; - -import android.content.Context; -import android.content.IntentFilter; -import android.content.Intent; +import android.app.Activity; import android.content.BroadcastReceiver; -import android.util.Log; +import android.content.Intent; +import android.content.IntentFilter; +import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; 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.ReadableType; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; - +import com.facebook.react.modules.core.DeviceEventManagerModule; 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 android.app.Application; +import android.os.Bundle; +import android.util.Log; -public class RNFirebaseMessaging extends ReactContextBaseJavaModule { +import android.content.Context; - private static final String TAG = "RNFirebaseMessaging"; - private static final String EVENT_NAME_TOKEN = "RNFirebaseRefreshToken"; - private static final String EVENT_NAME_NOTIFICATION = "RNFirebaseReceiveNotification"; - private static final String EVENT_NAME_SEND = "RNFirebaseUpstreamSend"; +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; +import java.util.UUID; - public static final String INTENT_NAME_TOKEN = "io.invertase.firebase.refreshToken"; - public static final String INTENT_NAME_NOTIFICATION = "io.invertase.firebase.ReceiveNotification"; - public static final String INTENT_NAME_SEND = "io.invertase.firebase.Upstream"; - - private IntentFilter mRefreshTokenIntentFilter; - private IntentFilter mReceiveNotificationIntentFilter; - private IntentFilter mReceiveSendIntentFilter; - private BroadcastReceiver mBroadcastReceiver; +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 reactContext) { super(reactContext); - mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN); - mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION); - mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND); - initRefreshTokenHandler(); - initMessageHandler(); - initSendHandler(); - Log.d(TAG, "New instance"); + mRNFirebaseLocalMessagingHelper = new RNFirebaseLocalMessagingHelper((Application) reactContext.getApplicationContext()); + mBadgeHelper = new BadgeHelper(reactContext.getApplicationContext()); + getReactApplicationContext().addLifecycleEventListener(this); + getReactApplicationContext().addActivityEventListener(this); + registerTokenRefreshHandler(); + registerMessageHandler(); + registerLocalMessageHandler(); } @Override public String getName() { - return TAG; + return "RNFirebaseMessaging"; } - private void initMessageHandler() { - Log.d(TAG, "RNFirebase initMessageHandler called"); - - if (mBroadcastReceiver == null) { - mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - RemoteMessage remoteMessage = intent.getParcelableExtra("data"); - Log.d(TAG, "Firebase onReceive: " + remoteMessage); - WritableMap params = Arguments.createMap(); - - params.putNull("data"); - params.putNull("notification"); - params.putString("id", remoteMessage.getMessageId()); - params.putString("messageId", remoteMessage.getMessageId()); - - - if (remoteMessage.getData().size() != 0) { - WritableMap dataMap = Arguments.createMap(); - Map data = remoteMessage.getData(); - - for (String key : data.keySet()) { - dataMap.putString(key, data.get(key)); - } - - params.putMap("data", dataMap); - } - - - if (remoteMessage.getNotification() != null) { - WritableMap notificationMap = Arguments.createMap(); - RemoteMessage.Notification notification = remoteMessage.getNotification(); - notificationMap.putString("title", notification.getTitle()); - notificationMap.putString("body", notification.getBody()); - notificationMap.putString("icon", notification.getIcon()); - notificationMap.putString("sound", notification.getSound()); - notificationMap.putString("tag", notification.getTag()); - params.putMap("notification", notificationMap); - } - - ReactContext ctx = getReactApplicationContext(); - Utils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params); - } - }; - + @ReactMethod + public void getInitialNotification(Promise promise) { + Activity activity = getCurrentActivity(); + if (activity == null) { + promise.resolve(null); + return; } - getReactApplicationContext().registerReceiver(mBroadcastReceiver, mReceiveNotificationIntentFilter); + promise.resolve(parseIntent(getCurrentActivity().getIntent())); } - /** - * - */ - private void initRefreshTokenHandler() { + @ReactMethod + public void requestPermissions() { + } + + @ReactMethod + public void getToken(Promise promise) { + Log.d(TAG, "Firebase token: " + FirebaseInstanceId.getInstance().getToken()); + promise.resolve(FirebaseInstanceId.getInstance().getToken()); + } + + @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() { + mRNFirebaseLocalMessagingHelper.cancelAllLocalNotifications(); + } + + @ReactMethod + public void removeDeliveredNotification(String notificationID) { + mRNFirebaseLocalMessagingHelper.removeDeliveredNotification(notificationID); + } + + @ReactMethod + public void removeAllDeliveredNotifications() { + mRNFirebaseLocalMessagingHelper.removeAllDeliveredNotifications(); + } + + @ReactMethod + public void subscribeToTopic(String topic) { + FirebaseMessaging.getInstance().subscribeToTopic(topic); + } + + @ReactMethod + public void unsubscribeFromTopic(String topic) { + FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); + } + + @ReactMethod + public void getScheduledLocalNotifications(Promise promise) { + ArrayList bundles = mRNFirebaseLocalMessagingHelper.getScheduledLocalNotifications(); + WritableArray array = Arguments.createArray(); + for (Bundle bundle : bundles) { + array.pushMap(Arguments.fromBundle(bundle)); + } + promise.resolve(array); + } + + @ReactMethod + public void setBadgeNumber(int badgeNumber) { + mBadgeHelper.setBadgeCount(badgeNumber); + } + + @ReactMethod + public void getBadgeNumber(Promise promise) { + promise.resolve(mBadgeHelper.getBadgeCount()); + } + + private void sendEvent(String eventName, Object params) { + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); + } + + private void registerTokenRefreshHandler() { + IntentFilter intentFilter = new IntentFilter("io.invertase.firebase.messaging.FCMRefreshToken"); getReactApplicationContext().registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - WritableMap params = Arguments.createMap(); - params.putString("token", intent.getStringExtra("token")); - ReactContext ctx = getReactApplicationContext(); - Log.d(TAG, "initRefreshTokenHandler received event " + EVENT_NAME_TOKEN); - Utils.sendEvent(ctx, EVENT_NAME_TOKEN, params); + if (getReactApplicationContext().hasActiveCatalystInstance()) { + String token = intent.getStringExtra("token"); + sendEvent("FCMTokenRefreshed", token); + } } - - ; - }, mRefreshTokenIntentFilter); + }, intentFilter); } @ReactMethod - public void subscribeToTopic(String topic, final Callback callback) { - try { - FirebaseMessaging.getInstance().subscribeToTopic(topic); - callback.invoke(null, topic); - } catch (Exception e) { - e.printStackTrace(); - Log.d(TAG, "Firebase token: " + e); - WritableMap error = Arguments.createMap(); - error.putString("message", e.getMessage()); - callback.invoke(error); - - } - } - - @ReactMethod - public void getToken(final Promise promise) { - try { - String token = FirebaseInstanceId.getInstance().getToken(); - Log.d(TAG, "Firebase token: " + token); - promise.resolve(token); - } catch (Exception e) { - promise.reject("messaging/unknown", e.getMessage(), e); - } - } - - @ReactMethod - public void unsubscribeFromTopic(String topic, final Callback callback) { - try { - FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); - callback.invoke(null, topic); - } catch (Exception e) { - WritableMap error = Arguments.createMap(); - error.putString("message", e.getMessage()); - callback.invoke(error); - } - } - - // String senderId, String messageId, String messageType, - @ReactMethod - public void send(ReadableMap params, final Promise promise) { - ReadableMap data = params.getMap("data"); + public void send(String senderId, ReadableMap payload) throws Exception { FirebaseMessaging fm = FirebaseMessaging.getInstance(); - RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(params.getString("sender")); - - remoteMessage.setMessageId(params.getString("id")); - remoteMessage.setMessageType(params.getString("type")); - - if (params.hasKey("ttl")) { - remoteMessage.setTtl(params.getInt("ttl")); - } - - if (params.hasKey("collapseKey")) { - remoteMessage.setCollapseKey(params.getString("collapseKey")); - } - - ReadableMapKeySetIterator iterator = data.keySetIterator(); + RemoteMessage.Builder message = new RemoteMessage.Builder(senderId + "@gcm.googleapis.com") + .setMessageId(UUID.randomUUID().toString()); + ReadableMapKeySetIterator iterator = payload.keySetIterator(); while (iterator.hasNextKey()) { String key = iterator.nextKey(); - ReadableType type = data.getType(key); - if (type == ReadableType.String) { - remoteMessage.addData(key, data.getString(key)); - } + String value = getStringFromReadableMap(payload, key); + message.addData(key, value); } + fm.send(message.build()); + } - try { - fm.send(remoteMessage.build()); - WritableMap res = Arguments.createMap(); - promise.resolve(null); - } catch (Exception e) { - Log.e(TAG, "send: error sending message", e); - promise.reject("messaging/unknown", e.getMessage(), e); + private String getStringFromReadableMap(ReadableMap map, String key) throws Exception { + switch (map.getType(key)) { + case String: + return map.getString(key); + case Number: + try { + return String.valueOf(map.getInt(key)); + } catch (Exception e) { + return String.valueOf(map.getDouble(key)); + } + case Boolean: + return String.valueOf(map.getBoolean(key)); + default: + throw new Exception("Unknown data type: " + map.getType(key).name() + " for message key " + key); } } - private void initSendHandler() { + private void registerMessageHandler() { + IntentFilter intentFilter = new IntentFilter("io.invertase.firebase.messaging.ReceiveNotification"); + getReactApplicationContext().registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - WritableMap params = Arguments.createMap(); - if (intent.getBooleanExtra("hasError", false)) { - WritableMap error = Arguments.createMap(); - error.putInt("code", intent.getIntExtra("errCode", 0)); - error.putString("message", intent.getStringExtra("errorMessage")); - params.putMap("err", error); - } else { - params.putNull("err"); + 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 data = message.getData(); + Set keysIterator = data.keySet(); + for (String key : keysIterator) { + params.putString(key, data.get(key)); + } + } + sendEvent("FCMNotificationReceived", params); + } - ReactContext ctx = getReactApplicationContext(); - Utils.sendEvent(ctx, EVENT_NAME_SEND, params); } - }, mReceiveSendIntentFilter); + }, 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()) { + sendEvent("FCMNotificationReceived", 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? + sendEvent("FCMNotificationReceived", parseIntent(intent)); } } diff --git a/lib/modules/messaging/index.js b/lib/modules/messaging/index.js index 2ee14fed..1f904f23 100644 --- a/lib/modules/messaging/index.js +++ b/lib/modules/messaging/index.js @@ -1,9 +1,30 @@ -import { NativeModules, NativeEventEmitter } from 'react-native'; +import { NativeModules, DeviceEventEmitter, Platform } from 'react-native'; import { Base } from './../base'; -import { promisify } from './../../utils'; const FirebaseMessaging = NativeModules.RNFirebaseMessaging; -const FirebaseMessagingEvt = new NativeEventEmitter(FirebaseMessaging); + +export const EVENT_TYPE = { + RefreshToken: 'FCMTokenRefreshed', + Notification: 'FCMNotificationReceived', +}; + +export const NOTIFICATION_TYPE = { + Remote: 'remote_notification', + NotificationResponse: 'notification_response', + WillPresent: 'will_present_notification', + Local: 'local_notification', +}; + +export const REMOTE_NOTIFICATION_RESULT = { + NewData: 'UIBackgroundFetchResultNewData', + NoData: 'UIBackgroundFetchResultNoData', + ResultFailed: 'UIBackgroundFetchResultFailed', +}; + +export const WILL_PRESENT_RESULT = { + All: 'UNNotificationPresentationOptionAll', + None: 'UNNotificationPresentationOptionNone', +}; type RemoteMessage = { id: string, @@ -14,6 +35,48 @@ type RemoteMessage = { data: Object, }; +/** + * IOS only finish function + * @param data + */ +function finish(data) { + if (Platform.OS !== 'ios') { + return; + } + + + if (!this._finishCalled && this._completionHandlerId) { + let result = Object.assign({}, 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); + return; + default: + return; + } + } +} + + /** * @class Messaging */ @@ -23,80 +86,150 @@ export default class Messaging extends Base { this.namespace = 'firebase:messaging'; } - /* - * WEB API + get EVENT_TYPE() { + return EVENT_TYPE; + } + + get NOTIFICATION_TYPE() { + return NOTIFICATION_TYPE; + } + + get REMOTE_NOTIFICATION_RESULT() { + return REMOTE_NOTIFICATION_RESULT; + } + + get WILL_PRESENT_RESULT() { + return WILL_PRESENT_RESULT; + } + + /** + * Returns the notification that triggered application open + * @returns {*} */ - // TODO move to new event emitter logic - onMessage(callback) { - this.log.info('Setting up onMessage callback'); - const sub = this._on('RNFirebaseReceiveNotification', callback, FirebaseMessagingEvt); - return promisify(() => sub, FirebaseMessaging)(sub); - } - - // TODO this is wrong - also there is no 'off' onMessage should return the unsubscribe function? - offMessage() { - this.log.info('Unlistening from onMessage (offMessage)'); - this._off('FirebaseReceiveNotification'); - } - - offMessageReceived(...args) { - return this.offMessage(...args); + getInitialNotification() { + return FirebaseMessaging.getInitialNotification(); } + /** + * Returns the fcm token for the current device + * @returns {*|Promise.} + */ getToken() { return FirebaseMessaging.getToken(); } - send(remoteMessage: RemoteMessage) { - if (!remoteMessage || !remoteMessage.data) return Promise.reject(new Error('Invalid remote message format provided.')); - return FirebaseMessaging.send(remoteMessage); + /** + * Create and display a local notification + * @param notification + * @returns {*} + */ + createLocalNotification(notification: Object) { + const _notification = Object.assign({}, notification); + _notification.id = _notification.id || new Date().getTime().toString(); + _notification.local_notification = true; + return FirebaseMessaging.createLocalNotification(_notification); } - // - listenForTokenRefresh(callback) { - this.log.info('Setting up listenForTokenRefresh callback'); - const sub = this._on('RNFirebaseRefreshToken', callback, FirebaseMessagingEvt); - return promisify(() => sub, FirebaseMessaging)(sub); + /** + * + * @param notification + * @returns {*} + */ + scheduleLocalNotification(notification: Object) { + const _notification = Object.assign({}, notification); + if (!notification.id) return Promise.reject(new Error('An id is required to schedule a local notification.')); + _notification.local_notification = true; + return FirebaseMessaging.scheduleLocalNotification(_notification); } - unlistenForTokenRefresh() { - this.log.info('Unlistening for TokenRefresh'); - this._off('RNFirebaseRefreshToken'); + /** + * Returns an array of all scheduled notifications + * @returns {Promise.} + */ + getScheduledLocalNotifications() { + return FirebaseMessaging.getScheduledLocalNotifications(); } - subscribeToTopic(topic) { - this.log.info(`subscribeToTopic ${topic}`); - const finalTopic = `/topics/${topic}`; - return promisify('subscribeToTopic', FirebaseMessaging)(finalTopic); + /** + * Cancel a local notification by id - using '*' will cancel + * all local notifications. + * @param id + * @returns {*} + */ + cancelLocalNotification(id: string) { + if (!id) return null; + if (id === '*') return FirebaseMessaging.cancelAllLocalNotifications(); + return FirebaseMessaging.cancelLocalNotification(id); } - unsubscribeFromTopic(topic) { - this.log.info(`unsubscribeFromTopic ${topic}`); - const finalTopic = `/topics/${topic}`; - return promisify('unsubscribeFromTopic', FirebaseMessaging)(finalTopic); + /** + * Remove a delivered notification - using '*' will remove + * all delivered notifications. + * @param id + * @returns {*} + */ + removeDeliveredNotification(id: string) { + if (!id) return null; + if (id === '*') return FirebaseMessaging.removeAllDeliveredNotifications(); + return FirebaseMessaging.removeDeliveredNotification(id); } - // New api - onRemoteMessage(callback) { - this.log.info('On remote message callback'); - const sub = this._on('messaging_remote_event_received', callback, FirebaseMessagingEvt); - return promisify(() => sub, FirebaseMessaging)(sub); + /** + * Set notification count badge number + */ + setBadgeNumber() { + FirebaseMessaging.setBadgeNumber(); } - onLocalMessage(callback) { - this.log.info('on local callback'); - const sub = this._on('messaging_local_event_received', callback, FirebaseMessagingEvt); - return promisify(() => sub, FirebaseMessaging)(sub); + /** + * set notification count badge number + * @returns {Promise.} + */ + getBadgeNumber() { + return FirebaseMessaging.getBadgeNumber(); } - listenForReceiveUpstreamSend(callback) { - this.log.info('Setting up send callback'); - const sub = this._on('FirebaseUpstreamSend', callback, FirebaseMessagingEvt); - return promisify(() => sub, FirebaseMessaging)(sub); + /** + * Subscribe to messages / notifications + * @param listener + * @returns {*} + */ + onMessageReceived(listener: Function) { + return DeviceEventEmitter.addListener(EVENT_TYPE.Notification, async(event) => { + const data = Object.assign({}, event); + + data.finish = finish; + + await listener(data); + + if (!data._finishCalled) { + data.finish(); + } + }); } - unlistenForReceiveUpstreamSend() { - this.log.info('Unlistening for send'); - this._off('FirebaseUpstreamSend'); + /** + * Subscribe to token refresh events + * @param listener + * @returns {*} + */ + onTokenRefresh(listener: Function) { + return DeviceEventEmitter.addListener(EVENT_TYPE.RefreshToken, listener); + } + + /** + * Subscribe to a topic + * @param topic + */ + subscribeToTopic(topic: String) { + FirebaseMessaging.subscribeToTopic(topic); + } + + /** + * Unsubscribe from a topic + * @param topic + */ + unsubscribeFromTopic(topic: String) { + FirebaseMessaging.unsubscribeFromTopic(topic); } }