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
50 changes: 48 additions & 2 deletions demo-ios-swift/demo-ios-swift/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,44 @@
<action selector="dispatchIdentify:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Sae-rS-iHh"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hEm-Bt-n01">
<rect key="frame" x="20" y="325" width="353" height="48"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="hEm-Hc-n04"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Identify (hashed email)"/>
<buttonConfiguration key="configuration" style="tinted" title="Identify (hashed email)">
<fontDescription key="titleFontDescription" type="system" weight="medium" pointSize="20"/>
</buttonConfiguration>
<connections>
<action selector="dispatchIdentifyHashed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="hEm-Ac-n16"/>
</connections>
</button>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="https://example.com/?oeid=&lt;sha256&gt;" textAlignment="natural" minimumFontSize="12" translatesAutoresizingMaskIntoConstraints="NO" id="uRl-Tf-n02">
<rect key="frame" x="20" y="403" width="353" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="uRl-Hc-n05"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
</textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uRl-Bt-n03">
<rect key="frame" x="20" y="458" width="353" height="48"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="uRb-Hc-n06"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Identify from URL"/>
<buttonConfiguration key="configuration" style="tinted" title="Identify from URL">
<fontDescription key="titleFontDescription" type="system" weight="medium" pointSize="20"/>
</buttonConfiguration>
<connections>
<action selector="dispatchIdentifyFromURL:" destination="BYZ-38-t0r" eventType="touchUpInside" id="uRb-Ac-n17"/>
</connections>
</button>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="pQa-vV-MeS">
<rect key="frame" x="20" y="330" width="353" height="385"/>
<rect key="frame" x="20" y="521" width="353" height="194"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
Expand All @@ -68,8 +104,17 @@
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="b7v-UN-xAf" secondAttribute="trailing" constant="20" id="IJw-hW-0Hm"/>
<constraint firstItem="pQa-vV-MeS" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="MEG-Mb-Lj2"/>
<constraint firstItem="b7v-UN-xAf" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="MO0-3A-9Nh"/>
<constraint firstItem="pQa-vV-MeS" firstAttribute="top" secondItem="b7v-UN-xAf" secondAttribute="bottom" constant="15" id="RHm-FX-7iM"/>
<constraint firstItem="pQa-vV-MeS" firstAttribute="top" secondItem="uRl-Bt-n03" secondAttribute="bottom" constant="15" id="RHm-FX-7iM"/>
<constraint firstItem="AKp-3F-KNX" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" constant="20" id="SNe-0s-I2Q"/>
<constraint firstItem="hEm-Bt-n01" firstAttribute="top" secondItem="b7v-UN-xAf" secondAttribute="bottom" constant="10" id="hEm-Tp-n07"/>
<constraint firstItem="hEm-Bt-n01" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="hEm-Ld-n08"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="hEm-Bt-n01" secondAttribute="trailing" constant="20" id="hEm-Tr-n09"/>
<constraint firstItem="uRl-Tf-n02" firstAttribute="top" secondItem="hEm-Bt-n01" secondAttribute="bottom" constant="30" id="uRl-Tp-n10"/>
<constraint firstItem="uRl-Tf-n02" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="uRl-Ld-n11"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="uRl-Tf-n02" secondAttribute="trailing" constant="20" id="uRl-Tr-n12"/>
<constraint firstItem="uRl-Bt-n03" firstAttribute="top" secondItem="uRl-Tf-n02" secondAttribute="bottom" constant="10" id="uRb-Tp-n13"/>
<constraint firstItem="uRl-Bt-n03" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="uRb-Ld-n14"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="uRl-Bt-n03" secondAttribute="trailing" constant="20" id="uRb-Tr-n15"/>
<constraint firstItem="AKp-3F-KNX" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="UII-5h-4Jn"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="pQa-vV-MeS" secondAttribute="trailing" constant="20" id="aUv-ov-L1P"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="pQa-vV-MeS" secondAttribute="bottom" constant="20" id="hrC-Rt-ujv"/>
Expand All @@ -81,6 +126,7 @@
<outlet property="identifyButton" destination="b7v-UN-xAf" id="JgO-1H-l2i"/>
<outlet property="identifyInput" destination="0Py-KR-2hS" id="Uda-2S-GRy"/>
<outlet property="identifyOutput" destination="pQa-vV-MeS" id="XQe-0Z-PkW"/>
<outlet property="identifyURLInput" destination="uRl-Tf-n02" id="uRl-Ot-n19"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,12 @@ private extension GAMBannerViewController {

func profile() {
do {
// `id` and `neighbors` are sent as given, so build them with `.extendedIdentifier`,
// which hashes Email addresses and phone numbers before they leave the device.
try OPTABLE!.profile(
traits: ["example": "value", "anotherExample": 123, "thirdExample": true]
traits: ["example": "value", "anotherExample": 123, "thirdExample": true],
id: OptableIdentifier.custom(nil, "demo-user-1234").extendedIdentifier,
neighbors: [OptableIdentifier.emailAddress("some.email@address.com").extendedIdentifier]
) { result in
switch result {
case let .success(optableTargeting):
Expand Down
55 changes: 55 additions & 0 deletions demo-ios-swift/demo-ios-swift/IdentifyViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@
import OptableSDK
import UIKit
import AppTrackingTransparency
import CryptoKit

// MARK: - IdentifyViewController
class IdentifyViewController: UIViewController {
// Outlets
@IBOutlet var identifyInput: UITextField!
@IBOutlet var identifyButton: UIButton!
@IBOutlet var identifyOutput: UITextView!
@IBOutlet var identifyURLInput: UITextField!

// Logging
private var networkLogObserver: (any NSObjectProtocol)?

override func 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 = "https://example.com/newsletter?oeid=\(sha256Email("some.email@address.com"))"
}

override func viewWillAppear(_ animated: Bool) {
Expand Down Expand Up @@ -72,6 +78,55 @@ class IdentifyViewController: UIViewController {
}

}

// dispatchIdentifyHashed() sends the Email as a SHA256 hashed in the app, as you would with a hash from your own backend.
// `.hem` sends it as given; passing a hash to `.emailAddress` would hash it a second time.
@IBAction func dispatchIdentifyHashed(_ sender: UIButton) {
view.endEditing(true)

// An empty Email would otherwise be sent as the SHA256 of an empty string
let email = (identifyInput.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
guard !email.isEmpty else {
identifyOutput.text = "Enter an Email to send it hashed."
return
}

do {
try OPTABLE!.identify([.hem(sha256Email(email))]) { result in
switch result {
case .success:
print("[OptableSDK] ✅ Success on /identify API call with a hashed Email")
case let .failure(error):
print("[OptableSDK] 🚫 Error on /identify API call: \(error)")
}
}
} catch {
print("[OptableSDK] 🚫 Exception on /identify API call: \(error)")
identifyOutput.text += "🚫 EXCEPTION: \(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 func dispatchIdentifyFromURL(_ sender: UIButton) {
view.endEditing(true)

do {
try OPTABLE!.tryIdentifyFromURL(identifyURLInput.text ?? "")
} catch {
print("[OptableSDK] 🚫 Exception on tryIdentifyFromURL: \(error)")
identifyOutput.text += "🚫 EXCEPTION: \(error)"
}
}
}

// MARK: - Helpers
private extension IdentifyViewController {
/// SHA256 of the Email, normalized the way the SDK does it for `.emailAddress`: whitespace removed, lowercased.
func sha256Email(_ email: String) -> String {
let normalized = email.components(separatedBy: .whitespacesAndNewlines).joined().lowercased()
return SHA256.hash(data: Data(normalized.utf8)).map { String(format: "%02x", $0) }.joined()
}
}

// MARK: - UITextFieldDelegate
Expand Down
33 changes: 15 additions & 18 deletions demo-ios-swift/demo-ios-swift/PrebidBannerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,18 @@ final class PrebidBannerViewController: UIViewController { // Outlets
}

// MARK: Actions
// This screen uses the async/await API; the GAM Banner screen shows the same flow with a completion handler.
@IBAction func loadBannerWithTargeting(_ sender: UIButton) {
do {
try OPTABLE!.targeting { [weak self] result in
// The completion handler is not called on the main thread, and ads must be loaded on it.
DispatchQueue.main.async {
switch result {
case let .success(optableTargeting):
print("[OptableSDK] ✅ Success on /targeting API call: \(optableTargeting)")
self?.loadBanner(optableTargeting)

case let .failure(error):
print("[OptableSDK] 🚫 Error on /targeting API call: \(error)")
self?.loadBanner()
}
}
// The Task inherits the main actor from this action, so the banner is loaded on the main thread.
Task {
do {
let optableTargeting = try await OPTABLE!.targeting()
print("[OptableSDK] ✅ Success on /targeting API call: \(optableTargeting)")
loadBanner(optableTargeting)
} catch {
print("[OptableSDK] 🚫 Error on /targeting API call: \(error)")
loadBanner()
}
} catch {
print("[OptableSDK] 🚫 Exception on /targeting API call: \(error)")
targetingLog = "🚫 EXCEPTION: \(error)"
}
}

Expand Down Expand Up @@ -153,8 +146,12 @@ private extension PrebidBannerViewController {

func profile() {
do {
// `id` and `neighbors` are sent as given, so build them with `.extendedIdentifier`,
// which hashes Email addresses and phone numbers before they leave the device.
try OPTABLE!.profile(
traits: ["example": "value", "anotherExample": 123, "thirdExample": true]
traits: ["example": "value", "anotherExample": 123, "thirdExample": true],
id: OptableIdentifier.custom(nil, "demo-user-1234").extendedIdentifier,
neighbors: [OptableIdentifier.emailAddress("some.email@address.com").extendedIdentifier]
) { result in
switch result {
case let .success(optableTargeting):
Expand Down
Loading