From bb3aaa5640348c2c95b4d33799b7bda666d8c963 Mon Sep 17 00:00:00 2001 From: John Rosendahl Date: Tue, 3 Nov 2026 15:39:39 -0700 Subject: [PATCH 1/2] Demo hashed IDs, identify-from-URL and profile id/neighbors in the Objective-C app The Objective-C counterpart of #93, minus async/await (not callable from Objective-C): - Identify screen: "Identify (hashed email)" sends OptableSDKIdentifierType_HEM, and a URL field plus "Identify from URL" calls tryIdentifyFromURL:error:. - GAM and Prebid banner screens: profile calls pass id "c:demo-user-1234" and a hashed-email neighbor. - AppDelegate: OptableDemoSHA256Email() helper (CommonCrypto), normalizing the email as the SDK does. Co-Authored-By: Claude Opus 5.5 --- demo-ios-objc/demo-ios-objc/AppDelegate.h | 3 ++ demo-ios-objc/demo-ios-objc/AppDelegate.m | 15 ++++++ .../demo-ios-objc/Base.lproj/Main.storyboard | 50 ++++++++++++++++++- .../demo-ios-objc/GAMBannerViewController.m | 10 ++-- .../demo-ios-objc/IdentifyViewController.h | 3 ++ .../demo-ios-objc/IdentifyViewController.m | 25 ++++++++++ .../PrebidBannerViewController.m | 10 ++-- 7 files changed, 106 insertions(+), 10 deletions(-) diff --git a/demo-ios-objc/demo-ios-objc/AppDelegate.h b/demo-ios-objc/demo-ios-objc/AppDelegate.h index ab0b3ee..9e52cfb 100644 --- a/demo-ios-objc/demo-ios-objc/AppDelegate.h +++ b/demo-ios-objc/demo-ios-objc/AppDelegate.h @@ -11,5 +11,8 @@ extern OptableSDK *OPTABLE; +/// SHA256 of the Email, normalized the way the SDK does it for OptableSDKIdentifierType_EmailAddress: whitespace removed, lowercased. +NSString *OptableDemoSHA256Email(NSString *email); + @interface AppDelegate : UIResponder @end diff --git a/demo-ios-objc/demo-ios-objc/AppDelegate.m b/demo-ios-objc/demo-ios-objc/AppDelegate.m index 12e850e..e4b72d8 100644 --- a/demo-ios-objc/demo-ios-objc/AppDelegate.m +++ b/demo-ios-objc/demo-ios-objc/AppDelegate.m @@ -14,9 +14,24 @@ @import PrebidMobile; @import GoogleMobileAds; +#import OptableSDK *OPTABLE = nil; +NSString *OptableDemoSHA256Email(NSString *email) { + NSArray *parts = [email componentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; + NSData *data = [[[parts componentsJoinedByString:@""] lowercaseString] dataUsingEncoding:NSUTF8StringEncoding]; + + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(data.bytes, (CC_LONG)data.length, digest); + + NSMutableString *hex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { + [hex appendFormat:@"%02x", digest[i]]; + } + return hex; +} + @interface AppDelegate () @end diff --git a/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard b/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard index e4b95bd..e85759e 100644 --- a/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard +++ b/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard @@ -51,8 +51,44 @@ + + + + + + + + + + - + @@ -68,8 +104,17 @@ - + + + + + + + + + + @@ -81,6 +126,7 @@ + diff --git a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m index dbdc66f..17c5f95 100644 --- a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m +++ b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m @@ -65,9 +65,10 @@ - (IBAction)loadBannerWithTargeting:(id)sender { [OPTABLE witnessWithEvent: @"GAMBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } error: &error]; + // `id` and `neighbors` are sent as given, so Email addresses and phone numbers must be hashed first. [OPTABLE profileWithTraits: @{ @"example": @"value", @"anotherExample": @123, @"thirdExample": @YES } - id: NULL - neighbors: NULL + id: @"c:demo-user-1234" + neighbors: @[[@"e:" stringByAppendingString:OptableDemoSHA256Email(@"some.email@address.com")]] error: &error]; } @@ -85,9 +86,10 @@ - (IBAction)loadBannerWithTargetingFromCache:(id)sender { [OPTABLE witnessWithEvent: @"GAMBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } error: &error]; + // `id` and `neighbors` are sent as given, so Email addresses and phone numbers must be hashed first. [OPTABLE profileWithTraits: @{ @"example": @"value", @"anotherExample": @123, @"thirdExample": @YES } - id: NULL - neighbors: NULL + id: @"c:demo-user-1234" + neighbors: @[[@"e:" stringByAppendingString:OptableDemoSHA256Email(@"some.email@address.com")]] error: &error]; } diff --git a/demo-ios-objc/demo-ios-objc/IdentifyViewController.h b/demo-ios-objc/demo-ios-objc/IdentifyViewController.h index 055e2ea..2fb4c0a 100644 --- a/demo-ios-objc/demo-ios-objc/IdentifyViewController.h +++ b/demo-ios-objc/demo-ios-objc/IdentifyViewController.h @@ -12,6 +12,9 @@ @property (weak, nonatomic) IBOutlet UITextField *identifyInput; @property (weak, nonatomic) IBOutlet UIButton *identifyButton; @property (weak, nonatomic) IBOutlet UITextView *identifyOutput; +@property (weak, nonatomic) IBOutlet UITextField *identifyURLInput; - (IBAction)dispatchIdentify:(id)sender; +- (IBAction)dispatchIdentifyHashed:(id)sender; +- (IBAction)dispatchIdentifyFromURL:(id)sender; @end diff --git a/demo-ios-objc/demo-ios-objc/IdentifyViewController.m b/demo-ios-objc/demo-ios-objc/IdentifyViewController.m index bd062e7..8765007 100644 --- a/demo-ios-objc/demo-ios-objc/IdentifyViewController.m +++ b/demo-ios-objc/demo-ios-objc/IdentifyViewController.m @@ -24,6 +24,10 @@ - (void)viewDidLoad { [super viewDidLoad]; _identifyInput.delegate = self; + _identifyURLInput.delegate = self; + + // A link like the ones in a newsletter Email, carrying the SHA256 of the reader's Email address + _identifyURLInput.text = [NSString stringWithFormat:@"https://example.com/newsletter?oeid=%@", OptableDemoSHA256Email(@"some.email@address.com")]; OptableSDKDelegate *delegate = (OptableSDKDelegate *)OPTABLE.delegate; delegate.identifyOutput = self.identifyOutput; @@ -71,6 +75,27 @@ - (IBAction)dispatchIdentify:(id)sender { }]; } +// dispatchIdentifyHashed: sends the Email as a SHA256 hashed in the app, as you would with a hash from your own backend. +// OptableSDKIdentifierType_HEM sends it as given; passing a hash as OptableSDKIdentifierType_EmailAddress would hash it a second time. +- (IBAction)dispatchIdentifyHashed:(id)sender { + [self.view endEditing: TRUE]; + + NSError *error = nil; + [OPTABLE identify: @[ + [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_HEM value:OptableDemoSHA256Email(_identifyInput.text ?: @"")], + ] + error: &error]; +} + +// dispatchIdentifyFromURL: passes a link to tryIdentifyFromURL:, as you would from a universal link handler. +// If the link has an `oeid` query parameter holding a SHA256, the SDK calls /identify with it; otherwise it does nothing. +- (IBAction)dispatchIdentifyFromURL:(id)sender { + [self.view endEditing: TRUE]; + + NSError *error = nil; + [OPTABLE tryIdentifyFromURL: _identifyURLInput.text ?: @"" error: &error]; +} + // MARK: - UITextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; diff --git a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m index 73842ce..6ba4ede 100644 --- a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m +++ b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m @@ -74,9 +74,10 @@ - (IBAction)loadBannerWithTargeting:(id)sender { [OPTABLE witnessWithEvent: @"PrebidBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } error: &error]; + // `id` and `neighbors` are sent as given, so Email addresses and phone numbers must be hashed first. [OPTABLE profileWithTraits: @{ @"example": @"value", @"anotherExample": @123, @"thirdExample": @YES } - id: NULL - neighbors: NULL + id: @"c:demo-user-1234" + neighbors: @[[@"e:" stringByAppendingString:OptableDemoSHA256Email(@"some.email@address.com")]] error: &error]; } @@ -94,9 +95,10 @@ - (IBAction)loadBannerWithTargetingFromCache:(id)sender { [OPTABLE witnessWithEvent: @"PrebidBannerViewController.loadBannerClicked" properties: @{ @"example": @"value" } error: &error]; + // `id` and `neighbors` are sent as given, so Email addresses and phone numbers must be hashed first. [OPTABLE profileWithTraits: @{ @"example": @"value", @"anotherExample": @123, @"thirdExample": @YES } - id: NULL - neighbors: NULL + id: @"c:demo-user-1234" + neighbors: @[[@"e:" stringByAppendingString:OptableDemoSHA256Email(@"some.email@address.com")]] error: &error]; } From 5623811a7758551da49e01a8a5113a780946c180 Mon Sep 17 00:00:00 2001 From: John Rosendahl Date: Mon, 9 Nov 2026 15:05:44 -0700 Subject: [PATCH 2/2] Don't send a hashed empty Email from the Objective-C demo Co-Authored-By: Claude Opus 5.5 --- demo-ios-objc/demo-ios-objc/IdentifyViewController.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/demo-ios-objc/demo-ios-objc/IdentifyViewController.m b/demo-ios-objc/demo-ios-objc/IdentifyViewController.m index 8765007..772685d 100644 --- a/demo-ios-objc/demo-ios-objc/IdentifyViewController.m +++ b/demo-ios-objc/demo-ios-objc/IdentifyViewController.m @@ -80,9 +80,16 @@ - (IBAction)dispatchIdentify:(id)sender { - (IBAction)dispatchIdentifyHashed:(id)sender { [self.view endEditing: TRUE]; + // An empty Email would otherwise be sent as the SHA256 of an empty string + NSString *email = [_identifyInput.text ?: @"" stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; + if (email.length == 0) { + _identifyOutput.text = @"Enter an Email to send it hashed."; + return; + } + NSError *error = nil; [OPTABLE identify: @[ - [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_HEM value:OptableDemoSHA256Email(_identifyInput.text ?: @"")], + [OptableSDKIdentifier identifierWithType:OptableSDKIdentifierType_HEM value:OptableDemoSHA256Email(email)], ] error: &error]; }