diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 5d81174..0076de2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -128,7 +128,9 @@ jobs: - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1.7.0 with: - xcode-version: latest-stable + # Avoid `latest-stable` (Xcode 17+ / Swift 6 defaults) breaking RoktUXHelper + # source builds until Rokt pins are updated upstream. + xcode-version: '^16.0.0' - name: Set Xcode Toolchain run: echo "TOOLCHAINS=com.apple.dt.toolchain.XcodeDefault" >> $GITHUB_ENV diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed00f6..b64bdb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Expo config plugin: optional `pinningDisabled` for `MPNetworkOptions` / `NetworkOptions` at SDK startup + +### Fixed + +- iOS sample CI: pin `Rokt-Widget` to `5.2.0` and `DcuiSchema` to `2.7.0` (avoids `2.8.x` schema floating under Rokt’s `~> 2.6` and breaking `RoktUXHelper` Swift compile); GitHub Actions stays on Xcode 16.x + ## [3.1.2] - 2026-05-28 ### Fixed diff --git a/ExpoTestApp/README.md b/ExpoTestApp/README.md index 9bf9cc2..7c5c250 100644 --- a/ExpoTestApp/README.md +++ b/ExpoTestApp/README.md @@ -268,17 +268,18 @@ dependencies { ## Plugin Configuration Options -| Option | Type | Description | -| ------------------------- | -------- | --------------------------------------------------------- | -| `iosApiKey` | string | mParticle iOS API key | -| `iosApiSecret` | string | mParticle iOS API secret | -| `androidApiKey` | string | mParticle Android API key | -| `androidApiSecret` | string | mParticle Android API secret | -| `logLevel` | string | Log level: `none`, `error`, `warning`, `debug`, `verbose` | -| `environment` | string | Environment: `development`, `production`, `autoDetect` | -| `useEmptyIdentifyRequest` | boolean | Initialize with empty identify request (default: true) | -| `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"]`) | +| Option | Type | Description | +| ------------------------- | -------- | --------------------------------------------------------------------------------------------------------- | +| `iosApiKey` | string | mParticle iOS API key | +| `iosApiSecret` | string | mParticle iOS API secret | +| `androidApiKey` | string | mParticle Android API key | +| `androidApiSecret` | string | mParticle Android API secret | +| `logLevel` | string | Log level: `none`, `error`, `warning`, `debug`, `verbose` | +| `environment` | string | Environment: `development`, `production`, `autoDetect` | +| `useEmptyIdentifyRequest` | boolean | Initialize with empty identify request (default: true) | +| `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 | +| `pinningDisabled` | boolean | Disable SSL pinning (iOS: `MPNetworkOptions.pinningDisabled`; Android: `setPinningDisabledInDevelopment`) | +| `androidKits` | string[] | Android kit dependencies (e.g., `["android-rokt-kit"]`) | diff --git a/README.md b/README.md index 57176ca..20d1e66 100644 --- a/README.md +++ b/README.md @@ -71,20 +71,21 @@ npx expo run:android ### Plugin Configuration Options -| Option | Type | Required | Description | -| ------------------------- | -------- | -------- | ------------------------------------------------------------------- | -| `iosApiKey` | string | Yes | iOS API key from mParticle dashboard | -| `iosApiSecret` | string | Yes | iOS API secret from mParticle dashboard | -| `androidApiKey` | string | Yes | Android API key from mParticle dashboard | -| `androidApiSecret` | string | Yes | Android API secret from mParticle dashboard | -| `logLevel` | string | No | Log level: `'none'`, `'error'`, `'warning'`, `'debug'`, `'verbose'` | -| `environment` | string | No | Environment: `'development'`, `'production'`, `'autoDetect'` | -| `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`) | +| Option | Type | Required | Description | +| ------------------------- | -------- | -------- | --------------------------------------------------------------------------------------------- | +| `iosApiKey` | string | Yes | iOS API key from mParticle dashboard | +| `iosApiSecret` | string | Yes | iOS API secret from mParticle dashboard | +| `androidApiKey` | string | Yes | Android API key from mParticle dashboard | +| `androidApiSecret` | string | Yes | Android API secret from mParticle dashboard | +| `logLevel` | string | No | Log level: `'none'`, `'error'`, `'warning'`, `'debug'`, `'verbose'` | +| `environment` | string | No | Environment: `'development'`, `'production'`, `'autoDetect'` | +| `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 | +| `pinningDisabled` | boolean | No | Disable SSL pinning (`MPNetworkOptions` on iOS; `setPinningDisabledInDevelopment` on 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`) | ### Example with Kits @@ -123,14 +124,14 @@ For global CNAME setup, add the optional shared `customBaseUrl` setting: **iOS:** - Adds mParticle SDK initialization to `AppDelegate` (supports both Swift and Objective-C) -- Sets `MPNetworkOptions.customBaseURL` before startup when `customBaseUrl` is configured +- Sets `MPNetworkOptions` (`customBaseURL` and/or `pinningDisabled`) before startup when those plugin options are 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 +- Sets `NetworkOptions` (`setCustomBaseURL` and/or `setPinningDisabledInDevelopment`) before startup when those plugin options are configured - Adds specified kit Maven dependencies to `build.gradle` ### Version Support diff --git a/plugin/src/withMParticle.ts b/plugin/src/withMParticle.ts index ae43c5f..e959437 100644 --- a/plugin/src/withMParticle.ts +++ b/plugin/src/withMParticle.ts @@ -63,6 +63,15 @@ export interface MParticlePluginProps { */ customBaseUrl?: string; + /** + * When true, disables SSL certificate pinning for mParticle network traffic. + * + * - **iOS:** Sets `MPNetworkOptions.pinningDisabled` before startup. + * - **Android:** Sets `NetworkOptions.Builder.setPinningDisabledInDevelopment(true)` + * (mParticle's Android API for proxy/debug builds; see Android SDK docs). + */ + pinningDisabled?: boolean; + /** * Android kit artifact names to include (version auto-detected from core SDK) * @example ['android-rokt-kit', 'android-amplitude-kit'] diff --git a/plugin/src/withMParticleAndroid.ts b/plugin/src/withMParticleAndroid.ts index 5c43b60..82bf3ef 100644 --- a/plugin/src/withMParticleAndroid.ts +++ b/plugin/src/withMParticleAndroid.ts @@ -10,6 +10,10 @@ import { getCustomBaseUrl } from './customBaseUrl'; // Tag used for mergeContents to identify code blocks added by this plugin const MPARTICLE_TAG = 'react-native-mparticle'; +function shouldEmitNetworkOptions(props: MParticlePluginProps): boolean { + return Boolean(getCustomBaseUrl(props) || props.pinningDisabled === true); +} + /** * Get the mParticle log level string for Android */ @@ -64,6 +68,7 @@ function generateKotlinInitCode(props: MParticlePluginProps): string { dataPlanVersion, } = props; const customBaseUrl = getCustomBaseUrl(props); + const pinningDisabled = props.pinningDisabled === true; const lines: string[] = [ '// mParticle SDK initialization', @@ -86,12 +91,17 @@ function generateKotlinInitCode(props: MParticlePluginProps): string { lines.push(` .dataplan("${dataPlanId}"${versionParam})`); } - if (customBaseUrl) { + if (shouldEmitNetworkOptions(props)) { lines.push(' .networkOptions('); lines.push(' NetworkOptions.builder()'); - lines.push( - ` .setCustomBaseURL(${JSON.stringify(customBaseUrl)})` - ); + if (customBaseUrl) { + lines.push( + ` .setCustomBaseURL(${JSON.stringify(customBaseUrl)})` + ); + } + if (pinningDisabled) { + lines.push(' .setPinningDisabledInDevelopment(true)'); + } lines.push(' .build()'); lines.push(' )'); } @@ -120,6 +130,7 @@ function generateJavaInitCode(props: MParticlePluginProps): string { dataPlanVersion, } = props; const customBaseUrl = getCustomBaseUrl(props); + const pinningDisabled = props.pinningDisabled === true; const lines: string[] = [ '// mParticle SDK initialization', @@ -142,12 +153,17 @@ function generateJavaInitCode(props: MParticlePluginProps): string { lines.push(` .dataplan("${dataPlanId}"${versionParam})`); } - if (customBaseUrl) { + if (shouldEmitNetworkOptions(props)) { lines.push(' .networkOptions('); lines.push(' NetworkOptions.builder()'); - lines.push( - ` .setCustomBaseURL(${JSON.stringify(customBaseUrl)})` - ); + if (customBaseUrl) { + lines.push( + ` .setCustomBaseURL(${JSON.stringify(customBaseUrl)})` + ); + } + if (pinningDisabled) { + lines.push(' .setPinningDisabledInDevelopment(true)'); + } lines.push(' .build()'); lines.push(' )'); } @@ -173,7 +189,7 @@ function getKotlinImports(props: MParticlePluginProps): string { 'import com.mparticle.identity.IdentityApiRequest', ]; - if (getCustomBaseUrl(props)) { + if (shouldEmitNetworkOptions(props)) { imports.push('import com.mparticle.networking.NetworkOptions'); } @@ -190,7 +206,7 @@ function getJavaImports(props: MParticlePluginProps): string { 'import com.mparticle.identity.IdentityApiRequest;', ]; - if (getCustomBaseUrl(props)) { + if (shouldEmitNetworkOptions(props)) { imports.push('import com.mparticle.networking.NetworkOptions;'); } diff --git a/plugin/src/withMParticleIOS.ts b/plugin/src/withMParticleIOS.ts index 5aa7f40..38918dc 100644 --- a/plugin/src/withMParticleIOS.ts +++ b/plugin/src/withMParticleIOS.ts @@ -136,13 +136,19 @@ function generateSwiftInitCode(props: MParticlePluginProps): string { } const customBaseUrl = getCustomBaseUrl(props); - if (customBaseUrl) { + const pinningDisabled = props.pinningDisabled === true; + if (customBaseUrl || pinningDisabled) { lines.push('let networkOptions = MPNetworkOptions()'); - lines.push( - `networkOptions.customBaseURL = URL(string: ${JSON.stringify( - customBaseUrl - )})` - ); + if (customBaseUrl) { + lines.push( + `networkOptions.customBaseURL = URL(string: ${JSON.stringify( + customBaseUrl + )})` + ); + } + if (pinningDisabled) { + lines.push('networkOptions.pinningDisabled = true'); + } lines.push('mParticleOptions.networkOptions = networkOptions'); } @@ -196,15 +202,21 @@ function generateObjcInitCode(props: MParticlePluginProps): string { } const customBaseUrl = getCustomBaseUrl(props); - if (customBaseUrl) { + const pinningDisabled = props.pinningDisabled === true; + if (customBaseUrl || pinningDisabled) { lines.push( 'MPNetworkOptions *networkOptions = [[MPNetworkOptions alloc] init];' ); - lines.push( - `networkOptions.customBaseURL = [NSURL URLWithString:@${JSON.stringify( - customBaseUrl - )}];` - ); + if (customBaseUrl) { + lines.push( + `networkOptions.customBaseURL = [NSURL URLWithString:@${JSON.stringify( + customBaseUrl + )}];` + ); + } + if (pinningDisabled) { + lines.push('networkOptions.pinningDisabled = YES;'); + } lines.push('mParticleOptions.networkOptions = networkOptions;'); } diff --git a/sample/README.md b/sample/README.md index f1154a4..6aa87ec 100644 --- a/sample/README.md +++ b/sample/README.md @@ -64,11 +64,7 @@ cd sample/ios pod install ``` -The sample Podfile pins the standard Rokt kit with: - -```ruby -pod 'mParticle-Rokt', '~> 9.2' -``` +The sample Podfile pins `mParticle-Rokt` `~> 9.2`, **`Rokt-Widget` `5.2.0`**, and **`DcuiSchema` `2.7.0`**. Rokt’s pods allow `DcuiSchema` to float within `~> 2.6`; when CocoaPods resolves **2.8.0+**, the schema adds `image` styling that can desync from the `RoktUXHelper` sources in that widget line and break Swift compile (`StyleTransformer` / `BaseStyles`). Bump these pins together when you adopt a newer Rokt iOS stack. The sample Android app pins `com.mparticle:android-core` and `com.mparticle:android-rokt-kit` to `5.79.0` so the Rokt session APIs and diff --git a/sample/ios/Podfile b/sample/ios/Podfile index 137d49d..5063ebf 100644 --- a/sample/ios/Podfile +++ b/sample/ios/Podfile @@ -34,6 +34,12 @@ target 'MParticleSample' do :app_path => "#{Pod::Config.instance.installation_root}/.." ) pod 'mParticle-Rokt', '~> 9.2' + # Exact Rokt-Widget keeps RoktUXHelper on the same version (Rokt pins them together). + pod 'Rokt-Widget', '5.2.0' + # RoktUXHelper declares `DcuiSchema` `~> 2.6`, which otherwise floats to 2.8.x; that + # schema release adds `image` styling APIs that the shipped RoktUXHelper 5.2.x Swift + # does not pass through yet (CI: StyleTransformer.swift / BaseStyles "missing image"). + pod 'DcuiSchema', '2.7.0' target 'MParticleSampleTests' do inherit! :complete