Skip to content

Commit

Permalink
feat(messaging, ios): new setAPNSToken API / getToken works on M1 Sim…
Browse files Browse the repository at this point in the history
…ulator
  • Loading branch information
mikehardy committed Feb 1, 2023
1 parent 47c20ca commit 8d75b36
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 23 deletions.
195 changes: 184 additions & 11 deletions packages/messaging/e2e/messaging.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,21 @@
*
*/

describe('messaging() modular', function () {
async function isSimulator() {
return await DeviceInfo.isEmulator();
}

async function isAPNSCapableSimulator() {
supportedAbis = await DeviceInfo.supportedAbis(); // looking for an ARM Simulator implying M1 host
iosVersionMajor = DeviceInfo.getSystemVersion().split('.')[0]; // looking for iOS16+
macOSVersionMajor = require('os').release().split('.')[0]; // host macOS13+ has Darwin kernel 22+
if (macOSVersionMajor >= 22 && supportedAbis.includes('ARM64E') && iosVersionMajor >= 16) {
return true;
}
return false;
}

describe('messaging()', function () {
describe('firebase v8 compatibility', function () {
describe('namespace', function () {
it('accessible from firebase.app()', function () {
Expand Down Expand Up @@ -74,9 +88,10 @@ describe('messaging() modular', function () {
});
it('successfully unregisters on ios', async function () {
if (device.getPlatform() === 'ios') {
should.equal(firebase.messaging().isDeviceRegisteredForRemoteMessages, true);
await firebase.messaging().unregisterDeviceForRemoteMessages();
should.equal(firebase.messaging().isDeviceRegisteredForRemoteMessages, false);
await firebase.messaging().registerDeviceForRemoteMessages();
should.equal(firebase.messaging().isDeviceRegisteredForRemoteMessages, true);
} else {
this.skip();
}
Expand Down Expand Up @@ -116,9 +131,84 @@ describe('messaging() modular', function () {
this.skip();
}
});
it('resolves null on ios if using simulator', async function () {
it('resolves on ios with token on supported simulators', async function () {
// Make sure we are registered for remote notifications, else no token
await firebase.messaging().registerDeviceForRemoteMessages();

if (device.getPlatform() === 'ios') {
should.equal(await firebase.messaging().getAPNSToken(), null);
apnsToken = await firebase.messaging().getAPNSToken();

simulator = await isSimulator();
aPNSCapableSimulator = await isAPNSCapableSimulator();

if (!simulator || (simulator && aPNSCapableSimulator)) {
apnsToken.should.be.a.String();
} else {
// unsupported iOS Simulator returns null (typically,
// can attempt-but-fail if M1 Simulator but not macOS13+/ios16+, rare combo)
if (apnsToken !== null) {
apnsToken.should.be.a.String();
}
}
} else {
this.skip();
}
});
});

describe('setAPNSToken', function () {
it('requires a token parameter', async function () {
try {
firebase.messaging().setAPNSToken();
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'token' expected a string value");
}
try {
firebase.messaging().setAPNSToken(123);
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'token' expected a string value");
return Promise.resolve();
}
});

it('verifies type parameter is valid if specified', async function () {
try {
firebase.messaging().setAPNSToken('typeparamtest', 123);
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'type' expected one of 'prod', 'sandbox', or 'unknown'");
}
try {
firebase.messaging().setAPNSToken('typeparamtest', 'bogus');
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'type' expected one of 'prod', 'sandbox', or 'unknown'");
}
});

it('resolves on android', async function () {
if (device.getPlatform() === 'android') {
should.equal(await firebase.messaging().setAPNSToken('foo'), null);
} else {
this.skip();
}
});

it('correctly sets new token on ios', async function () {
if (device.getPlatform() === 'ios') {
originalAPNSToken = await firebase.messaging().getAPNSToken();
// 74657374696E67746F6B656E is hex for "testingtoken"
await firebase.messaging().setAPNSToken('74657374696E67746F6B656E', 'unknown');
newAPNSToken = await firebase.messaging().getAPNSToken();
newAPNSToken.should.eql('74657374696E67746F6B656E');
newAPNSToken.should.not.eql(originalAPNSToken);
if (originalAPNSToken !== null) {
await firebase.messaging().setAPNSToken(originalAPNSToken);
}
} else {
this.skip();
}
});
});
Expand Down Expand Up @@ -367,7 +457,7 @@ describe('messaging() modular', function () {
});
});

describe('modular', function () {
describe('firebase v9 modular API', function () {
describe('getMessaging', function () {
it('pass app as argument', function () {
const { getMessaging } = messagingModular;
Expand Down Expand Up @@ -418,7 +508,7 @@ describe('messaging() modular', function () {
});
});

describe('isDeviceRegisteredForRemoteMessages', function () {
describe('isDeviceRegisteredForRemoteMessages default state', function () {
it('returns true on android', function () {
const { getMessaging, isDeviceRegisteredForRemoteMessages } = messagingModular;

Expand All @@ -430,7 +520,7 @@ describe('messaging() modular', function () {
});
});

describe('unregisterDeviceForRemoteMessages', function () {
describe('remote message device register / unregister', function () {
it('resolves on android, remains registered', async function () {
const {
getMessaging,
Expand All @@ -450,12 +540,14 @@ describe('messaging() modular', function () {
getMessaging,
unregisterDeviceForRemoteMessages,
isDeviceRegisteredForRemoteMessages,
registerDeviceForRemoteMessages,
} = messagingModular;

if (device.getPlatform() === 'ios') {
should.equal(isDeviceRegisteredForRemoteMessages(getMessaging()), true);
await unregisterDeviceForRemoteMessages(getMessaging());
should.equal(isDeviceRegisteredForRemoteMessages(getMessaging()), false);
await registerDeviceForRemoteMessages(getMessaging());
should.equal(isDeviceRegisteredForRemoteMessages(getMessaging()), true);
} else {
this.skip();
}
Expand Down Expand Up @@ -499,10 +591,91 @@ describe('messaging() modular', function () {
this.skip();
}
});
it('resolves null on ios if using simulator', async function () {
const { getMessaging, getAPNSToken } = messagingModular;
it('resolves on ios with token on supported simulators', async function () {
// Make sure we are registered for remote notifications, else no token
const { getMessaging, getAPNSToken, registerDeviceForRemoteMessages } = messagingModular;
await registerDeviceForRemoteMessages(getMessaging());

if (device.getPlatform() === 'ios') {
should.equal(await getAPNSToken(getMessaging()), null);
apnsToken = await getAPNSToken(getMessaging());

simulator = await isSimulator();
aPNSCapableSimulator = await isAPNSCapableSimulator();

if (!simulator || (simulator && aPNSCapableSimulator)) {
apnsToken.should.be.a.String();
} else {
// unsupported iOS Simulator returns null (typically,
// can attempt-but-fail if M1 Simulator but not macOS13+/ios16+, rare combo)
if (apnsToken !== null) {
apnsToken.should.be.a.String();
}
}
} else {
this.skip();
}
});
});

describe('setAPNSToken', function () {
it('requires a token parameter', async function () {
const { getMessaging, setAPNSToken } = messagingModular;
try {
setAPNSToken(getMessaging());
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'token' expected a string value");
}
try {
setAPNSToken(getMessaging(), 123);
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'token' expected a string value");
return Promise.resolve();
}
});

it('verifies type parameter is valid if specified', async function () {
const { getMessaging, setAPNSToken } = messagingModular;
try {
setAPNSToken(getMessaging(), 'typeparamtest', 123);
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'type' expected one of 'prod', 'sandbox', or 'unknown'");
}
try {
setAPNSToken(getMessaging(), 'typeparamtest', 'bogus');
return Promise.reject(new Error('Did not throw Error.'));
} catch (e) {
e.message.should.containEql("'type' expected one of 'prod', 'sandbox', or 'unknown'");
}
});

it('resolves on android', async function () {
const { getMessaging, setAPNSToken } = messagingModular;
if (device.getPlatform() === 'android') {
should.equal(await setAPNSToken(getMessaging(), 'foo'), null);
} else {
this.skip();
}
});

it('correctly sets new token on ios', async function () {
const { getMessaging, getAPNSToken, setAPNSToken } = messagingModular;
if (device.getPlatform() === 'ios') {
originalAPNSToken = await getAPNSToken(getMessaging());
// 74657374696E67746F6B656E6D6F64756C6172 is hex for "testingtokenmodular"
await firebase
.messaging()
.setAPNSToken('74657374696E67746F6B656E6D6F64756C6172', 'unknown');
newAPNSToken = await firebase.messaging().getAPNSToken();
newAPNSToken.should.eql('74657374696E67746F6B656E6D6F64756C6172');
newAPNSToken.should.not.eql(originalAPNSToken);
if (originalAPNSToken !== null) {
await setAPNSToken(getMessaging(), originalAPNSToken);
}
} else {
this.skip();
}
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ - (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)n
(RCTRootView *)[UIApplication sharedApplication].delegate.window.rootViewController.view;
}

#if !(TARGET_IPHONE_SIMULATOR)
// #if !(TARGET_IPHONE_SIMULATOR)
if ([[RNFBJSON shared] getBooleanValue:@"messaging_ios_auto_register_for_remote_messages"
defaultValue:YES]) {
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
#endif
// #endif

if (notification.userInfo[UIApplicationLaunchOptionsRemoteNotificationKey]) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
Expand All @@ -108,7 +108,7 @@ - (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)n
}
}

#if !(TARGET_IPHONE_SIMULATOR)
// #if !(TARGET_IPHONE_SIMULATOR)
// When an app launches in the background (BG mode) and is launched with the notification
// launch option the app delegate method
// application:didReceiveRemoteNotification:fetchCompletionHandler: will not get called unless
Expand All @@ -118,7 +118,7 @@ - (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)n
// `messaging_ios_auto_register_for_remote_messages` as this is most likely an app launching
// as a result of a remote notification - so has been registered previously
[[UIApplication sharedApplication] registerForRemoteNotifications];
#endif
// #endif
} else {
if (rctRootView != nil) {
isHeadless = NO;
Expand Down
49 changes: 42 additions & 7 deletions packages/messaging/ios/RNFBMessaging/RNFBMessagingModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ - (NSDictionary *)constantsToExport {
: (NSString *)senderId
: (RCTPromiseResolveBlock)resolve
: (RCTPromiseRejectBlock)reject) {
#if !(TARGET_IPHONE_SIMULATOR)
// #if !(TARGET_IPHONE_SIMULATOR)
if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == NO) {
[RNFBSharedUtils
rejectPromiseWithUserInfo:reject
Expand All @@ -126,7 +126,7 @@ - (NSDictionary *)constantsToExport {
}];
return;
}
#endif
// #endif

[[FIRMessaging messaging]
retrieveFCMTokenForSenderID:senderId
Expand Down Expand Up @@ -160,7 +160,17 @@ - (NSDictionary *)constantsToExport {
if (apnsToken) {
resolve([RNFBMessagingSerializer APNSTokenFromNSData:apnsToken]);
} else {
#if !(TARGET_IPHONE_SIMULATOR)
#if TARGET_IPHONE_SIMULATOR
#if !TARGET_CPU_ARM64
DLog(@"RNFBMessaging getAPNSToken - Simulator without APNS support detected, with no token "
@"set. Use setAPNSToken with an arbitrary string if needed for testing.")
resolve([NSNull null]);
return;
#endif
DLog(@"RNFBMessaging getAPNSToken - ARM64 Simulator detected, but no APNS token set. Assuming "
@"APNS token is possible. macOS13+ / iOS16+ / M1 mac required for assumption to be valid. "
@"Use setAPNSToken in testing if needed.");
#endif
if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == NO) {
[RNFBSharedUtils
rejectPromiseWithUserInfo:reject
Expand All @@ -172,11 +182,28 @@ - (NSDictionary *)constantsToExport {
}];
return;
}
#endif
resolve([NSNull null]);
}
}

RCT_EXPORT_METHOD(setAPNSToken
: (NSString *)token
: (NSString *)type
: (RCTPromiseResolveBlock)resolve
: (RCTPromiseRejectBlock)reject) {
// Default to unknown (determined by provisioning profile) type, but user may have passed type as
// param
FIRMessagingAPNSTokenType tokenType = FIRMessagingAPNSTokenTypeUnknown;
if (type != nil && [@"prod" isEqualToString:type]) {
tokenType = FIRMessagingAPNSTokenTypeProd;
} else if (type != nil && [@"sandbox" isEqualToString:type]) {
tokenType = FIRMessagingAPNSTokenTypeSandbox;
}

[[FIRMessaging messaging] setAPNSToken:[RNFBMessagingSerializer APNSTokenDataFromNSString:token]
type:tokenType];
resolve([NSNull null]);
}

RCT_EXPORT_METHOD(getIsHeadless : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) {
RNFBMessagingNSNotificationCenter *notifCenter =
[RNFBMessagingNSNotificationCenter sharedInstance];
Expand Down Expand Up @@ -267,12 +294,19 @@ - (NSDictionary *)constantsToExport {
: (RCTPromiseResolveBlock)resolve
: (RCTPromiseRejectBlock)reject) {
#if TARGET_IPHONE_SIMULATOR
#if !TARGET_CPU_ARM64
// Do the registration on this unsupported simulator, but don't set up to wait for a token that
// won't arrive
[[UIApplication sharedApplication] registerForRemoteNotifications];
resolve(@([RCTConvert BOOL:@(YES)]));
return;
#endif
DLog(@"RNFBMessaging registerForRemoteNotifications ARM64 Simulator detected, attempting real "
@"registration. macOS13+ / iOS16+ / M1 mac required or will timeout.")
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
if (@available(iOS 10.0, *)) {
if (@available(iOS 10.0, *)) {
#pragma pop
if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == YES) {
resolve(@([RCTConvert BOOL:@(YES)]));
Expand All @@ -286,7 +320,8 @@ - (NSDictionary *)constantsToExport {
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] registerForRemoteNotifications];
});
} else {
}
else {
[RNFBSharedUtils
rejectPromiseWithUserInfo:reject
userInfo:[@{
Expand Down
Loading

0 comments on commit 8d75b36

Please sign in to comment.