Skip to content
Open
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
10 changes: 6 additions & 4 deletions .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ lint:
- checkov@3.2.507
- dotenv-linter@3.3.0
# ESLint 9+ defaults to flat config only; this repo uses .eslintrc.js (ESLint 8 style).
# Trunk runs ESLint in an isolated env without the repo's node_modules; bundle the same
# plugins/parser as package.json so @typescript-eslint/* resolves (CI + local).
# Trunk runs ESLint in an isolated env without the repo's node_modules; bundle the root
# TypeScript plugins plus the sample app's React Native shareable config.
- eslint@8.57.1:
packages:
- '@typescript-eslint/eslint-plugin@5.62.0'
- '@typescript-eslint/parser@5.62.0'
- '@react-native/eslint-config@0.84.0'
- '@typescript-eslint/eslint-plugin@8.56.1'
- '@typescript-eslint/parser@8.56.1'
- 'eslint-config-prettier@8.10.0'
- 'eslint-plugin-jest@29.15.0'
- 'eslint-plugin-prettier@4.2.1'
- git-diff-check
- ktlint@0.43.2
Expand Down
46 changes: 46 additions & 0 deletions ExpoTestApp/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ const { RoktLayoutView, RoktEventManager } = MParticle;
// Create event emitter for Rokt events
const eventManagerEmitter = new NativeEventEmitter(RoktEventManager);

const generateGuid = () =>
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, character => {
const random = Math.floor(Math.random() * 16);
const value = character === 'x' ? random : 8 + (random % 4);
return value.toString(16);
});

export default function App() {
const [eventName, setEventName] = useState('Test Event');
const [status, setStatus] = useState('SDK initialized via native code');
Expand Down Expand Up @@ -257,6 +264,31 @@ export default function App() {
});
};

const handleRoktClose = () => {
MParticle.Rokt.close()
.then(() => {
addLog('Rokt close called');
setStatus('Rokt close called');
})
.catch((error: any) => {
addLog(`Rokt close error: ${JSON.stringify(error)}`);
});
};

const handleRoktSession = () => {
const sessionId = generateGuid();

MParticle.Rokt.setSessionId(sessionId)
.then(() => MParticle.Rokt.getSessionId())
.then((currentSessionId: string | null) => {
addLog(`Rokt session ID: ${currentSessionId ?? 'none'}`);
setStatus(`Rokt session ID: ${currentSessionId ?? 'none'}`);
})
.catch((error: any) => {
addLog(`Rokt session error: ${JSON.stringify(error)}`);
});
};

return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
Expand Down Expand Up @@ -368,6 +400,20 @@ export default function App() {
>
<Text style={styles.buttonText}>Shoppable Ads</Text>
</TouchableOpacity>

<TouchableOpacity
style={[styles.button, styles.roktButton]}
onPress={handleRoktClose}
>
<Text style={styles.buttonText}>Close Rokt</Text>
</TouchableOpacity>

<TouchableOpacity
style={[styles.button, styles.roktButtonAlt]}
onPress={handleRoktSession}
>
<Text style={styles.buttonText}>Rokt Session</Text>
</TouchableOpacity>
</View>

{/* Rokt Embedded Placeholder */}
Expand Down
55 changes: 24 additions & 31 deletions ExpoTestApp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ This app tests the Expo config plugin integration for the mParticle React Native
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"ios": {
"deploymentTarget": "15.6"
}
}
],
[
"react-native-mparticle",
{
Expand All @@ -34,6 +42,7 @@ This app tests the Expo config plugin integration for the mParticle React Native
"androidApiSecret": "YOUR_ANDROID_API_SECRET",
"logLevel": "verbose",
"environment": "development",
"iosCustomBaseURL": "https://cname.example.com",
"iosKits": ["mParticle-Rokt"],
"androidKits": ["android-rokt-kit"]
}
Expand Down Expand Up @@ -81,18 +90,19 @@ The app also includes Rokt placement testing via the mParticle Rokt kit:
- **Overlay**: Loads a full-screen overlay Rokt placement that appears on top of the app content.
- **Bottom Sheet**: Loads a bottom sheet Rokt placement that slides up from the bottom of the screen.
- **Shoppable Ads**: Calls `MParticle.Rokt.selectShoppableAds` with a staging placement identifier and checkout-style attributes (see implementation guide below).
- **Close Rokt**: Calls `MParticle.Rokt.close()`.
- **Rokt Session**: Calls `MParticle.Rokt.setSessionId()` and `MParticle.Rokt.getSessionId()`.

The Rokt section also demonstrates:

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

### Implementation guide: Shoppable Ads (`selectShoppableAds`) and iOS payment extensions

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.
On Android, the Rokt session APIs require `android-core` and
`android-rokt-kit` `5.77.0` or newer.

#### JavaScript: `selectShoppableAds`
### Implementation guide: Shoppable Ads (`selectShoppableAds`)

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

Expand All @@ -118,29 +128,7 @@ Listen for `RoktCallback` and `RoktEvents` on `RoktEventManager` to observe load

**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.

#### iOS native: `RoktStripePaymentExtension` (payment extensions)

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.

In `ios/MParticleExpoTest/AppDelegate.swift`, the test app:

1. Imports the Stripe payment extension module provided with the Rokt / kit stack: `import RoktStripePaymentExtension`.
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).
3. Registers it: `MParticle.sharedInstance().rokt.register(paymentExtension)`.

```swift
import RoktStripePaymentExtension

// After MParticle.sharedInstance().start(with: mParticleOptions):
if let paymentExtension = RoktStripePaymentExtension(applePayMerchantId: "merchant.your.id") {
MParticle.sharedInstance().rokt.register(paymentExtension)
}
```

**Important:**

- 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.
- Ensure the **mParticle Rokt kit** (and transitive Rokt dependencies) are installed so `RoktStripePaymentExtension` resolves—same as configuring `iosKits`: `["mParticle-Rokt"]` in `app.json`.
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.

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

Expand Down Expand Up @@ -168,11 +156,12 @@ Check `ios/MParticleExpoTest/AppDelegate.swift` for:
mParticleOptions.environment = .development
let identifyRequest = MPIdentityApiRequest.withEmptyUser()
mParticleOptions.identifyRequest = identifyRequest
let networkOptions = MPNetworkOptions()
networkOptions.customBaseURL = URL(string: "https://cname.example.com")
mParticleOptions.networkOptions = networkOptions
MParticle.sharedInstance().start(with: mParticleOptions)
```

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.

#### Objective-C AppDelegate (Legacy)

For older Expo SDK versions, check `ios/MParticleExpoTest/AppDelegate.mm` for:
Expand All @@ -192,6 +181,9 @@ For older Expo SDK versions, check `ios/MParticleExpoTest/AppDelegate.mm` for:
mParticleOptions.environment = MPEnvironmentDevelopment;
MPIdentityApiRequest *identifyRequest = [MPIdentityApiRequest requestWithEmptyUser];
mParticleOptions.identifyRequest = identifyRequest;
MPNetworkOptions *networkOptions = [[MPNetworkOptions alloc] init];
networkOptions.customBaseURL = [NSURL URLWithString:@"https://cname.example.com"];
mParticleOptions.networkOptions = networkOptions;
[[MParticle sharedInstance] startWithOptions:mParticleOptions];
```

Expand All @@ -204,7 +196,7 @@ Check `ios/Podfile` for:
```ruby
pre_install do |installer|
installer.pod_targets.each do |pod|
if pod.name == 'mParticle-Apple-SDK' || pod.name == 'mParticle-Rokt' || pod.name == 'Rokt-Widget'
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'
def pod.build_type;
Pod::BuildType.new(:linkage => :dynamic, :packaging => :framework)
end
Expand All @@ -216,7 +208,7 @@ Check `ios/Podfile` for:
- Kit pods (if specified):

```ruby
pod 'mParticle-Rokt'
pod 'mParticle-Rokt', '~> 9.2'
```

### Verify Android Integration
Expand Down Expand Up @@ -293,4 +285,5 @@ dependencies {
| `dataPlanId` | string | Data plan ID for validation |
| `dataPlanVersion` | number | Data plan version |
| `iosKits` | string[] | iOS kit pod names (e.g., `["mParticle-Rokt"]`) |
| `iosCustomBaseURL` | string | iOS custom base URL for global CNAME setup |
| `androidKits` | string[] | Android kit dependencies (e.g., `["android-rokt-kit"]`) |
9 changes: 9 additions & 0 deletions ExpoTestApp/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
"favicon": "./assets/favicon.png"
},
"plugins": [
[
"expo-build-properties",
{
"ios": {
"deploymentTarget": "15.6"
}
}
],
[
"react-native-mparticle",
{
Expand All @@ -37,6 +45,7 @@
"logLevel": "verbose",
"useEmptyIdentifyRequest": true,
"environment": "development",
"iosCustomBaseURL": "https://cname.example.com",
"iosKits": ["mParticle-Rokt"],
"androidKits": ["android-rokt-kit"]
}
Expand Down
69 changes: 69 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Migration Guides

This document provides migration guidance for changes in `react-native-mparticle`.
Release versions and changelog entries are generated by the Release Draft workflow.

## Migrating to the mParticle Apple SDK 9.2.0 Rokt update

This update aligns the React Native wrapper with `mParticle-Apple-SDK` and
`mParticle-Rokt` `9.2.0`. The Apple Rokt kit now resolves `Rokt-Widget` `~> 5.2`
and `RoktContracts` `~> 2.0`.

### Dependency Changes

For standard Rokt placements on iOS, use:

```ruby
pod 'mParticle-Rokt', '~> 9.2'
```

Do not add `Rokt-Widget` directly to this React Native wrapper's podspec. Apps
receive it transitively through `mParticle-Rokt`.

### React Native Rokt API

The wrapper exposes these Rokt APIs to JavaScript:

```ts
MParticle.Rokt.close(): Promise<void>
MParticle.Rokt.setSessionId(sessionId: string): Promise<void>
MParticle.Rokt.getSessionId(): Promise<string | null>
```

`close()` is supported on iOS and Android. Session APIs are backed by the iOS
mParticle Rokt kit. On Android, apps that use these session APIs must use
`android-core` and `android-rokt-kit` `5.77.0` or newer.

### Expo Config Plugin

Use `iosKits: ["mParticle-Rokt"]` for standard Rokt placements:

```json
[
"react-native-mparticle",
{
"iosApiKey": "YOUR_IOS_API_KEY",
"iosApiSecret": "YOUR_IOS_API_SECRET",
"iosKits": ["mParticle-Rokt"]
}
]
```

The plugin pins generated `mParticle-Rokt` pods to `~> 9.2`. It does not add
payment-extension pods or URL callback forwarding in this release.

For global CNAME setup, configure `iosCustomBaseURL`:

```json
{
"iosCustomBaseURL": "https://cname.example.com"
}
```

The plugin applies this through `MPNetworkOptions.customBaseURL` before
mParticle starts. There is no runtime JavaScript setter because the Rokt kit
reads this setting during initialization.

### Notes

- The React Native API intentionally does not expose `handleURLCallback`.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ npx expo run:android
| `dataPlanId` | string | No | Data plan ID for validation |
| `dataPlanVersion` | number | No | Data plan version |
| `iosKits` | string[] | No | iOS kit pod names (e.g., `['mParticle-Rokt']`) |
| `iosCustomBaseURL` | string | No | iOS custom base URL for global CNAME setup |
| `androidKits` | string[] | No | Android kit artifact names (e.g., `['android-rokt-kit']`) |
| `useEmptyIdentifyRequest` | boolean | No | Use empty user identify request at init (default: `true`) |

Expand All @@ -100,6 +101,7 @@ npx expo run:android
"androidApiSecret": "YOUR_ANDROID_API_SECRET",
"environment": "development",
"logLevel": "verbose",
"iosCustomBaseURL": "https://cname.example.com",
"iosKits": ["mParticle-Rokt", "mParticle-Amplitude"],
"androidKits": ["android-rokt-kit", "android-amplitude-kit"]
}
Expand All @@ -114,6 +116,7 @@ npx expo run:android
**iOS:**

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

Expand Down Expand Up @@ -219,6 +222,12 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau
mParticleOptions.onAttributionComplete = { (attributionResult, error) in
NSLog(@"Attribution Complete. attributionResults = %@", attributionResult.linkInfo)
}

// Optional global CNAME setup. Configure before start.
let networkOptions = MPNetworkOptions()
networkOptions.customBaseURL = URL(string: "https://cname.example.com")
mParticleOptions.networkOptions = networkOptions

MParticle.sharedInstance().start(with: mParticleOptions)
return true
}
Expand Down Expand Up @@ -260,12 +269,33 @@ Next, you'll need to start the SDK:
NSLog(@"Attribution Complete. attributionResults = %@", attributionResult.linkInfo)
}

// Optional global CNAME setup. Configure before start.
MPNetworkOptions *networkOptions = [[MPNetworkOptions alloc] init];
networkOptions.customBaseURL = [NSURL URLWithString:@"https://cname.example.com"];
mParticleOptions.networkOptions = networkOptions;

[[MParticle sharedInstance] startWithOptions:mParticleOptions];

return YES;
}
```

### Rokt iOS Setup

For standard Rokt placements, add the mParticle Rokt kit:

```ruby
pod 'mParticle-Rokt', '~> 9.2'
```

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.

See [MIGRATING.md](./MIGRATING.md) for release-specific migration guidance.

For Android integrations that use `MParticle.Rokt.setSessionId()` or
`MParticle.Rokt.getSessionId()`, `android-core` and `android-rokt-kit`
`5.77.0` or newer are required.

See [Identity](http://docs.mparticle.com/developers/sdk/ios/identity/) for more information on supplying an `MPIdentityApiRequest` object during SDK initialization.

4. Remember to start Metro with:
Expand Down
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
Expand Down
Loading
Loading