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
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
50 changes: 19 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 Down Expand Up @@ -81,18 +89,21 @@ 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.79.0` or newer. If configured, the shared Expo
`customBaseUrl` setting is applied to Android through
`NetworkOptions.setCustomBaseURL`.

#### 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 +129,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 @@ -171,8 +160,6 @@ Check `ios/MParticleExpoTest/AppDelegate.swift` for:
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 Down Expand Up @@ -204,7 +191,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 +203,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 +280,5 @@ dependencies {
| `dataPlanId` | string | Data plan ID for validation |
| `dataPlanVersion` | number | Data plan version |
| `iosKits` | string[] | iOS kit pod names (e.g., `["mParticle-Rokt"]`) |
| `customBaseUrl` | string | Custom base URL for global CNAME setup on iOS and Android |
| `androidKits` | string[] | Android kit dependencies (e.g., `["android-rokt-kit"]`) |
8 changes: 8 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 Down
70 changes: 70 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 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.79.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 the shared `customBaseUrl` setting:

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

The plugin applies this through `MPNetworkOptions.customBaseURL` on iOS and
`NetworkOptions.setCustomBaseURL` on Android 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`.
55 changes: 51 additions & 4 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']`) |
| `customBaseUrl` | string | No | Custom base URL for global CNAME setup on iOS and Android |
| `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 Down Expand Up @@ -109,17 +110,27 @@ npx expo run:android
}
```

For global CNAME setup, add the optional shared `customBaseUrl` setting:

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

### What the Plugin Does

**iOS:**

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

**Android:**

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

### Version Support
Expand Down Expand Up @@ -219,6 +230,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 +277,34 @@ 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.79.0` or newer are required. Android CNAME setup through
`customBaseUrl` also requires `android-core` `5.79.0` or newer.

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 All @@ -285,20 +324,28 @@ and build your workspace from xCode.
For more help, see [the Android set up docs](https://docs.mparticle.com/developers/sdk/android/getting-started/#create-an-input).

```kotlin
package com.example.myapp;
package com.example.myapp

import android.app.Application;
import com.mparticle.MParticle;
import android.app.Application
import com.mparticle.MParticle
import com.mparticle.MParticleOptions
import com.mparticle.networking.NetworkOptions

class MyApplication : Application() {
fun onCreate() {
override fun onCreate() {
super.onCreate()
val options: MParticleOptions = MParticleOptions.builder(this)
.credentials("REPLACE ME WITH KEY", "REPLACE ME WITH SECRET")
//optional
.logLevel(MParticle.LogLevel.VERBOSE)
//optional
.identify(identifyRequest)
//optional global CNAME setup
.networkOptions(
NetworkOptions.builder()
.setCustomBaseURL("https://cname.example.com")
.build()
)
//optional
.identifyTask(
BaseIdentityTask()
Expand Down
Loading
Loading