From 6977b4795da4d8b46ce33a18baa0e097c11e72a3 Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Mon, 7 Nov 2016 19:27:34 -0800 Subject: [PATCH] Updated native SDK code, Android data types, and iOS isAppInFocus * iOS fixes - Added `isAppInFocus` field to notification callbacks. - Fixed rare case where opened callback could fire 2 times. - Some compatibility fixes when using other notification SDKs. - Fixed cases where body and title were not correctly set on OSNotification. - Fixed setLogLevel to correctly use integer value. * Android updates - Google project number is now used from OneSignal instead of through startInit - Added priority and collapseId to OSNotificationPayload - collapseId (AKA collapse_key) will be used to replace notifications in a future update. * Android fixes - Fixed issue with additionalData being a string - Added groupedNotifications field back since 2.0 release. - Fixed issue where disabling badges was not being respected in some cases. - If send priority was set to high(10), then the notification will be displayed with PRIORITY_MAX - Added syncHashedEmail validation. - Background images on notifications will now be top left aligned. - Background images are now only supported on Android 4.1+. --- LICENSE | 17 +- build-extras-onesignal.gradle | 5 +- package.json | 2 +- plugin.xml | 9 +- src/android/com/plugin/gcm/OneSignalPush.java | 76 +++-- src/ios/OneSignal.h | 39 +-- src/ios/OneSignal.m | 277 +++--------------- src/ios/OneSignalHelper.h | 3 +- src/ios/OneSignalHelper.m | 124 ++++---- src/ios/OneSignalPush.h | 2 +- src/ios/OneSignalPush.m | 4 +- src/ios/OneSignalSelectorHelpers.h | 36 +++ src/ios/OneSignalSelectorHelpers.m | 107 +++++++ src/ios/UNUserNotificationCenter+OneSignal.h | 39 +++ src/ios/UNUserNotificationCenter+OneSignal.m | 226 ++++++++++++++ 15 files changed, 596 insertions(+), 370 deletions(-) create mode 100644 src/ios/OneSignalSelectorHelpers.h create mode 100644 src/ios/OneSignalSelectorHelpers.m create mode 100644 src/ios/UNUserNotificationCenter+OneSignal.h create mode 100644 src/ios/UNUserNotificationCenter+OneSignal.m diff --git a/LICENSE b/LICENSE index 59804ff7..826624de 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Modified MIT License -Copyright 2015 OneSignal +Copyright 2016 OneSignal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -39,21 +39,6 @@ Includes portions from the Google GcmClient demo project: See the License for the specific language governing permissions and limitations under the License. -Includes Portions Copyright 2014 StackMob: - Copyright 2014 StackMob - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Includes portions from RootTools: Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks diff --git a/build-extras-onesignal.gradle b/build-extras-onesignal.gradle index 98d385b2..fc6af9ed 100644 --- a/build-extras-onesignal.gradle +++ b/build-extras-onesignal.gradle @@ -1,7 +1,6 @@ def manifest = new XmlSlurper().parse(file("AndroidManifest.xml")) android.defaultConfig { applicationId manifest.@package.text() - manifestPlaceholders = [manifestApplicationId: "${android.defaultConfig.applicationId}", - onesignal_app_id: "", // Use from code for now. - onesignal_google_project_number: ""] + manifestPlaceholders = [onesignal_app_id: "", // Use from code for now. + onesignal_google_project_number: "REMOTE"] } \ No newline at end of file diff --git a/package.json b/package.json index 7059e3bb..f2a40467 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "2.0.6", + "version": "2.0.7", "name": "onesignal-cordova-plugin", "cordova_name": "OneSignal Push Notifications", "description": "OneSignal is a high volume Push Notification service for mobile apps. In addition to basic notification delivery, OneSignal also provides tools to localize, target, schedule, and automate notifications that you send.", diff --git a/plugin.xml b/plugin.xml index 26376857..0f895c3e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="2.0.7"> OneSignal Push Notifications @@ -25,7 +25,7 @@ - + @@ -95,6 +95,9 @@ + + + @@ -111,6 +114,8 @@ + + diff --git a/src/android/com/plugin/gcm/OneSignalPush.java b/src/android/com/plugin/gcm/OneSignalPush.java index dc958615..b3508a3c 100644 --- a/src/android/com/plugin/gcm/OneSignalPush.java +++ b/src/android/com/plugin/gcm/OneSignalPush.java @@ -78,19 +78,8 @@ public class OneSignalPush extends CordovaPlugin { // This is to prevent an issue where if two Javascript calls are made to OneSignal expecting a callback then only one would fire. private static void callbackSuccess(CallbackContext callbackContext, JSONObject jsonObject) { - if(jsonObject == null){ // in case there are no data + if (jsonObject == null) // in case there are no data jsonObject = new JSONObject(); - } - - if(jsonObject.has("payload")) { - try { - JSONObject payload = jsonObject.getJSONObject("payload"); - if (payload.has("additionalData")) { - payload.put("additionalData", new JSONObject(payload.getString("additionalData"))); - jsonObject.put("payload", payload); - } - } catch (Throwable t) {t.printStackTrace();} - } PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, jsonObject); pluginResult.setKeepCallback(true); @@ -98,9 +87,8 @@ private static void callbackSuccess(CallbackContext callbackContext, JSONObject } private static void callbackError(CallbackContext callbackContext, JSONObject jsonObject) { - if(jsonObject == null) { // in case there are no data + if (jsonObject == null) // in case there are no data jsonObject = new JSONObject(); - } PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, jsonObject); pluginResult.setKeepCallback(true); @@ -169,12 +157,13 @@ public void idsAvailable(String userId, String registrationId) { try { jsonIds.put("userId", userId); if (registrationId != null) - jsonIds.put("pushToken", registrationId); + jsonIds.put("pushToken", registrationId); else - jsonIds.put("pushToken", ""); + jsonIds.put("pushToken", ""); callbackSuccess(jsIdsAvailableCallBack, jsonIds); - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } } @@ -184,7 +173,8 @@ public void idsAvailable(String userId, String registrationId) { else if (SEND_TAGS.equals(action)) { try { OneSignal.sendTags(data.getJSONObject(0)); - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } result = true; @@ -193,10 +183,10 @@ else if (DELETE_TAGS.equals(action)) { try { Collection list = new ArrayList(); for (int i = 0; i < data.length(); i++) - list.add(data.get(i).toString()); + list.add(data.get(i).toString()); OneSignal.deleteTags(list); result = true; - } catch (Throwable t) { + } catch (Throwable t) { t.printStackTrace(); } } @@ -208,7 +198,8 @@ else if (ENABLE_VIBRATE.equals(action)) { try { OneSignal.enableVibrate(data.getBoolean(0)); result = true; - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } } @@ -216,7 +207,8 @@ else if (ENABLE_SOUND.equals(action)) { try { OneSignal.enableSound(data.getBoolean(0)); result = true; - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } } @@ -224,7 +216,8 @@ else if (SET_SUBSCRIPTION.equals(action)) { try { OneSignal.setSubscription(data.getBoolean(0)); result = true; - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } } @@ -233,20 +226,21 @@ else if (POST_NOTIFICATION.equals(action)) { JSONObject jo = data.getJSONObject(0); final CallbackContext jsPostNotificationCallBack = callbackContext; OneSignal.postNotification(jo, - new PostNotificationResponseHandler() { - @Override - public void onSuccess(JSONObject response) { - callbackSuccess(jsPostNotificationCallBack, response); - } - - @Override - public void onFailure(JSONObject response) { - callbackError(jsPostNotificationCallBack, response); - } - }); + new PostNotificationResponseHandler() { + @Override + public void onSuccess(JSONObject response) { + callbackSuccess(jsPostNotificationCallBack, response); + } + + @Override + public void onFailure(JSONObject response) { + callbackError(jsPostNotificationCallBack, response); + } + }); result = true; - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } } @@ -263,14 +257,16 @@ else if (SET_LOG_LEVEL.equals(action)) { try { JSONObject jo = data.getJSONObject(0); OneSignal.setLogLevel(jo.getInt("logLevel"), jo.getInt("visualLevel")); - } catch(Throwable t) { + } + catch(Throwable t) { t.printStackTrace(); } } else if (CLEAR_ONESIGNAL_NOTIFICATIONS.equals(action)) { try { OneSignal.clearOneSignalNotifications(); - } catch(Throwable t) { + } + catch(Throwable t) { t.printStackTrace(); } } @@ -295,7 +291,8 @@ public CordovaNotificationReceivedHandler(CallbackContext inCallbackContext) { public void notificationReceived(OSNotification notification) { try { callbackSuccess(jsNotificationReceivedCallBack, new JSONObject(notification.stringify())); - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } } @@ -313,7 +310,8 @@ public CordovaNotificationOpenedHandler(CallbackContext inCallbackContext) { public void notificationOpened(OSNotificationOpenResult result) { try { callbackSuccess(jsNotificationOpenedCallBack, new JSONObject(result.stringify())); - } catch (Throwable t) { + } + catch (Throwable t) { t.printStackTrace(); } } diff --git a/src/ios/OneSignal.h b/src/ios/OneSignal.h index 22519010..b0b88821 100755 --- a/src/ios/OneSignal.h +++ b/src/ios/OneSignal.h @@ -44,7 +44,7 @@ REST API: https://documentation.onesignal.com/docs/server-api-overview Create Notification API: https://documentation.onesignal.com/docs/notifications-create-notification - ***/ +***/ #import @@ -54,8 +54,8 @@ @protocol OSUserNotificationCenterDelegate @optional -- (void)userNotificationCenter:(id)center willPresentNotification:(id)notification withCompletionHandler:(void (^)(NSUInteger options))completionHandler; -- (void)userNotificationCenter:(id)center didReceiveNotificationResponse:(id)response withCompletionHandler:(void (^)())completionHandler; +- (void)userNotificationCenter:(id)center willPresentNotification:(id)notification withCompletionHandler:(void (^)(NSUInteger options))completionHandler __deprecated_msg("Can use your own delegate as normal."); +- (void)userNotificationCenter:(id)center didReceiveNotificationResponse:(id)response withCompletionHandler:(void (^)())completionHandler __deprecated_msg("Can use your own delegate as normal."); @end #endif @@ -148,6 +148,9 @@ typedef OSNotificationDisplayType OSInFocusDisplayOption; Set to false when app is in focus and in-app alerts are disabled, or the remote notification is silent. */ @property(readonly, getter=wasShown)BOOL shown; +/* Set to true if the app was in focus when the notification */ +@property(readonly, getter=wasAppInFocus)BOOL isAppInFocus; + /* Set to true when the received notification is silent Silent means there is no alert, sound, or badge payload in the aps dictionary requires remote-notification within UIBackgroundModes array of the Info.plist */ @@ -155,7 +158,7 @@ typedef OSNotificationDisplayType OSInFocusDisplayOption; /* iOS 10+: Indicates wether or not the received notification has mutableContent : 1 assigned to its payload Used for UNNotificationServiceExtension to launch extension. - */ +*/ #if XC8_AVAILABLE @property(readonly, getter=hasMutableContent)BOOL mutableContent; #endif @@ -190,27 +193,27 @@ typedef void (^OSHandleNotificationReceivedBlock)(OSNotification* notification); typedef void (^OSHandleNotificationActionBlock)(OSNotificationOpenedResult * result); /*Dictionary of keys to pass alongside the init serttings*/ - + /*Let OneSignal directly promt for push notifications on init*/ extern NSString * const kOSSettingsKeyAutoPrompt; - + /*Enable the default in-app alerts*/ extern NSString * const kOSSettingsKeyInAppAlerts; /*Enable In-App display of Launch URLs*/ extern NSString * const kOSSettingsKeyInAppLaunchURL; -/* iOS10+ - +/* iOS10+ - Set notificaion's in-focus display option. Value must be an OSNotificationDisplayType enum - */ +*/ extern NSString * const kOSSettingsKeyInFocusDisplayOption; /** - OneSignal provides a high level interface to interact with OneSignal's push service. - OneSignal is a singleton for applications which use a globally available client to share configuration settings. - You should avoid creating instances of this class at all costs. Instead, access its instance methods. - Include `#import ` in your application files to access OneSignal's methods. + OneSignal provides a high level interface to interact with OneSignal's push service. + OneSignal is a singleton for applications which use a globally available client to share configuration settings. + You should avoid creating instances of this class at all costs. Instead, access its instance methods. + Include `#import ` in your application files to access OneSignal's methods. **/ @interface OneSignal : NSObject @@ -227,16 +230,16 @@ typedef NS_ENUM(NSUInteger, ONE_S_LOG_LEVEL) { /** Initialize OneSignal. Sends push token to OneSignal so you can later send notifications. - */ +*/ // - Initialization + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId; + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId handleNotificationAction:(OSHandleNotificationActionBlock)actionCallback; + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId handleNotificationAction:(OSHandleNotificationActionBlock)actionCallback settings:(NSDictionary*)settings; -+ (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId handleNotificationReceived:(OSHandleNotificationReceivedBlock)receivedCallback handleNotificationAction:(OSHandleNotificationActionBlock)actionCallback settings:(NSDictionary*)settings; ++ (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId handleNotificationReceived:(OSHandleNotificationReceivedBlock)erceivedCallback handleNotificationAction:(OSHandleNotificationActionBlock)actionCallback settings:(NSDictionary*)settings; + (NSString*)app_id; - + // Only use if you passed FALSE to autoRegister + (void)registerForPushNotifications; @@ -276,10 +279,10 @@ typedef NS_ENUM(NSUInteger, ONE_S_LOG_LEVEL) { // Optional method that sends us the user's email as an anonymized hash so that we can better target and personalize notifications sent to that user across their devices. + (void)syncHashedEmail:(NSString*)email; -// - iOS 10 BETA features currently only available on XCode 8 & iOS 10.0+ +// - iOS 10 features currently only available on XCode 8 & iOS 10.0+ #if XC8_AVAILABLE -+ (void)setNotificationCenterDelegate:(id)delegate; -+ (id)notificationCenterDelegate; ++ (void)setNotificationCenterDelegate:(id)delegate __deprecated_msg("Can use your own delegate as normal."); ++ (id)notificationCenterDelegate __deprecated_msg("Can use your own delegate as normal."); #endif @end diff --git a/src/ios/OneSignal.m b/src/ios/OneSignal.m index ad6b75a2..22662b53 100755 --- a/src/ios/OneSignal.m +++ b/src/ios/OneSignal.m @@ -37,6 +37,8 @@ #import "OneSignalHelper.h" #import "NSObject+Extras.h" #import "NSString+Hash.h" +#import "UNUserNotificationCenter+OneSignal.h" +#import "OneSignalSelectorHelpers.h" #import #import @@ -74,8 +76,8 @@ NSString * const kOSSettingsKeyInFocusDisplayOption = @"kOSSettingsKeyInFocusDisplayOption"; @implementation OneSignal - -NSString* const ONESIGNAL_VERSION = @"020116"; + +NSString* const ONESIGNAL_VERSION = @"020201"; static NSString* mSDKType = @"native"; static BOOL coldStartFromTapOnNotification = NO; static BOOL registeredWithApple = NO; //Has attempted to register for push notifications with Apple. @@ -228,12 +230,11 @@ + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId if ([OneSignalTrackIAP canTrack]) trackIAPPurchase = [[OneSignalTrackIAP alloc] init]; + #if XC8_AVAILABLE if (NSClassFromString(@"UNUserNotificationCenter")) { - #if XC8_AVAILABLE - [OneSignalHelper registerAsUNNotificationCenterDelegate]; - [OneSignalHelper clearCachedMedia]; - #endif + [OneSignalHelper clearCachedMedia]; } + #endif return self; } @@ -662,9 +663,9 @@ + (void)registerUser { if (ASIdentifierManagerClass) { id asIdManager = [ASIdentifierManagerClass valueForKey:@"sharedManager"]; if ([[asIdManager valueForKey:@"advertisingTrackingEnabled"] isEqual:[NSNumber numberWithInt:1]]) - dataDic[@"as_id"] = [[asIdManager valueForKey:@"advertisingIdentifier"] UUIDString]; + dataDic[@"as_id"] = [[asIdManager valueForKey:@"advertisingIdentifier"] UUIDString]; else - dataDic[@"as_id"] = @"OptedOut"; + dataDic[@"as_id"] = @"OptedOut"; } UIApplicationReleaseMode releaseMode = [OneSignalMobileProvision releaseMode]; @@ -781,6 +782,8 @@ + (void)sendPurchases:(NSArray*)purchases { } + (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive { + // Should be called first, other methods relay on this global state below. + [OneSignalHelper lastMessageReceived:messageDict]; NSDictionary* customDict = [messageDict objectForKey:@"os_data"]; if (!customDict) @@ -788,8 +791,7 @@ + (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive { BOOL inAppAlert = false; if (isActive) { - - if(![[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"]) { + if (![[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"]) { [[NSUserDefaults standardUserDefaults] setObject:@(OSNotificationDisplayTypeInAppAlert) forKey:@"ONESIGNAL_ALERT_OPTION"]; [[NSUserDefaults standardUserDefaults] synchronize]; } @@ -797,20 +799,18 @@ + (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive { int iaaoption = [[[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"] intValue]; inAppAlert = iaaoption == OSNotificationDisplayTypeInAppAlert; - [OneSignalHelper lastMessageReceived:messageDict]; - //Make sure it is not a silent one do display, if inAppAlerts are enabled if (inAppAlert && ![OneSignalHelper isRemoteSilentNotification:messageDict]) { - NSArray* titleAndBody = [OneSignalHelper getPushTitleBody:messageDict]; + NSDictionary* titleAndBody = [OneSignalHelper getPushTitleBody:messageDict]; id oneSignalAlertViewDelegate = [[OneSignalAlertViewDelegate alloc] initWithMessageDict:messageDict]; - UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:titleAndBody[0] ? titleAndBody[0] : @"" - message:titleAndBody[1] ? titleAndBody[1] : @"" + UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:titleAndBody[@"title"] + message:titleAndBody[@"body"] delegate:oneSignalAlertViewDelegate cancelButtonTitle:@"Close" otherButtonTitles:nil, nil]; - //Add Buttons + // Add Buttons NSArray *additionalData = [OneSignalHelper getActionButtons]; if (additionalData) { for(id button in additionalData) @@ -819,8 +819,8 @@ + (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive { [alertView show]; - //Message received that was displayed (Foreground + InAppAlert is true) - //Call Received Block + // Message received that was displayed (Foreground + InAppAlert is true) + // Call Received Block [OneSignalHelper handleNotificationReceived:OSNotificationDisplayTypeInAppAlert]; return; @@ -843,12 +843,12 @@ + (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive { actionSelected = messageDict[@"custom"][@"a"][@"actionSelected"]; type = OSNotificationActionTypeActionTaken; } - if(messageDict[@"actionSelected"]) { + if (messageDict[@"actionSelected"]) { actionSelected = messageDict[@"actionSelected"]; type = OSNotificationActionTypeActionTaken; } - //Call Action Block + // Call Action Block [OneSignalHelper handleNotificationAction:type actionID:actionSelected displayType:OSNotificationDisplayTypeNotification]; [OneSignal handleNotificationOpened:messageDict isActive:isActive actionType:type displayType:OSNotificationDisplayTypeNotification]; } @@ -950,9 +950,9 @@ + (int) getNotificationTypes { if (mDeviceToken) { if ([OneSignalHelper isCapableOfGettingNotificationTypes]) - return [[UIApplication sharedApplication] currentUserNotificationSettings].types; + return [[UIApplication sharedApplication] currentUserNotificationSettings].types; else - return NOTIFICATION_TYPE_ALL; + return NOTIFICATION_TYPE_ALL; } return -1; @@ -1093,11 +1093,10 @@ + (void)processLocalActionBasedNotification:(UILocalNotification*) notification } #if XC8_AVAILABLE - static id notificationCenterDelegate; + (void) setNotificationCenterDelegate:(id)delegate { - if(!NSClassFromString(@"UNNotification")) { + if (!NSClassFromString(@"UNNotification")) { onesignal_Log(ONE_S_LL_ERROR, @"Cannot assign delegate. Please make sure you are running on iOS 10+."); return; } @@ -1108,119 +1107,6 @@ + (void) setNotificationCenterDelegate:(id)del return notificationCenterDelegate; } - -- (void)userNotificationCenter:(id)center didReceiveNotificationResponse:(id)response withCompletionHandler:(void(^)())completionHandler { - - NSDictionary* usrInfo = [[[[response performSelector:@selector(notification)] valueForKey:@"request"] valueForKey:@"content"] valueForKey:@"userInfo"]; - if (!usrInfo || [usrInfo count] == 0) { - [OneSignal tunnelToDelegate:center :response :completionHandler]; - return; - } - - NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init], - *customDict = [[NSMutableDictionary alloc] init], - *additionalData = [[NSMutableDictionary alloc] init]; - NSMutableArray *optionsDict = [[NSMutableArray alloc] init]; - - NSMutableDictionary* buttonsDict = usrInfo[@"os_data"][@"buttons"]; - NSMutableDictionary* custom = usrInfo[@"custom"]; - if (buttonsDict) { - [userInfo addEntriesFromDictionary:usrInfo]; - NSArray* o = buttonsDict[@"o"]; - if (o) - [optionsDict addObjectsFromArray:o]; - } - - else if (custom) { - [userInfo addEntriesFromDictionary:usrInfo]; - [customDict addEntriesFromDictionary:custom]; - NSDictionary *a = customDict[@"a"]; - NSArray *o = userInfo[@"o"]; - if (a) - [additionalData addEntriesFromDictionary:a]; - if (o) - [optionsDict addObjectsFromArray:o]; - } - - else { - BOOL isActive = [UIApplication sharedApplication].applicationState == UIApplicationStateActive && - [[[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"] intValue] != OSNotificationDisplayTypeNotification; - [OneSignal notificationOpened:usrInfo isActive:isActive]; - [OneSignal tunnelToDelegate:center :response :completionHandler]; - return; - } - - NSMutableArray* buttonArray = [[NSMutableArray alloc] init]; - for (NSDictionary* button in optionsDict) { - NSString * text = button[@"n"] != nil ? button[@"n"] : @""; - NSString * buttonID = button[@"i"] != nil ? button[@"i"] : text; - NSDictionary * buttonToAppend = [[NSDictionary alloc] initWithObjects:@[text, buttonID] forKeys:@[@"text", @"id"]]; - [buttonArray addObject:buttonToAppend]; - } - - additionalData[@"actionSelected"] = [response valueForKey:@"actionIdentifier"]; - additionalData[@"actionButtons"] = buttonArray; - - NSDictionary* os_data = usrInfo[@"os_data"]; - if (os_data) { - [userInfo addEntriesFromDictionary:os_data]; - if(userInfo[@"os_data"][@"buttons"][@"m"]) - userInfo[@"aps"] = @{@"alert" : userInfo[@"os_data"][@"buttons"][@"m"]}; - [userInfo addEntriesFromDictionary:additionalData]; - } - else { - customDict[@"a"] = additionalData; - userInfo[@"custom"] = customDict; - if(userInfo[@"m"]) - userInfo[@"aps"] = @{ @"alert" : userInfo[@"m"] }; - } - - BOOL isActive = [UIApplication sharedApplication].applicationState == UIApplicationStateActive && - [[[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"] intValue] != OSNotificationDisplayTypeNotification; - - - [OneSignal notificationOpened:userInfo isActive:isActive]; - [OneSignal tunnelToDelegate:center :response :completionHandler]; - -} - -+ (void)tunnelToDelegate:(id)center :(id)response :(void (^)())handler { - - if ([[OneSignal notificationCenterDelegate] respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) - [[OneSignal notificationCenterDelegate] userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:handler]; - else - handler(); -} - --(void)userNotificationCenter:(id)center willPresentNotification:(id)notification withCompletionHandler:(void (^)(NSUInteger options))completionHandler { - - // Proxy to user if listening to delegate and overrides the method. - if ([[OneSignal notificationCenterDelegate] respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) - [[OneSignal notificationCenterDelegate] userNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler]; - else { - //Set the completionHandler options based on the ONESIGNAL_ALERT_OPTION value. - NSUInteger completionHandlerOptions = 0; - if(![[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"]) { - [[NSUserDefaults standardUserDefaults] setObject:@(OSNotificationDisplayTypeInAppAlert) forKey:@"ONESIGNAL_ALERT_OPTION"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } - - NSInteger alert_option = ((NSNumber*)[[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"]).integerValue; - switch (alert_option) { - case OSNotificationDisplayTypeNone: completionHandlerOptions = 0; break; - case OSNotificationDisplayTypeInAppAlert: completionHandlerOptions = 1; break; - case OSNotificationDisplayTypeNotification: completionHandlerOptions = 7; break; - default: break; - } - - completionHandler(completionHandlerOptions); - - //Call notificationOpened if no alert (MSB not set) - NSDictionary* usrInfo = [[[notification valueForKey:@"request"] valueForKey:@"content"] valueForKey:@"userInfo"]; - [OneSignal notificationOpened:usrInfo isActive:YES]; - - } -} #endif + (void)syncHashedEmail:(NSString *)email { @@ -1254,80 +1140,8 @@ + (void)syncHashedEmail:(NSString *)email { @end -static BOOL checkIfInstanceOverridesSelector(Class instance, SEL selector) { - Class instSuperClass = [instance superclass]; - return [instance instanceMethodForSelector: selector] != [instSuperClass instanceMethodForSelector: selector]; -} - - -static Class getClassWithProtocolInHierarchy(Class searchClass, Protocol* protocolToFind) { - if (!class_conformsToProtocol(searchClass, protocolToFind)) { - if ([searchClass superclass] == nil) - return nil; - Class foundClass = getClassWithProtocolInHierarchy([searchClass superclass], protocolToFind); - if (foundClass) - return foundClass; - return searchClass; - } - return searchClass; -} - -static void injectSelector(Class newClass, SEL newSel, Class addToClass, SEL makeLikeSel) { - Method newMeth = class_getInstanceMethod(newClass, newSel); - IMP imp = method_getImplementation(newMeth); - const char* methodTypeEncoding = method_getTypeEncoding(newMeth); - BOOL successful = class_addMethod(addToClass, makeLikeSel, imp, methodTypeEncoding); - - if (!successful) { - class_addMethod(addToClass, newSel, imp, methodTypeEncoding); - newMeth = class_getInstanceMethod(addToClass, newSel); - Method orgMeth = class_getInstanceMethod(addToClass, makeLikeSel); - method_exchangeImplementations(orgMeth, newMeth); - } -} -//Try to find out which class to inject to -static void injectToProperClass(SEL newSel, SEL makeLikeSel, NSArray* delegateSubclasses, Class myClass, Class delegateClass) { - - // Find out if we should inject in delegateClass or one of its subclasses. - //CANNOT use the respondsToSelector method as it returns TRUE to both implementing and inheriting a method - //We need to make sure the class actually implements the method (overrides) and not inherits it to properly perform the call - //Start with subclasses then the delegateClass - - for(Class subclass in delegateSubclasses) - if(checkIfInstanceOverridesSelector(subclass, makeLikeSel)) { - injectSelector(myClass, newSel, subclass, makeLikeSel); - return; - } - - //No subclass overrides the method, try to inject in delegate class - injectSelector(myClass, newSel, delegateClass, makeLikeSel); - -} -static NSArray* ClassGetSubclasses(Class parentClass) { - - int numClasses = objc_getClassList(NULL, 0); - Class *classes = NULL; - - classes = (Class *)malloc(sizeof(Class) * numClasses); - numClasses = objc_getClassList(classes, numClasses); - - NSMutableArray *result = [NSMutableArray array]; - for (NSInteger i = 0; i < numClasses; i++) { - Class superClass = classes[i]; - do { - superClass = class_getSuperclass(superClass); - } while(superClass && superClass != parentClass); - - if (superClass == nil) continue; - [result addObject:classes[i]]; - } - - free(classes); - - return result; -} @interface OneSignalTracker () + (void)onFocus:(BOOL)toBackground; @@ -1337,7 +1151,7 @@ @implementation UIApplication (Swizzling) static Class delegateClass = nil; // Store an array of all UIAppDelegate subclasses to iterate over in cases where UIAppDelegate swizzled methods are not overriden in main AppDelegate -//But rather in one of the subclasses +// But rather in one of the subclasses static NSArray* delegateSubclasses = nil; +(Class)delegateClass { @@ -1355,7 +1169,7 @@ - (void)oneSignalDidRegisterForRemoteNotifications:(UIApplication*)app deviceTok - (void)oneSignalDidFailRegisterForRemoteNotification:(UIApplication*)app error:(NSError*)err { - if(err.code == 3000 && [(NSString*)[err.userInfo objectForKey:NSLocalizedDescriptionKey] containsString:@"no valid 'aps-environment'"]) { + if(err.code == 3000 && [((NSString*)[err.userInfo objectForKey:NSLocalizedDescriptionKey]) rangeOfString:@"no valid 'aps-environment'"].location != NSNotFound) { //User did not enable push notification capability [OneSignal setErrorNotificationType]; [OneSignal onesignal_Log:ONE_S_LL_ERROR message:@"'Push Notification' capability not turned on. Make sure it is enabled by going to your Project Target -> Capability."]; @@ -1391,14 +1205,13 @@ - (void)oneSignalReceivedRemoteNotification:(UIApplication*)application userInfo // User Tap on Notification while app was in background - OR - Notification received (silent or not, foreground or background) on iOS 7+ - (void) oneSignalRemoteSilentNotification:(UIApplication*)application UserInfo:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult)) completionHandler { - if([OneSignal app_id]) { - - //Call notificationAction if app is active -> not a silent notification but rather user tap on notification - //Unless iOS 10+ then call remoteSilentNotification instead. - if([UIApplication sharedApplication].applicationState == UIApplicationStateActive && userInfo[@"aps"][@"alert"]) + if ([OneSignal app_id]) { + // Call notificationAction if app is active -> not a silent notification but rather user tap on notification + // Unless iOS 10+ then call remoteSilentNotification instead. + if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive && userInfo[@"aps"][@"alert"]) [OneSignal notificationOpened:userInfo isActive:YES]; - else [OneSignal remoteSilentNotification:application UserInfo:userInfo]; - + else + [OneSignal remoteSilentNotification:application UserInfo:userInfo]; } if ([self respondsToSelector:@selector(oneSignalRemoteSilentNotification:UserInfo:fetchCompletionHandler:)]) { @@ -1406,7 +1219,7 @@ - (void) oneSignalRemoteSilentNotification:(UIApplication*)application UserInfo: return; } - //Make sure not a cold start from tap on notification (OS doesn't call didReceiveRemoteNotification) + // Make sure not a cold start from tap on notification (OS doesn't call didReceiveRemoteNotification) if ([self respondsToSelector:@selector(oneSignalReceivedRemoteNotification:userInfo:)] && ![[OneSignal valueForKey:@"coldStartFromTapOnNotification"] boolValue]) [self oneSignalReceivedRemoteNotification:application userInfo:userInfo]; @@ -1477,10 +1290,23 @@ + (void)load { if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"7.0")) return; - NSLog(@"Loaded"); + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"UIApplication(Swizzling) Loaded!"]; - //Swizzle App delegate + // Swizzle UIApplication delegate method_exchangeImplementations(class_getInstanceMethod(self, @selector(setDelegate:)), class_getInstanceMethod(self, @selector(setOneSignalDelegate:))); + + + #if XC8_AVAILABLE + // Swizzle UNUserNotificationCenter delegate + Class UNUserNotificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); + if (!UNUserNotificationCenterClass) + return; + + injectToProperClass(@selector(setOneSignalUNDelegate:), + @selector(setDelegate:), @[], [sizzleUNUserNotif class], UNUserNotificationCenterClass); + + [OneSignalHelper registerAsUNNotificationCenterDelegate]; + #endif } - (void) setOneSignalDelegate:(id)delegate { @@ -1526,14 +1352,6 @@ - (void) setOneSignalDelegate:(id)delegate { //Used to track how long the app has been closed injectToProperClass(@selector(oneSignalApplicationWillTerminate:), @selector(applicationWillTerminate:), delegateSubclasses, self.class, delegateClass); - - /* iOS 10.0: UNUserNotificationCenterDelegate instead of UIApplicationDelegate for methods handling opening app from notification - Make sure AppDelegate does not conform to this protocol */ -#if XC8_AVAILABLE - if([OneSignalHelper isiOS10Plus]) - [OneSignalHelper conformsToUNProtocol]; -#endif - [self setOneSignalDelegate:delegate]; } @@ -1555,5 +1373,6 @@ +(UIViewController*)topmostController:(UIViewController*)base { @end + #pragma clang diagnostic pop #pragma clang diagnostic pop diff --git a/src/ios/OneSignalHelper.h b/src/ios/OneSignalHelper.h index aaabdeff..0e52f4ef 100644 --- a/src/ios/OneSignalHelper.h +++ b/src/ios/OneSignalHelper.h @@ -38,7 +38,7 @@ + (void) displayWebView:(NSURL*)url; // - Notification Opened -+ (NSArray*)getPushTitleBody:(NSDictionary*)messageDict; ++ (NSDictionary*)getPushTitleBody:(NSDictionary*)messageDict; + (NSArray*)getActionButtons; + (void)lastMessageReceived:(NSDictionary*)message; + (void)notificationBlocks:(OSHandleNotificationReceivedBlock)receivedBlock :(OSHandleNotificationActionBlock)actionBlock; @@ -50,7 +50,6 @@ #if XC8_AVAILABLE + (void)registerAsUNNotificationCenterDelegate; + (void) requestAuthorization; -+ (void)conformsToUNProtocol; + (void)clearCachedMedia; + (id)prepareUNNotificationRequest:(NSDictionary *)data :(NSDictionary *)userInfo; #endif diff --git a/src/ios/OneSignalHelper.m b/src/ios/OneSignalHelper.m index b8e8749b..1ac8b50a 100644 --- a/src/ios/OneSignalHelper.m +++ b/src/ios/OneSignalHelper.m @@ -68,30 +68,32 @@ @implementation OSNotificationPayload - (id)initWithRawMessage:(NSDictionary*)message { self = [super init]; - if(self && message) { - _rawPayload = [NSDictionary dictionaryWithDictionary:message];; + if (self && message) { + _rawPayload = [NSDictionary dictionaryWithDictionary:message]; - if(_rawPayload[@"aps"][@"content-available"]) + if (_rawPayload[@"aps"][@"content-available"]) _contentAvailable = (BOOL)_rawPayload[@"aps"][@"content-available"]; - else _contentAvailable = NO; + else + _contentAvailable = NO; - if(_rawPayload[@"aps"][@"badge"]) + if (_rawPayload[@"aps"][@"badge"]) _badge = (int)_rawPayload[@"aps"][@"badge"]; else _badge = (int)_rawPayload[@"badge"]; _actionButtons = _rawPayload[@"o"]; - if(!_actionButtons) + if (!_actionButtons) _actionButtons = _rawPayload[@"os_data"][@"buttons"][@"o"]; if(_rawPayload[@"aps"][@"sound"]) _sound = _rawPayload[@"aps"][@"sound"]; else if(_rawPayload[@"s"]) _sound = _rawPayload[@"s"]; - else _sound = _rawPayload[@"os_data"][@"buttons"][@"s"]; + else + _sound = _rawPayload[@"os_data"][@"buttons"][@"s"]; if(_rawPayload[@"custom"]) { NSDictionary * custom = _rawPayload[@"custom"]; - if(custom[@"a"]) + if (custom[@"a"]) _additionalData = [custom[@"a"] copy]; _notificationID = custom[@"i"]; _launchURL = custom[@"u"]; @@ -115,22 +117,23 @@ - (id)initWithRawMessage:(NSDictionary*)message { if(_rawPayload[@"m"]) { id m = _rawPayload[@"m"]; - if([m isKindOfClass:[NSDictionary class]]) { + if ([m isKindOfClass:[NSDictionary class]]) { _body = m[@"body"]; _title = m[@"title"]; _subtitle = m[@"subtitle"]; } - //Content-only - else _body = m; + else + _body = m; } else if(_rawPayload[@"aps"][@"alert"]) { id a = message[@"aps"][@"alert"]; - if([a isKindOfClass:[NSDictionary class]]) { + if ([a isKindOfClass:[NSDictionary class]]) { _body = a[@"body"]; _title = a[@"title"]; _subtitle = a[@"subtitle"]; } - else _title = a; + else + _body = a; } else if(_rawPayload[@"os_data"][@"buttons"][@"m"]) { NSDictionary * m = _rawPayload[@"os_data"][@"buttons"][@"m"]; @@ -139,12 +142,13 @@ - (id)initWithRawMessage:(NSDictionary*)message { _subtitle = m[@"subtitle"]; } } + return self; } @end @implementation OSNotification -@synthesize payload = _payload, shown = _shown, silentNotification = _silentNotification, displayType = _displayType; +@synthesize payload = _payload, shown = _shown, isAppInFocus = _isAppInFocus, silentNotification = _silentNotification, displayType = _displayType; #if XC8_AVAILABLE @synthesize mutableContent = _mutableContent; @@ -165,11 +169,12 @@ - (id)initWithPayload:(OSNotificationPayload *)payload displayType:(OSNotificati _shown = true; - BOOL isActive = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; + _isAppInFocus = [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; //If remote silent -> shown = false //If app is active and in-app alerts are not enabled -> shown = false - if(_silentNotification || (isActive && [[NSUserDefaults standardUserDefaults] boolForKey:@"ONESIGNAL_ALERT_OPTION"] == OSNotificationDisplayTypeNone)) + if (_silentNotification || + (_isAppInFocus && [[NSUserDefaults standardUserDefaults] boolForKey:@"ONESIGNAL_ALERT_OPTION"] == OSNotificationDisplayTypeNone)) _shown = false; } @@ -216,10 +221,11 @@ - (NSString*)stringify { if(self.displayType) [obj setObject:@(self.displayType) forKeyedSubscript: @"displayType"]; - if(self.shown) - [obj setObject:@(self.shown) forKeyedSubscript: @"shown"]; - if(self.silentNotification) + [obj setObject:@(self.shown) forKeyedSubscript: @"shown"]; + [obj setObject:@(self.isAppInFocus) forKeyedSubscript: @"isAppInFocus"]; + + if (self.silentNotification) [obj setObject:@(self.silentNotification) forKeyedSubscript: @"silentNotification"]; @@ -311,55 +317,61 @@ + (NSArray*)getActionButtons { return lastMessageReceived[@"o"]; } -+ (NSArray*)getPushTitleBody:(NSDictionary*)messageDict { ++ (NSDictionary*)getPushTitleBody:(NSDictionary*)messageDict { NSString *title = messageDict[@"m"][@"title"]; - NSString *body = messageDict[@"m"][@"body"]; - if(!title) { - if([messageDict[@"aps"][@"alert"] isKindOfClass:[NSDictionary class]]) + if (!title) { + if ([messageDict[@"aps"][@"alert"] isKindOfClass:[NSDictionary class]]) title = messageDict[@"aps"][@"alert"][@"title"]; - else title = messageDict[@"aps"][@"alert"]; + else + title = messageDict[@"aps"][@"alert"]; } - if(!title) + if (!title) title = messageDict[@"os_data"][@"buttons"][@"m"][@"title"]; - if(!title) + if (!title) title = [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleNameKey]; - if(!title) title = @""; + if (!title) + title = @""; + - if(!body && [messageDict[@"aps"][@"alert"] isKindOfClass:[NSDictionary class]]) + NSString *body = messageDict[@"m"][@"body"]; + if (!body && [messageDict[@"aps"][@"alert"] isKindOfClass:[NSDictionary class]]) body = messageDict[@"aps"][@"alert"][@"body"]; - if(!body) + if (!body) body = messageDict[@"os_data"][@"buttons"][@"m"][@"body"]; - if(!body) + if (!body) body = @""; - return @[title, body]; + return @{@"title" : title, @"body": body}; } // Prevent the OSNotification blocks from firing if we receive a Non-OneSignal remote push + (BOOL)isOneSignalPayload { - if(!lastMessageReceived) return NO; + if (!lastMessageReceived) + return NO; return lastMessageReceived[@"custom"][@"i"] || lastMessageReceived[@"os_data"][@"i"]; } + (void)handleNotificationReceived:(OSNotificationDisplayType)displayType { - if (!handleNotificationReceived || ![self isOneSignalPayload]) return; - + if (!handleNotificationReceived || ![self isOneSignalPayload]) + return; OSNotificationPayload *payload = [[OSNotificationPayload alloc] initWithRawMessage:lastMessageReceived]; OSNotification *notification = [[OSNotification alloc] initWithPayload:payload displayType:displayType]; //Prevent duplicate calls to same action static NSString* lastMessageID = @""; - if([payload.notificationID isEqualToString:lastMessageID]) return; + if ([payload.notificationID isEqualToString:lastMessageID]) + return; lastMessageID = payload.notificationID; handleNotificationReceived(notification); } + (void)handleNotificationAction:(OSNotificationActionType)actionType actionID:(NSString*)actionID displayType:(OSNotificationDisplayType)displayType { - if (!handleNotificationAction || ![self isOneSignalPayload]) return; + if (!handleNotificationAction || ![self isOneSignalPayload]) + return; OSNotificationAction *action = [[OSNotificationAction alloc] initWithActionType:actionType :actionID]; OSNotificationPayload *payload = [[OSNotificationPayload alloc] initWithRawMessage:lastMessageReceived]; @@ -368,7 +380,8 @@ + (void)handleNotificationAction:(OSNotificationActionType)actionType actionID:( //Prevent duplicate calls to same action static NSString* lastMessageID = @""; - if([payload.notificationID isEqualToString:lastMessageID]) return; + if ([payload.notificationID isEqualToString:lastMessageID]) + return; lastMessageID = payload.notificationID; handleNotificationAction(result); @@ -480,25 +493,23 @@ + (void)requestAuthorization { [[NSClassFromString(@"UNUserNotificationCenter") currentNotificationCenter] requestAuthorizationWithOptions:7 completionHandler:^(BOOL granted, NSError * _Nullable error) {}]; } -+ (void)conformsToUNProtocol { - if (class_conformsToProtocol([UIApplication delegateClass], NSProtocolFromString(@"UNUserNotificationCenterDelegate"))) { - [OneSignal onesignal_Log:ONE_S_LL_ERROR message:@"Implementing iOS 10's UNUserNotificationCenterDelegate protocol will result in unexpected outcome. Instead, conform to our similar OSUserNotificationCenterDelegate protocol."]; - } -} - + (void)registerAsUNNotificationCenterDelegate { + Class UNNofiCenterClass = NSClassFromString(@"UNUserNotificationCenter"); + if (!UNNofiCenterClass) + return; - if(!NSClassFromString(@"UNUserNotificationCenter")) return; - [NSClassFromString(@"UNUserNotificationCenter") currentNotificationCenter].delegate = [self sharedInstance]; - + UNUserNotificationCenter *curNotifCenter = [UNNofiCenterClass currentNotificationCenter]; + if (!curNotifCenter.delegate) + curNotifCenter.delegate = (id)[self sharedInstance]; } + (id)prepareUNNotificationRequest:(NSDictionary *)data :(NSDictionary *)userInfo { - if(!NSClassFromString(@"UNNotificationAction") || !NSClassFromString(@"UNNotificationRequest")) return NULL; + if (!NSClassFromString(@"UNNotificationAction") || !NSClassFromString(@"UNNotificationRequest")) + return NULL; NSMutableArray * actionArray = [[NSMutableArray alloc] init]; - for( NSDictionary* button in data[@"o"]) { + for(NSDictionary* button in data[@"o"]) { NSString* title = button[@"n"] != NULL ? button[@"n"] : @""; NSString* buttonID = button[@"i"] != NULL ? button[@"i"] : title; id action = [NSClassFromString(@"UNNotificationAction") actionWithIdentifier:buttonID title:title options:UNNotificationActionOptionForeground]; @@ -517,7 +528,7 @@ + (id)prepareUNNotificationRequest:(NSDictionary *)data :(NSDictionary *)userInf id content = [[NSClassFromString(@"UNMutableNotificationContent") alloc] init]; [content setValue:@"__dynamic__" forKey:@"categoryIdentifier"]; - if(data[@"m"]) { + if (data[@"m"]) { if([data[@"m"] isKindOfClass:[NSDictionary class]]) { if(data[@"m"][@"title"]) [content setValue:data[@"m"][@"title"] forKey:@"title"]; @@ -526,26 +537,26 @@ + (id)prepareUNNotificationRequest:(NSDictionary *)data :(NSDictionary *)userInf if(data[@"m"][@"subtitle"]) [content setValue:data[@"m"][@"subtitle"] forKey:@"subtitle"]; } - else [content setValue:data[@"m"] forKey:@"body"]; + else + [content setValue:data[@"m"] forKey:@"body"]; } else if(data[@"aps"][@"alert"]) { - if([data[@"aps"][@"alert"] isKindOfClass:[NSDictionary class]]) { + if ([data[@"aps"][@"alert"] isKindOfClass:[NSDictionary class]]) { [content setValue:data[@"aps"][@"alert"][@"title"] forKey:@"title"]; [content setValue:data[@"aps"][@"alert"][@"body"] forKey:@"body"]; [content setValue:data[@"aps"][@"alert"][@"subtitle"] forKey:@"subtitle"]; } - else [content setValue:data[@"aps"][@"alert"] forKey:@"body"]; + else + [content setValue:data[@"aps"][@"alert"] forKey:@"body"]; } [content setValue:userInfo forKey:@"userInfo"]; - if(data[@"s"]) { - + if (data[@"s"]) { id defaultSound = [NSClassFromString(@"UNNotificationSound") performSelector:@selector(soundNamed:) withObject:data[@"s"]]; [content setValue:defaultSound forKey:@"sound"]; } - else [content setValue:[NSClassFromString(@"UNNotificationSound") performSelector:@selector(defaultSound)] forKey:@"sound"]; @@ -595,7 +606,7 @@ + (id)prepareUNNotificationRequest:(NSDictionary *)data :(NSDictionary *)userInf id trigger = [NSClassFromString(@"UNTimeIntervalNotificationTrigger") triggerWithTimeInterval:0.25 repeats:NO]; - return [NSClassFromString(@"UNNotificationRequest") requestWithIdentifier:@"__dynamic__"content:content trigger:trigger]; + return [NSClassFromString(@"UNNotificationRequest") requestWithIdentifier:[self randomStringWithLength:16] content:content trigger:trigger]; } + (void)addnotificationRequest:(NSDictionary *)data :(NSDictionary *)userInfo { @@ -671,7 +682,6 @@ + (void)enqueueRequest:(NSURLRequest*)request onSuccess:(OSResultSuccessBlock)su } + (void)enqueueRequest:(NSURLRequest*)request onSuccess:(OSResultSuccessBlock)successBlock onFailure:(OSFailureBlock)failureBlock isSynchronous:(BOOL)isSynchronous { - [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message: [NSString stringWithFormat:@"request.body: %@", [[NSString alloc]initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]]]; if (isSynchronous) { diff --git a/src/ios/OneSignalPush.h b/src/ios/OneSignalPush.h index b4616ad7..14808023 100644 --- a/src/ios/OneSignalPush.h +++ b/src/ios/OneSignalPush.h @@ -1,7 +1,7 @@ /** * Modified MIT License * - * Copyright 2015 OneSignal + * Copyright 2016 OneSignal * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/ios/OneSignalPush.m b/src/ios/OneSignalPush.m index f741bad4..47da6d24 100644 --- a/src/ios/OneSignalPush.m +++ b/src/ios/OneSignalPush.m @@ -1,7 +1,7 @@ /** * Modified MIT License * - * Copyright 2015 OneSignal + * Copyright 2016 OneSignal * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -232,7 +232,7 @@ - (void)syncHashedEmail:(CDVInvokedUrlCommand*)command { - (void)setLogLevel:(CDVInvokedUrlCommand*)command { NSDictionary* options = command.arguments[0]; - [OneSignal setLogLevel:(NSInteger)options[@"logLevel"] visualLevel:(NSInteger)options[@"visualLevel"]]; + [OneSignal setLogLevel:[options[@"logLevel"] intValue] visualLevel:[options[@"visualLevel"] intValue]]; } // Android only diff --git a/src/ios/OneSignalSelectorHelpers.h b/src/ios/OneSignalSelectorHelpers.h new file mode 100644 index 00000000..eddb9dc2 --- /dev/null +++ b/src/ios/OneSignalSelectorHelpers.h @@ -0,0 +1,36 @@ +/** + * Modified MIT License + * + * Copyright 2016 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OneSignalSelectorHelpers_h +#define OneSignalSelectorHelpers_h + +BOOL checkIfInstanceOverridesSelector(Class instance, SEL selector); +Class getClassWithProtocolInHierarchy(Class searchClass, Protocol* protocolToFind); +NSArray* ClassGetSubclasses(Class parentClass); +void injectToProperClass(SEL newSel, SEL makeLikeSel, NSArray* delegateSubclasses, Class myClass, Class delegateClass); + +#endif /* OneSignalSelectorHelpers_h */ diff --git a/src/ios/OneSignalSelectorHelpers.m b/src/ios/OneSignalSelectorHelpers.m new file mode 100644 index 00000000..85492fbf --- /dev/null +++ b/src/ios/OneSignalSelectorHelpers.m @@ -0,0 +1,107 @@ +/** + * Modified MIT License + * + * Copyright 2016 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#import +#import + +#import "OneSignalSelectorHelpers.h" + +BOOL checkIfInstanceOverridesSelector(Class instance, SEL selector) { + Class instSuperClass = [instance superclass]; + return [instance instanceMethodForSelector: selector] != [instSuperClass instanceMethodForSelector: selector]; +} + + +Class getClassWithProtocolInHierarchy(Class searchClass, Protocol* protocolToFind) { + if (!class_conformsToProtocol(searchClass, protocolToFind)) { + if ([searchClass superclass] == nil) + return nil; + Class foundClass = getClassWithProtocolInHierarchy([searchClass superclass], protocolToFind); + if (foundClass) + return foundClass; + return searchClass; + } + return searchClass; +} + +void injectSelector(Class newClass, SEL newSel, Class addToClass, SEL makeLikeSel) { + Method newMeth = class_getInstanceMethod(newClass, newSel); + IMP imp = method_getImplementation(newMeth); + const char* methodTypeEncoding = method_getTypeEncoding(newMeth); + BOOL successful = class_addMethod(addToClass, makeLikeSel, imp, methodTypeEncoding); + + if (!successful) { + class_addMethod(addToClass, newSel, imp, methodTypeEncoding); + newMeth = class_getInstanceMethod(addToClass, newSel); + Method orgMeth = class_getInstanceMethod(addToClass, makeLikeSel); + method_exchangeImplementations(orgMeth, newMeth); + } +} + +// Try to find out which class to inject to +void injectToProperClass(SEL newSel, SEL makeLikeSel, NSArray* delegateSubclasses, Class myClass, Class delegateClass) { + + // Find out if we should inject in delegateClass or one of its subclasses. + // CANNOT use the respondsToSelector method as it returns TRUE to both implementing and inheriting a method + // We need to make sure the class actually implements the method (overrides) and not inherits it to properly perform the call + // Start with subclasses then the delegateClass + + for(Class subclass in delegateSubclasses) { + if (checkIfInstanceOverridesSelector(subclass, makeLikeSel)) { + injectSelector(myClass, newSel, subclass, makeLikeSel); + return; + } + } + + // No subclass overrides the method, try to inject in delegate class + injectSelector(myClass, newSel, delegateClass, makeLikeSel); + +} + +NSArray* ClassGetSubclasses(Class parentClass) { + + int numClasses = objc_getClassList(NULL, 0); + Class *classes = NULL; + + classes = (Class *)malloc(sizeof(Class) * numClasses); + numClasses = objc_getClassList(classes, numClasses); + + NSMutableArray *result = [NSMutableArray array]; + for (NSInteger i = 0; i < numClasses; i++) { + Class superClass = classes[i]; + do { + superClass = class_getSuperclass(superClass); + } while(superClass && superClass != parentClass); + + if (superClass == nil) continue; + [result addObject:classes[i]]; + } + + free(classes); + + return result; +} diff --git a/src/ios/UNUserNotificationCenter+OneSignal.h b/src/ios/UNUserNotificationCenter+OneSignal.h new file mode 100644 index 00000000..941ce97b --- /dev/null +++ b/src/ios/UNUserNotificationCenter+OneSignal.h @@ -0,0 +1,39 @@ +/** + * Modified MIT License + * + * Copyright 2016 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UNUserNotificationCenter_OneSignal_h +#define UNUserNotificationCenter_OneSignal_h + +#import "OneSignal.h" + +#if XC8_AVAILABLE +@interface sizzleUNUserNotif : NSObject +@end +#endif + + +#endif /* UNUserNotificationCenter_OneSignal_h */ diff --git a/src/ios/UNUserNotificationCenter+OneSignal.m b/src/ios/UNUserNotificationCenter+OneSignal.m new file mode 100644 index 00000000..28e0f688 --- /dev/null +++ b/src/ios/UNUserNotificationCenter+OneSignal.m @@ -0,0 +1,226 @@ +/** + * Modified MIT License + * + * Copyright 2016 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#import +#import + +#import "UNUserNotificationCenter+OneSignal.h" +#import "OneSignal.h" +#import "OneSignalSelectorHelpers.h" + + +#if XC8_AVAILABLE + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +@interface OneSignal (UN_extra) ++ (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive; +@end + +// This class hooks into the following iSO 10 UNUserNotificationCenterDelegate selectors: +// - userNotificationCenter:willPresentNotification:withCompletionHandler: +// - Reads kOSSettingsKeyInFocusDisplayOption to respect it's setting. +// - userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: +// - Used to process opening a notifications. +// - The presents of this selector tells iOS to no longer fire `application:didReceiveRemoteNotification:fetchCompletionHandler:`. +// We call this to maintain existing behavior. + +@implementation sizzleUNUserNotif + +static Class delegateUNClass = nil; + +// Store an array of all UIAppDelegate subclasses to iterate over in cases where UIAppDelegate swizzled methods are not overriden in main AppDelegate +// But rather in one of the subclasses +static NSArray* delegateUNSubclasses = nil; + +// Take the received delegate and swizzle in our own hooks. +// - Selector will be called once if developer does not set a UNUserNotificationCenter delegate. +// - Selector will be called a 2nd time if the developer does set one. +- (void) setOneSignalUNDelegate:(id)delegate { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"sizzleUNUserNotif setOneSignalUNDelegate Fired!"]; + + delegateUNClass = getClassWithProtocolInHierarchy([delegate class], @protocol(UNUserNotificationCenterDelegate)); + delegateUNSubclasses = ClassGetSubclasses(delegateUNClass); + + injectToProperClass(@selector(onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler:), + @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:), delegateUNSubclasses, [sizzleUNUserNotif class], delegateUNClass); + + injectToProperClass(@selector(onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), + @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), delegateUNSubclasses, [sizzleUNUserNotif class], delegateUNClass); + + [self setOneSignalUNDelegate:delegate]; +} + +// Apples docs - Called when a notification is delivered to a foreground app. +- (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler: Fired!"]; + + // Depercated - [OneSignal notificationCenterDelegate] - Now handled by swizzling. + // Proxy to user if listening to delegate and overrides the method. + if ([[OneSignal notificationCenterDelegate] respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) { + [[OneSignal notificationCenterDelegate] userNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler]; + return; + } + + // Set the completionHandler options based on the ONESIGNAL_ALERT_OPTION value. + if (![[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"]) { + [[NSUserDefaults standardUserDefaults] setObject:@(OSNotificationDisplayTypeInAppAlert) forKey:@"ONESIGNAL_ALERT_OPTION"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + + NSUInteger completionHandlerOptions = 0; + NSInteger alert_option = [[NSUserDefaults standardUserDefaults] integerForKey:@"ONESIGNAL_ALERT_OPTION"]; + switch (alert_option) { + case OSNotificationDisplayTypeNone: completionHandlerOptions = 0; break; // Nothing + case OSNotificationDisplayTypeInAppAlert: completionHandlerOptions = 3; break; // Badge + Sound + case OSNotificationDisplayTypeNotification: completionHandlerOptions = 7; break; // Badge + Sound + Notification + default: break; + } + + // Call notificationOpened if no alert (MSB not set) + [OneSignal notificationOpened:notification.request.content.userInfo isActive:YES]; + + if ([self respondsToSelector:@selector(onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler:)]) + [self onesignalUserNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler]; + + // Calling completionHandler for the following reasons: + // App dev may have not implented userNotificationCenter:willPresentNotification. + // App dev may have implemented this selector but forgot to call completionHandler(). + // Note - iOS only uses the first call to completionHandler(). + completionHandler(completionHandlerOptions); +} + +// Apple's docs - Called to let your app know which action was selected by the user for a given notification. +- (void)onesignalUserNotificationCenter:(id)center didReceiveNotificationResponse:(id)response withCompletionHandler:(void(^)())completionHandler { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: Fired!"]; + + NSDictionary* usrInfo = [[[[response performSelector:@selector(notification)] valueForKey:@"request"] valueForKey:@"content"] valueForKey:@"userInfo"]; + if (!usrInfo || [usrInfo count] == 0) { + [sizzleUNUserNotif tunnelToDelegate:center response:response handler:completionHandler]; + return; + } + + NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init], + *customDict = [[NSMutableDictionary alloc] init], + *additionalData = [[NSMutableDictionary alloc] init]; + NSMutableArray *optionsDict = [[NSMutableArray alloc] init]; + + NSMutableDictionary* buttonsDict = usrInfo[@"os_data"][@"buttons"]; + NSMutableDictionary* custom = usrInfo[@"custom"]; + if (buttonsDict) { + [userInfo addEntriesFromDictionary:usrInfo]; + NSArray* o = buttonsDict[@"o"]; + if (o) + [optionsDict addObjectsFromArray:o]; + } + else if (custom) { + [userInfo addEntriesFromDictionary:usrInfo]; + [customDict addEntriesFromDictionary:custom]; + NSDictionary *a = customDict[@"a"]; + NSArray *o = userInfo[@"o"]; + if (a) + [additionalData addEntriesFromDictionary:a]; + if (o) + [optionsDict addObjectsFromArray:o]; + } + else { + BOOL isActive = [UIApplication sharedApplication].applicationState == UIApplicationStateActive && + [[[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"] intValue] != OSNotificationDisplayTypeNotification; + [OneSignal notificationOpened:usrInfo isActive:isActive]; + [sizzleUNUserNotif tunnelToDelegate:center response:response handler:completionHandler]; + return; + } + + NSMutableArray* buttonArray = [[NSMutableArray alloc] init]; + for (NSDictionary* button in optionsDict) { + NSString * text = button[@"n"] != nil ? button[@"n"] : @""; + NSString * buttonID = button[@"i"] != nil ? button[@"i"] : text; + NSDictionary * buttonToAppend = [[NSDictionary alloc] initWithObjects:@[text, buttonID] forKeys:@[@"text", @"id"]]; + [buttonArray addObject:buttonToAppend]; + } + + additionalData[@"actionSelected"] = [response valueForKey:@"actionIdentifier"]; + additionalData[@"actionButtons"] = buttonArray; + + NSDictionary* os_data = usrInfo[@"os_data"]; + if (os_data) { + [userInfo addEntriesFromDictionary:os_data]; + if (userInfo[@"os_data"][@"buttons"][@"m"]) + userInfo[@"aps"] = @{@"alert" : userInfo[@"os_data"][@"buttons"][@"m"]}; + [userInfo addEntriesFromDictionary:additionalData]; + } + else { + customDict[@"a"] = additionalData; + userInfo[@"custom"] = customDict; + if(userInfo[@"m"]) + userInfo[@"aps"] = @{ @"alert" : userInfo[@"m"] }; + } + + UIApplication *sharedApp = [UIApplication sharedApplication]; + + BOOL isActive = sharedApp.applicationState == UIApplicationStateActive && + [[[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"] intValue] != OSNotificationDisplayTypeNotification; + + if ([OneSignal app_id]) + [OneSignal notificationOpened:userInfo isActive:isActive]; + [sizzleUNUserNotif tunnelToDelegate:center response:response handler:completionHandler]; + + // Call orginal selector if one was set. + if ([self respondsToSelector:@selector(onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) + [self onesignalUserNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler]; + else if ([sharedApp.delegate respondsToSelector:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]) { + // Call depercated pre-iOS 10 selector if one as set on the AppDelegate. + // NOTE: Should always be true as our AppDelegate swizzling should be there unless something else unsizzled it. + [sharedApp.delegate application:sharedApp didReceiveRemoteNotification:usrInfo fetchCompletionHandler:^(UIBackgroundFetchResult result) { + // Call iOS 10's compleationHandler from iOS 9's completion handler. + completionHandler(); + }]; + } +} + +// Depercated - [OneSignal notificationCenterDelegate] - Now handled by swizzling. +// Just need to keep the `handler();` call ++ (void)tunnelToDelegate:(id)center response:(id)response handler:(void (^)())handler { + + if ([[OneSignal notificationCenterDelegate] respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) + [[OneSignal notificationCenterDelegate] userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:handler]; + else + handler(); +} + +@end + +#pragma clang diagnostic pop +#pragma clang diagnostic pop + +#endif