Skip to content

Commit

Permalink
[messaging] getToken/deleteToken/onTokenRefresh improvements (#1805)
Browse files Browse the repository at this point in the history
[IOS] [BUGFIX] [MESSAGING] fix getToken() always returning initialToken (#1510)

[ANDROID] [NOTICE] [MESSAGING] deprecate RNFirebaseInstanceIdService in favour of FirebaseMessagingService's onNewToken event. Remove this service from your AndroidManifest.xml

[IOS] [ANDROID] [INTERNAL] [MESSAGING] getToken & deleteToken now use Firebase InstanceID token management apis (with an FCM scope).
  • Loading branch information
Salakar authored Jan 3, 2019
1 parent a8ac5ef commit 1d83962
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package io.invertase.firebase.messaging;

import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.google.firebase.iid.FirebaseInstanceIdService;

public class RNFirebaseInstanceIdService extends FirebaseInstanceIdService {
public static final String TOKEN_REFRESH_EVENT = "messaging-token-refresh";
// public static final String TOKEN_REFRESH_EVENT = "messaging-token-refresh";
private static final String TAG = "RNFInstanceIdService";

// TODO now deprecated, remove in v6
@Override
public void onTokenRefresh() {
Log.d(TAG, "onTokenRefresh event received");

// Build an Intent to pass the token to the RN Application
Intent tokenRefreshEvent = new Intent(TOKEN_REFRESH_EVENT);

// Broadcast it so it is only available to the RN Application
LocalBroadcastManager
.getInstance(this)
.sendBroadcast(tokenRefreshEvent);
Log.d(TAG, "DEPRECATED onTokenRefresh event received");
//
// // Build an Intent to pass the token to the RN Application
// Intent tokenRefreshEvent = new Intent(TOKEN_REFRESH_EVENT);
//
// // Broadcast it so it is only available to the RN Application
// LocalBroadcastManager
// .getInstance(this)
// .sendBroadcast(tokenRefreshEvent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.RemoteMessage;

import java.io.IOException;

import javax.annotation.Nonnull;

import io.invertase.firebase.Utils;
Expand All @@ -39,14 +41,13 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
new IntentFilter(RNFirebaseMessagingService.MESSAGE_EVENT)
);

// Subscribe to token refresh events
// Subscribe to new token events
localBroadcastManager.registerReceiver(
new RefreshTokenReceiver(),
new IntentFilter(RNFirebaseInstanceIdService.TOKEN_REFRESH_EVENT)
new IntentFilter(RNFirebaseMessagingService.NEW_TOKEN_EVENT)
);
}


@Override
public String getName() {
return "RNFirebaseMessaging";
Expand All @@ -57,13 +58,24 @@ public void getToken(Promise promise) {
try {
String senderId = FirebaseApp.getInstance().getOptions().getGcmSenderId();
String token = FirebaseInstanceId
.getInstance()
.getToken(senderId, "FCM");
Log.d(TAG, "Firebase token: " + token);
.getInstance()
.getToken(senderId, FirebaseMessaging.INSTANCE_ID_SCOPE);
promise.resolve(token);
} catch (Throwable e) {
e.printStackTrace();
promise.reject(null,e.getMessage());
e.printStackTrace();
promise.reject("messaging/fcm-token-error", e.getMessage());
}
}

@ReactMethod
public void deleteToken(Promise promise) {
try {
String senderId = FirebaseApp.getInstance().getOptions().getGcmSenderId();
FirebaseInstanceId.getInstance().deleteToken(senderId, FirebaseMessaging.INSTANCE_ID_SCOPE);
promise.resolve(null);
} catch (Throwable e) {
e.printStackTrace();
promise.reject("messaging/fcm-token-error", e.getMessage());
}
}

Expand Down Expand Up @@ -111,9 +123,7 @@ public void sendMessage(ReadableMap messageMap, Promise promise) {
}
}

FirebaseMessaging
.getInstance()
.send(mb.build());
FirebaseMessaging.getInstance().send(mb.build());

// TODO: Listen to onMessageSent and onSendError for better feedback?
promise.resolve(null);
Expand Down Expand Up @@ -177,12 +187,29 @@ private class RefreshTokenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
String token = FirebaseInstanceId
.getInstance()
.getToken();
Log.d(TAG, "Received new FCM token: " + token);
Log.d(TAG, "Received new messaging token.");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String token = null;
String senderId = FirebaseApp.getInstance().getOptions().getGcmSenderId();

try {
token = FirebaseInstanceId
.getInstance()
.getToken(senderId, FirebaseMessaging.INSTANCE_ID_SCOPE);
} catch (IOException e) {
Log.d(TAG, "onNewToken error", e);
}

if (token != null) {
Log.d(TAG, "Sending new messaging token event.");
Utils.sendEvent(getReactApplicationContext(), "messaging_token_refreshed", token);
}
}
});

Utils.sendEvent(getReactApplicationContext(), "messaging_token_refreshed", token);
thread.start();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,21 @@
import io.invertase.firebase.Utils;

public class RNFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "RNFMessagingService";

public static final String MESSAGE_EVENT = "messaging-message";
public static final String NEW_TOKEN_EVENT = "messaging-token-refresh";
public static final String REMOTE_NOTIFICATION_EVENT = "notifications-remote-notification";
private static final String TAG = "RNFMessagingService";

@Override
public void onNewToken(String token) {
Log.d(TAG, "onNewToken event received");

Intent newTokenEvent = new Intent(NEW_TOKEN_EVENT);
LocalBroadcastManager
.getInstance(this)
.sendBroadcast(newTokenEvent);
}

@Override
public void onMessageReceived(RemoteMessage message) {
Expand Down
42 changes: 27 additions & 15 deletions ios/RNFirebase/messaging/RNFirebaseMessaging.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import <FirebaseMessaging/FirebaseMessaging.h>
#import <FirebaseInstanceID/FIRInstanceID.h>

#import <React/RCTEventDispatcher.h>
#import <React/RCTConvert.h>
Expand Down Expand Up @@ -108,24 +107,37 @@ - (void)messaging:(nonnull FIRMessaging *)messaging

// ** Start React Module methods **
RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (initialToken) {
resolve(initialToken);
} else if ([[FIRInstanceID instanceID] token]) {
resolve([[FIRInstanceID instanceID] token]);
if (initialToken) {
resolve(initialToken);
initialToken = nil;
} else if ([[FIRMessaging messaging] FCMToken]) {
resolve([[FIRMessaging messaging] FCMToken]);
} else {
NSString * senderId = [[FIRApp defaultApp] options].GCMSenderID;
[[FIRMessaging messaging] retrieveFCMTokenForSenderID:senderId completion:^(NSString * _Nullable FCMToken, NSError * _Nullable error) {
if (error) {
reject(@"messaging/fcm-token-error", @"Failed to retrieve FCM token.", error);
} else if (FCMToken) {
resolve(FCMToken);
} else {
resolve([NSNull null]);
}
}];
}
}

RCT_EXPORT_METHOD(deleteToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
NSString * senderId = [[FIRApp defaultApp] options].GCMSenderID;
[[FIRMessaging messaging] deleteFCMTokenForSenderID:senderId completion:^(NSError * _Nullable error) {
if (error) {
reject(@"messaging/fcm-token-error", @"Failed to delete FCM token.", error);
} else {
NSString * senderId = [[FIRApp defaultApp] options].GCMSenderID;
[[FIRMessaging messaging] retrieveFCMTokenForSenderID:senderId completion:^(NSString * _Nullable FCMToken, NSError * _Nullable error) {
if (error) {
reject(@"messaging/fcm-token-error", @"Failed to retrieve FCM token.", error);
} else if (FCMToken) {
resolve(FCMToken);
} else {
resolve([NSNull null]);
}
}];
resolve([NSNull null]);
}
}];
}


RCT_EXPORT_METHOD(getAPNSToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
NSData *apnsToken = [FIRMessaging messaging].APNSToken;
if (apnsToken) {
Expand Down
4 changes: 2 additions & 2 deletions src/modules/messaging/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ export default class Messaging extends ModuleBase {
return getNativeModule(this).getToken();
}

deleteToken(authorizedEntity?: string, scope?: string): Promise<void> {
return this.app.iid().deleteToken(authorizedEntity, scope);
deleteToken(): Promise<void> {
return getNativeModule(this).deleteToken();
}

onMessage(nextOrObserver: OnMessage | OnMessageObserver): () => any {
Expand Down
14 changes: 7 additions & 7 deletions tests/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@
</intent-filter>
</service>

<service
android:name="io.invertase.firebase.messaging.RNFirebaseInstanceIdService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<!--<service-->
<!--android:name="io.invertase.firebase.messaging.RNFirebaseInstanceIdService"-->
<!--android:exported="false">-->
<!--<intent-filter>-->
<!--<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />-->
<!--</intent-filter>-->
<!--</service>-->

<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />

Expand Down
137 changes: 137 additions & 0 deletions tests/e2e/messaging/messaging.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
describe('messaging()', () => {
describe('requestPermission()', () => {
it('returns fcm token', async () => {
if (device.getPlatform() === 'android') {
await firebase.messaging().requestPermission();
}
});
});

describe('hasPermission()', () => {
it('returns fcm token', async () => {
const bool = await firebase.messaging().hasPermission();
if (device.getPlatform() === 'android') {
should.equal(bool, true);
}
});
});

describe('RemoteMessage', () => {
it('builds a remote message', async () => {
const message = new firebase.messaging.RemoteMessage();
message.messageId.should.be.a.String(); // default
message.setMessageId('123');
message.messageId.should.equal('123');

message.setData({ foo: 'bar' });
message.data.should.be.a.Object();

should.equal(message.ttl, 3600); // default
message.setTtl(666);
should.equal(message.ttl, 666);

should.equal(message.to, undefined);
message.setTo('some-topic');

message.build();
});
});

describe('sendMessage()', () => {
it('sends a message', async () => {
const message = new firebase.messaging.RemoteMessage();
message.setData({ foo: 'bar' });
message.setMessageType('data');
message.setTo(
`${firebase.app().options.messagingSenderId}@gcm.googleapis.com`
);
await firebase.messaging().sendMessage(message);
// TODO test with new firebase admin testing api
// const { promise, resolve, reject } = Promise.defer();
// const unsubscribe = firebase.messaging().onMessage(msg => {
// resolve();
// });
// await promise;
// unsubscribe();
});
});

describe('subscribeToTopic()', () => {
it('subscribe without error', async () => {
await firebase.messaging().subscribeToTopic('foo-bar-baz');
// TODO test subscription with new firebase testing api
});
});

describe('unsubscribeFromTopic()', () => {
it('unsubscribe without error', async () => {
await firebase.messaging().unsubscribeFromTopic('foo-bar-baz');
// TODO test unsub with new firebase testing api
});
});

describe('getToken()', () => {
it('returns fcm token', async () => {
const token = await firebase.messaging().getToken();
token.should.be.a.String();
});
});

describe('onTokenRefresh()', () => {
it('triggers when token changes', async () => {
let refreshedToken = null;
let unsubscribe = null;

const tokenBefore = await firebase.messaging().getToken();
tokenBefore.should.be.a.String();

const { promise, resolve, reject } = Promise.defer();
unsubscribe = firebase.messaging().onTokenRefresh(newToken => {
unsubscribe();

try {
newToken.should.be.a.String();
tokenBefore.should.not.equal(newToken);
} catch (e) {
return reject(e);
}

refreshedToken = newToken;
return resolve();
});

await firebase.messaging().deleteToken();
await sleep(250);
await firebase.iid().delete();
await sleep(250);
await firebase.iid().get();
await sleep(250);

const tokenAfter = await firebase.messaging().getToken();
tokenAfter.should.be.a.String();
tokenBefore.should.not.equal(tokenAfter);

// TODO ios triggers twice, on initial token and new
if (device.getPlatform() === 'android') {
tokenAfter.should.equal(refreshedToken);
}

await promise;

await sleep(500);
});
});

describe('deleteToken()', () => {
it('deletes the current fcm token', async () => {
const tokenBefore = await firebase.messaging().getToken();
tokenBefore.should.be.a.String();
await firebase.messaging().deleteToken();

const tokenAfter = await firebase.messaging().getToken();
tokenAfter.should.be.a.String();
tokenBefore.should.not.equal(tokenAfter);
await sleep(500);
});
});
});
1 change: 1 addition & 0 deletions tests/index.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./app');
Loading

0 comments on commit 1d83962

Please sign in to comment.