From 1108901d366b1c640808f7653faad398f6a309cb Mon Sep 17 00:00:00 2001 From: Nori Date: Wed, 14 Sep 2016 12:22:33 +0900 Subject: [PATCH] add cloud messaging for android --- README.md | 70 ++++++- android/build.gradle | 1 + .../firestack/FirestackCloudMessaging.java | 192 ++++++++++++++++++ .../firestack/FirestackInstanceIdService.java | 33 +++ .../firestack/FirestackMessagingService.java | 72 +++++++ .../fullstack/firestack/FirestackPackage.java | 2 +- lib/firestack.js | 27 ++- lib/modules/cloudmessaging.js | 58 ++++++ 8 files changed, 442 insertions(+), 13 deletions(-) create mode 100644 android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java create mode 100644 android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java create mode 100644 android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java create mode 100644 lib/modules/cloudmessaging.js diff --git a/README.md b/README.md index 7787585..3a76bed 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ public class MainApplication extends Application implements ReactApplication { protected List getPackages() { return Arrays.asList( new MainReactPackage(), - new FirestackPackage() + new FirestackPackage(getApplicationContext()) ); } }; @@ -143,6 +143,22 @@ dependencies { } ``` +Add to `AndroidManifest.xml` file +```diff + ++ ++ ++ ++ ++ + ++ ++ ++ ++ ++ +``` + ## Firebase setup The Firestack library is intended on making it easy to work with [Firebase](https://firebase.google.com/) and provides a small native shim to the Firebase native code. @@ -667,6 +683,58 @@ Firebase provides some static values based upon the server. We can use the `Serv const timestamp = firestack.ServerValue.TIMESTAMP ``` +### Cloud Messaging + +Access the device registration token + +```javascript + firestack.cloudMessaging.getToken().then(function (token) { + console.log('device token', token); + }); +``` + +Monitor token generation + +```javascript + // add listener + firestack.cloudMessaging.listenForTokenRefresh(function (token) { + console.log('refresh device token', token); + }); + + // remove listener + firestack.cloudMessaging.unlistenForTokenRefresh(); +``` + +Subscribe to topic + +```javascript + firestack.cloudMessaging.subscribeToTopic("topic_name").then(function (topic) { + console.log('Subscribe:'+topic); + }).catch(function(err){ + console.error(err); + }); +``` + +Unsubscribe from topic + +```javascript + firestack.cloudMessaging.unsubscribeFromTopic("topic_name").then(function (topic) { + console.log('unsubscribe:'+topic); + }).catch(function(err){ + console.error(err); + }); +``` + +Receive Messages + +```javascript + firestack.cloudMessaging.listenForReceiveNotification((msg) =>{ + console.log('Receive Messages:'+msg.data); + console.log('Receive Messages:'+msg.notification); + + }); +``` + ### Events #### on() diff --git a/android/build.gradle b/android/build.gradle index 6987551..9288c13 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -24,4 +24,5 @@ dependencies { compile 'com.google.firebase:firebase-analytics:9.4.0' compile 'com.google.firebase:firebase-database:9.4.0' compile 'com.google.firebase:firebase-storage:9.4.0' + compile 'com.google.firebase:firebase-messaging:9.4.0' } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java b/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java new file mode 100644 index 0000000..84b8e81 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java @@ -0,0 +1,192 @@ +package io.fullstack.firestack; + +import java.util.Map; + +import android.content.Context; +import android.content.IntentFilter; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.Promise; +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.WritableMap; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.RemoteMessage; + +/** + * Created by nori on 2016/09/12. + */ +public class FirestackCloudMessaging extends ReactContextBaseJavaModule { + + private static final String TAG = "FirestackCloudMessaging"; + private static final String EVENT_NAME_TOKEN = "FirestackRefreshToken"; + private static final String EVENT_NAME_NOTIFICATION = "FirestackReceiveNotification"; + private static final String EVENT_NAME_SEND = "FirestackUpstreamSend"; + + public static final String INTENT_NAME_TOKEN = "io.fullstack.firestack.refreshToken"; + public static final String INTENT_NAME_NOTIFICATION = "io.fullstack.firestack.ReceiveNotification"; + public static final String INTENT_NAME_SEND = "io.fullstack.firestack.Upstream"; + + private ReactContext mReactContext; + private IntentFilter mRefreshTokenIntentFilter; + private IntentFilter mReceiveNotificationIntentFilter; + private IntentFilter mReceiveSendIntentFilter; + + public FirestackCloudMessaging(ReactApplicationContext reactContext) { + super(reactContext); + mReactContext = 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"); + } + + @Override + public String getName() { + return TAG; + } + + @ReactMethod + public void getToken(final Callback callback) { + + try { + String token = FirebaseInstanceId.getInstance().getToken(); + Log.d(TAG, "Firebase token: " + token); + callback.invoke(null, token); + } catch (Exception e) { + WritableMap error = Arguments.createMap(); + error.putString("message", e.getMessage()); + callback.invoke(error); + } + } + + /** + * + */ + private void initRefreshTokenHandler() { + getReactApplicationContext().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + WritableMap params = Arguments.createMap(); + params.putString("token", intent.getStringExtra("token")); + FirestackUtils.sendEvent(mReactContext, EVENT_NAME_TOKEN, params); + + } + + ; + }, mRefreshTokenIntentFilter); + } + + @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 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); + } + } + + private void initMessageHandler() { + getReactApplicationContext().registerReceiver(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(); + if (remoteMessage.getData().size() != 0) { + WritableMap dataMap = Arguments.createMap(); + Map data = remoteMessage.getData(); + //Set keysIterator = data.keySet(); + for (String key : data.keySet()) { + dataMap.putString(key, data.get(key)); + } + params.putMap("data", dataMap); + } else { + params.putNull("data"); + } + 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); + } else { + params.putNull("notification"); + } + FirestackUtils.sendEvent(mReactContext, EVENT_NAME_NOTIFICATION, params); + } + }, mReceiveNotificationIntentFilter); + } + + @ReactMethod + public void send(String senderId, Integer messageId, String messageType, ReadableMap params) { + FirebaseMessaging fm = FirebaseMessaging.getInstance(); + RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(senderId); + remoteMessage.setMessageId(messageId.toString()); + remoteMessage.setMessageType(messageType); + ReadableMapKeySetIterator iterator = params.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + ReadableType type = params.getType(key); + if (type == ReadableType.String) { + remoteMessage.addData(key, params.getString(key)); + Log.d(TAG, "Firebase send: " + key); + Log.d(TAG, "Firebase send: " + params.getString(key)); + } + } + fm.send(remoteMessage.build()); + } + + private void initSendHandler() { + 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"); + } + FirestackUtils.sendEvent(mReactContext, EVENT_NAME_SEND, params); + } + }, mReceiveSendIntentFilter); + } +} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java b/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java new file mode 100644 index 0000000..8922ef4 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java @@ -0,0 +1,33 @@ +package io.fullstack.firestack; + +/** + * Created by nori on 2016/09/12. + */ +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 FirestackInstanceIdService extends FirebaseInstanceIdService { + + private static final String TAG = "FSInstanceIdService"; + + /** + * + */ + @Override + public void onTokenRefresh() { + String refreshedToken = FirebaseInstanceId.getInstance().getToken(); + Log.d(TAG, "Refreshed token: " + refreshedToken); + + + // send Intent + Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_TOKEN); + Bundle bundle = new Bundle(); + bundle.putString("token", refreshedToken); + i.putExtras(bundle); + sendBroadcast(i); + } +} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java b/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java new file mode 100644 index 0000000..485e762 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java @@ -0,0 +1,72 @@ +package io.fullstack.firestack; + +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; + +public class FirestackMessagingService 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()); + } + if (remoteMessage.getNotification() != null) { + + } + Intent i = new Intent(FirestackCloudMessaging.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(FirestackCloudMessaging.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(FirestackCloudMessaging.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/fullstack/firestack/FirestackPackage.java b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java index c555bad..5596030 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java @@ -31,7 +31,7 @@ public List createNativeModules(ReactApplicationContext reactConte modules.add(new FirestackDatabaseModule(reactContext)); modules.add(new FirestackAnalyticsModule(reactContext)); modules.add(new FirestackStorageModule(reactContext)); - + modules.add(new FirestackCloudMessaging(reactContext)); return modules; } diff --git a/lib/firestack.js b/lib/firestack.js index 07a2893..b0a6d7d 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -25,6 +25,7 @@ import {Database} from './modules/database' import {Analytics} from './modules/analytics' import {Storage} from './modules/storage' import {Presence} from './modules/presence' +import {CloudMessaging} from './modules/cloudmessaging' let log; export class Firestack extends Singleton { @@ -52,7 +53,7 @@ export class Firestack extends Singleton { this._auth = new Authentication(this, this.options); } - configure(opts={}) { + configure(opts = {}) { if (!this.configurePromise) { const firestackOptions = Object.assign({}, this.options, opts); try { @@ -64,14 +65,14 @@ export class Firestack extends Singleton { log.info('Calling native configureWithOptions') this.configurePromise = promisify('configureWithOptions', FirestackModule)(firestackOptions) - .then((configuredProperties) => { - log.info('Native configureWithOptions success', configuredProperties); - this.configured = true; - this.firestackOptions = configuredProperties; - return configuredProperties; - }).catch((err) => { - log.error('Native error occurred while calling configure', err); - }) + .then((configuredProperties) => { + log.info('Native configureWithOptions success', configuredProperties); + this.configured = true; + this.firestackOptions = configuredProperties; + return configuredProperties; + }).catch((err) => { + log.error('Native error occurred while calling configure', err); + }) } return this.configurePromise; } @@ -90,7 +91,7 @@ export class Firestack extends Singleton { if (!this._auth) { this._auth = new Authentication(this); } return this._auth; } - // database + // database get database() { if (!this._db) { this._db = new Database(this); } return this._db; @@ -115,7 +116,11 @@ export class Firestack extends Singleton { if (!this._presence) { this._presence = new Presence(this); } return this._presence; } - + // CloudMessaging + get cloudMessaging() { + if (!this._cloudMessaging) { this._cloudMessaging = new CloudMessaging(this); } + return this._cloudMessaging; + } // Storage // // /** diff --git a/lib/modules/cloudmessaging.js b/lib/modules/cloudmessaging.js new file mode 100644 index 0000000..4bc5f0e --- /dev/null +++ b/lib/modules/cloudmessaging.js @@ -0,0 +1,58 @@ +import {NativeModules, NativeEventEmitter} from 'react-native'; +const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; +const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); + +import promisify from '../utils/promisify' +import { Base, ReferenceBase } from './base' +export class CloudMessaging extends Base { + constructor(firestack, options = {}) { + super(firestack, options); + } + get namespace() { + return 'firestack:cloudMessaging' + } + getToken() { + this.log.info('getToken for cloudMessaging'); + return promisify('getToken', FirestackCloudMessaging)(); + } + send(senderId,messageId,messageType,msg){ + FirestackCloudMessaging.send(senderId,messageId,messageType,msg); + } + listenForTokenRefresh(callback) { + this.log.info('Setting up listenForTokenRefresh callback'); + const sub = this._on('FirestackRefreshToken', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + unlistenForTokenRefresh() { + this.log.info('Unlistening for TokenRefresh'); + this._off('FirestackRefreshToken'); + } + subscribeToTopic(topic) { + this.log.info('subscribeToTopic ' + topic); + return promisify('subscribeToTopic', FirestackCloudMessaging)(topic); + } + unsubscribeFromTopic(topic) { + this.log.info('unsubscribeFromTopic ' + topic); + return promisify('unsubscribeFromTopic', FirestackCloudMessaging)(topic); + } + listenForReceiveNotification(callback) { + this.log.info('Setting up listenForReceiveNotification callback'); + const sub = this._on('FirestackReceiveNotification', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + unlistenForReceiveNotification() { + this.log.info('Unlistening for ReceiveNotification'); + this._off('FirestackRefreshToken'); + } + listenForReceiveUpstreamSend(callback) { + this.log.info('Setting up send callback'); + const sub = this._on('FirestackUpstreamSend', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + unlistenForReceiveUpstreamSend() { + this.log.info('Unlistening for send'); + this._off('FirestackUpstreamSend'); + } +} + +export default CloudMessaging \ No newline at end of file