diff --git a/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.h b/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.h index b119bcd4d..7e565e87c 100644 --- a/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.h +++ b/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.h @@ -29,9 +29,10 @@ // This project exisits to make testing OneSignal SDK changes. #import +#import #import -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; diff --git a/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m b/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m index 635b87d19..8c2daa5dd 100644 --- a/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m +++ b/iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m @@ -59,6 +59,7 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDiction - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // [FIRApp configure]; + [UNUserNotificationCenter currentNotificationCenter].delegate = self; NSLog(@"Bundle URL: %@", [[NSBundle mainBundle] bundleURL]); // Uncomment to test LogListener @@ -197,17 +198,66 @@ - (void)applicationDidBecomeActive:(UIApplication *)application { - (void)applicationWillTerminate:(UIApplication *)application { } -// Remote +- (void)onLogEvent:(OneSignalLogEvent * _Nonnull)event { + NSLog(@"Dev App onLogEvent: %@", event.entry); +} + +#pragma mark - Manual Integration APIs (for use when swizzling is disabled) + +// Forward the APNs device token to OneSignal so it can register the device for push - (void)application:(UIApplication *)application -didReceiveRemoteNotification:(NSDictionary *)userInfo -fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - - NSLog(@"application:didReceiveRemoteNotification:fetchCompletionHandler: %@", userInfo); - completionHandler(UIBackgroundFetchResultNoData); + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + NSLog(@"Dev App application:didRegisterForRemoteNotificationsWithDeviceToken %@", deviceToken); + [OneSignal.Notifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } -- (void)onLogEvent:(OneSignalLogEvent * _Nonnull)event { - NSLog(@"Dev App onLogEvent: %@", event.entry); +// Forward APNs registration failures so OneSignal can log and retry appropriately +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + NSLog(@"Dev App application:didFailToRegisterForRemoteNotificationsWithError %@", error); + [OneSignal.Notifications didFailToRegisterForRemoteNotificationsWithError:error]; +} + +// Forward background / silent notifications for content-available processing +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + NSLog(@"Dev App application:didReceiveRemoteNotification %@", userInfo); + [OneSignal.Notifications didReceiveRemoteNotification:userInfo + completionHandler:completionHandler]; +} + +// Forward foreground notifications so the SDK can invoke onWillDisplayNotification listeners +// and determine whether to show a banner. Completion returns nil for IAM previews. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { + NSLog(@"Dev App userNotificationCenter:willPresentNotification %@", notification); + [OneSignal.Notifications + willPresentNotificationWithPayload:notification.request.content.userInfo + completion:^(OSNotification *notif) { + if (notif) { + if (@available(iOS 14.0, *)) { + completionHandler(UNNotificationPresentationOptionBanner | + UNNotificationPresentationOptionList | + UNNotificationPresentationOptionSound); + } else { + completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound); + } + } else { + completionHandler(UNNotificationPresentationOptionNone); + } + }]; +} + +// Forward notification tap / action so the SDK can fire onClickNotification listeners +// and handle deep links and action buttons +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler { + NSLog(@"Dev App userNotificationCenter:didReceiveNotificationResponse %@", response); + [OneSignal.Notifications didReceiveNotificationResponse:response]; + completionHandler(); } @end diff --git a/iOS_SDK/OneSignalDevApp/OneSignalDevApp/Info.plist b/iOS_SDK/OneSignalDevApp/OneSignalDevApp/Info.plist index 1bd38fe74..bd8031ea4 100644 --- a/iOS_SDK/OneSignalDevApp/OneSignalDevApp/Info.plist +++ b/iOS_SDK/OneSignalDevApp/OneSignalDevApp/Info.plist @@ -82,5 +82,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + OneSignal_disable_swizzling + diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h index 8b71d9258..3f903b4a7 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h @@ -155,6 +155,9 @@ #define GDPR_CONSENT_GRANTED @"GDPR_CONSENT_GRANTED" #define ONESIGNAL_REQUIRE_PRIVACY_CONSENT @"OneSignal_require_privacy_consent" +// Swizzling +#define ONESIGNAL_DISABLE_SWIZZLING @"OneSignal_disable_swizzling" + // Badge handling #define ONESIGNAL_DISABLE_BADGE_CLEARING @"OneSignal_disable_badge_clearing" #define ONESIGNAL_APP_GROUP_NAME_KEY @"OneSignal_app_groups_key" diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UIApplicationDelegate+OneSignalNotifications.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UIApplicationDelegate+OneSignalNotifications.m index 12aca3cd7..563429203 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UIApplicationDelegate+OneSignalNotifications.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UIApplicationDelegate+OneSignalNotifications.m @@ -111,10 +111,10 @@ + (BOOL)swizzledClassInHeirarchy:(Class)delegateClass { return false; } -- (void)oneSignalDidRegisterForRemoteNotifications:(UIApplication*)app deviceToken:(NSData*)inDeviceToken { +- (void)oneSignalDidRegisterForRemoteNotifications:(UIApplication*)app deviceToken:(NSData*)deviceToken { [OneSignalNotificationsAppDelegate traceCall:@"oneSignalDidRegisterForRemoteNotifications:deviceToken:"]; - [OSNotificationsManager didRegisterForRemoteNotifications:app deviceToken:inDeviceToken]; + [OSNotificationsManager processRegisteredDeviceToken:deviceToken]; SwizzlingForwarder *forwarder = [[SwizzlingForwarder alloc] initWithTarget:self @@ -125,14 +125,14 @@ - (void)oneSignalDidRegisterForRemoteNotifications:(UIApplication*)app deviceTok application:didRegisterForRemoteNotificationsWithDeviceToken: ) ]; - [forwarder invokeWithArgs:@[app, inDeviceToken]]; + [forwarder invokeWithArgs:@[app, deviceToken]]; } - (void)oneSignalDidFailRegisterForRemoteNotification:(UIApplication*)app error:(NSError*)err { [OneSignalNotificationsAppDelegate traceCall:@"oneSignalDidFailRegisterForRemoteNotification:error:"]; if ([OneSignalConfigManager getAppId]) - [OSNotificationsManager handleDidFailRegisterForRemoteNotification:err]; + [OSNotificationsManager processFailedRemoteNotificationsRegistration:err]; SwizzlingForwarder *forwarder = [[SwizzlingForwarder alloc] initWithTarget:self @@ -169,18 +169,10 @@ - (void) oneSignalReceiveRemoteNotification:(UIApplication*)application UserInfo let appState = [UIApplication sharedApplication].applicationState; let isVisibleNotification = userInfo[@"aps"][@"alert"] != nil; - // iOS 9 - Notification was tapped on - // https://medium.com/posts-from-emmerge/ios-push-notification-background-fetch-demystified-7090358bb66e - // - NOTE: We do not have the extra logic for the notifiation center or double tap home button cases - // of "inactive" on notification received the link above describes. - // Omiting that complex logic as iOS 9 usage stats are very low (12/11/2020) and these are rare cases. - if ([OSDeviceUtils isIOSVersionLessThan:@"10.0"] && appState == UIApplicationStateInactive && isVisibleNotification) { - [OSNotificationsManager notificationReceived:userInfo wasOpened:YES]; - } - else if (appState == UIApplicationStateActive && isVisibleNotification) + if (appState == UIApplicationStateActive && isVisibleNotification) [OSNotificationsManager notificationReceived:userInfo wasOpened:NO]; else - startedBackgroundJob = [OSNotificationsManager receiveRemoteNotification:application UserInfo:userInfo completionHandler:forwarder.hasReceiver ? nil : completionHandler]; + startedBackgroundJob = [OSNotificationsManager processReceivedRemoteNotification:userInfo completionHandler:forwarder.hasReceiver ? nil : completionHandler]; } if (forwarder.hasReceiver) { diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m index 0de40559a..ede5b9ff4 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m @@ -293,7 +293,7 @@ - (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler: Fired! %@", notification.request.content.body]]; - [OSNotificationsManager handleWillPresentNotificationInForegroundWithPayload:notification.request.content.userInfo withCompletion:^(OSNotification *responseNotif) { + [OSNotificationsManager processWillPresentNotificationWithPayload:notification.request.content.userInfo completion:^(OSNotification *responseNotif) { UNNotificationPresentationOptions displayType = responseNotif != nil ? (UNNotificationPresentationOptions)7 : (UNNotificationPresentationOptions)0; finishProcessingNotification(notification, center, displayType, completionHandler, self); }]; @@ -377,19 +377,7 @@ + (BOOL)isDismissEvent:(UNNotificationResponse *)response { } + (void)processiOS10Open:(UNNotificationResponse*)response { - if (![OneSignalConfigManager getAppId]) - return; - - if ([OneSignalNotificationsUNUserNotificationCenter isDismissEvent:response]) - return; - - if (![OneSignalCoreHelper isOneSignalPayload:response.notification.request.content.userInfo]) - return; - - let userInfo = [OneSignalCoreHelper formatApsPayloadIntoStandard:response.notification.request.content.userInfo - identifier:response.actionIdentifier]; - - [OSNotificationsManager notificationReceived:userInfo wasOpened:YES]; + [OSNotificationsManager processNotificationResponse:response]; } // Calls depercated pre-iOS 10 selector if one is set on the AppDelegate. diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h index a53d03d74..cff6ac32b 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h @@ -65,6 +65,18 @@ NS_SWIFT_NAME(onClick(event:)); + (void)addPermissionObserver:(NSObject*_Nonnull)observer NS_REFINED_FOR_SWIFT; + (void)removePermissionObserver:(NSObject*_Nonnull)observer NS_REFINED_FOR_SWIFT; + (void)clearAll; +// Manual integration APIs (for use when swizzling is disabled via Info.plist) ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *_Nonnull)deviceToken + NS_SWIFT_NAME(didRegisterForRemoteNotifications(deviceToken:)); ++ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *_Nonnull)error + NS_SWIFT_NAME(didFailToRegisterForRemoteNotifications(error:)); ++ (void)didReceiveRemoteNotification:(NSDictionary *_Nonnull)userInfo completionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler + NS_SWIFT_NAME(didReceiveRemoteNotification(userInfo:completionHandler:)); ++ (void)willPresentNotificationWithPayload:(NSDictionary *_Nonnull)payload completion:(OSNotificationDisplayResponse _Nonnull)completion + NS_SWIFT_NAME(willPresentNotification(payload:completion:)); ++ (void)didReceiveNotificationResponse:(UNNotificationResponse *_Nonnull)response + NS_SWIFT_NAME(didReceiveNotificationResponse(_:)); ++ (void)setBadgeCount:(NSInteger)badgeCount; @end @@ -82,7 +94,7 @@ NS_SWIFT_NAME(onClick(event:)); @property (class, weak, nonatomic, nullable) id delegate; + (Class _Nonnull)Notifications; -+ (void)start; ++ (void)startSwizzling; + (void)setColdStartFromTapOnNotification:(BOOL)coldStartFromTapOnNotification; + (BOOL)getColdStartFromTapOnNotification; @@ -116,10 +128,17 @@ NS_SWIFT_NAME(onClick(event:)); + (void)handleNotificationActionWithUrl:(NSString* _Nullable)url actionID:(NSString* _Nonnull)actionID; + (void)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll; -+ (BOOL)receiveRemoteNotification:(UIApplication* _Nonnull)application UserInfo:(NSDictionary* _Nonnull)userInfo completionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; + (void)notificationReceived:(NSDictionary* _Nonnull)messageDict wasOpened:(BOOL)opened; -+ (void)handleWillPresentNotificationInForegroundWithPayload:(NSDictionary * _Nonnull)payload withCompletion:(OSNotificationDisplayResponse _Nonnull)completion; -+ (void)didRegisterForRemoteNotifications:(UIApplication *_Nonnull)app deviceToken:(NSData *_Nonnull)inDeviceToken; -+ (void)handleDidFailRegisterForRemoteNotification:(NSError*_Nonnull)err; + (void)checkProvisionalAuthorizationStatus; ++ (void)registerLifecycleObserver; ++ (BOOL)isSwizzlingDisabled; + +// Internal entry points called by swizzled delegate paths +// These bypass the swizzling-active guard so the SDK doesn't block its own calls ++ (void)processRegisteredDeviceToken:(NSData *_Nonnull)deviceToken; ++ (void)processFailedRemoteNotificationsRegistration:(NSError *_Nonnull)error; ++ (BOOL)processReceivedRemoteNotification:(NSDictionary *_Nonnull)userInfo completionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; ++ (void)processWillPresentNotificationWithPayload:(NSDictionary *_Nonnull)payload completion:(OSNotificationDisplayResponse _Nonnull)completion; ++ (void)processNotificationResponse:(UNNotificationResponse *_Nonnull)response; + @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m index 95e29d101..d1e44de81 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m @@ -161,6 +161,15 @@ +(void)setDelegate:(id)delegate { static UIBackgroundTaskIdentifier _mediaBackgroundTask; static BOOL _disableBadgeClearing = NO; ++ (BOOL)isSwizzlingDisabled { + static BOOL _disabled = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + id plistValue = [[NSBundle mainBundle] objectForInfoDictionaryKey:ONESIGNAL_DISABLE_SWIZZLING]; + _disabled = plistValue != nil && [plistValue boolValue]; + }); + return _disabled; +} static BOOL _coldStartFromTapOnNotification = NO; // Set to false as soon as it's read. @@ -235,7 +244,7 @@ + (void)setPushSubscriptionId:(NSString *)pushSubscriptionId { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" -+ (void)start { ++ (void)startSwizzling { // Swizzle - UIApplication delegate //TODO: do the equivalent in the notificaitons module injectSelector( @@ -252,9 +261,6 @@ + (void)start { @selector(onesignalSetApplicationIconBadgeNumber:) ); [OneSignalNotificationsUNUserNotificationCenter setup]; - - [self registerLifecycleObserver]; - } #pragma clang diagnostic pop @@ -444,9 +450,16 @@ + (BOOL)registerForAPNsToken { return true; } -+ (void)didRegisterForRemoteNotifications:(UIApplication *)app - deviceToken:(NSData *)inDeviceToken { - let parsedDeviceToken = [NSString hexStringFromData:inDeviceToken]; ++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + if (![self isSwizzlingDisabled]) { + [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"OneSignal: didRegisterForRemoteNotificationsWithDeviceToken: ignored because swizzling is active. Remove the manual call or set OneSignal_disable_swizzling to true in Info.plist."]; + return; + } + [self processRegisteredDeviceToken:deviceToken]; +} + ++ (void)processRegisteredDeviceToken:(NSData *)deviceToken { + let parsedDeviceToken = [NSString hexStringFromData:deviceToken]; [OneSignalLog onesignalLog:ONE_S_LL_INFO message: [NSString stringWithFormat:@"Device Registered with Apple: %@", parsedDeviceToken]]; @@ -465,7 +478,15 @@ + (void)didRegisterForRemoteNotifications:(UIApplication *)app [self sendPushTokenToDelegate]; } -+ (void)handleDidFailRegisterForRemoteNotification:(NSError*)err { ++ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError*)err { + if (![self isSwizzlingDisabled]) { + [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"OneSignal: didFailToRegisterForRemoteNotificationsWithError: ignored because swizzling is active. Remove the manual call or set OneSignal_disable_swizzling to true in Info.plist."]; + return; + } + [self processFailedRemoteNotificationsRegistration:err]; +} + ++ (void)processFailedRemoteNotificationsRegistration:(NSError*)err { OSNotificationsManager.waitingForApnsResponse = false; if (err.code == 3000) { @@ -651,9 +672,19 @@ + (NSString*)checkForProcessedDups:(NSDictionary*)customDict lastMessageId:(NSSt return nil; } -+ (void)handleWillPresentNotificationInForegroundWithPayload:(NSDictionary *)payload withCompletion:(OSNotificationDisplayResponse)completion { ++ (void)willPresentNotificationWithPayload:(NSDictionary *)payload completion:(OSNotificationDisplayResponse)completion { + if (![self isSwizzlingDisabled]) { + [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"OneSignal: willPresentNotificationWithPayload:completion: ignored because swizzling is active. Remove the manual call or set OneSignal_disable_swizzling to true in Info.plist."]; + completion([OSNotification new]); + return; + } + [self processWillPresentNotificationWithPayload:payload completion:completion]; +} + ++ (void)processWillPresentNotificationWithPayload:(NSDictionary *)payload completion:(OSNotificationDisplayResponse)completion { // check to make sure the app is in focus and it's a OneSignal notification - if (![OneSignalCoreHelper isOneSignalPayload:payload] + if ([OSPrivacyConsentController shouldLogMissingPrivacyConsentErrorWithMethodName:nil] + || ![OneSignalCoreHelper isOneSignalPayload:payload] || UIApplication.sharedApplication.applicationState == UIApplicationStateBackground) { completion([OSNotification new]); return; @@ -662,10 +693,16 @@ + (void)handleWillPresentNotificationInForegroundWithPayload:(NSDictionary *)pay OSDisplayableNotification *osNotification = [OSDisplayableNotification parseWithApns:payload]; if ([osNotification additionalData][ONESIGNAL_IAM_PREVIEW]) { + [self notificationReceived:payload wasOpened:NO]; completion(nil); return; } - [self handleWillShowInForegroundForNotification:osNotification completion:completion]; + + OSNotificationDisplayResponse wrappedCompletion = ^(OSNotification *notification) { + [self notificationReceived:payload wasOpened:NO]; + completion(notification); + }; + [self handleWillShowInForegroundForNotification:osNotification completion:wrappedCompletion]; } + (void)handleWillShowInForegroundForNotification:(OSDisplayableNotification *)notification completion:(OSNotificationDisplayResponse)completion { @@ -801,19 +838,7 @@ + (void)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { return; } - [OneSignalBadgeHelpers updateCachedBadgeValue:0 usePreviousBadgeCount:false]; - - if (@available(iOS 16.0, *)) { - [[UNUserNotificationCenter currentNotificationCenter] setBadgeCount:0 withCompletionHandler:^(NSError * _Nullable error) { - if (error) { - [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"clearBadgeCount encountered error setting badge count: %@", error]]; - } - }]; - } else { - [OneSignalCoreHelper runOnMainThread:^{ - [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; - }]; - } + [self setBadgeCount:0]; } + (BOOL)handleIAMPreview:(OSNotification *)notification { @@ -910,9 +935,18 @@ + (void)fireClickListenersForUnprocessedEvents { _unprocessedClickEvents = [NSMutableArray new]; } -+ (BOOL)receiveRemoteNotification:(UIApplication*)application UserInfo:(NSDictionary*)userInfo completionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - var startedBackgroundJob = false; - ++ (void)didReceiveRemoteNotification:(NSDictionary*)userInfo completionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + if (![self isSwizzlingDisabled]) { + [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"OneSignal: didReceiveRemoteNotification:completionHandler: ignored because swizzling is active. Remove the manual call or set OneSignal_disable_swizzling to true in Info.plist."]; + return; + } + BOOL startedBackgroundJob = [self processReceivedRemoteNotification:userInfo completionHandler:completionHandler]; + if (!startedBackgroundJob) { + completionHandler(UIBackgroundFetchResultNewData); + } +} + ++ (BOOL)processReceivedRemoteNotification:(NSDictionary*)userInfo completionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSDictionary* richData = nil; // TODO: Look into why the userInfo payload would be different here for displaying vs opening.... // Check for buttons or attachments pre-2.4.0 version @@ -922,27 +956,23 @@ + (BOOL)receiveRemoteNotification:(UIApplication*)application UserInfo:(NSDictio // Generate local notification for action button and/or attachments. if (richData) { let osNotification = [OSNotification parseWithApns:userInfo]; - - if ([OSDeviceUtils isIOSVersionGreaterThanOrEqual:@"10.0"]) { - startedBackgroundJob = true; - [self addNotificationRequest:osNotification completionHandler:completionHandler]; - } + [self addNotificationRequest:osNotification completionHandler:completionHandler]; + return YES; } // Method was called due to a tap on a notification - Fire open notification - else if (application.applicationState == UIApplicationStateActive) { + else if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) { _lastMessageReceived = userInfo; if ([OneSignalCoreHelper isDisplayableNotification:userInfo]) { [self notificationReceived:userInfo wasOpened:YES]; } - return startedBackgroundJob; } // content-available notification received in the background else { _lastMessageReceived = userInfo; } - - return startedBackgroundJob; + + return NO; } + (void)addNotificationRequest:(OSNotification*)notification @@ -1002,6 +1032,45 @@ + (UNNotificationRequest*)prepareUNNotificationRequest:(OSNotification*)notifica return [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger]; } +#pragma mark - Manual Integration APIs (for use when swizzling is disabled) + ++ (void)didReceiveNotificationResponse:(UNNotificationResponse *)response { + if (![self isSwizzlingDisabled]) { + [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"OneSignal: didReceiveNotificationResponse: ignored because swizzling is active. Remove the manual call or set OneSignal_disable_swizzling to true in Info.plist."]; + return; + } + [self processNotificationResponse:response]; +} + ++ (void)processNotificationResponse:(UNNotificationResponse *)response { + if ([OSPrivacyConsentController shouldLogMissingPrivacyConsentErrorWithMethodName:nil]) + return; + if (![OneSignalConfigManager getAppId]) + return; + if ([@"com.apple.UNNotificationDismissActionIdentifier" isEqual:response.actionIdentifier]) + return; + if (![OneSignalCoreHelper isOneSignalPayload:response.notification.request.content.userInfo]) + return; + NSDictionary *userInfo = [OneSignalCoreHelper formatApsPayloadIntoStandard:response.notification.request.content.userInfo + identifier:response.actionIdentifier]; + [self notificationReceived:userInfo wasOpened:YES]; +} + ++ (void)setBadgeCount:(NSInteger)badgeCount { + [OneSignalBadgeHelpers updateCachedBadgeValue:badgeCount usePreviousBadgeCount:false]; + if (@available(iOS 16.0, *)) { + [[UNUserNotificationCenter currentNotificationCenter] setBadgeCount:badgeCount withCompletionHandler:^(NSError * _Nullable error) { + if (error) { + [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"setBadgeCount encountered error setting badge count: %@", error]]; + } + }]; + } else { + [OneSignalCoreHelper runOnMainThread:^{ + [UIApplication sharedApplication].applicationIconBadgeNumber = badgeCount; + }]; + } +} + - (void)dealloc { [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"OSNotificationsManager observer deallocated"]; [[NSNotificationCenter defaultCenter] removeObserver:self]; diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotificationsTests/OneSignalNotificationsTests.swift b/iOS_SDK/OneSignalSDK/OneSignalNotificationsTests/OneSignalNotificationsTests.swift index 326d98cd5..e178a5732 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotificationsTests/OneSignalNotificationsTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalNotificationsTests/OneSignalNotificationsTests.swift @@ -63,7 +63,7 @@ final class OneSignalNotificationsTests: XCTestCase { func testClearBadgesWhenAppEntersForeground() throws { // NotificationManager Start to register lifecycle listener - OSNotificationsManager.start() + OSNotificationsManager.startSwizzling() // Set badge count > 0 let expectation = self.expectation(description: "Badge set") setBadgeCount(1) { @@ -88,7 +88,7 @@ final class OneSignalNotificationsTests: XCTestCase { func testDontclearBadgesWhenAppBecomesActive() throws { // NotificationManager Start to register lifecycle listener - OSNotificationsManager.start() + OSNotificationsManager.startSwizzling() // Set badge count > 0 let expectation = self.expectation(description: "Badge set") setBadgeCount(1) { @@ -113,7 +113,7 @@ final class OneSignalNotificationsTests: XCTestCase { func testUpdateNotificationTypesOnAppEntersForeground() throws { // NotificationManager Start to register lifecycle listener - OSNotificationsManager.start() + OSNotificationsManager.startSwizzling() OSNotificationsManager.delegate = self diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index ad65bf2ca..bbdecb547 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -749,9 +749,11 @@ + (void)onSessionEnding:(NSArray *)lastInfluences { #pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation UIApplication (OneSignal) + (void)load { + [OSDialogInstanceManager setSharedOSDialogInstance:[OneSignalDialogController sharedInstance]]; + [OSNotificationsManager registerLifecycleObserver]; - if ([self shouldDisableBasedOnProcessArguments]) { - [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"OneSignal method swizzling is disabled. Make sure the feature is enabled for production."]; + if ([OSNotificationsManager isSwizzlingDisabled]) { + [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"OneSignal method swizzling is disabled via Info.plist. Developers must manually forward notification delegate methods to OneSignal."]; return; } [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"UIApplication(OneSignal) LOADED!"]; @@ -779,13 +781,11 @@ + (void)load { return; } - [OSNotificationsManager start]; + [OSNotificationsManager startSwizzling]; [[OSMigrationController new] migrate]; // sessionLaunchTime = [NSDate date]; // TODO: sessionLaunchTime used to always be set in load - - [OSDialogInstanceManager setSharedOSDialogInstance:[OneSignalDialogController sharedInstance]]; } /* @@ -797,12 +797,6 @@ - (void)onesignalSetApplicationIconBadgeNumber:(NSInteger)badge { [self onesignalSetApplicationIconBadgeNumber:badge]; } -+(BOOL) shouldDisableBasedOnProcessArguments { - if ([NSProcessInfo.processInfo.arguments containsObject:@"DISABLE_ONESIGNAL_SWIZZLING"]) { - return YES; - } - return NO; -} @end #pragma clang diagnostic pop diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift index 5ffabb522..ab6875512 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/App/OneSignalSwiftUIExampleApp.swift @@ -26,6 +26,7 @@ */ import SwiftUI +import UserNotifications import OneSignalFramework @main @@ -47,7 +48,7 @@ struct OneSignalSwiftUIExampleApp: App { // MARK: - App Delegate -class AppDelegate: NSObject, UIApplicationDelegate { +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { // Keys for caching SDK state in UserDefaults private let cachedIAMPausedKey = "CachedInAppMessagesPaused" @@ -59,6 +60,8 @@ class AppDelegate: NSObject, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { + UNUserNotificationCenter.current().delegate = self + // Set consent required before init (must be set before initWithContext) let consentRequired = UserDefaults.standard.bool(forKey: cachedConsentRequiredKey) let privacyConsent = UserDefaults.standard.bool(forKey: cachedPrivacyConsentKey) @@ -91,6 +94,49 @@ class AppDelegate: NSObject, UIApplicationDelegate { return true } + // MARK: - Manual Integration APIs (for use when swizzling is disabled) + + func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + OneSignal.Notifications.didRegisterForRemoteNotifications(deviceToken: deviceToken) + } + + func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error) { + OneSignal.Notifications.didFailToRegisterForRemoteNotifications(error: error as NSError) + } + + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + OneSignal.Notifications.didReceiveRemoteNotification(userInfo: userInfo, + completionHandler: completionHandler) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + OneSignal.Notifications.willPresentNotification( + payload: notification.request.content.userInfo) { notif in + if notif != nil { + if #available(iOS 14.0, *) { + completionHandler([.banner, .list, .sound]) + } else { + completionHandler([.alert, .sound]) + } + } else { + completionHandler([]) + } + } + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + OneSignal.Notifications.didReceiveNotificationResponse(response) + completionHandler() + } + private func setupLogListener() { OneSignal.Debug.setLogLevel(.LL_VERBOSE) OneSignal.Debug.addLogListener(SDKLogListener.shared) diff --git a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist index 82b16a177..287ffecb7 100644 --- a/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist +++ b/iOS_SDK/OneSignalSwiftUIExample/OneSignalSwiftUIExample/Info.plist @@ -54,5 +54,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + OneSignal_disable_swizzling +