Skip to content
This repository was archived by the owner on Jul 22, 2020. It is now read-only.

Commit 8dbf1db

Browse files
authored
[ITBL-5309] Add handleUniversalLink: method (#59)
* Add resolveAppLinkURL:callback: method with a URL callback in addition to getAndTrackDeeplink * Make sure we call getAndTrackDeeplink: and resolveApplinkURL: on the main queue * Add tests for resolveApplinkURL: method, check that the callback is run on the main thread * Replace resolveApplinkURL: with handleUniversalLink: that creates a new IterableAction and executes it with IterableActionRunner Add IterableActionContext specifying the context the action is executing in (including the source – push or universal link) * Fix warnings * Fix a typo * Fix another typo
1 parent 485feb6 commit 8dbf1db

19 files changed

+233
-69
lines changed

Iterable-iOS-SDK.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
68E32EF61AC21D37000CEBE1 /* CommerceItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 6804B8651AC10426000A126B /* CommerceItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
4949
921AE47A20AA2E94009CFC9F /* IterableNotificationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 921AE47920AA2E94009CFC9F /* IterableNotificationResponseTests.m */; };
5050
921AE47C20AA43D5009CFC9F /* IterableActionRunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 921AE47B20AA43D5009CFC9F /* IterableActionRunnerTests.m */; };
51+
924E781D20E43C2F003CEC14 /* IterableActionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E95160EFC943628C8A6BC430 /* IterableActionContext.m */; };
5152
9267AA1820992F9C006AF394 /* IterableAppExtensions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9267AA0F20992F9C006AF394 /* IterableAppExtensions.framework */; };
5253
9267AA1D20992F9C006AF394 /* IterableAppExtensionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9267AA1C20992F9C006AF394 /* IterableAppExtensionsTests.m */; };
5354
9267AA1F20992F9C006AF394 /* IterableAppExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 9267AA1120992F9C006AF394 /* IterableAppExtensions.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -76,6 +77,7 @@
7677
AC60AB4B20BDDD5100BF6BEC /* IterableUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC60AB4A20BDDD5100BF6BEC /* IterableUtilTests.m */; };
7778
E95160B3683E5A47EC313472 /* IterableAppIntegration+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E9516F6B446F6E9AB537C83F /* IterableAppIntegration+Private.h */; };
7879
E951624B2D970B91EE110E56 /* IterableAPI+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = E95162E0F83DDB6177F24360 /* IterableAPI+Internal.h */; };
80+
E951656A49CFFB4BED48AF3B /* IterableActionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E9516F2F4291E19DC6625187 /* IterableActionContext.h */; };
7981
E9516B4E1A6D7B8CB1314D1F /* IterableAPI+Deprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = E9516BD2E9DDA6AE024F6A63 /* IterableAPI+Deprecated.h */; };
8082
E9516D3401091CA4D87B10B9 /* IterableAPI+Deprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = E9516D87A090ED58BED61C6B /* IterableAPI+Deprecated.m */; };
8183
/* End PBXBuildFile section */
@@ -190,10 +192,12 @@
190192
AC60AB4A20BDDD5100BF6BEC /* IterableUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IterableUtilTests.m; sourceTree = "<group>"; };
191193
BE5CCE23DD4BB53759B5D427 /* Pods-Iterable-iOS-SDK.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Iterable-iOS-SDK.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Iterable-iOS-SDK/Pods-Iterable-iOS-SDK.debug.xcconfig"; sourceTree = "<group>"; };
192194
DAB696718A4F07CC09FB5CB1 /* Pods-Iterable-iOS-SDKTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Iterable-iOS-SDKTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Iterable-iOS-SDKTests/Pods-Iterable-iOS-SDKTests.release.xcconfig"; sourceTree = "<group>"; };
195+
E95160EFC943628C8A6BC430 /* IterableActionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IterableActionContext.m; sourceTree = "<group>"; };
193196
E95162E0F83DDB6177F24360 /* IterableAPI+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IterableAPI+Internal.h"; sourceTree = "<group>"; };
194197
E95162E4972E90805BB61534 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = file.yml; path = .travis.yml; sourceTree = "<group>"; };
195198
E9516BD2E9DDA6AE024F6A63 /* IterableAPI+Deprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IterableAPI+Deprecated.h"; sourceTree = "<group>"; };
196199
E9516D87A090ED58BED61C6B /* IterableAPI+Deprecated.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "IterableAPI+Deprecated.m"; sourceTree = "<group>"; };
200+
E9516F2F4291E19DC6625187 /* IterableActionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IterableActionContext.h; sourceTree = "<group>"; };
197201
E9516F6B446F6E9AB537C83F /* IterableAppIntegration+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IterableAppIntegration+Private.h"; sourceTree = "<group>"; };
198202
/* End PBXFileReference section */
199203

@@ -301,6 +305,8 @@
301305
AC60AB4720BDD4DC00BF6BEC /* IterableUtil.m */,
302306
92F03EEA20D899CE00C3BFCA /* IterableConfig.h */,
303307
92F03EEB20D899CE00C3BFCA /* IterableConfig.m */,
308+
E95160EFC943628C8A6BC430 /* IterableActionContext.m */,
309+
E9516F2F4291E19DC6625187 /* IterableActionContext.h */,
304310
);
305311
path = "Iterable-iOS-SDK";
306312
sourceTree = "<group>";
@@ -431,6 +437,7 @@
431437
AC60AB4820BDD4DC00BF6BEC /* IterableUtil.h in Headers */,
432438
E9516B4E1A6D7B8CB1314D1F /* IterableAPI+Deprecated.h in Headers */,
433439
E951624B2D970B91EE110E56 /* IterableAPI+Internal.h in Headers */,
440+
E951656A49CFFB4BED48AF3B /* IterableActionContext.h in Headers */,
434441
);
435442
runOnlyForDeploymentPostprocessing = 0;
436443
};
@@ -719,6 +726,7 @@
719726
0995DBDD1CFFF7FE00D50F19 /* IterableLogging.m in Sources */,
720727
926B33E520B7A091004DD7CE /* IterableAttributionInfo.m in Sources */,
721728
E9516D3401091CA4D87B10B9 /* IterableAPI+Deprecated.m in Sources */,
729+
924E781D20E43C2F003CEC14 /* IterableActionContext.m in Sources */,
722730
);
723731
runOnlyForDeploymentPostprocessing = 0;
724732
};

Iterable-iOS-SDK/IterableAPI.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,18 @@ typedef void (^OnFailureHandler)(NSString *reason, NSData *_Nullable data);
513513
514514
@discussion passes the string of the redirected URL to the callback, returns the original webpageURL if not an iterable link
515515
*/
516-
+(void) getAndTrackDeeplink:(NSURL *)webpageURL callbackBlock:(ITEActionBlock)callbackBlock;
516+
+ (void)getAndTrackDeeplink:(NSURL *)webpageURL callbackBlock:(ITEActionBlock)callbackBlock;
517+
518+
/**
519+
* Handles a Universal Link
520+
* For Iterable links, it will track the click and retrieve the original URL,
521+
* pass it to `IterableURLDelegate` for handling
522+
* If it's not an Iterable link, it just passes the same URL to `IterableURLDelegate`
523+
*
524+
* @param url the URL obtained from `[NSUserActivity webpageURL]`
525+
* @return YES if it is an Iterable link, or the value returned from `IterableURLDelegate` otherwise
526+
*/
527+
+ (BOOL)handleUniversalLink:(NSURL *)url;
517528

518529
@end
519530

Iterable-iOS-SDK/IterableAPI.m

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#import "IterableConstants.h"
3030
#import "IterableDeeplinkManager.h"
3131
#import "IterableAppIntegration+Private.h"
32+
#import "IterableActionRunner.h"
3233

3334
@interface IterableAPI ()
3435

@@ -457,12 +458,27 @@ + (IterableAPI *)sharedInstanceWithApiKey:(NSString *)apiKey andUserId:(NSString
457458
}
458459

459460
// documented in IterableAPI.h
460-
+(void) getAndTrackDeeplink:(NSURL *)webpageURL callbackBlock:(ITEActionBlock)callbackBlock
461+
+ (void)getAndTrackDeeplink:(NSURL *)webpageURL callbackBlock:(ITEActionBlock)callbackBlock
461462
{
462463
IterableDeeplinkManager *deeplinkManager = [IterableDeeplinkManager instance];
463464
[deeplinkManager getAndTrackDeeplink:webpageURL callbackBlock:callbackBlock];
464465
}
465466

467+
// documented in IterableAPI.h
468+
+ (BOOL)handleUniversalLink:(NSURL *)url {
469+
IterableDeeplinkManager *deeplinkManager = [IterableDeeplinkManager instance];
470+
if ([deeplinkManager isIterableDeeplink:url]) {
471+
[deeplinkManager getAndTrackDeeplink:url callbackBlock:^(NSString *originalUrlString) {
472+
IterableAction *action = [IterableAction actionOpenUrl:originalUrlString];
473+
[IterableActionRunner executeAction:action from:IterableActionSourceUniversalLink];
474+
}];
475+
return YES;
476+
} else {
477+
IterableAction *action = [IterableAction actionOpenUrl:url.absoluteString];
478+
return [IterableActionRunner executeAction:action from:IterableActionSourceUniversalLink];
479+
}
480+
}
481+
466482
// documented in IterableAPI.h
467483
- (instancetype)initWithApiKey:(NSString *)apiKey andEmail:(NSString *)email
468484
{

Iterable-iOS-SDK/IterableAction.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ extern NSString *const IterableActionTypeOpenUrl;
5656
*/
5757
+ (instancetype)actionFromDictionary:(NSDictionary *)dictionary;
5858

59+
/**
60+
* Creates a new `IterableAction` with type `IterableActionTypeOpenUrl`
61+
* and the specified URL
62+
* @param url URL to open
63+
* @return `IterableAction` instance
64+
*/
65+
+ (instancetype)actionOpenUrl:(NSString *)url;
66+
5967
////////////////////
6068
/// @name Methods
6169
////////////////////

Iterable-iOS-SDK/IterableAction.m

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,19 @@ @interface IterableAction ()
1919
@implementation IterableAction
2020

2121
+ (instancetype)actionFromDictionary:(NSDictionary *)dictionary {
22-
if (dictionary != nil)
22+
if (dictionary != nil) {
2323
return [[self alloc] initWithDictionary:dictionary];
24-
else
24+
} else {
2525
return nil;
26+
}
27+
}
28+
29+
+ (instancetype)actionOpenUrl:(NSString *)url {
30+
if (url != nil) {
31+
return [self actionFromDictionary:@{@"type": IterableActionTypeOpenUrl, @"data": url}];
32+
} else {
33+
return nil;
34+
}
2635
}
2736

2837
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// Created by Victor Babenko on 6/27/18.
3+
// Copyright (c) 2018 Iterable. All rights reserved.
4+
//
5+
6+
#import <Foundation/Foundation.h>
7+
#import "IterableAction.h"
8+
9+
/**
10+
* Enum representing the source of the action: push notification, universal link, etc.
11+
*/
12+
typedef NS_ENUM(NSInteger, IterableActionSource) {
13+
/** Push Notification */
14+
IterableActionSourcePush,
15+
16+
/** Universal Link */
17+
IterableActionSourceUniversalLink
18+
};
19+
20+
/**
21+
* An object representing the action to execute and the context it is executing in
22+
*
23+
*/
24+
@interface IterableActionContext : NSObject
25+
26+
/**
27+
* Action to execute
28+
*/
29+
@property (nonatomic, readonly) IterableAction *action;
30+
31+
/**
32+
* Source of the action: push notification, universal link, etc.
33+
*/
34+
@property (nonatomic, readonly) IterableActionSource source;
35+
36+
/**
37+
* Create an `IterableActionContext` object with the given action and source
38+
* @param action Action to execute
39+
* @param source Source of the action
40+
* @return `IterableActionContext` instance
41+
*/
42+
+ (instancetype)contextWithAction:(IterableAction *)action source:(IterableActionSource)source;
43+
44+
@end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// Created by Victor Babenko on 6/27/18.
3+
// Copyright (c) 2018 Iterable. All rights reserved.
4+
//
5+
6+
#import "IterableActionContext.h"
7+
8+
9+
@implementation IterableActionContext
10+
11+
- (instancetype)initWithAction:(IterableAction *)action source:(IterableActionSource)source {
12+
self = [super init];
13+
if (self) {
14+
_action = action;
15+
_source = source;
16+
}
17+
return self;
18+
}
19+
20+
+ (instancetype)contextWithAction:(IterableAction *)action source:(IterableActionSource)source {
21+
return [[IterableActionContext alloc] initWithAction:action source:source];
22+
}
23+
24+
@end

Iterable-iOS-SDK/IterableActionRunner.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
#import <Foundation/Foundation.h>
1010
#import "IterableAction.h"
11+
#import "IterableActionContext.h"
1112

1213
@interface IterableActionRunner : NSObject
1314

14-
+ (void)executeAction:(IterableAction *)action;
15+
+ (BOOL)executeAction:(IterableAction *)action from:(IterableActionSource)source;
1516

1617
@end

Iterable-iOS-SDK/IterableActionRunner.m

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,56 +10,63 @@
1010
#import "IterableAPI.h"
1111
#import "IterableAPI+Internal.h"
1212
#import "IterableLogging.h"
13+
#import "IterableActionContext.h"
1314

1415
@implementation IterableActionRunner
1516

16-
+ (void)executeAction:(IterableAction *)action {
17+
+ (BOOL)executeAction:(IterableAction *)action from:(IterableActionSource)source {
1718
// Do not handle actions and try to open Safari for URLs unless the SDK is initialized with a new init method
1819
if ([IterableAPI sharedInstance].sdkCompatEnabled) {
19-
return;
20+
return NO;
2021
}
2122

23+
IterableActionContext *context = [IterableActionContext contextWithAction:action source:source];
24+
2225
if ([action isOfType:IterableActionTypeOpenUrl]) {
2326
// Open deeplink, use delegate handler
24-
[self openURL:[NSURL URLWithString:action.data] action:action];
27+
return [self openURL:[NSURL URLWithString:action.data] context:context];
2528
}
2629
else {
2730
// Call a custom action if available
28-
[self callCustomActionIfSpecified:action];
31+
return [self callCustomActionIfSpecified:action context:context];
2932
}
3033
}
3134

32-
+ (void)openURL:(NSURL *)url action:(IterableAction *)action {
35+
+ (BOOL)openURL:(NSURL *)url context:(IterableActionContext *)context {
3336
if (url == nil) {
34-
return;
37+
return NO;
3538
}
3639

37-
if ([[IterableAPI sharedInstance].config.urlDelegate handleIterableURL:url fromAction:action]) {
38-
return;
40+
if ([[IterableAPI sharedInstance].config.urlDelegate handleIterableURL:url context:context]) {
41+
return YES;
3942
}
4043

41-
// Open http/https links in the browser
42-
NSString *scheme = url.scheme;
43-
if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) {
44-
if (@available(iOS 10.0, *)) {
45-
[[UIApplication sharedApplication] openURL:url
46-
options:@{}
47-
completionHandler:^(BOOL success) {
48-
if (!success) {
49-
LogError(@"Could not open the URL: %@", url.absoluteString);
50-
}
51-
}];
52-
}
53-
else {
54-
[[UIApplication sharedApplication] openURL:url];
44+
if (context.source == IterableActionSourcePush) {
45+
// Open http/https links in the browser
46+
NSString *scheme = url.scheme;
47+
if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) {
48+
if (@available(iOS 10.0, *)) {
49+
[[UIApplication sharedApplication] openURL:url
50+
options:@{}
51+
completionHandler:^(BOOL success) {
52+
if (!success) {
53+
LogError(@"Could not open the URL: %@", url.absoluteString);
54+
}
55+
}];
56+
} else {
57+
[[UIApplication sharedApplication] openURL:url];
58+
}
59+
return YES;
5560
}
5661
}
62+
return NO;
5763
}
5864

59-
+ (void)callCustomActionIfSpecified:(IterableAction *)action {
65+
+ (BOOL)callCustomActionIfSpecified:(IterableAction *)action context:(IterableActionContext *)context {
6066
if (action.type.length > 0) {
61-
[[IterableAPI sharedInstance].config.customActionDelegate handleIterableCustomAction:action];
67+
return [[IterableAPI sharedInstance].config.customActionDelegate handleIterableCustomAction:action context:context];
6268
}
69+
return NO;
6370
}
6471

6572
@end

Iterable-iOS-SDK/IterableAppIntegration.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ + (void)performDefaultNotificationAction:(NSDictionary *)userInfo api:(IterableA
6464
[api trackPushOpen:userInfo dataFields:dataFields];
6565

6666
//Execute the action
67-
[IterableActionRunner executeAction:action];
67+
[IterableActionRunner executeAction:action from:IterableActionSourcePush];
6868
}
6969

7070
+ (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
@@ -122,7 +122,7 @@ + (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNoti
122122
}
123123

124124
//Execute the action
125-
[IterableActionRunner executeAction:action];
125+
[IterableActionRunner executeAction:action from:IterableActionSourcePush];
126126

127127
if (completionHandler) {
128128
completionHandler();
@@ -131,7 +131,7 @@ + (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNoti
131131

132132
+ (IterableAction *)legacyDefaultActionFromPayload:(NSDictionary *)userInfo {
133133
if (userInfo[ITBL_PAYLOAD_DEEP_LINK_URL] != nil) {
134-
return [IterableAction actionFromDictionary:@{@"type": IterableActionTypeOpenUrl, @"data": userInfo[ITBL_PAYLOAD_DEEP_LINK_URL]}];
134+
return [IterableAction actionOpenUrl:userInfo[ITBL_PAYLOAD_DEEP_LINK_URL]];
135135
} else {
136136
return nil;
137137
}

0 commit comments

Comments
 (0)