Skip to content

Commit 98f1bbc

Browse files
authored
feat(rokt): support mParticle Apple SDK 9.2 (#324)
1 parent a435a6e commit 98f1bbc

27 files changed

Lines changed: 1004 additions & 445 deletions

File tree

.trunk/trunk.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ lint:
4242
- checkov@3.2.507
4343
- dotenv-linter@3.3.0
4444
# ESLint 9+ defaults to flat config only; this repo uses .eslintrc.js (ESLint 8 style).
45-
# Trunk runs ESLint in an isolated env without the repo's node_modules; bundle the same
46-
# plugins/parser as package.json so @typescript-eslint/* resolves (CI + local).
45+
# Trunk runs ESLint in an isolated env without the repo's node_modules; bundle the root
46+
# TypeScript plugins plus the sample app's React Native shareable config.
4747
- eslint@8.57.1:
4848
packages:
49-
- '@typescript-eslint/eslint-plugin@5.62.0'
50-
- '@typescript-eslint/parser@5.62.0'
49+
- '@react-native/eslint-config@0.84.0'
50+
- '@typescript-eslint/eslint-plugin@8.56.1'
51+
- '@typescript-eslint/parser@8.56.1'
5152
- 'eslint-config-prettier@8.10.0'
53+
- 'eslint-plugin-jest@29.15.0'
5254
- 'eslint-plugin-prettier@4.2.1'
5355
- git-diff-check
5456
- ktlint@0.43.2

ExpoTestApp/App.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ const { RoktLayoutView, RoktEventManager } = MParticle;
2626
// Create event emitter for Rokt events
2727
const eventManagerEmitter = new NativeEventEmitter(RoktEventManager);
2828

29+
const generateGuid = () =>
30+
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, character => {
31+
const random = Math.floor(Math.random() * 16);
32+
const value = character === 'x' ? random : 8 + (random % 4);
33+
return value.toString(16);
34+
});
35+
2936
export default function App() {
3037
const [eventName, setEventName] = useState('Test Event');
3138
const [status, setStatus] = useState('SDK initialized via native code');
@@ -257,6 +264,31 @@ export default function App() {
257264
});
258265
};
259266

267+
const handleRoktClose = () => {
268+
MParticle.Rokt.close()
269+
.then(() => {
270+
addLog('Rokt close called');
271+
setStatus('Rokt close called');
272+
})
273+
.catch((error: any) => {
274+
addLog(`Rokt close error: ${JSON.stringify(error)}`);
275+
});
276+
};
277+
278+
const handleRoktSession = () => {
279+
const sessionId = generateGuid();
280+
281+
MParticle.Rokt.setSessionId(sessionId)
282+
.then(() => MParticle.Rokt.getSessionId())
283+
.then((currentSessionId: string | null) => {
284+
addLog(`Rokt session ID: ${currentSessionId ?? 'none'}`);
285+
setStatus(`Rokt session ID: ${currentSessionId ?? 'none'}`);
286+
})
287+
.catch((error: any) => {
288+
addLog(`Rokt session error: ${JSON.stringify(error)}`);
289+
});
290+
};
291+
260292
return (
261293
<SafeAreaView style={styles.container}>
262294
<StatusBar barStyle="dark-content" />
@@ -368,6 +400,20 @@ export default function App() {
368400
>
369401
<Text style={styles.buttonText}>Shoppable Ads</Text>
370402
</TouchableOpacity>
403+
404+
<TouchableOpacity
405+
style={[styles.button, styles.roktButton]}
406+
onPress={handleRoktClose}
407+
>
408+
<Text style={styles.buttonText}>Close Rokt</Text>
409+
</TouchableOpacity>
410+
411+
<TouchableOpacity
412+
style={[styles.button, styles.roktButtonAlt]}
413+
onPress={handleRoktSession}
414+
>
415+
<Text style={styles.buttonText}>Rokt Session</Text>
416+
</TouchableOpacity>
371417
</View>
372418

373419
{/* Rokt Embedded Placeholder */}

ExpoTestApp/README.md

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ This app tests the Expo config plugin integration for the mParticle React Native
2525
{
2626
"expo": {
2727
"plugins": [
28+
[
29+
"expo-build-properties",
30+
{
31+
"ios": {
32+
"deploymentTarget": "15.6"
33+
}
34+
}
35+
],
2836
[
2937
"react-native-mparticle",
3038
{
@@ -81,18 +89,21 @@ The app also includes Rokt placement testing via the mParticle Rokt kit:
8189
- **Overlay**: Loads a full-screen overlay Rokt placement that appears on top of the app content.
8290
- **Bottom Sheet**: Loads a bottom sheet Rokt placement that slides up from the bottom of the screen.
8391
- **Shoppable Ads**: Calls `MParticle.Rokt.selectShoppableAds` with a staging placement identifier and checkout-style attributes (see implementation guide below).
92+
- **Close Rokt**: Calls `MParticle.Rokt.close()`.
93+
- **Rokt Session**: Calls `MParticle.Rokt.setSessionId()` and `MParticle.Rokt.getSessionId()`.
8494

8595
The Rokt section also demonstrates:
8696

8797
- Platform-specific attributes (iOS vs Android configurations)
8898
- Rokt event listeners for callbacks and placement events
8999
- Using `RoktLayoutView` as an embedded placeholder component
90100

91-
### Implementation guide: Shoppable Ads (`selectShoppableAds`) and iOS payment extensions
92-
93-
This mirrors the recent SDK work (Shoppable Ads API on iOS and the Expo test app wiring) and how to pair it with native payment registration.
101+
On Android, the Rokt session APIs require `android-core` and
102+
`android-rokt-kit` `5.79.0` or newer. If configured, the shared Expo
103+
`customBaseUrl` setting is applied to Android through
104+
`NetworkOptions.setCustomBaseURL`.
94105

95-
#### JavaScript: `selectShoppableAds`
106+
### Implementation guide: Shoppable Ads (`selectShoppableAds`)
96107

97108
Use `MParticle.Rokt.selectShoppableAds(identifier, attributes, roktConfig?)` when you need the Shoppable Ads experience instead of `selectPlacements`.
98109

@@ -118,29 +129,7 @@ Listen for `RoktCallback` and `RoktEvents` on `RoktEventManager` to observe load
118129

119130
**Android:** `selectShoppableAds` is not implemented on Android yet; the native module logs a warning and does not run the Shoppable Ads flow. Plan for iOS-only behavior until Android support ships.
120131

121-
#### iOS native: `RoktStripePaymentExtension` (payment extensions)
122-
123-
Shoppable Ads flows that use Apple Pay / Stripe integration expect a **payment extension** to be registered on mParticle’s Rokt interface after the SDK starts.
124-
125-
In `ios/MParticleExpoTest/AppDelegate.swift`, the test app:
126-
127-
1. Imports the Stripe payment extension module provided with the Rokt / kit stack: `import RoktStripePaymentExtension`.
128-
2. After `MParticle.sharedInstance().start(with: mParticleOptions)`, constructs `RoktStripePaymentExtension(applePayMerchantId: "...")` with your **Apple Pay merchant ID** (replace `merchant.dummy` with your real `merchant.*` identifier from Apple Developer).
129-
3. Registers it: `MParticle.sharedInstance().rokt.register(paymentExtension)`.
130-
131-
```swift
132-
import RoktStripePaymentExtension
133-
134-
// After MParticle.sharedInstance().start(with: mParticleOptions):
135-
if let paymentExtension = RoktStripePaymentExtension(applePayMerchantId: "merchant.your.id") {
136-
MParticle.sharedInstance().rokt.register(paymentExtension)
137-
}
138-
```
139-
140-
**Important:**
141-
142-
- The Expo config plugin **does not** generate the payment extension block today. After `expo prebuild`, add or merge this code into `AppDelegate.swift` (inside the same app launch path as mParticle init). If you regenerate native projects with `--clean`, re-apply this snippet.
143-
- Ensure the **mParticle Rokt kit** (and transitive Rokt dependencies) are installed so `RoktStripePaymentExtension` resolves—same as configuring `iosKits`: `["mParticle-Rokt"]` in `app.json`.
132+
The Expo plugin test app installs the standard mParticle Rokt kit only. Payment-extension installation and native URL callback forwarding are not generated by the plugin in this release.
144133

145134
All activity is logged in the Activity Log section at the bottom of the screen.
146135

@@ -171,8 +160,6 @@ Check `ios/MParticleExpoTest/AppDelegate.swift` for:
171160
MParticle.sharedInstance().start(with: mParticleOptions)
172161
```
173162

174-
For Shoppable Ads with Apple Pay / Stripe, you may also need to register `RoktStripePaymentExtension` after `start`—see **Implementation guide: Shoppable Ads (`selectShoppableAds`) and iOS payment extensions** above.
175-
176163
#### Objective-C AppDelegate (Legacy)
177164

178165
For older Expo SDK versions, check `ios/MParticleExpoTest/AppDelegate.mm` for:
@@ -204,7 +191,7 @@ Check `ios/Podfile` for:
204191
```ruby
205192
pre_install do |installer|
206193
installer.pod_targets.each do |pod|
207-
if pod.name == 'mParticle-Apple-SDK' || pod.name == 'mParticle-Rokt' || pod.name == 'Rokt-Widget'
194+
if pod.name == 'mParticle-Apple-SDK' || pod.name == 'mParticle-Apple-SDK-ObjC' || pod.name == 'mParticle-Apple-SDK-Swift' || pod.name == 'mParticle-Rokt' || pod.name == 'Rokt-Widget' || pod.name == 'RoktContracts'
208195
def pod.build_type;
209196
Pod::BuildType.new(:linkage => :dynamic, :packaging => :framework)
210197
end
@@ -216,7 +203,7 @@ Check `ios/Podfile` for:
216203
- Kit pods (if specified):
217204

218205
```ruby
219-
pod 'mParticle-Rokt'
206+
pod 'mParticle-Rokt', '~> 9.2'
220207
```
221208

222209
### Verify Android Integration
@@ -293,4 +280,5 @@ dependencies {
293280
| `dataPlanId` | string | Data plan ID for validation |
294281
| `dataPlanVersion` | number | Data plan version |
295282
| `iosKits` | string[] | iOS kit pod names (e.g., `["mParticle-Rokt"]`) |
283+
| `customBaseUrl` | string | Custom base URL for global CNAME setup on iOS and Android |
296284
| `androidKits` | string[] | Android kit dependencies (e.g., `["android-rokt-kit"]`) |

ExpoTestApp/app.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@
2727
"favicon": "./assets/favicon.png"
2828
},
2929
"plugins": [
30+
[
31+
"expo-build-properties",
32+
{
33+
"ios": {
34+
"deploymentTarget": "15.6"
35+
}
36+
}
37+
],
3038
[
3139
"react-native-mparticle",
3240
{

MIGRATING.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Migration Guides
2+
3+
This document provides migration guidance for changes in `react-native-mparticle`.
4+
Release versions and changelog entries are generated by the Release Draft workflow.
5+
6+
## Migrating to the mParticle Apple SDK 9.2.0 Rokt update
7+
8+
This update aligns the React Native wrapper with `mParticle-Apple-SDK` and
9+
`mParticle-Rokt` `9.2.0`. The Apple Rokt kit now resolves `Rokt-Widget` `~> 5.2`
10+
and `RoktContracts` `~> 2.0`.
11+
12+
### Dependency Changes
13+
14+
For standard Rokt placements on iOS, use:
15+
16+
```ruby
17+
pod 'mParticle-Rokt', '~> 9.2'
18+
```
19+
20+
Do not add `Rokt-Widget` directly to this React Native wrapper's podspec. Apps
21+
receive it transitively through `mParticle-Rokt`.
22+
23+
### React Native Rokt API
24+
25+
The wrapper exposes these Rokt APIs to JavaScript:
26+
27+
```ts
28+
MParticle.Rokt.close(): Promise<void>
29+
MParticle.Rokt.setSessionId(sessionId: string): Promise<void>
30+
MParticle.Rokt.getSessionId(): Promise<string | null>
31+
```
32+
33+
`close()` is supported on iOS and Android. Session APIs are backed by the iOS
34+
mParticle Rokt kit. On Android, apps that use these session APIs must use
35+
`android-core` and `android-rokt-kit` `5.79.0` or newer.
36+
37+
### Expo Config Plugin
38+
39+
Use `iosKits: ["mParticle-Rokt"]` for standard Rokt placements:
40+
41+
```json
42+
[
43+
"react-native-mparticle",
44+
{
45+
"iosApiKey": "YOUR_IOS_API_KEY",
46+
"iosApiSecret": "YOUR_IOS_API_SECRET",
47+
"iosKits": ["mParticle-Rokt"]
48+
}
49+
]
50+
```
51+
52+
The plugin pins generated `mParticle-Rokt` pods to `~> 9.2`. It does not add
53+
payment-extension pods or URL callback forwarding in this release.
54+
55+
For global CNAME setup, configure the shared `customBaseUrl` setting:
56+
57+
```json
58+
{
59+
"customBaseUrl": "https://cname.example.com"
60+
}
61+
```
62+
63+
The plugin applies this through `MPNetworkOptions.customBaseURL` on iOS and
64+
`NetworkOptions.setCustomBaseURL` on Android before mParticle starts. There is
65+
no runtime JavaScript setter because the Rokt kit reads this setting during
66+
initialization.
67+
68+
### Notes
69+
70+
- The React Native API intentionally does not expose `handleURLCallback`.

README.md

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ npx expo run:android
8282
| `dataPlanId` | string | No | Data plan ID for validation |
8383
| `dataPlanVersion` | number | No | Data plan version |
8484
| `iosKits` | string[] | No | iOS kit pod names (e.g., `['mParticle-Rokt']`) |
85+
| `customBaseUrl` | string | No | Custom base URL for global CNAME setup on iOS and Android |
8586
| `androidKits` | string[] | No | Android kit artifact names (e.g., `['android-rokt-kit']`) |
8687
| `useEmptyIdentifyRequest` | boolean | No | Use empty user identify request at init (default: `true`) |
8788

@@ -109,17 +110,27 @@ npx expo run:android
109110
}
110111
```
111112

113+
For global CNAME setup, add the optional shared `customBaseUrl` setting:
114+
115+
```json
116+
{
117+
"customBaseUrl": "https://cname.example.com"
118+
}
119+
```
120+
112121
### What the Plugin Does
113122

114123
**iOS:**
115124

116125
- Adds mParticle SDK initialization to `AppDelegate` (supports both Swift and Objective-C)
126+
- Sets `MPNetworkOptions.customBaseURL` before startup when `customBaseUrl` is configured
117127
- Configures `pre_install` hook in Podfile for dynamic framework linking
118128
- Adds specified kit pod dependencies
119129

120130
**Android:**
121131

122132
- Adds mParticle SDK initialization to `MainApplication` (supports both Kotlin and Java)
133+
- Sets `NetworkOptions.setCustomBaseURL` before startup when `customBaseUrl` is configured
123134
- Adds specified kit Maven dependencies to `build.gradle`
124135

125136
### Version Support
@@ -219,6 +230,12 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau
219230
mParticleOptions.onAttributionComplete = { (attributionResult, error) in
220231
NSLog(@"Attribution Complete. attributionResults = %@", attributionResult.linkInfo)
221232
}
233+
234+
// Optional global CNAME setup. Configure before start.
235+
let networkOptions = MPNetworkOptions()
236+
networkOptions.customBaseURL = URL(string: "https://cname.example.com")
237+
mParticleOptions.networkOptions = networkOptions
238+
222239
MParticle.sharedInstance().start(with: mParticleOptions)
223240
return true
224241
}
@@ -260,12 +277,34 @@ Next, you'll need to start the SDK:
260277
NSLog(@"Attribution Complete. attributionResults = %@", attributionResult.linkInfo)
261278
}
262279
280+
// Optional global CNAME setup. Configure before start.
281+
MPNetworkOptions *networkOptions = [[MPNetworkOptions alloc] init];
282+
networkOptions.customBaseURL = [NSURL URLWithString:@"https://cname.example.com"];
283+
mParticleOptions.networkOptions = networkOptions;
284+
263285
[[MParticle sharedInstance] startWithOptions:mParticleOptions];
264286
265287
return YES;
266288
}
267289
```
268290
291+
### Rokt iOS Setup
292+
293+
For standard Rokt placements, add the mParticle Rokt kit:
294+
295+
```ruby
296+
pod 'mParticle-Rokt', '~> 9.2'
297+
```
298+
299+
In Expo apps, use `iosKits: ["mParticle-Rokt"]` for standard Rokt placements. The Expo plugin does not add payment-extension pods or URL callback forwarding in this release.
300+
301+
See [MIGRATING.md](./MIGRATING.md) for release-specific migration guidance.
302+
303+
For Android integrations that use `MParticle.Rokt.setSessionId()` or
304+
`MParticle.Rokt.getSessionId()`, `android-core` and `android-rokt-kit`
305+
`5.79.0` or newer are required. Android CNAME setup through
306+
`customBaseUrl` also requires `android-core` `5.79.0` or newer.
307+
269308
See [Identity](http://docs.mparticle.com/developers/sdk/ios/identity/) for more information on supplying an `MPIdentityApiRequest` object during SDK initialization.
270309
271310
4. Remember to start Metro with:
@@ -285,20 +324,28 @@ and build your workspace from xCode.
285324
For more help, see [the Android set up docs](https://docs.mparticle.com/developers/sdk/android/getting-started/#create-an-input).
286325
287326
```kotlin
288-
package com.example.myapp;
327+
package com.example.myapp
289328
290-
import android.app.Application;
291-
import com.mparticle.MParticle;
329+
import android.app.Application
330+
import com.mparticle.MParticle
331+
import com.mparticle.MParticleOptions
332+
import com.mparticle.networking.NetworkOptions
292333
293334
class MyApplication : Application() {
294-
fun onCreate() {
335+
override fun onCreate() {
295336
super.onCreate()
296337
val options: MParticleOptions = MParticleOptions.builder(this)
297338
.credentials("REPLACE ME WITH KEY", "REPLACE ME WITH SECRET")
298339
//optional
299340
.logLevel(MParticle.LogLevel.VERBOSE)
300341
//optional
301342
.identify(identifyRequest)
343+
//optional global CNAME setup
344+
.networkOptions(
345+
NetworkOptions.builder()
346+
.setCustomBaseURL("https://cname.example.com")
347+
.build()
348+
)
302349
//optional
303350
.identifyTask(
304351
BaseIdentityTask()

0 commit comments

Comments
 (0)