Skip to content

Commit b3044eb

Browse files
authored
Email (#336)
* Email SDK Changes • Begins to add support for email in the iOS SDK * Multi-Requests • Changed the SDK so that for certain HTTP requests (ie. registering a user or sending location), the SDK will send two HTTP requests (separate for Push user ID and Email user ID). • The method to execute multiple HTTP requests will wait until both finish to trigger a callback. Each request is passed in with a key string. The callback returns key/value pairs where the key is associated with the same key. • Added tests covering the additional functionality * Email Logout • Included app_id as a parameter for the email logout request • Added a test for email logout • Fixed an issue where setEmail: did not correctly set the current subscription state email user ID * Faster Tests • Got rid of a thread synchronization line that wasn't necessary and slowed down unit tests by nearly 2x. * Splitting/Refactoring Tests • Began (but did not finish) the work of splitting up unit tests so that they are no longer all in one giant file/class and are a bit more modular • Fixed the Create/Edit device REST requests in the application which were using incorrect 'email' vs. 'identifier' parameter banes * Re-Attempt Failed Network Requests • This implements a system so that the SDK will reattempt failed network requests under two possible situations: 1. The HTTP status code is 500+, indicating a server error. 2. The HTTP status code is 0, which in iOS indicates no internet connection • The re-attempt is only made one time, after waiting 15 seconds. • The success/fail callbacks are not called until the re-attempt is made. If the second attempt fails, the failure callback is called. * Fix HTTP Request Re-Attempt Time Definitions • The re-attempt define statements were incorrect due to debugging, fixed them to be the correct values. * Multiple Re-Attempts • Changes the SDK so that multiple re-attempts can be made for failed HTTP requests • Currently, all failed requests (HTTP code 500+) will be attempted 3 times total. * OneSignal Email SDK Feature • Built out most of the Email feature for the SDK • Improved the failed HTTP request re-attempt code to be a bit more readable • Put a lot of commonly used strings into a single CommonDefines header file • Made it so that calls to `setEmail:` will be delayed under two conditions: 1. The player_id is not set, meaning `registerUserInternal()` has not yet finished 2. No email_auth_code was passed in as a parameter to `setEmail:` and the GetIOSParams request has not yet finished. * Fix Tests • Fixed some issues that broke unit tests due to email. • Fixed an issue that added an extra / to the Create Device endpoint path. * OneSignal SDK + Email • Added convenience methods to set/logout/etc. from email without completion blocks • Added another test to cover unauthenticated email • Fixed some errors with tests • Changed verbose logging with HTTP requests to log HTTP parameters as pretty-printed JSON for easier debugging with rest clients • Persists the email_auth_token and email address in NSUserDefaults for use to authenticate in future sessions • This commit now correctly sends in the email_auth_hash in the correct HTTP requests (send location, send tags, etc) • The iOS Params file is now downloaded at launch in all cases (since it now effects email) * Email Fixes • Made some changes to parameter names for a few endpoints to match what the backend requires • The backend needs the client to call UpdateDeviceToken even when the user just receives a new email player_id. Added this HTTP request in the setEmail: method. • Adjusted unit tests to account for the fact that all successful calls to setEmail: will end with a call to UpdateDeviceToken in one way or another • Added some more checks to the unit tests for Email to ensure it is correctly setting the email and also logs out correctly • Adjusted the dev app to include an email text field and Set Email + Logout Email buttons to make testing easier. * Add Carrier Name • Added "carrier" as a parameter on registration. Uses reflection to access the Core Telephony framework (if available) and uses it to attach the carrier name to the body of the request. • Fixes an issue with unauthenticated email users where some requests (on_session, update location, etc) weren't being called for Email • Added emailUserId and emailAddress as parameters in the dictionary & string representations of OSSubscriptionState * Fixed Tests • Changed the OSSubscriptionState `description` property to include Email & Email player id in the previous commit. • This broke a test checking the `description` property. • Removed this description value as it seems somewhat superfluous to test. * Email Callbacks on Main Thread • Because some developers will probably make UI adjustments when the completion block for setEmail() is called, I have changed the SDK to always call the success/fail completion blocks on the main thread. * Add Email to Swift Demo • Adds Email functionality to the Swift demo project, both the ability to setUnauthenticatedEmail() and logoutEmail() • Fixes some minor compiler warnings due to optional string variables • Adds warning to the syncEmail() method in the SDK so that it will print a warning if the email is invalid or nil. Otherwise developers may not notice that the email isn't actually getting synced since no error is returned. * Adds Invalid Email Test • Checks to make sure that the SDK rejects invalid emails. * Add require_email_auth tests • Added tests to ensure the SDK correctly handles the require_email_auth parameter from iOS params • If require_email_auth == true, the SDK should reject attempts to use `setUnauthenticatedEmail:` * Streamline Email Tests • Puts commonly used email test setup into its own method to avoid duplication • Adds more conditions to the testRequiresEmailAuth: test * Remove Hardcoded URL's • Unit tests used hardcoded URL's for checking HTTP request correctness • Unit tests now build use the same compiler definition to get the server URL & api version as the SDK does. • Moved the URL & API version definitions into the Common Definitions header file * Split Email & Subscription Observer • Removed email-related properties and functionality from OSSubscriptionState and created a separate Email Subscription Observer (OSEmailSubscription) • Created a new protocol so that developers can add email observers and get updated whenever the values change. • Added tests for the new email subscription observer • Fixed an issue where a change in the email player ID in response to registration/device token update would not have been reflected in the SDK * Track In-App Purchase Safety Check • Checks to make sure the SKPaymentQueue class responds to the canMakePayments() selector before calling it. • Cleans up email-related OneSignal.h header declarations, adds some comments, and moves unnecessary typedefs to a more appropriate header file (OneSignalClient.h) * Update Server URL • Mistakenly committed staging server as the OneSignal server URL in common defines * Add Email to OneSignal.getPermissionSubscriptionState() • Adds an Email subscription state property to the result of getPermissionSubscriptionState() • This allows developers to ask if the user is subscribed to onesignal email. * Avoid Duplicate Requests • If setEmail() is called, and then gets called later on with the same parameters (same email address), there is no need to perform another HTTP request • This was implemented in a previous commit but would have failed if using email in an unauthenticated state, since emailAuthToken is nil. • The NSString comparison would return false if one/both strings were nil • Fixed it so that if both strings are nil (unauthenticated state), it will correctly identify it as a duplicate request if the emails match (existing email and email passed in as parameter to setEmail()) * Fix iOS Params Request • The SDK was calling a method to download the iOS parameters file whenever init() was called on the OneSignal SDK • Since some wrapper SDK's will call init() twice, this caused an unnecessary duplicate call to downloadIOSParams • Resolved by using a static boolean flag to prevent duplicate calls • Adjusted some of the tests to reflect this change. * Change setUnauthenticatedEmail • We've decided to rename setUnauthenticatedEmail: to setEmail: (but without an emailAuthToken parameter) * Null Email Auth Token • Some wrapper SDK's pass in NSNull instead of nil, which caused an issue with the setEmail: method. • Added a check for this condition. * Add (unfinished) test for on_focus • Check to make sure the SDK is correctly sending on_focus events for both email and push player ID's * Add Email Tests • Adds tests to ensure that: • The register user request will send out two requests, one for email player ID (if it exists) and one for push • on_focus events also send out two requests, one for email & push * Fix Failing Test Build • CI build appears to be failing for a strange issue on line 805 of OneSignal.m callbackSet.failureBlock(error); • Added some parentheses to ensure the correct codepath is being followed. * Remove Extraneous Log Statements • Removes extraneous log statements • Fixes header comments for OSEmailSubscription class * Update Travis Config • Travis build is failing due to an 'undeclared identifier' error. However this doesn't appear to be correct, attempting to change Travis yml settings. • Remove an unnecessary log statement * Fix Test • Travis refuses to build, attempt to fix by redefining the `error` variable causing the issue. * Change Travis Build Scheme • Travis doesn't appear to have support for running tests targeted at iOS 11.2. Changed config file back to iOS 11.0 * Fix Test Build Issue • Remove the 'error' variable entirely. • Travis CI complains this variable is undeclared.....
1 parent 94dd9b4 commit b3044eb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2483
-508
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ before_install:
55
script:
66
- xcodebuild -list
77
- xcodebuild build -scheme OneSignal-Dynamic
8-
- xcodebuild -scheme UnitTests -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPad Air,OS=11.0' test
8+
- xcodebuild -scheme UnitTests -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8 Plus,OS=11.0' test

Examples/SwiftExample/OneSignalDemo/AppDelegate.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, OSPermissionObserver, OSS
4646
let notificationReceivedBlock: OSHandleNotificationReceivedBlock = { notification in
4747

4848
print("Received Notification: \(notification!.payload.notificationID)")
49-
print("launchURL = \(notification?.payload.launchURL)")
50-
print("content_available = \(notification?.payload.contentAvailable)")
49+
print("launchURL = \(notification?.payload.launchURL ?? "None")")
50+
print("content_available = \(notification?.payload.contentAvailable ?? false)")
5151
}
5252

5353
let notificationOpenedBlock: OSHandleNotificationActionBlock = { result in
5454
// This block gets called when the user reacts to a notification received
5555
let payload: OSNotificationPayload? = result?.notification.payload
5656

5757
print("Message = \(payload!.body)")
58-
print("badge number = \(payload?.badge)")
59-
print("notification sound = \(payload?.sound)")
58+
print("badge number = \(payload?.badge ?? 0)")
59+
print("notification sound = \(payload?.sound ?? "None")")
6060

6161
if let additionalData = result!.notification.payload!.additionalData {
6262
print("additionalData = \(additionalData)")
@@ -104,7 +104,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, OSPermissionObserver, OSS
104104

105105
let onesignalInitSettings = [kOSSettingsKeyAutoPrompt: false, kOSSettingsKeyInAppLaunchURL: true, ]
106106

107-
OneSignal.initWithLaunchOptions(launchOptions, appId: "<REPLACE_WITH_YOUR_ONESIGNAL_APP_ID", handleNotificationReceived: notificationReceivedBlock, handleNotificationAction: notificationOpenedBlock, settings: onesignalInitSettings)
107+
OneSignal.initWithLaunchOptions(launchOptions, appId: "b2f7f966-d8cc-11e4-bed1-df8f05be55ba", handleNotificationReceived: notificationReceivedBlock, handleNotificationAction: notificationOpenedBlock, settings: onesignalInitSettings)
108108

109109
OneSignal.inFocusDisplayType = OSNotificationDisplayType.notification
110110

Examples/SwiftExample/OneSignalDemo/Base.lproj/Main.storyboard

Lines changed: 65 additions & 16 deletions
Large diffs are not rendered by default.

Examples/SwiftExample/OneSignalDemo/ViewController.swift

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,16 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
3535
@IBOutlet weak var allowNotificationsSwitch: UISwitch!
3636
@IBOutlet weak var setSubscriptionLabel: UILabel!
3737
@IBOutlet weak var registerForPushNotificationsButton: UIButton!
38+
@IBOutlet weak var setEmailButton: UIButton!
39+
@IBOutlet weak var emailTextField: UITextField!
40+
@IBOutlet weak var setEmailActivityIndicatorView: UIActivityIndicatorView!
41+
@IBOutlet weak var logoutEmailButton: UIButton!
3842

3943
override func viewDidLoad() {
4044
super.viewDidLoad()
4145

46+
OneSignal.setLogLevel(.LL_VERBOSE, visualLevel: .LL_NONE);
47+
4248
let status: OSPermissionSubscriptionState = OneSignal.getPermissionSubscriptionState()
4349
let isSubscribed = status.subscriptionStatus.subscribed
4450

@@ -50,20 +56,64 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
5056
}
5157
OneSignal.add(self as OSPermissionObserver)
5258
OneSignal.add(self as OSSubscriptionObserver)
59+
60+
self.emailTextField.delegate = self;
61+
}
62+
63+
func displayMessageToUser(_ title : String, _ message : String, actions : [UIAlertAction]?=nil) {
64+
65+
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
66+
67+
for action in actions ?? [UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)] {
68+
alertController.addAction(action);
69+
}
70+
71+
self.present(alertController, animated: true, completion: nil)
5372
}
5473

5574
func displaySettingsNotification() {
56-
let message = NSLocalizedString("Please turn on notifications by going to Settings > Notifications > Allow Notifications", comment: "Alert message when the user has denied access to the notifications")
57-
let alertController = UIAlertController(title: "OneSignal Example", message: message, preferredStyle: .alert)
58-
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil))
59-
alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .`default`, handler: { action in
75+
var actions = [UIAlertAction]();
76+
77+
actions.append(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil));
78+
actions.append(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .`default`, handler: { action in
6079
if #available(iOS 10.0, *) {
6180
UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!, options: [:], completionHandler: nil)
6281
} else {
6382
// Fallback on earlier versions
6483
}
65-
}))
66-
self.present(alertController, animated: true, completion: nil)
84+
}));
85+
86+
self.displayMessageToUser(NSLocalizedString("OneSignal Example", comment: "title for the alert"), NSLocalizedString("Please turn on notifications by going to Settings > Notifications > Allow Notifications", comment: "Alert message when the user has denied access to the notifications"), actions: actions);
87+
}
88+
89+
func changeEmailAnimationState(_ animating : Bool) {
90+
UIView.animate(withDuration: 0.14) {
91+
self.setEmailButton.alpha = animating ? 0.0 : 1.0;
92+
};
93+
94+
self.setEmailButton.isEnabled = !animating;
95+
self.setEmailButton.adjustsImageWhenDisabled = false;
96+
animating ? self.setEmailActivityIndicatorView.startAnimating() : self.setEmailActivityIndicatorView.stopAnimating();
97+
}
98+
99+
func setEmail() {
100+
self.emailTextField.resignFirstResponder();
101+
102+
guard let emailAddress = self.emailTextField.text else { return };
103+
104+
self.changeEmailAnimationState(true);
105+
106+
OneSignal.setUnauthenticatedEmail(emailAddress, withSuccess: {
107+
self.changeEmailAnimationState(false);
108+
109+
print("Successfully set email");
110+
111+
self.logoutEmailButton.isEnabled = true;
112+
}) { (error) in
113+
self.changeEmailAnimationState(false);
114+
115+
self.displayMessageToUser(NSLocalizedString("An Error Occurred", comment: "Title alerting the user that an error occurred"), NSLocalizedString("Encountered the following error while attempting to set unauthenticated email:\n\n\(error.debugDescription)", comment: "Shows the error to the user"));
116+
};
67117
}
68118

69119
func onOSPermissionChanged(_ stateChanges: OSPermissionStateChanges!) {
@@ -82,6 +132,8 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
82132
}
83133

84134
func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges!) {
135+
136+
85137
if stateChanges.from.subscribed && !stateChanges.to.subscribed { // NOT SUBSCRIBED != DENIED
86138
allowNotificationsSwitch.isOn = false
87139
setSubscriptionLabel.text = "Set Subscription OFF"
@@ -93,6 +145,8 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
93145
registerForPushNotificationsButton.backgroundColor = UIColor.green
94146
registerForPushNotificationsButton.isUserInteractionEnabled = false
95147
}
148+
149+
self.logoutEmailButton.isEnabled = stateChanges.to.emailUserId != nil;
96150
}
97151

98152
@IBAction func onRegisterForPushNotificationsButton(_ sender: UIButton) {
@@ -115,6 +169,7 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
115169

116170
@IBAction func onSendTagsButton(_ sender: UIButton) {
117171

172+
118173
let tags: [AnyHashable : Any] = [
119174
"some_key" : "some_value",
120175
"users_name" : "Jon",
@@ -126,28 +181,41 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
126181
OneSignal.sendTags(tags, onSuccess: { result in
127182
print("Tags sent - \(result!)")
128183
}) { error in
129-
print("Error sending tags: \(error?.localizedDescription)")
184+
print("Error sending tags: \(error?.localizedDescription ?? "None")")
130185
}
131186
}
132187

133188
@IBAction func onGetTagsButton(_ sender: UIButton) {
134189
OneSignal.getTags({ tags in
135190
print("tags - \(tags!)")
136191
}, onFailure: { error in
137-
print("Error getting tags - \(error?.localizedDescription)")
192+
print("Error getting tags - \(error?.localizedDescription ?? "None")")
138193
// errorWithDomain - OneSignalError
139194
// code - HTTP error code from the OneSignal server
140195
// userInfo - JSON OneSignal responded with
141196
})
142197
}
143198

144-
@IBAction func onDeleteOrUpdateTagsButton(_ sender: UIButton) {
145-
//OneSignal.deleteTag("some_key")
146-
OneSignal.deleteTags(["some_key", "users_name", "has_followers", "added_review"])
147-
// To update tags simply add new ones
148-
OneSignal.sendTags(["finished_level" : "60"])
199+
200+
@IBAction func setEmailButtonPressed(_ sender : UIButton) {
201+
// NOTE: Because of implementation details, a call to setEmail() or setUnauthenticatedEmail() can sometimes take 30 seconds or more.
202+
203+
// OneSignal now has the ability to send emails to your users
204+
// There are two methods to do so. Authenticated and Unauthenticated
205+
// We heavily recommend taking the time to use authenticated setEmail with your backend
206+
207+
self.setEmail();
208+
}
209+
210+
@IBAction func logoutEmailButtonPressed(_ sender: UIButton) {
211+
OneSignal.logoutEmail(success: {
212+
print("Successfully logged out of email");
213+
}) { (error) in
214+
self.displayMessageToUser(NSLocalizedString("An Error Occurred", comment: "Title to show that an error occurred"), NSLocalizedString("An error occurred while attempting to log out of the email for this device:\n\n\(error.debugDescription)", comment: "Displays the error that occurred"));
215+
};
149216
}
150217

218+
151219
// User IDs
152220
@IBAction func onGetIDsButton(_ sender: UIButton) {
153221
//getPermissionSubscriptionState
@@ -161,16 +229,22 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
161229
let userSubscriptionSetting = status.subscriptionStatus.userSubscriptionSetting
162230
print("userSubscriptionSetting = \(userSubscriptionSetting)")
163231
let userID = status.subscriptionStatus.userId
164-
print("userID = \(userID)")
232+
print("userID = \(userID ?? "None")")
165233
let pushToken = status.subscriptionStatus.pushToken
166-
print("pushToken = \(pushToken)")
234+
print("pushToken = \(pushToken ?? "None")")
235+
let emailId = status.subscriptionStatus.emailUserId;
236+
print("Email userID = \(emailId ?? "None")");
167237
}
168238

169239
@IBAction func onSyncEmailButton(_ sender: UIButton) {
170240
// 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.
171-
let testEmail = "[email protected]"
172-
OneSignal.syncHashedEmail(testEmail)
173-
print("sync hashedEmail successful")
241+
242+
if let email = self.emailTextField.text, email.isEmpty == false {
243+
OneSignal.syncHashedEmail(email)
244+
print("sync hashedEmail successful")
245+
} else {
246+
self.displayMessageToUser(NSLocalizedString("No Email Exists", comment: "Explains that no email is currently set"), NSLocalizedString("You must fill out the email address in the text field before syncing it.", comment: "Explains that no email is currently set in the text field"));
247+
}
174248
}
175249

176250
@IBAction func onPromptLocationButton(_ sender: UIButton) {
@@ -247,3 +321,15 @@ class ViewController: UIViewController, OSPermissionObserver, OSSubscriptionObse
247321
}
248322
}
249323
}
324+
325+
extension ViewController : UITextFieldDelegate {
326+
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
327+
textField.resignFirstResponder();
328+
329+
if (textField.text?.count ?? 0 > 0) {
330+
self.setEmail();
331+
}
332+
333+
return true;
334+
}
335+
}

iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#import <UIKit/UIKit.h>
3232
#import <OneSignal/OneSignal.h>
3333

34-
@interface AppDelegate : UIResponder <UIApplicationDelegate, OSPermissionObserver, OSSubscriptionObserver>
34+
@interface AppDelegate : UIResponder <UIApplicationDelegate, OSPermissionObserver, OSSubscriptionObserver, OSEmailSubscriptionObserver>
3535

3636
@property (strong, nonatomic) UIWindow *window;
3737

iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
6060

6161
id notificationReceiverBlock = ^(OSNotification *notification) {
6262
NSLog(@"Received Notification - %@", notification.payload.notificationID);
63+
64+
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:11];
6365
};
6466

6567
[OneSignal initWithLaunchOptions:launchOptions
66-
appId:@"b2f7f966-d8cc-11e4-bed1-df8f05be55ba"
68+
appId:@"5dc0b8c7-335a-4c4c-9ed4-266cbf2158ac"
6769
handleNotificationReceived:notificationReceiverBlock
6870
handleNotificationAction:openNotificationHandler
6971
settings:@{kOSSettingsKeyAutoPrompt: @false,
@@ -76,6 +78,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
7678

7779
[OneSignal addPermissionObserver:self];
7880
[OneSignal addSubscriptionObserver:self];
81+
[OneSignal addEmailSubscriptionObserver:self];
7982

8083
NSLog(@"UNUserNotificationCenter.delegate: %@", UNUserNotificationCenter.currentNotificationCenter.delegate);
8184

@@ -92,6 +95,11 @@ - (void) onOSPermissionChanged:(OSPermissionStateChanges*)stateChanges {
9295
NSLog(@"HERE");
9396
}
9497

98+
-(void)onOSEmailSubscriptionChanged:(OSEmailSubscriptionStateChanges *)stateChanges {
99+
NSLog(@"onOSEmailSubscriptionChanged: %@", stateChanges);
100+
101+
}
102+
95103

96104
- (void)applicationWillResignActive:(UIApplication *)application {
97105
}
@@ -115,9 +123,6 @@ - (void)applicationDidBecomeActive:(UIApplication *)application {
115123
// kFIRParameterCampaign: @"some title"
116124
// }];
117125

118-
NSString* test = @"{ \
119-
\"user_list\": [] \
120-
}";
121126
}
122127

123128

0 commit comments

Comments
 (0)