Skip to content
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
17 changes: 9 additions & 8 deletions Example/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ target 'mParticleExample' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
use_frameworks!

# Local monorepo SDK (same checkout as this Example/ directory)
pod 'mParticle-Apple-SDK-Swift', :path => '../mParticle-Apple-SDK-Swift'
# Local monorepo SDK (podspecs live at the repo root).
pod 'mParticle-Apple-SDK-Swift', :path => '..'
pod 'mParticle-Apple-SDK-ObjC', :path => '..'
pod 'mParticle-Apple-SDK', :path => '..'

pod 'mParticle-Apple-Media-SDK/mParticleMediaNoLocation', '~> 1.7.0'
# Media SDK 1.7.x requires mParticle-Apple-SDK/mParticleNoLocation ~> 8.37 and cannot resolve against SDK 9.
# pod 'mParticle-Apple-Media-SDK/mParticleMediaNoLocation', '~> 1.7.0'

# Rokt-Widget pulls in RoktUXHelper (~> 0.10) transitively. This explicit line is only to resolve it
# from Git (tag) instead of CocoaPods trunk when CDN/trunk is unreliable — remove it if trunk works.
pod 'RoktUXHelper', :git => 'https://github.com/ROKT/rokt-ux-helper-ios.git', :tag => '0.10.4'
# Rokt-Widget 5.4.0 requires RoktUXHelper 2.x.
pod 'RoktUXHelper', :git => 'https://github.com/ROKT/rokt-ux-helper-ios.git', :tag => '2.0.0'

# Rokt iOS SDK 5.1.x (aligned with Kits/rokt/rokt/Package.swift). Use Git tag; not on CocoaPods trunk.
pod 'Rokt-Widget', :git => 'https://github.com/ROKT/rokt-sdk-ios.git', :tag => '5.1.0'
# Rokt iOS SDK 5.4.0 (session id + token handoff). Use Git tag; not on CocoaPods trunk.
pod 'Rokt-Widget', :git => 'https://github.com/ROKT/rokt-sdk-ios.git', :tag => '5.4.0'

# Local Rokt kit — uses the same local mParticle-Apple-SDK above (single resolved copy via :path).
# RoktContracts resolves from CocoaPods trunk per podspecs (~> 2.0).
Expand Down
10 changes: 10 additions & 0 deletions Example/mParticleExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
76A5F011073E72F6F0C415DB /* Pods_mParticleExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2EA4B28DA755816DD1F32FC /* Pods_mParticleExampleTests.framework */; };
D37FB8E9236208E200E47B6D /* Dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D37FB8E8236208E200E47B6D /* Dummy.swift */; };
DBD54A12224941630053BD20 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DBD54A11224941630053BD20 /* AppDelegate.m */; };
A1B2C3D4E5F60718293A4B50 /* RoktHybridWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B52 /* RoktHybridWebViewController.m */; };
A1B2C3D4E5F60718293A4B54 /* rokt-hybrid.html in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B53 /* rokt-hybrid.html */; };
DBD54A15224941630053BD20 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DBD54A14224941630053BD20 /* ViewController.m */; };
DBD54A18224941630053BD20 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DBD54A16224941630053BD20 /* Main.storyboard */; };
DBD54A1A224941640053BD20 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DBD54A19224941640053BD20 /* Assets.xcassets */; };
Expand Down Expand Up @@ -66,6 +68,9 @@
DBD54A0D224941630053BD20 /* mParticleExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mParticleExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
DBD54A10224941630053BD20 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
DBD54A11224941630053BD20 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
A1B2C3D4E5F60718293A4B51 /* RoktHybridWebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoktHybridWebViewController.h; sourceTree = "<group>"; };
A1B2C3D4E5F60718293A4B52 /* RoktHybridWebViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoktHybridWebViewController.m; sourceTree = "<group>"; };
A1B2C3D4E5F60718293A4B53 /* rokt-hybrid.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "rokt-hybrid.html"; sourceTree = "<group>"; };
DBD54A13224941630053BD20 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
DBD54A14224941630053BD20 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
DBD54A17224941630053BD20 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -166,6 +171,9 @@
DBD54A11224941630053BD20 /* AppDelegate.m */,
DBD54A13224941630053BD20 /* ViewController.h */,
DBD54A14224941630053BD20 /* ViewController.m */,
A1B2C3D4E5F60718293A4B51 /* RoktHybridWebViewController.h */,
A1B2C3D4E5F60718293A4B52 /* RoktHybridWebViewController.m */,
A1B2C3D4E5F60718293A4B53 /* rokt-hybrid.html */,
D37FB8E8236208E200E47B6D /* Dummy.swift */,
DBD54A16224941630053BD20 /* Main.storyboard */,
DBD54A19224941640053BD20 /* Assets.xcassets */,
Expand Down Expand Up @@ -306,6 +314,7 @@
buildActionMask = 2147483647;
files = (
DBD54A1D224941640053BD20 /* LaunchScreen.storyboard in Resources */,
A1B2C3D4E5F60718293A4B54 /* rokt-hybrid.html in Resources */,
DBD54A1A224941640053BD20 /* Assets.xcassets in Resources */,
DBD54A18224941630053BD20 /* Main.storyboard in Resources */,
);
Expand Down Expand Up @@ -419,6 +428,7 @@
buildActionMask = 2147483647;
files = (
DBD54A15224941630053BD20 /* ViewController.m in Sources */,
A1B2C3D4E5F60718293A4B50 /* RoktHybridWebViewController.m in Sources */,
DBD54A20224941640053BD20 /* main.m in Sources */,
D37FB8E9236208E200E47B6D /* Dummy.swift in Sources */,
DBD54A12224941630053BD20 /* AppDelegate.m in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Example/mParticleExample/AppDelegate.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#import "AppDelegate.h"
#import <mParticle_Apple_SDK/mParticle.h>
#import <mParticle_Apple_SDK_ObjC/mParticle.h>
#import <AdSupport/AdSupport.h>
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import "PLCrashReportTextFormatter+StackTrace.h"
Expand Down
21 changes: 21 additions & 0 deletions Example/mParticleExample/RoktHybridWebViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#import <UIKit/UIKit.h>

@class MPRoktSession;

NS_ASSUME_NONNULL_BEGIN

/**
* Loads a bundled mParticle Web SDK page and seeds Rokt launcherOptions from a native session.
*/
@interface RoktHybridWebViewController : UIViewController

- (instancetype)initWithSession:(MPRoktSession *)session NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;

@end

NS_ASSUME_NONNULL_END
174 changes: 174 additions & 0 deletions Example/mParticleExample/RoktHybridWebViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#import "RoktHybridWebViewController.h"

#import <WebKit/WebKit.h>
#import <mParticle_Apple_SDK_ObjC/mParticle.h>

static NSString *const kRoktHybridMessageHandlerName = @"roktSession";
static NSString *const kRoktHybridHTMLResource = @"rokt-hybrid";

@interface MPEWeakScriptMessageHandler : NSObject <WKScriptMessageHandler>
@property (nonatomic, weak) id<WKScriptMessageHandler> target;
@end

@implementation MPEWeakScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
[self.target userContentController:userContentController didReceiveScriptMessage:message];
}
@end

@interface RoktHybridWebViewController () <WKScriptMessageHandler>

@property (nonatomic, strong) MPRoktSession *session;
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) MPEWeakScriptMessageHandler *messageHandler;

@end

@implementation RoktHybridWebViewController

- (instancetype)initWithSession:(MPRoktSession *)session {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_session = session;
}
return self;
}

- (void)dealloc {
[_webView.configuration.userContentController removeScriptMessageHandlerForName:kRoktHybridMessageHandlerName];
}

- (void)viewDidLoad {
[super viewDidLoad];

self.title = @"Rokt WebView";
self.view.backgroundColor = [UIColor systemBackgroundColor];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(dismissHybrid)];

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKUserScript *handoffScript = [[WKUserScript alloc] initWithSource:[self handoffJavaScript]
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:YES];
[configuration.userContentController addUserScript:handoffScript];
self.messageHandler = [[MPEWeakScriptMessageHandler alloc] init];
self.messageHandler.target = self;
[configuration.userContentController addScriptMessageHandler:self.messageHandler name:kRoktHybridMessageHandlerName];

self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
self.webView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.webView];

[NSLayoutConstraint activateConstraints:@[
[self.webView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.webView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.webView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.webView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]
]];

[[MParticle sharedInstance] initializeWKWebView:self.webView];

NSURL *htmlURL = [[NSBundle mainBundle] URLForResource:kRoktHybridHTMLResource withExtension:@"html"];
if (!htmlURL) {
NSLog(@"Rokt hybrid: bundled rokt-hybrid.html is missing");
return;
}
[self.webView loadFileURL:htmlURL allowingReadAccessToURL:htmlURL.URLByDeletingLastPathComponent];
}

- (void)dismissHybrid {
[self dismissViewControllerAnimated:YES completion:nil];
}

- (NSString *)handoffJavaScript {
NSMutableDictionary *handoff = [NSMutableDictionary dictionary];
if (self.session.sessionId.length > 0) {
handoff[@"sessionId"] = self.session.sessionId;
}
if (self.session.sessionToken.length > 0) {
handoff[@"sessionToken"] = self.session.sessionToken;
}
if (self.session.expiresAt != nil) {
handoff[@"expiresAt"] = self.session.expiresAt;
}

NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:handoff options:0 error:&error];
if (!jsonData || error) {
NSLog(@"Rokt hybrid: failed to serialize session handoff: %@", error);
return @"window.__ROKT_HANDOFF = {};";
}

NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return [NSString stringWithFormat:@"window.__ROKT_HANDOFF = %@;", json];
}

- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
if (![message.name isEqualToString:kRoktHybridMessageHandlerName]) {
return;
}
if (![message.body isKindOfClass:[NSDictionary class]]) {
NSLog(@"Rokt hybrid: expected dictionary from webview, got %@", [message.body class]);
return;
}

NSDictionary *body = (NSDictionary *)message.body;
NSString *sessionId = [self stringValue:body[@"sessionId"]];
if (sessionId.length == 0) {
NSLog(@"Rokt hybrid: webview posted an empty sessionId");
return;
}

NSString *sessionToken = [self stringValue:body[@"sessionToken"]];
NSNumber *expiresAt = [self numberValue:body[@"expiresAt"]];
if (sessionToken.length == 0 || expiresAt == nil) {
NSLog(@"Rokt hybrid: webview posted an incomplete token-bearing session");
return;
}
MPRoktSession *session = [[MPRoktSession alloc] initWithSessionId:sessionId
sessionToken:sessionToken
expiresAt:expiresAt];

NSLog(@"Rokt hybrid: applying web session to native (id=%@, token=%@)",
session.sessionId,
session.sessionToken.length > 0 ? @"present" : @"nil");

[[MParticle sharedInstance].rokt setSession:session];
NSDictionary<NSString *, NSString *> *attributes = @{
@"email": @"j.smit@example.com",
@"firstname": @"Jenny",
@"lastname": @"Smith",
@"sandbox": @"true",
@"mobile": @"(555)867-5309"
};
[[MParticle sharedInstance].rokt selectPlacements:@"RoktLayout" attributes:attributes];
}

- (nullable NSString *)stringValue:(id)value {
if ([value isKindOfClass:[NSString class]]) {
return (NSString *)value;
}
if ([value isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)value stringValue];
}
return nil;
}

- (nullable NSNumber *)numberValue:(id)value {
if ([value isKindOfClass:[NSNumber class]]) {
return (NSNumber *)value;
}
if ([value isKindOfClass:[NSString class]]) {
NSString *string = (NSString *)value;
if (string.length == 0) {
return nil;
}
return @([string longLongValue]);
}
return nil;
}

@end
56 changes: 52 additions & 4 deletions Example/mParticleExample/ViewController.m
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#import "ViewController.h"
#import <mParticle_Apple_SDK/mParticle.h>
#import "RoktHybridWebViewController.h"
#import <mParticle_Apple_SDK_ObjC/mParticle.h>
@import RoktContracts;
#if __has_include(<mParticle_Apple_Media_SDK-Swift.h>)
#import <mParticle_Apple_Media_SDK-Swift.h>
#define MPE_HAS_MEDIA_SDK 1
#endif
#import <AdSupport/AdSupport.h>
#import "AdSupport/ASIdentifierManager.h"
#if TARGET_OS_IOS == 1 && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
Expand Down Expand Up @@ -170,7 +174,7 @@ - (void)setupSectionData {
// Debug & Info
@[@"Display Current User", @"Force Upload", @"Check Kit Status"],
// Rokt
@[@"Display Rokt Overlay Placement", @"Display Rokt Dark Mode Overlay", @"Display Rokt Embedded Placement", @"Display Rokt Overlay (auto close)"]
@[@"Display Rokt Overlay Placement", @"Display Rokt Dark Mode Overlay", @"Display Rokt Embedded Placement", @"Display Rokt Overlay (auto close)", @"Show Rokt Session", @"Open WebView with Native Session"]
];

_sectionSelectorNames = @[
Expand All @@ -190,7 +194,7 @@ - (void)setupSectionData {
// Debug & Info
@[@"displayCurrentUser", @"forceUpload", @"checkKitStatus"],
// Rokt
@[@"selectOverlayPlacement", @"selectDarkOverlayPlacement", @"selectEmbeddedPlacement", @"selectOverlayPlacementAutoClose"]
@[@"selectOverlayPlacement", @"selectDarkOverlayPlacement", @"selectEmbeddedPlacement", @"selectOverlayPlacementAutoClose", @"showRoktSession", @"openWebViewWithNativeSession"]
];
}

Expand Down Expand Up @@ -306,7 +310,7 @@ - (void)selectOverlayPlacement {
@"mobile": @"(555)867-5309"
};

[[MParticle sharedInstance].rokt selectPlacements:@"RoktLayout" attributes:customAttributes];
[[MParticle sharedInstance].rokt selectPlacements:@"MSDKOverlayLayout" attributes:customAttributes];
}

- (void)selectDarkOverlayPlacement {
Expand Down Expand Up @@ -372,6 +376,46 @@ - (void)selectOverlayPlacementAutoClose {
});
}

- (NSString *)truncatedRoktSessionToken:(NSString *)token {
if (token.length == 0) {
return @"(none)";
}
if (token.length <= 16) {
return [NSString stringWithFormat:@"%@ (len %lu)", token, (unsigned long)token.length];
}
return [NSString stringWithFormat:@"%@… (len %lu)", [token substringToIndex:12], (unsigned long)token.length];
Comment thread
BrandonStalnaker marked this conversation as resolved.
}

- (void)showRoktSession {
MPRoktSession *session = [[MParticle sharedInstance].rokt getSession];
if (!session) {
[self showAlertWithTitle:@"Rokt Session"
message:@"No Rokt session yet. Display a native placement first, then try again. getSession also returns nil after the token expires."];
return;
}

NSString *message = [NSString stringWithFormat:@"sessionId: %@\nsessionToken: %@\nexpiresAt: %@",
session.sessionId,
[self truncatedRoktSessionToken:session.sessionToken],
session.expiresAt ?: @"(none)"];
NSLog(@"Rokt session handoff:\n%@", message);
[self showAlertWithTitle:@"Rokt Session" message:message];
}

- (void)openWebViewWithNativeSession {
MPRoktSession *session = [[MParticle sharedInstance].rokt getSession];
if (!session) {
[self showAlertWithTitle:@"Rokt Session"
message:@"No Rokt session yet. Display a native placement first so getSession can return an id and token."];
return;
}

RoktHybridWebViewController *hybrid = [[RoktHybridWebViewController alloc] initWithSession:session];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:hybrid];
nav.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nav animated:YES completion:nil];
}

- (void)getAudience {
MParticle *mParticle = [MParticle sharedInstance];

Expand All @@ -385,6 +429,7 @@ - (void)getAudience {
}

- (void)logCustomMediaEvents {
#if MPE_HAS_MEDIA_SDK
MPMediaSession *mediaSession = [[MPMediaSession alloc]
initWithCoreSDK:[MParticle sharedInstance]
mediaContentId:@"1234567"
Expand All @@ -398,6 +443,9 @@ - (void)logCustomMediaEvents {
[mediaSession logPlayWithOptions:nil];
[mediaSession logMediaContentEndWithOptions:nil];
[mediaSession logMediaSessionEndWithOptions:nil];
#else
NSLog(@"mParticle Media SDK is not linked in this Example Podfile (incompatible with SDK 9).");
#endif
}

- (void)logTimedEvent {
Expand Down
Loading
Loading