aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Example/Messaging/App/iOS/NotificationsController.swift8
-rw-r--r--Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m45
-rw-r--r--Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m142
3 files changed, 157 insertions, 38 deletions
diff --git a/Example/Messaging/App/iOS/NotificationsController.swift b/Example/Messaging/App/iOS/NotificationsController.swift
index a6b1544..eef6c57 100644
--- a/Example/Messaging/App/iOS/NotificationsController.swift
+++ b/Example/Messaging/App/iOS/NotificationsController.swift
@@ -121,7 +121,6 @@ class NotificationsController: NSObject {
// MARK: - UNUserNotificationCenterDelegate
@available(iOS 10.0, *)
extension NotificationsController: UNUserNotificationCenterDelegate {
-
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@@ -132,4 +131,11 @@ extension NotificationsController: UNUserNotificationCenterDelegate {
print("\(jsonString)")
completionHandler([.alert, .badge, .sound])
}
+
+ func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+ print("Received notification response")
+ let jsonString = response.notification.request.content.userInfo.jsonString ?? "{}"
+ print("\(jsonString)")
+ completionHandler()
+ }
}
diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m
index 9138c50..1e1cbf3 100644
--- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m
+++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m
@@ -53,6 +53,8 @@ void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler(
void (^handler)(UIBackgroundFetchResult));
void FCM_swizzle_willPresentNotificationWithHandler(
id self, SEL _cmd, id center, id notification, void (^handler)(NSUInteger));
+void FCM_swizzle_didReceiveNotificationResponseWithHandler(
+ id self, SEL _cmd, id center, id response, void (^handler)());
@end
@@ -90,6 +92,7 @@ void FCM_swizzle_willPresentNotificationWithHandler(
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@interface FakeUserNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
@property(nonatomic) BOOL willPresentWasCalled;
+@property(nonatomic) BOOL didReceiveResponseWasCalled;
@end
@implementation FakeUserNotificationCenterDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
@@ -98,6 +101,9 @@ void FCM_swizzle_willPresentNotificationWithHandler(
completionHandler {
self.willPresentWasCalled = YES;
}
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
+ self.didReceiveResponseWasCalled = YES;
+}
@end
#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@@ -219,7 +225,7 @@ void FCM_swizzle_willPresentNotificationWithHandler(
}
// Use a fake delegate that doesn't actually implement the needed delegate method.
-// Our swizzled method should still be called.
+// Our swizzled method should not be called.
- (void)testIncompleteUserNotificationCenterDelegateMethod {
// Early exit if running on pre iOS 10
@@ -229,20 +235,17 @@ void FCM_swizzle_willPresentNotificationWithHandler(
IncompleteUserNotificationCenterDelegate *delegate =
[[IncompleteUserNotificationCenterDelegate alloc] init];
[self.mockProxy swizzleUserNotificationCenterDelegate:delegate];
- SEL selector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
- XCTAssertTrue([delegate respondsToSelector:selector]);
- // Invoking delegate method should also invoke our swizzled method
- // The swizzled method uses the +sharedProxy, which should be
- // returning our mocked proxy.
- // Use non-nil, proper classes, otherwise our SDK bails out.
- [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
- willPresentNotification:[self generateMockNotification]
- withCompletionHandler:^(NSUInteger options) {}];
- // Verify our swizzled method was called
- OCMVerify(FCM_swizzle_willPresentNotificationWithHandler);
+ // Because the incomplete delete does not implement either of the optional delegate methods, we
+ // should swizzle nothing. If we had swizzled them, then respondsToSelector: would return YES
+ // even though the delegate does not implement the methods.
+ SEL willPresentSelector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
+ XCTAssertFalse([delegate respondsToSelector:willPresentSelector]);
+ SEL didReceiveResponseSelector =
+ @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
+ XCTAssertFalse([delegate respondsToSelector:didReceiveResponseSelector]);
}
-// Use an object that does actually implement the needed method. Both should be called.
+// Use an object that does actually implement the optional methods. Both should be called.
- (void)testSwizzledUserNotificationsCenterDelegate {
// Early exit if running on pre iOS 10
if (![UNNotification class]) {
@@ -261,6 +264,14 @@ void FCM_swizzle_willPresentNotificationWithHandler(
OCMVerify(FCM_swizzle_willPresentNotificationWithHandler);
// Verify our original method was called
XCTAssertTrue(delegate.willPresentWasCalled);
+
+ [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
+ didReceiveNotificationResponse:[self generateMockNotificationResponse]
+ withCompletionHandler:^{}];
+ // Verify our swizzled method was called
+ OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler);
+ // Verify our original method was called
+ XCTAssertTrue(delegate.didReceiveResponseWasCalled);
}
- (id)generateMockNotification {
@@ -274,6 +285,14 @@ void FCM_swizzle_willPresentNotificationWithHandler(
return mockNotification;
}
+- (id)generateMockNotificationResponse {
+ // Stub out: response.[mock notification above]
+ id mockNotificationResponse = OCMClassMock([UNNotificationResponse class]);
+ id mockNotification = [self generateMockNotification];
+ OCMStub([mockNotificationResponse notification]).andReturn(mockNotification);
+ return mockNotificationResponse;
+}
+
#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@end
diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m
index 214bd41..8790132 100644
--- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m
+++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m
@@ -28,6 +28,8 @@ static void * UserNotificationObserverContext = &UserNotificationObserverContext
static NSString *kUserNotificationWillPresentSelectorString =
@"userNotificationCenter:willPresentNotification:withCompletionHandler:";
+static NSString *kUserNotificationDidReceiveResponseSelectorString =
+ @"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:";
@interface FIRMessagingRemoteNotificationsProxy ()
@@ -219,10 +221,26 @@ static NSString *kUserNotificationWillPresentSelectorString =
if ([delegate conformsToProtocol:userNotificationCenterProtocol]) {
SEL willPresentNotificationSelector =
NSSelectorFromString(kUserNotificationWillPresentSelectorString);
- [self swizzleSelector:willPresentNotificationSelector
- inClass:[delegate class]
- withImplementation:(IMP)FCM_swizzle_willPresentNotificationWithHandler
- inProtocol:userNotificationCenterProtocol];
+ // Swizzle the optional method
+ // "userNotificationCenter:willPresentNotification:withCompletionHandler:", if it is
+ // implemented. Do not swizzle otherwise, as an implementation *will* be created, which will
+ // fool iOS into thinking that this method is implemented, and therefore not send notifications
+ // to the fallback method in the app delegate
+ // "application:didReceiveRemoteNotification:fetchCompletionHandler:".
+ if ([delegate respondsToSelector:willPresentNotificationSelector]) {
+ [self swizzleSelector:willPresentNotificationSelector
+ inClass:[delegate class]
+ withImplementation:(IMP)FCM_swizzle_willPresentNotificationWithHandler
+ inProtocol:userNotificationCenterProtocol];
+ }
+ SEL didReceiveNotificationResponseSelector =
+ NSSelectorFromString(kUserNotificationDidReceiveResponseSelectorString);
+ if ([delegate respondsToSelector:didReceiveNotificationResponseSelector]) {
+ [self swizzleSelector:didReceiveNotificationResponseSelector
+ inClass:[delegate class]
+ withImplementation:(IMP)FCM_swizzle_didReceiveNotificationResponseWithHandler
+ inProtocol:userNotificationCenterProtocol];
+ }
self.currentUserNotificationCenterDelegate = delegate;
self.hasSwizzledUserNotificationDelegate = YES;
}
@@ -235,6 +253,7 @@ static NSString *kUserNotificationWillPresentSelectorString =
}
SEL willPresentNotificationSelector =
NSSelectorFromString(kUserNotificationWillPresentSelectorString);
+ // Call unswizzle methods, even if the method was not implemented (it will fail gracefully).
[self unswizzleSelector:willPresentNotificationSelector
inClass:[self.currentUserNotificationCenterDelegate class]];
self.currentUserNotificationCenterDelegate = nil;
@@ -526,14 +545,97 @@ void FCM_swizzle_willPresentNotificationWithHandler(
return;
}
- // Valid original method signature, go ahead to swizzle.
+ // Attempt to access the user info
+ id notificationUserInfo = userInfoFromNotification(notification);
+
+ if (!notificationUserInfo) {
+ // Could not access notification.request.content.userInfo.
+ callOriginalMethodIfAvailable();
+ return;
+ }
+
+ [[FIRMessaging messaging] appDidReceiveMessage:notificationUserInfo];
+ // Execute the original implementation.
+ callOriginalMethodIfAvailable();
+}
+
+/**
+ * Swizzle the notification handler for iOS 10+ devices.
+ * Signature of original handler is as below:
+ * - (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ * didReceiveNotificationResponse:(UNNotificationResponse *)response
+ * withCompletionHandler:(void (^)(void))completionHandler
+ * In order to make FCM SDK compile and compatible with iOS SDKs before iOS 10, hide the
+ * parameter types from the swizzling implementation.
+ */
+void FCM_swizzle_didReceiveNotificationResponseWithHandler(
+ id self, SEL _cmd, id center, id response, void (^handler)()) {
+
+ FIRMessagingRemoteNotificationsProxy *proxy = [FIRMessagingRemoteNotificationsProxy sharedProxy];
+ IMP original_imp = [proxy originalImplementationForSelector:_cmd];
+
+ void (^callOriginalMethodIfAvailable)() = ^{
+ if (original_imp) {
+ ((void (*)(id, SEL, id, id, void (^)(void)))original_imp)(
+ self, _cmd, center, response, handler);
+ }
+ return;
+ };
+
+ Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter");
+ Class responseClass = NSClassFromString(@"UNNotificationResponse");
+ if (!center || ![center isKindOfClass:[notificationCenterClass class]]) {
+ // Invalid parameter type from the original method.
+ // Do not swizzle, just execute the original method.
+ callOriginalMethodIfAvailable();
+ return;
+ }
+
+ if (!response || ![response isKindOfClass:[responseClass class]]) {
+ // Invalid parameter type from the original method.
+ // Do not swizzle, just execute the original method.
+ callOriginalMethodIfAvailable();
+ return;
+ }
+
+ if (!handler) {
+ // Invalid parameter type from the original method.
+ // Do not swizzle, just execute the original method.
+ callOriginalMethodIfAvailable();
+ return;
+ }
+
+ // Try to access the response.notification property
+ SEL notificationSelector = NSSelectorFromString(@"notification");
+ if (![response respondsToSelector:notificationSelector]) {
+ // Cannot access the .notification property.
+ callOriginalMethodIfAvailable();
+ return;
+ }
+ id notificationClass = NSClassFromString(@"UNNotification");
+ id notification = getNamedPropertyFromObject(response, @"notification", notificationClass);
+
+ // With a notification object, use the common code to reach deep into notification
+ // (notification.request.content.userInfo)
+ id notificationUserInfo = userInfoFromNotification(notification);
+ if (!notificationUserInfo) {
+ // Could not access notification.request.content.userInfo.
+ callOriginalMethodIfAvailable();
+ return;
+ }
+
+ [[FIRMessaging messaging] appDidReceiveMessage:notificationUserInfo];
+ // Execute the original implementation.
+ callOriginalMethodIfAvailable();
+}
+
+id userInfoFromNotification(id notification) {
+
// Select the userInfo field from UNNotification.request.content.userInfo.
SEL requestSelector = NSSelectorFromString(@"request");
if (![notification respondsToSelector:requestSelector]) {
- // This is not the expected notification handler. Do not swizzle, just execute the original
- // method.
- callOriginalMethodIfAvailable();
- return;
+ // Cannot access the request property.
+ return nil;
}
Class requestClass = NSClassFromString(@"UNNotificationRequest");
id notificationRequest = getNamedPropertyFromObject(notification, @"request", requestClass);
@@ -541,10 +643,8 @@ void FCM_swizzle_willPresentNotificationWithHandler(
SEL notificationContentSelector = NSSelectorFromString(@"content");
if (!notificationRequest
|| ![notificationRequest respondsToSelector:notificationContentSelector]) {
- // This is not the expected notification handler. Do not swizzle, just execute the original
- // method.
- callOriginalMethodIfAvailable();
- return;
+ // Cannot access the content property.
+ return nil;
}
Class contentClass = NSClassFromString(@"UNNotificationContent");
id notificationContent = getNamedPropertyFromObject(notificationRequest,
@@ -554,25 +654,19 @@ void FCM_swizzle_willPresentNotificationWithHandler(
SEL notificationUserInfoSelector = NSSelectorFromString(@"userInfo");
if (!notificationContent
|| ![notificationContent respondsToSelector:notificationUserInfoSelector]) {
- // This is not the expected notification handler. Do not swizzle, just execute the original
- // method.
- callOriginalMethodIfAvailable();
- return;
+ // Cannot access the userInfo property.
+ return nil;
}
id notificationUserInfo = getNamedPropertyFromObject(notificationContent,
@"userInfo",
[NSDictionary class]);
if (!notificationUserInfo) {
- // This is not the expected notification handler. Do not swizzle, just execute the original
- // method.
- callOriginalMethodIfAvailable();
- return;
+ // This is not the expected notification handler.
+ return nil;
}
- [[FIRMessaging messaging] appDidReceiveMessage:notificationUserInfo];
- // Execute the original implementation.
- callOriginalMethodIfAvailable();
+ return notificationUserInfo;
}
void FCM_swizzle_applicationReceivedRemoteMessage(