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
54 changes: 52 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
contents: read
pull-requests: read
checks: write
id-token: write

Check failure on line 15 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

excessive-permissions

pull-request.yml:15: overly broad permissions: id-token: write is overly broad at the workflow level

jobs:
trunk-check:
Expand All @@ -23,9 +23,9 @@
contents: read
pull-requests: read
steps:
- name: Checkout

Check warning on line 26 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

pull-request.yml:26: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
uses: actions/checkout@v7

Check failure on line 27 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

unpinned-uses

pull-request.yml:27: unpinned action reference: action is not pinned to a hash (required by blanket policy)
- uses: actions/setup-node@v7

Check failure on line 28 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

unpinned-uses

pull-request.yml:28: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
node-version: 24
cache: yarn
Expand All @@ -33,15 +33,15 @@
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Trunk Check
uses: trunk-io/trunk-action@v1

Check failure on line 36 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

unpinned-uses

pull-request.yml:36: unpinned action reference: action is not pinned to a hash (required by blanket policy)

js-tests:
name: 'JS Tests'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'

Check warning on line 42 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

pull-request.yml:42: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
uses: actions/checkout@v7

Check failure on line 43 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

unpinned-uses

pull-request.yml:43: unpinned action reference: action is not pinned to a hash (required by blanket policy)
- uses: actions/setup-node@v7

Check failure on line 44 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

unpinned-uses

pull-request.yml:44: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
node-version: 24
cache: yarn
Expand All @@ -59,8 +59,8 @@
name: 'Android Unit Tests'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'

Check warning on line 62 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

pull-request.yml:62: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
uses: actions/checkout@v7

Check failure on line 63 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

unpinned-uses

pull-request.yml:63: unpinned action reference: action is not pinned to a hash (required by blanket policy)
- name: 'Run Android Unit Tests'
working-directory: android
run: ./gradlew test
Expand All @@ -69,7 +69,7 @@
name: 'Android Lint'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'

Check warning on line 72 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

pull-request.yml:72: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
uses: actions/checkout@v7
- name: 'Run Android lint'
working-directory: android
Expand All @@ -79,7 +79,7 @@
name: 'Android kotlin lint'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'

Check warning on line 82 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

pull-request.yml:82: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
uses: actions/checkout@v7
- name: 'Run Android kotlin lint'
working-directory: android
Expand All @@ -89,7 +89,7 @@
name: Android Sample App
runs-on: ubuntu-latest
steps:
- name: Checkout

Check warning on line 92 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

artipacked

pull-request.yml:92: credential persistence through GitHub Actions artifacts: does not set persist-credentials: false
uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
Expand All @@ -107,16 +107,30 @@
working-directory: sample
run: |
rm -f yarn.lock
# Yarn 1 keeps its unpacked copy of a local .tgz under .tmp in the restored cache, keyed
# only by the file's path, so without this the sample installs an old build of this package.
rm -rf "$(yarn cache dir)/.tmp"
yarn install

- name: Run Android sample app
working-directory: sample/android
run: ./gradlew assembleDebug

ios-sample-app:
# Keep this display name in sync with branch protection required checks ("iOS Sample App").
name: iOS Sample App
# The CocoaPods leg keeps the display name the "main" ruleset requires ("iOS Sample App").
name: ${{ matrix.check_name }}
runs-on: macos-15
strategy:
fail-fast: false
matrix:
include:
- ios_deps: cocoapods
check_name: iOS Sample App
- ios_deps: spm
check_name: iOS Sample App (SPM)
env:
# sample/ios/Podfile takes the mParticle SDKs from Swift Package Manager unless this is 1.
MP_USE_COCOAPODS: ${{ matrix.ios_deps == 'cocoapods' && '1' || '0' }}
steps:
- name: Checkout
uses: actions/checkout@v7
Expand Down Expand Up @@ -156,12 +170,29 @@
working-directory: sample
run: |
rm -f yarn.lock
# Yarn 1 keeps its unpacked copy of a local .tgz under .tmp in the restored cache, keyed
# only by the file's path, so without this the sample installs an old build of this package.
rm -rf "$(yarn cache dir)/.tmp"
yarn install

- name: Install Ruby dependencies
working-directory: sample
run: bundle install

# A tvOS app must get the opt-out error, not silently lose this pod (see the podspec's platforms).
- name: Check that a tvOS Podfile stops with the iOS-only error
if: matrix.ios_deps == 'spm'
working-directory: sample/ios
run: |
sed -i '' "s/^platform :ios, '15.6'/platform :tvos, '15.6'/" Podfile
if bundle exec pod install > tvos-pod-install.log 2>&1; then
cat tvos-pod-install.log
echo "pod install succeeded for a tvOS target; it should stop with the iOS-only error."
exit 1
fi
git checkout -- Podfile
grep -F '[mParticle] Swift Package Manager mode is iOS only' tvos-pod-install.log || { cat tvos-pod-install.log; exit 1; }

- name: Build iOS sample app and run native unit tests
working-directory: sample/ios
run: |
Expand All @@ -169,13 +200,32 @@
set -o pipefail && xcodebuild -workspace MParticleSample.xcworkspace \
-configuration Debug \
-scheme MParticleSample \
-destination 'id=${{ steps.simulator.outputs.udid }}' \

Check notice on line 203 in .github/workflows/pull-request.yml

View workflow job for this annotation

GitHub Actions / Scan GitHub Actions workflows

template-injection

pull-request.yml:203: code injection via template expansion: may expand into attacker-controllable code
-derivedDataPath ios/build \
-UseModernBuildSystem=YES \
test \
-only-testing:MParticleSampleTests/RCTConvertCommerceMappingTests \
-only-testing:MParticleSampleTests/RoktNativeLayoutComponentViewSizingTests \
-only-testing:MParticleSampleTests/RoktNativeLayoutComponentViewPropsTests \
-only-testing:MParticleSampleTests/RNMParticleDuplicateSDKTests \
-only-testing:MParticleSampleTests/RNMPRoktEventMapperTests \
-only-testing:MParticleSampleTests/RNMPRoktConfigFactoryTests \
-only-testing:MParticleSampleTests/RNMPRoktSwiftTests \
| bundle exec xcpretty -k

- name: Archive iOS sample app and check for a single copy of each SDK
working-directory: sample/ios
run: |
set -o pipefail && xcodebuild -workspace MParticleSample.xcworkspace \
-configuration Release \
-scheme MParticleSample \
-destination 'generic/platform=iOS' \
-derivedDataPath ios/build \
-archivePath ios/build/MParticleSample.xcarchive \
CODE_SIGNING_ALLOWED=NO \
archive \
| bundle exec xcpretty -k
../../scripts/ios/check-single-copy.sh ios/build/MParticleSample.xcarchive/Products/Applications/MParticleSample.app

pr-notify:
if: >
Expand Down
37 changes: 27 additions & 10 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,35 @@

This document provides migration guidance for changes in `react-native-mparticle`.

## iOS: the mParticle SDKs now come from Swift Package Manager

**Breaking, iOS only.** React Native and this package still install with CocoaPods, but the mParticle core SDK and its kits now come from Swift Package Manager by default, linked into the app target. CocoaPods trunk becomes read-only on 2 December 2026. An app with no kits needs no change. An app that declares kit pods fails `pod install` until it moves them, or opts out. See [README › Swift Package Manager](./README.md#swift-package-manager).

To move a bare React Native app:

1. In `ios/Podfile`, remove every mParticle and Rokt pod you declared, such as `pod 'mParticle-Rokt'`, `pod 'mParticle-Apple-SDK'` or `pod 'Rokt-Widget'`.
2. Remove the mParticle, Rokt, `RoktContracts`, `RoktUXHelper` and `DcuiSchema` names from any `pre_install` hook that makes them dynamic frameworks. If the hook lists nothing else, delete it.
3. If you added mParticle or Rokt Swift packages to the app target by hand, remove them in Xcode (target › General › Frameworks, Libraries, and Embedded Content, and the project's Package Dependencies). `pod install` adds them back, pinned.
4. Above the first `target` block, list your kits by CocoaPods name: `$RNMParticleSPMKits = ['mParticle-Rokt']`.
5. Run `pod install`. It prints each package it adds, and fails with a list of pods to remove if any would add a second copy of the SDK.
6. Build, run a Debug build once to confirm no red box, and commit the `.xcodeproj` change and `Package.resolved`.

A Swift `AppDelegate` needs no change: `import mParticle_Apple_SDK` resolves from the Swift package.

Expo apps need no change beyond `npx expo prebuild --clean`: the plugin maps `iosKits` to their Swift packages. A kit the plugin does not know goes in `iosSpmKits`.

To stay on CocoaPods for now, add `$RNMParticleDisableSPM = true` at the top of `ios/Podfile`, before any `target` block, and keep your pods (Expo: `"iosDependencyManager": "cocoapods"`). This is deprecated, and a Podfile with a tvOS target that uses this package must do it.

## Migrating embedded placements to placeholder names

`MParticle.Rokt.selectPlacements` can find each embedded `RoktLayoutView` by its
`MParticle.Rokt.selectPlacements` finds each embedded `RoktLayoutView` by its
`placeholderName`, so apps no longer need a ref, `findNodeHandle`, or to wait for
the view to mount before calling it. This is not a breaking change: the map of
placeholder names to React tags still works. We recommend moving to names the
next time you touch the integration.
the view to mount before calling it.

**Breaking:** the map of placeholder names to React tags has been removed, and
`placeholders` must be an array of names. A map passed from plain JavaScript logs
an error, and the placement is requested without embedded views. Earlier 3.x
releases accept both forms, so you can switch to names before you upgrade.

Before, tag-based:

Expand Down Expand Up @@ -48,23 +70,18 @@ return <MParticle.RoktLayoutView placeholderName="Location1" />;
To migrate, remove the ref, the `findNodeHandle` import and any `onLayout`
handler or timer used to delay the call, then pass an array of placeholder
names. Each name must match the `placeholderName` of a `RoktLayoutView`, the same
key the map uses today.
key the map used.

### Behavior changes to check

- **A named placeholder that has not mounted yet delays the request by up to 2
seconds.** The SDK waits for the view, then calls Rokt with the views it has.
A misspelled or never-rendered name therefore arrives 2 seconds late, and is
logged as `Cannot resolve placeholder`.
- **`null` in the map form is resolved by name.** `findNodeHandle` returns `null`
before the view mounts; that placeholder was skipped before and is now looked
up by its key.
- **A waiting call can end in `PlacementFailure`.** If `close()` runs, or a newer
call with the same identifier replaces it, before its placeholders mount, the
waiting call emits `PlacementFailure` instead of being dropped silently.

The map form is planned for removal in a future major version.

## Migrating from versions < 3.0.0

`3.0.0` moved iOS to the mParticle Apple SDK **9.x**. Later 3.x releases raised
Expand Down
73 changes: 73 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// swift-tools-version: 6.0
// Experimental: for React Native's Swift Package Manager mode (React Native 0.87 or later), which
// React Native itself marks as not for production. CocoaPods apps do not use this file.
//
// The ReactNative and React-GeneratedCode packages are the local packages React Native's autolinker
// generates for each app, at these paths relative to this package, the same layout other React
// Native libraries use. Revisit them when React Native publishes a remote Swift package.

import PackageDescription

let package = Package(
name: "ReactNativeMparticle",
// React Native's autolinker requires iOS 15; the mParticle SDK declares the same.
platforms: [.iOS(.v15)],
products: [
.library(name: "ReactNativeMparticle", targets: ["ReactNativeMparticle"]),
],
dependencies: [
.package(name: "ReactNative", path: "../../../../xcframeworks"),
.package(name: "React-GeneratedCode", path: "../../../ios"),
// Same URLs as the mParticle kits use, so SwiftPM unifies them into one package identity.
.package(url: "https://github.com/mParticle/mparticle-apple-sdk", "9.2.2"..<"10.0.0"),
.package(url: "https://github.com/ROKT/rokt-contracts-apple.git", "2.0.0"..<"3.0.0"),
],
targets: [
// A separate target: SwiftPM cannot mix Swift and Objective-C++ in one target.
.target(
name: "RNMParticleSwift",
dependencies: [.product(name: "RoktContracts", package: "rokt-contracts-apple")],
path: "ios/RNMParticle/Swift"
),
.target(
name: "ReactNativeMparticle",
dependencies: [
"RNMParticleSwift",
.product(name: "ReactHeaders", package: "ReactNative"),
.product(name: "ReactNativeHeaders", package: "ReactNative"),
.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"),
.product(name: "ReactAppHeaders", package: "React-GeneratedCode"),
.product(name: "mParticle-Apple-SDK", package: "mparticle-apple-sdk"),
.product(name: "RoktContracts", package: "rokt-contracts-apple"),
],
path: ".",
sources: [
"ios/RNMParticle/RNMPRokt.h",
"ios/RNMParticle/RNMPRokt.mm",
"ios/RNMParticle/RNMPRoktSwift.h",
"ios/RNMParticle/RNMPSDKImports.h",
"ios/RNMParticle/RNMParticle.h",
"ios/RNMParticle/RNMParticle.mm",
"ios/RNMParticle/RoktEventManager.h",
"ios/RNMParticle/RoktEventManager.mm",
"ios/RNMParticle/RoktLayoutManager.m",
"ios/RNMParticle/RoktNativeLayoutComponentView.h",
"ios/RNMParticle/RoktNativeLayoutComponentView.mm",
"ios/RNMParticle/RoktPlaceholderRegistry.h",
"ios/RNMParticle/RoktPlaceholderRegistry.m",
],
publicHeadersPath: "ios",
// Without RCT_NEW_ARCH_ENABLED the TurboModule and Fabric code compiles out, and the app
// crashes looking up the RoktNativeLayout component.
cSettings: [.define("RCT_NEW_ARCH_ENABLED", to: "1"), .headerSearchPath("ios/RNMParticle")],
cxxSettings: [
.define("RCT_NEW_ARCH_ENABLED", to: "1"),
.headerSearchPath("ios/RNMParticle"),
.define("DEBUG", .when(configuration: .debug)),
.define("NDEBUG", .when(configuration: .release)),
],
linkerSettings: [.linkedFramework("UIKit"), .linkedFramework("Foundation"), .linkedFramework("CoreGraphics")]
),
],
cxxLanguageStandard: .cxx20
)
Loading
Loading