Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement auto-close creatives on nav away #72

Merged
merged 18 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 76 additions & 70 deletions Example/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

38 changes: 34 additions & 4 deletions Example/CreativeUITest/CreativeUITest.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ - (void)testLoadCreative_clickClose_closesCreative {

- (void)testLoadCreative_fillOutFormAndSubmit_launchesSmsAppWithPrePopulatedText {
[self launchAppWithMode:@"production"];
[app.buttons[@"Push me to clear the User!"] tap];
[app.buttons[@"Push me for Creative!"] tap];

// Fill in the email
Expand Down Expand Up @@ -90,9 +91,22 @@ - (void)testLoadCreative_clickPrivacyLink_opensPrivacyPageInWebApp {
XCTAssertTrue([app.webViews.links[@"Privacy"] waitForExistenceWithTimeout:5.0]);
[app.webViews.links[@"Privacy"] tap];

// Verify that the privacy page is visible in the external browser
XCUIApplication *safariApp = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.mobilesafari"];
XCTAssertTrue([safariApp.staticTexts[@"Privacy Policy"] waitForExistenceWithTimeout:5.0]);
// Wait for a moment to allow the app to react
sleep(5);

// Check if the app is no longer active
XCTAssertFalse([app.webViews.links[@"Privacy"] waitForExistenceWithTimeout:5.0]);

// AWS Device Farm doesn't always acknowledge separate apps, leading to flakiness here
NSString *envHost = [[[NSProcessInfo processInfo] environment] objectForKey:@"COM_ATTENTIVE_EXAMPLE_HOST"];
if ([envHost isEqualToString:@"local"]) {
// Verify that the privacy page is visible in the external browser
XCUIApplication *safariApp = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.mobilesafari"];
BOOL privacyPolicyExists = [safariApp.staticTexts[@"Privacy Policy"] waitForExistenceWithTimeout:5.0];
BOOL messagingPrivacyPolicyExists = [safariApp.staticTexts[@"Messaging Privacy Policy"] waitForExistenceWithTimeout:5.0];

XCTAssertTrue(privacyPolicyExists || messagingPrivacyPolicyExists);
}
}


Expand All @@ -104,6 +118,22 @@ - (void)testLoadCreative_inDebugMode_showsDebugMessage {
XCTAssertTrue([app.staticTexts[@"Debug output JSON"] waitForExistenceWithTimeout:5.0]);
}

- (void)testLoadCreative_clickProductPage_closesCreative {
[self launchAppWithMode:@"production"];
[app.buttons[@"Push me for Creative!"] tap];

// Click privacy link
XCTAssertTrue([app.webViews.links[@"Privacy"] waitForExistenceWithTimeout:5.0]);
[app.tabBars.buttons[@"Product"] tap];

// Verify that the product page is visible
XCTAssertTrue([app.buttons[@"Add To Cart"] waitForExistenceWithTimeout:5.0]);

// Nav back, and verify the creative is closed
[app.tabBars.buttons[@"Main"] tap];
XCTAssertTrue([app.buttons[@"Push me for Creative!"] waitForExistenceWithTimeout:5.0]);
}


+ (void)clearCookies {
NSLog(@"Clearing cookies!");
Expand All @@ -119,7 +149,7 @@ + (void)clearCookies {

+ (void)resetUserDefaults {
// Reset user defaults for example app, not the test runner
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:@"com.attentive.Example"];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:@"com.attentive.ExampleTest"];
}


Expand Down
2 changes: 2 additions & 0 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UIStatusBarStyle = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
Expand Down Expand Up @@ -468,6 +469,7 @@
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UIStatusBarStyle = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
Expand Down
6 changes: 6 additions & 0 deletions Example/Example/ProductViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ - (void)viewDidLoad {
ATTNProductViewEvent* productView = [[ATTNProductViewEvent alloc] initWithItems:@[ item ]];

[[ATTNEventTracker sharedInstance] recordEvent:productView];
}

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];

[self showToast:@"Product View event sent"];
}


- (IBAction)addToCartButtonPressed:(id)sender {
ATTNItem* item = [self buildItem];
ATTNAddToCartEvent* addToCart = [[ATTNAddToCartEvent alloc] initWithItems:@[ item ]];
Expand Down
20 changes: 20 additions & 0 deletions Example/Example/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#import "ImportAttentiveSDK.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIButton *creativeButton;
@property (weak, nonatomic) IBOutlet UIButton *sendIdentifiersButton;
@property (weak, nonatomic) IBOutlet UIButton *clearUserButton;
Expand All @@ -22,6 +23,23 @@ - (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor systemGray3Color];

CGFloat contentWidth = self.scrollView.bounds.size.width;
CGFloat contentHeight = self.scrollView.bounds.size.height * 3;
self.scrollView.contentSize = CGSizeMake(contentWidth, contentHeight);

CGFloat subviewHeight = (CGFloat)120;
CGFloat currentViewOffset = (CGFloat)600;

while (currentViewOffset < contentHeight) {
CGRect frame = CGRectInset(CGRectMake(0, currentViewOffset, contentWidth, subviewHeight), 5, 5);
CGFloat hue = currentViewOffset / contentHeight;
UIView *subview = [[UIView alloc] initWithFrame:frame];
subview.backgroundColor = [UIColor colorWithHue:hue saturation:1 brightness:1 alpha:1];
[self.scrollView addSubview:subview];

currentViewOffset += subviewHeight;
}

// Replace with your Attentive domain to test with your Attentive account
_domain = @"YOUR_ATTENTIVE_DOMAIN";
_mode = @"production";
Expand Down Expand Up @@ -89,6 +107,8 @@ - (void)clearCookies {
}

- (IBAction)clearUserButtonPressed:(id)sender {

NSLog(@"Clear user button pressed!");
[sdk clearUser];
}

Expand Down
10 changes: 7 additions & 3 deletions Sources/ATTNAPI.m
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ - (void)sendEventInternalForRequest:(EventRequest*)request userIdentity:(ATTNUse
} else {
// The response is an HTTP response because the URL had an HTTPS scheme
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if ([httpResponse statusCode] != 200) {
if ([httpResponse statusCode] > 400) {
message = [NSString stringWithFormat:@"Error sending the event. Incorrect status code: '%ld'", (long)[httpResponse statusCode]];
} else {
message = [NSString stringWithFormat:@"Successfully sent event of type '%@'", request.eventNameAbbreviation];
Expand Down Expand Up @@ -349,7 +349,7 @@ - (void)sendUserIdentityInternal:(ATTNUserIdentity*)userIdentity domain:(NSStrin
} else {
// The response is an HTTP response because the URL had an HTTPS scheme
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if ([httpResponse statusCode] != 200) {
if ([httpResponse statusCode] > 400) {
message = [NSString stringWithFormat:@"Error sending the event. Incorrect status code: '%ld'", (long)[httpResponse statusCode]];
} else {
message = @"Successfully sent user identity event";
Expand Down Expand Up @@ -510,7 +510,11 @@ + (NSString* _Nullable)extractDomainFromTag:(NSString*)tag {
return nil;
}

return [tag substringWithRange:domainRange];
NSString* regionalizedDomain = [tag substringWithRange:domainRange];

NSLog(@"Identified regionalized attentive domain: %@", regionalizedDomain);

return regionalizedDomain;
}

// For testing only
Expand Down
2 changes: 2 additions & 0 deletions Sources/ATTNSDK.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extern NSString * const CREATIVE_TRIGGER_STATUS_CLOSED;
extern NSString * const CREATIVE_TRIGGER_STATUS_NOT_OPENED;
extern NSString * const CREATIVE_TRIGGER_STATUS_NOT_CLOSED;

extern NSString * const SDK_VERSION;

typedef void (^ATTNCreativeTriggerCompletionHandler)(NSString * triggerStatus);


Expand Down
48 changes: 35 additions & 13 deletions Sources/ATTNSDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
// Status passed to the ATTNCreativeTriggerCompletionHandler when the Creative is not closed due to an unknown
// exception
NSString *const CREATIVE_TRIGGER_STATUS_NOT_CLOSED = @"CREATIVE_TRIGGER_STATUS_NOT_CLOSED";
NSString *const SDK_VERSION = @"0.4.3-beta.0";

NSString *const visibilityEvent = @"document-visibility:";

@implementation ATTNSDK {
UIView *_parentView;
Expand All @@ -35,6 +36,8 @@ @implementation ATTNSDK {
ATTNCreativeTriggerCompletionHandler _triggerHandler;
}

static BOOL isCreativeOpen = NO;

- (id)initWithDomain:(NSString *)domain {
NSLog(@"init attentive_ios_sdk v%@", SDK_VERSION);
return [self initWithDomain:domain mode:@"production"];
Expand Down Expand Up @@ -100,9 +103,10 @@ - (void)trigger:(UIView *)theView handler:(ATTNCreativeTriggerCompletionHandler)

[[wkWebViewConfiguration userContentController] addScriptMessageHandler:self name:@"log"];

NSString *userScriptWithEventListener = @"window.addEventListener('message', (event) => {if (event.data && event.data.__attentive && event.data.__attentive.action === 'CLOSE') {window.webkit.messageHandlers.log.postMessage(event.data.__attentive.action);}}, false);";
NSString *userScriptWithEventListener = [NSString stringWithFormat:@"window.addEventListener('message', (event) => {if (event.data && event.data.__attentive) {window.webkit.messageHandlers.log.postMessage(event.data.__attentive.action);}}, false);window.addEventListener('visibilitychange', (event) => {window.webkit.messageHandlers.log.postMessage(`%@ ${document.hidden}`);}, false);", visibilityEvent];

WKUserScript *wkUserScript = [[WKUserScript alloc] initWithSource:userScriptWithEventListener injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:FALSE];

[[wkWebViewConfiguration userContentController] addUserScript:wkUserScript];

_webView = [[WKWebView alloc] initWithFrame:theView.frame configuration:wkWebViewConfiguration];
Expand Down Expand Up @@ -180,18 +184,18 @@ - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigat

- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"web event message: %@", message.body);
if ([message.body isEqualToString:@"CLOSE"]) {
@try {
[_webView removeFromSuperview];
if (self->_triggerHandler != nil) {
self->_triggerHandler(CREATIVE_TRIGGER_STATUS_CLOSED);
}
} @catch (NSException *e) {
NSLog(@"Exception when closing creative: %@", e.reason);
if (self->_triggerHandler != nil) {
self->_triggerHandler(CREATIVE_TRIGGER_STATUS_NOT_CLOSED);
}
}

[self closeCreative];
} else if ([message.body isEqualToString:@"IMPRESSION"]) {

NSLog(@"Creative opened and generated impression event");
isCreativeOpen = YES;
} else if ([message.body isEqualToString:[NSString stringWithFormat:@"%@ true", visibilityEvent]] && isCreativeOpen == YES) {
NSLog(@"Nav away from creative, closing");

[self closeCreative];
}
}

Expand All @@ -216,6 +220,24 @@ - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigati
}
}

- (void)closeCreative {
NSLog(@"Closing creative");
@try {
[_webView removeFromSuperview];
isCreativeOpen = NO;
if (self->_triggerHandler != nil) {
self->_triggerHandler(CREATIVE_TRIGGER_STATUS_CLOSED);
}

NSLog(@"Successfully closed creative");
} @catch (NSException *e) {
NSLog(@"Exception when closing creative: %@", e.reason);
if (self->_triggerHandler != nil) {
self->_triggerHandler(CREATIVE_TRIGGER_STATUS_NOT_CLOSED);
}
}
}

- (ATTNAPI *)getApi {
return _api;
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Internal/ATTNVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@

// This should match the Podspec version
// If there's a way to define the version in one place and use it both here and the Podspec then we should do it - I don't know of a way
static NSString* const SDK_VERSION = @"0.4.3-beta.0";
NSString* const SDK_VERSION = @"0.4.3-beta.1";

#endif /* ATTNVersion_h */
2 changes: 1 addition & 1 deletion attentive-ios-sdk.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'attentive-ios-sdk'
s.version = '0.4.3-beta.0'
s.version = '0.4.3-beta.1'
s.summary = 'Attentive IOS SDK'

# This description is used to generate tags and improve search results.
Expand Down
Loading