From 209aead84ac0e3a27e04f1199082035d608cd72a Mon Sep 17 00:00:00 2001 From: Joao Dordio Date: Wed, 4 Feb 2026 17:54:58 +0000 Subject: [PATCH 1/2] WIP --- Iterable-React-Native-SDK.podspec | 2 +- example/ios/Podfile | 10 ++-- .../project.pbxproj | 48 +++++++++++++------ ios/RNIterableAPI/ReactIterableAPI.swift | 4 +- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/Iterable-React-Native-SDK.podspec b/Iterable-React-Native-SDK.podspec index e85f0bf44..9266eb7c2 100644 --- a/Iterable-React-Native-SDK.podspec +++ b/Iterable-React-Native-SDK.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.private_header_files = "ios/**/*.h" # Load Iterables iOS SDK as a dependency - s.dependency "Iterable-iOS-SDK", "6.6.3" + s.dependency "Iterable-iOS-SDK", "6.6.5" # Basic Swift support s.pod_target_xcconfig = { diff --git a/example/ios/Podfile b/example/ios/Podfile index 43e1952fb..0ac37d977 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -10,16 +10,14 @@ require Pod::Executable.execute_command('node', ['-p', platform :ios, min_ios_version_supported prepare_react_native_project! -linkage = ENV['USE_FRAMEWORKS'] -if linkage != nil - Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green - # IMPORTANT: This is needed to use the Swift code from Iterable-iOS-SDK in the RNIterableAPI module - use_frameworks! :linkage => :dynamic -end +use_frameworks! :linkage => :static target 'ReactNativeSdkExample' do config = use_native_modules! + # Override iOS SDK with local path for testing + pod 'Iterable-iOS-SDK', :git => 'https://github.com/Iterable/iterable-swift-sdk', :branch => 'fix/SDK-290-Export-iOS-sdk-header-file' + use_react_native!( :path => config[:reactNativePath], # An absolute path to your application root. diff --git a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj index 766c9b681..e5afa69ff 100644 --- a/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeSdkExample.xcodeproj/project.pbxproj @@ -14,8 +14,8 @@ 77E3B5782EA71A4B001449CE /* JwtTokenModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = 77E3B5752EA71A4B001449CE /* JwtTokenModule.mm */; }; 77E3B5792EA71A4B001449CE /* JwtTokenModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E3B5762EA71A4B001449CE /* JwtTokenModule.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - 81F6A9EA0E1CCC1AD730C5D9 /* libPods-ReactNativeSdkExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 56080B9DEED42A97AD1B3D5C /* libPods-ReactNativeSdkExample.a */; }; A3A40C20801B8F02005FA4C0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */; }; + AC01E155D54B61A112A941D0 /* Pods_ReactNativeSdkExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5678078CD07EAA1976090CF7 /* Pods_ReactNativeSdkExample.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -38,7 +38,7 @@ 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 1FC6B09E65A7BD9F6864C5D8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeSdkExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 3A95ED4563D4389808EDEA8F /* Pods-ReactNativeSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample.debug.xcconfig"; sourceTree = ""; }; - 56080B9DEED42A97AD1B3D5C /* libPods-ReactNativeSdkExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeSdkExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5678078CD07EAA1976090CF7 /* Pods_ReactNativeSdkExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReactNativeSdkExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 779227312DFA3FB500D69EC0 /* ReactNativeSdkExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExample-Bridging-Header.h"; sourceTree = ""; }; 779227322DFA3FB500D69EC0 /* ReactNativeSdkExampleTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactNativeSdkExampleTests-Bridging-Header.h"; sourceTree = ""; }; 779227332DFA3FB500D69EC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ReactNativeSdkExample/AppDelegate.swift; sourceTree = ""; }; @@ -62,7 +62,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 81F6A9EA0E1CCC1AD730C5D9 /* libPods-ReactNativeSdkExample.a in Frameworks */, + AC01E155D54B61A112A941D0 /* Pods_ReactNativeSdkExample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -105,7 +105,7 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 56080B9DEED42A97AD1B3D5C /* libPods-ReactNativeSdkExample.a */, + 5678078CD07EAA1976090CF7 /* Pods_ReactNativeSdkExample.framework */, ); name = Frameworks; sourceTree = ""; @@ -277,14 +277,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-frameworks.sh\"\n"; @@ -320,14 +316,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeSdkExample/Pods-ReactNativeSdkExample-resources.sh\"\n"; @@ -533,6 +525,17 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", + ); IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD = ""; LDPLUSPLUS = ""; @@ -555,7 +558,10 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -607,6 +613,17 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", + ); IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD = ""; LDPLUSPLUS = ""; @@ -628,7 +645,10 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/ios/RNIterableAPI/ReactIterableAPI.swift b/ios/RNIterableAPI/ReactIterableAPI.swift index 80c0a9cab..e9302401c 100644 --- a/ios/RNIterableAPI/ReactIterableAPI.swift +++ b/ios/RNIterableAPI/ReactIterableAPI.swift @@ -425,7 +425,9 @@ import React unsubscribedMessageTypeIds: unsubscribedMessageTypeIds, subscribedMessageTypeIds: subscribedMessageTypeIds, campaignId: finalCampaignId, - templateId: finalTemplateId) + templateId: finalTemplateId, + onSuccess: nil, + onFailure: nil) } @objc(setReadForMessage:read:) From 28fe8553eeced776994ed5d7af323b7e9e535a39 Mon Sep 17 00:00:00 2001 From: Joao Dordio Date: Wed, 4 Feb 2026 22:02:45 +0000 Subject: [PATCH 2/2] Added Expo config plugin and fixed static linkage Swift header import --- Iterable-React-Native-SDK.podspec | 3 + TESTING_GUIDE.md | 367 ++++++++++++++++++++++++++ app.plugin.js | 4 + ios/RNIterableAPI/RNIterableAPI.mm | 4 + package.json | 20 +- plugin/__tests__/plugin.test.ts | 249 +++++++++++++++++ plugin/jest.config.js | 12 + plugin/src/index.ts | 49 ++++ plugin/src/withIterableAutolinking.ts | 41 +++ plugin/src/withIterablePodfile.ts | 145 ++++++++++ plugin/tsconfig.json | 20 ++ tsconfig.build.json | 2 +- tsconfig.json | 3 +- yarn.lock | 353 ++++++++++++++++++++++++- 14 files changed, 1260 insertions(+), 12 deletions(-) create mode 100644 TESTING_GUIDE.md create mode 100644 app.plugin.js create mode 100644 plugin/__tests__/plugin.test.ts create mode 100644 plugin/jest.config.js create mode 100644 plugin/src/index.ts create mode 100644 plugin/src/withIterableAutolinking.ts create mode 100644 plugin/src/withIterablePodfile.ts create mode 100644 plugin/tsconfig.json diff --git a/Iterable-React-Native-SDK.podspec b/Iterable-React-Native-SDK.podspec index 9266eb7c2..75385786a 100644 --- a/Iterable-React-Native-SDK.podspec +++ b/Iterable-React-Native-SDK.podspec @@ -24,7 +24,10 @@ Pod::Spec.new do |s| 'DEFINES_MODULE' => 'YES', 'CLANG_ENABLE_MODULES' => 'YES', 'SWIFT_VERSION' => '5.0', + 'SWIFT_OBJC_INTERFACE_HEADER_NAME' => 'Iterable_React_Native_SDK-Swift.h', "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), + # Include paths to generated Swift header for static framework builds + 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Iterable-React-Native-SDK/Iterable_React_Native_SDK.framework/Headers" "${PODS_TARGET_SRCROOT}/ios/RNIterableAPI"', } install_modules_dependencies(s) diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md new file mode 100644 index 000000000..cd4116e4e --- /dev/null +++ b/TESTING_GUIDE.md @@ -0,0 +1,367 @@ +# Testing Guide: Iterable iOS Static Linkage Fix + +This guide walks you through testing the iOS static linkage changes and the new Expo config plugin. + +--- + +## Prerequisites + +Before starting, ensure you have the following installed: + +```bash +# Check Node.js (v18+ recommended) +node --version + +# Check Yarn +yarn --version + +# Check Ruby (for CocoaPods) +ruby --version + +# Check CocoaPods +pod --version + +# Check Xcode CLI tools +xcode-select -p +``` + +If missing any, install them: + +```bash +# Install Node.js via Homebrew +brew install node + +# Install Yarn +npm install -g yarn + +# Install CocoaPods +sudo gem install cocoapods + +# Install Xcode CLI tools (if not installed) +xcode-select --install +``` + +--- + +## Part 1: Test the React Native SDK Example App + +### Step 1: Navigate to the React Native SDK + +```bash +cd /Users/joao.dordio/dev/repos/codereview/ReactNativeStatic/react-native-sdk +``` + +### Step 2: Install Dependencies + +```bash +yarn install +``` + +### Step 3: Build the SDK (includes plugin) + +```bash +yarn build +``` + +This will: +- Build the React Native SDK with `react-native-builder-bob` +- Compile the Expo config plugin TypeScript to `plugin/build/` + +### Step 4: Verify Plugin Build + +```bash +ls -la plugin/build/ +``` + +You should see: +``` +index.js +index.d.ts +withIterableAutolinking.js +withIterableAutolinking.d.ts +withIterablePodfile.js +withIterablePodfile.d.ts +``` + +### Step 5: Navigate to Example App + +```bash +cd example +``` + +### Step 6: Install Example App Dependencies + +```bash +yarn install +``` + +### Step 7: Install iOS Pods + +```bash +cd ios +pod install --repo-update +cd .. +``` + +**Expected output**: Pod installation completes without errors. Look for: +- `Iterable-iOS-SDK` being installed +- `Iterable-React-Native-SDK` being installed +- No "transitive dependencies" errors + +### Step 8: Build and Run iOS App + +**Option A: Using Yarn (recommended)** +```bash +yarn ios +``` + +**Option B: Using Xcode** +```bash +open ios/ReactNativeSdkExample.xcworkspace +``` +Then in Xcode: +1. Select a simulator (e.g., iPhone 15) +2. Press `Cmd + B` to build +3. Press `Cmd + R` to run + +### Step 9: Verify Build Success + +The build should complete without these errors: +- ❌ `'Iterable_React_Native_SDK-Swift.h' file not found` +- ❌ `transitive dependencies include statically linked binaries` +- ❌ Duplicate pod declaration errors + +If the app launches in the simulator, the fix is working. + +--- + +## Part 2: Test the Expo Config Plugin + +### Step 1: Run Plugin Unit Tests + +```bash +# From the react-native-sdk root +cd /Users/joao.dordio/dev/repos/codereview/ReactNativeStatic/react-native-sdk + +yarn test:plugin +``` + +**Expected output**: All tests pass: +``` +PASS __tests__/plugin.test.ts + withIterableAutolinking + disableAutolinking + ✓ should add @iterable/react-native-sdk to autolinking exclude list + ✓ should preserve existing exclude entries + ✓ should be idempotent - not duplicate entries + withIterablePodfile + ensureStaticLinkage + ✓ should keep existing static linkage unchanged + ✓ should convert dynamic linkage to static + ✓ should convert bare use_frameworks! to static linkage + injectIterablePods + ✓ should inject Iterable pods after use_expo_modules! + ... +``` + +### Step 2: Create a Test Expo App (Optional - Full Integration Test) + +```bash +# Create a new directory for testing +cd /tmp +npx create-expo-app@latest iterable-expo-test --template blank +cd iterable-expo-test +``` + +### Step 3: Link Local SDK to Test App + +```bash +# Add the local SDK as a dependency +yarn add /Users/joao.dordio/dev/repos/codereview/ReactNativeStatic/react-native-sdk +``` + +### Step 4: Configure the Plugin + +Edit `app.json`: +```json +{ + "expo": { + "name": "iterable-expo-test", + "slug": "iterable-expo-test", + "plugins": [ + ["@iterable/react-native-sdk", { "enableNotificationExtension": true }] + ] + } +} +``` + +### Step 5: Run Expo Prebuild + +```bash +npx expo prebuild --clean +``` + +### Step 6: Verify Podfile Transformation + +```bash +cat ios/Podfile +``` + +**Verify these entries exist:** + +1. Static linkage is set: + ```ruby + use_frameworks! :linkage => :static + ``` + +2. Iterable pods with dynamic override: + ```ruby + # Iterable SDK (Expo Managed Workflow) + pod 'Iterable-React-Native-SDK', :path => '../node_modules/@iterable/react-native-sdk', :linkage => :dynamic + pod 'Iterable-iOS-SDK', :linkage => :dynamic + ``` + +3. Notification extension target (if enabled): + ```ruby + target 'IterableNotifications' do + use_frameworks! :linkage => :dynamic + pod 'Iterable-iOS-AppExtensions' + end + ``` + +### Step 7: Test Idempotency + +```bash +# Run prebuild again without --clean +npx expo prebuild + +# Check for duplicates +grep -c "Iterable-React-Native-SDK" ios/Podfile +``` + +**Expected**: Should output `1` (not duplicated) + +### Step 8: Build the Expo App + +```bash +npx expo run:ios +``` + +--- + +## Part 3: Verify iOS SDK Header Fix + +### Step 1: Navigate to iOS SDK + +```bash +cd /Users/joao.dordio/dev/repos/codereview/ReactNativeStatic/iterable-swift-sdk +``` + +### Step 2: Check Header Files Have C++ Guards + +```bash +# Check IterableSDK.h +grep -A2 "__cplusplus" swift-sdk/IterableSDK.h +``` + +**Expected output:** +```c +#ifdef __cplusplus +extern "C" { +#endif +``` + +```bash +# Check IterableAppExtensions.h +grep -A2 "__cplusplus" notification-extension/IterableAppExtensions.h +``` + +**Expected output:** +```c +#ifdef __cplusplus +extern "C" { +#endif +``` + +### Step 3: Check Resource Bundle Name + +```bash +grep "resource_bundles" Iterable-iOS-SDK.podspec +``` + +**Expected output:** +```ruby +s.resource_bundles = {'IterableSDKResources' => 'swift-sdk/Resources/**/*.{storyboard,xib,xcassets,xcdatamodeld}' } +``` + +--- + +## Troubleshooting + +### Error: `'Iterable_React_Native_SDK-Swift.h' file not found` + +This means the C++ linkage fix isn't being picked up. Ensure: +1. You're using the correct iOS SDK branch: `fix/SDK-290-Export-iOS-sdk-header-file` +2. Run `pod cache clean --all` and reinstall pods + +### Error: Pod install fails with dependency errors + +```bash +cd ios +pod deintegrate +pod cache clean --all +pod install --repo-update +``` + +### Error: Plugin build fails + +```bash +# Rebuild the plugin +cd /Users/joao.dordio/dev/repos/codereview/ReactNativeStatic/react-native-sdk +rm -rf plugin/build +yarn build:plugin +``` + +### Error: Tests fail with module not found + +```bash +# Install test dependencies +cd plugin +yarn add -D ts-jest @types/jest +``` + +--- + +## Quick Commands Reference + +```bash +# Build everything +cd /Users/joao.dordio/dev/repos/codereview/ReactNativeStatic/react-native-sdk +yarn install && yarn build + +# Run plugin tests +yarn test:plugin + +# Run example app +cd example && yarn ios + +# Clean rebuild +yarn clean && yarn build + +# Check git diff of your changes +cd /Users/joao.dordio/dev/repos/codereview/ReactNativeStatic/react-native-sdk +git diff --stat +``` + +--- + +## Success Criteria + +You've successfully tested the changes if: + +1. ✅ `yarn build` completes without errors +2. ✅ `yarn test:plugin` - All tests pass +3. ✅ Example app builds and runs on iOS simulator +4. ✅ No Swift header file not found errors +5. ✅ Expo prebuild generates correct Podfile entries +6. ✅ Running prebuild twice doesn't duplicate entries diff --git a/app.plugin.js b/app.plugin.js new file mode 100644 index 000000000..b486c2981 --- /dev/null +++ b/app.plugin.js @@ -0,0 +1,4 @@ +// This file is the entry point for the Expo config plugin. +// It re-exports the compiled plugin from the plugin directory. + +module.exports = require('./plugin/build'); diff --git a/ios/RNIterableAPI/RNIterableAPI.mm b/ios/RNIterableAPI/RNIterableAPI.mm index 91955f797..11e0809da 100644 --- a/ios/RNIterableAPI/RNIterableAPI.mm +++ b/ios/RNIterableAPI/RNIterableAPI.mm @@ -17,7 +17,11 @@ typedef NS_ENUM(NSInteger, InAppShowResponse) { skip = 1, }; +#if __has_include() +#import +#else #import "Iterable_React_Native_SDK-Swift.h" +#endif @interface RNIterableAPI () @end diff --git a/package.json b/package.json index f6ca88c1f..054f52b1a 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "android", "ios", "cpp", + "plugin/build", + "plugin/src", + "app.plugin.js", "*.podspec", "scripts/autoCreatePackageInfo.js", "scripts/createPackageInfoFile.js", @@ -36,14 +39,16 @@ "add_build_info": "node scripts/autoCreatePackageInfo.js", "example": "yarn workspace @iterable/react-native-sdk-example", "test": "jest", + "test:plugin": "cd plugin && jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "typecheck": "tsc", "lint": "eslint \"**/*.{js,ts,tsx}\"", "docs": "typedoc", - "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", + "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib plugin/build", "prebuild": "yarn add_build_info", - "build": "yarn add_build_info && yarn bob build", + "build": "yarn add_build_info && yarn bob build && yarn build:plugin", + "build:plugin": "cd plugin && tsc", "prepare": "yarn build", "release": "release-it" }, @@ -69,6 +74,7 @@ "devDependencies": { "@commitlint/config-conventional": "^19.6.0", "@evilmartians/lefthook": "^1.5.0", + "@expo/config-plugins": "^9.0.0", "@react-native-community/cli": "18.0.0", "@react-native/babel-preset": "0.79.3", "@react-native/eslint-config": "0.79.3", @@ -79,6 +85,7 @@ "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "^13.3.3", "@types/jest": "^29.5.5", + "@types/node": "^20.0.0", "@types/react": "^19.0.0", "@typescript-eslint/eslint-plugin": "^8.13.0", "@typescript-eslint/parser": "^8.13.0", @@ -101,6 +108,7 @@ "react-native-webview": "^13.14.1", "react-test-renderer": "19.0.0", "release-it": "^17.10.0", + "ts-jest": "^29.1.0", "turbo": "^1.10.7", "typedoc": "^0.28.13", "typedoc-plugin-coverage": "^3.3.0", @@ -118,10 +126,18 @@ "react-native-webview": "*" }, "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, "expo": { "optional": true } }, + "expo": { + "plugins": [ + "./app.plugin.js" + ] + }, "sideEffects": false, "workspaces": [ "example" diff --git a/plugin/__tests__/plugin.test.ts b/plugin/__tests__/plugin.test.ts new file mode 100644 index 000000000..7d34868ad --- /dev/null +++ b/plugin/__tests__/plugin.test.ts @@ -0,0 +1,249 @@ +import { disableAutolinking } from '../src/withIterableAutolinking'; +import { + ensureStaticLinkage, + injectIterablePods, + injectNotificationExtensionTarget, +} from '../src/withIterablePodfile'; + +// Config type for testing +interface TestConfig { + name: string; + slug: string; + autolinking?: { + ios?: { + exclude?: string[]; + }; + }; +} + +describe('withIterableAutolinking', () => { + describe('disableAutolinking', () => { + it('should add @iterable/react-native-sdk to autolinking exclude list', () => { + const config: TestConfig = { name: 'test', slug: 'test' }; + const result = disableAutolinking(config); + + expect(result.autolinking?.ios?.exclude).toContain('@iterable/react-native-sdk'); + }); + + it('should preserve existing exclude entries', () => { + const config: TestConfig = { + name: 'test', + slug: 'test', + autolinking: { + ios: { + exclude: ['some-other-package'], + }, + }, + }; + const result = disableAutolinking(config); + + expect(result.autolinking?.ios?.exclude).toContain('some-other-package'); + expect(result.autolinking?.ios?.exclude).toContain('@iterable/react-native-sdk'); + }); + + it('should be idempotent - not duplicate entries', () => { + const config: TestConfig = { name: 'test', slug: 'test' }; + + // Run twice + let result = disableAutolinking(config); + result = disableAutolinking(result); + + const excludeList = result.autolinking?.ios?.exclude ?? []; + const iterableEntries = excludeList.filter((e) => e === '@iterable/react-native-sdk'); + expect(iterableEntries.length).toBe(1); + }); + }); +}); + +describe('withIterablePodfile', () => { + describe('ensureStaticLinkage', () => { + it('should keep existing static linkage unchanged', () => { + const podfile = ` +platform :ios, '13.0' +use_frameworks! :linkage => :static + +target 'MyApp' do + use_native_modules! +end +`; + const result = ensureStaticLinkage(podfile); + expect(result).toBe(podfile); + }); + + it('should convert dynamic linkage to static', () => { + const podfile = ` +platform :ios, '13.0' +use_frameworks! :linkage => :dynamic + +target 'MyApp' do + use_native_modules! +end +`; + const result = ensureStaticLinkage(podfile); + expect(result).toContain('use_frameworks! :linkage => :static'); + expect(result).not.toContain(':dynamic'); + }); + + it('should convert bare use_frameworks! to static linkage', () => { + const podfile = ` +platform :ios, '13.0' +use_frameworks! + +target 'MyApp' do + use_native_modules! +end +`; + const result = ensureStaticLinkage(podfile); + expect(result).toContain('use_frameworks! :linkage => :static'); + }); + }); + + describe('injectIterablePods', () => { + const baseExpoPodfile = ` +platform :ios, '13.0' +use_frameworks! :linkage => :static + +target 'MyApp' do + use_native_modules! + use_expo_modules! + use_react_native!( + :path => config[:reactNativePath] + ) +end +`; + + it('should inject Iterable pods after use_expo_modules!', () => { + const result = injectIterablePods(baseExpoPodfile); + + expect(result).toContain("pod 'Iterable-React-Native-SDK'"); + expect(result).toContain("pod 'Iterable-iOS-SDK'"); + expect(result).toContain(':linkage => :dynamic'); + expect(result).toContain('# Iterable SDK (Expo Managed Workflow)'); + }); + + it('should inject after use_react_native! if use_expo_modules! is not present', () => { + const podfile = ` +platform :ios, '13.0' +use_frameworks! :linkage => :static + +target 'MyApp' do + use_native_modules! + use_react_native!( + :path => config[:reactNativePath] + ) +end +`; + const result = injectIterablePods(podfile); + + expect(result).toContain("pod 'Iterable-React-Native-SDK'"); + expect(result).toContain("pod 'Iterable-iOS-SDK'"); + }); + + it('should inject after use_native_modules! as last fallback', () => { + const podfile = ` +platform :ios, '13.0' +use_frameworks! :linkage => :static + +target 'MyApp' do + use_native_modules! +end +`; + const result = injectIterablePods(podfile); + + expect(result).toContain("pod 'Iterable-React-Native-SDK'"); + }); + + it('should be idempotent - not duplicate pods', () => { + let result = injectIterablePods(baseExpoPodfile); + result = injectIterablePods(result); + + const matches = result.match(/pod 'Iterable-React-Native-SDK'/g) ?? []; + expect(matches.length).toBe(1); + }); + + it('should include correct path for local pod', () => { + const result = injectIterablePods(baseExpoPodfile); + + expect(result).toContain(":path => '../node_modules/@iterable/react-native-sdk'"); + }); + }); + + describe('injectNotificationExtensionTarget', () => { + const basePodfile = ` +platform :ios, '13.0' +use_frameworks! :linkage => :static + +target 'MyApp' do + use_native_modules! +end +`; + + it('should add IterableNotifications target', () => { + const result = injectNotificationExtensionTarget(basePodfile, 'IterableNotifications'); + + expect(result).toContain("target 'IterableNotifications' do"); + expect(result).toContain('use_frameworks! :linkage => :dynamic'); + expect(result).toContain("pod 'Iterable-iOS-AppExtensions'"); + }); + + it('should use custom target name when provided', () => { + const result = injectNotificationExtensionTarget(basePodfile, 'MyCustomNotifications'); + + expect(result).toContain("target 'MyCustomNotifications' do"); + }); + + it('should be idempotent - not duplicate target', () => { + let result = injectNotificationExtensionTarget(basePodfile, 'IterableNotifications'); + result = injectNotificationExtensionTarget(result, 'IterableNotifications'); + + const matches = result.match(/target 'IterableNotifications'/g) ?? []; + expect(matches.length).toBe(1); + }); + }); +}); + +describe('Full Podfile Transformation', () => { + it('should correctly transform a typical Expo Podfile', () => { + const expoPodfile = ` +require_relative '../node_modules/expo/scripts/autolinking' +require_relative '../node_modules/react-native/scripts/react_native_pods' + +platform :ios, '13.4' +install! 'cocoapods', :deterministic_uuids => false + +prepare_react_native_project! + +target 'MyExpoApp' do + use_expo_modules! + + config = use_native_modules! + use_frameworks! :linkage => :static + + use_react_native!( + :path => config[:reactNativePath], + :hermes_enabled => true + ) + + post_install do |installer| + react_native_post_install(installer) + end +end +`; + + let result = ensureStaticLinkage(expoPodfile); + result = injectIterablePods(result); + result = injectNotificationExtensionTarget(result, 'IterableNotifications'); + + // Verify static linkage is preserved + expect(result).toContain('use_frameworks! :linkage => :static'); + + // Verify Iterable pods are injected with dynamic override + expect(result).toContain("pod 'Iterable-React-Native-SDK'"); + expect(result).toContain("pod 'Iterable-iOS-SDK'"); + expect(result).toContain(':linkage => :dynamic'); + + // Verify notification extension target is added + expect(result).toContain("target 'IterableNotifications'"); + expect(result).toContain("pod 'Iterable-iOS-AppExtensions'"); + }); +}); diff --git a/plugin/jest.config.js b/plugin/jest.config.js new file mode 100644 index 000000000..dcb55d2dc --- /dev/null +++ b/plugin/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + rootDir: '.', + testMatch: ['**/__tests__/**/*.test.ts'], + moduleFileExtensions: ['ts', 'js', 'json'], + collectCoverageFrom: ['src/**/*.ts'], + coverageDirectory: 'coverage', + transform: { + '^.+\\.ts$': ['ts-jest', { tsconfig: 'tsconfig.json' }], + }, +}; diff --git a/plugin/src/index.ts b/plugin/src/index.ts new file mode 100644 index 000000000..c8ef64cbd --- /dev/null +++ b/plugin/src/index.ts @@ -0,0 +1,49 @@ +import { ConfigPlugin, createRunOncePlugin, withPlugins } from '@expo/config-plugins'; +import { withIterableAutolinkingDisable } from './withIterableAutolinking'; +import { withIterablePodfile } from './withIterablePodfile'; + +const pkg = require('../../package.json'); + +/** + * Expo Config Plugin for Iterable React Native SDK + * + * This plugin handles the complex iOS build configuration required for + * Iterable SDK integration with Expo managed workflows: + * + * 1. Disables iOS autolinking for @iterable/react-native-sdk to prevent + * duplicate Podfile entries + * + * 2. Adds dynamic framework linkage for Iterable pods while keeping + * React Native statically linked (Expo requirement) + * + * 3. Optionally adds IterableNotifications target for push notification + * service extension + */ + +export interface IterablePluginProps { + /** + * Whether to add the IterableNotifications target for push notification + * service extension. Defaults to false. + */ + enableNotificationExtension?: boolean; + + /** + * Custom name for the notification extension target. + * Defaults to 'IterableNotifications'. + */ + notificationExtensionTargetName?: string; +} + +const withIterable: ConfigPlugin = (config, props) => { + const pluginProps: IterablePluginProps = props ?? {}; + + return withPlugins(config, [ + [withIterableAutolinkingDisable, pluginProps], + [withIterablePodfile, pluginProps], + ]); +}; + +export default createRunOncePlugin(withIterable, pkg.name, pkg.version); + +export { withIterableAutolinkingDisable } from './withIterableAutolinking'; +export { withIterablePodfile } from './withIterablePodfile'; diff --git a/plugin/src/withIterableAutolinking.ts b/plugin/src/withIterableAutolinking.ts new file mode 100644 index 000000000..765308c93 --- /dev/null +++ b/plugin/src/withIterableAutolinking.ts @@ -0,0 +1,41 @@ +import { ConfigPlugin } from '@expo/config-plugins'; +import { IterablePluginProps } from './index'; + +/** + * Disables iOS autolinking for @iterable/react-native-sdk. + * + * This prevents duplicate Podfile entries since we manually inject + * the Iterable pods with custom linkage configuration. + */ +export const withIterableAutolinkingDisable: ConfigPlugin = ( + config, + _props +) => { + return disableAutolinking(config); +}; + +/** + * Modifies the Expo config to exclude @iterable/react-native-sdk from + * iOS autolinking. + * + * Note: autolinking is not in the base ExpoConfig type but is supported at runtime. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function disableAutolinking(config: T): T { + // Cast to any for dynamic property access (autolinking not in ExpoConfig types) + const cfg = config as any; + + // Initialize autolinking config if not present + cfg.autolinking = cfg.autolinking ?? {}; + cfg.autolinking.ios = cfg.autolinking.ios ?? {}; + cfg.autolinking.ios.exclude = cfg.autolinking.ios.exclude ?? []; + + const sdkPackageName = '@iterable/react-native-sdk'; + + // Only add if not already excluded (idempotency) + if (!cfg.autolinking.ios.exclude.includes(sdkPackageName)) { + cfg.autolinking.ios.exclude.push(sdkPackageName); + } + + return config; +} diff --git a/plugin/src/withIterablePodfile.ts b/plugin/src/withIterablePodfile.ts new file mode 100644 index 000000000..1b2402ed0 --- /dev/null +++ b/plugin/src/withIterablePodfile.ts @@ -0,0 +1,145 @@ +import { ConfigPlugin, withDangerousMod } from '@expo/config-plugins'; +import * as fs from 'fs'; +import * as path from 'path'; +import { IterablePluginProps } from './index'; + +// Marker comment for idempotency checks +const ITERABLE_PODS_MARKER = '# Iterable SDK (Expo Managed Workflow)'; + +/** + * Modifies the Podfile to add Iterable SDK pods with dynamic linkage. + * + * This plugin: + * 1. Ensures use_frameworks! :linkage => :static is present (Expo requirement) + * 2. Injects Iterable pods with :linkage => :dynamic override + * 3. Optionally adds IterableNotifications target for push notification extension + */ +export const withIterablePodfile: ConfigPlugin = (config, props) => { + return withDangerousMod(config, [ + 'ios', + async (config) => { + const podfilePath = path.join(config.modRequest.platformProjectRoot, 'Podfile'); + + if (!fs.existsSync(podfilePath)) { + throw new Error(`Podfile not found at ${podfilePath}`); + } + + let contents = fs.readFileSync(podfilePath, 'utf-8'); + + // Apply transformations + contents = ensureStaticLinkage(contents); + contents = injectIterablePods(contents); + + if (props?.enableNotificationExtension) { + const targetName = props.notificationExtensionTargetName ?? 'IterableNotifications'; + contents = injectNotificationExtensionTarget(contents, targetName); + } + + fs.writeFileSync(podfilePath, contents); + return config; + }, + ]); +}; + +/** + * Ensures the Podfile has use_frameworks! :linkage => :static. + * This is required for Expo compatibility. + */ +export function ensureStaticLinkage(contents: string): string { + // Check if static linkage is already configured + if (contents.includes('use_frameworks! :linkage => :static')) { + return contents; + } + + // Replace dynamic linkage with static if present + if (contents.includes('use_frameworks! :linkage => :dynamic')) { + return contents.replace( + /use_frameworks!\s*:linkage\s*=>\s*:dynamic/g, + 'use_frameworks! :linkage => :static' + ); + } + + // Replace bare use_frameworks! with static linkage + if (contents.match(/use_frameworks!\s*(?!\s*:linkage)/)) { + return contents.replace(/use_frameworks!\s*(?!\s*:linkage)/, 'use_frameworks! :linkage => :static'); + } + + return contents; +} + +/** + * Injects Iterable SDK pods with dynamic linkage into the main target. + * Uses marker comments for idempotency. + */ +export function injectIterablePods(contents: string): string { + // Check if already injected (idempotency) + if (contents.includes(ITERABLE_PODS_MARKER)) { + return contents; + } + + const iterablePods = ` + ${ITERABLE_PODS_MARKER} + pod 'Iterable-React-Native-SDK', :path => '../node_modules/@iterable/react-native-sdk', :linkage => :dynamic + pod 'Iterable-iOS-SDK', :linkage => :dynamic +`; + + // Try to inject after use_expo_modules! first (preferred location for Expo apps) + if (contents.includes('use_expo_modules!')) { + return contents.replace( + /(use_expo_modules![^\n]*\n)/, + `$1${iterablePods}` + ); + } + + // Fallback: inject after use_react_native! + if (contents.includes('use_react_native!')) { + // Find the closing of use_react_native! block (handles multi-line) + const useReactNativeRegex = /(use_react_native!\s*\([^)]*\))/s; + const match = contents.match(useReactNativeRegex); + + if (match) { + return contents.replace(useReactNativeRegex, `$1\n${iterablePods}`); + } + } + + // Last fallback: inject after use_native_modules! + if (contents.includes('use_native_modules!')) { + return contents.replace( + /(use_native_modules![^\n]*\n)/, + `$1${iterablePods}` + ); + } + + // If no known markers found, inject at the end of the first target block + const targetRegex = /(target\s+['"][^'"]+['"]\s+do\s*\n)/; + if (contents.match(targetRegex)) { + return contents.replace(targetRegex, `$1${iterablePods}`); + } + + throw new Error( + 'Could not find a suitable location to inject Iterable pods. ' + + 'Please ensure your Podfile has a valid target block.' + ); +} + +/** + * Injects the IterableNotifications target for push notification service extension. + */ +export function injectNotificationExtensionTarget(contents: string, targetName: string): string { + const targetMarker = `target '${targetName}'`; + + // Check if already injected (idempotency) + if (contents.includes(targetMarker)) { + return contents; + } + + const notificationTarget = ` +target '${targetName}' do + use_frameworks! :linkage => :dynamic + pod 'Iterable-iOS-AppExtensions' +end +`; + + // Append to the end of the file + return contents.trimEnd() + '\n' + notificationTarget; +} diff --git a/plugin/tsconfig.json b/plugin/tsconfig.json new file mode 100644 index 000000000..1ffbb4493 --- /dev/null +++ b/plugin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./build", + "rootDir": "./src", + "strict": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "build", "__tests__"] +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 3c0636adf..07d388cfb 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig", - "exclude": ["example", "lib"] + "exclude": ["example", "lib", "plugin"] } diff --git a/tsconfig.json b/tsconfig.json index f9287edfb..450e80e37 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,5 +25,6 @@ "strict": true, "target": "ESNext", "verbatimModuleSyntax": true - } + }, + "exclude": ["plugin"] } diff --git a/yarn.lock b/yarn.lock index 8e584876d..e2746d635 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41,6 +41,15 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:~7.10.4": + version: 7.10.4 + resolution: "@babel/code-frame@npm:7.10.4" + dependencies: + "@babel/highlight": ^7.10.4 + checksum: feb4543c8a509fe30f0f6e8d7aa84f82b41148b963b826cd330e34986f649a85cb63b2f13dd4effdf434ac555d16f14940b8ea5f4433297c2f5ff85486ded019 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.27.2, @babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0": version: 7.28.4 resolution: "@babel/compat-data@npm:7.28.4" @@ -264,6 +273,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 5a251a6848e9712aea0338f659a1a3bd334d26219d5511164544ca8ec20774f098c3a6661e9da65a0d085c745c00bb62c8fada38a62f08fa1f8053bc0aeb57e4 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-validator-identifier@npm:7.27.1" @@ -299,6 +315,18 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.10.4": + version: 7.25.9 + resolution: "@babel/highlight@npm:7.25.9" + dependencies: + "@babel/helper-validator-identifier": ^7.25.9 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + picocolors: ^1.0.0 + checksum: a6e0ac0a1c4bef7401915ca3442ab2b7ae4adf360262ca96b91396bfb9578abb28c316abf5e34460b780696db833b550238d9256bdaca60fade4ba7a67645064 + languageName: node + linkType: hard + "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": version: 7.28.4 resolution: "@babel/parser@npm:7.28.4" @@ -1815,6 +1843,63 @@ __metadata: languageName: node linkType: hard +"@expo/config-plugins@npm:^9.0.0": + version: 9.1.7 + resolution: "@expo/config-plugins@npm:9.1.7" + dependencies: + "@expo/config-types": ^53.0.0 + "@expo/json-file": ~9.1.3 + "@expo/plist": ^0.3.3 + "@expo/sdk-runtime-versions": ^1.0.0 + chalk: ^4.1.2 + debug: ^4.3.5 + getenv: ^1.0.0 + glob: ^10.4.2 + resolve-from: ^5.0.0 + semver: ^7.5.4 + slash: ^3.0.0 + slugify: ^1.6.6 + xcode: ^3.0.1 + xml2js: 0.6.0 + checksum: 8eba71d88100c1c0572307256900b10b8375d3fac2496d0486e7d9d66d8dc4a979e907a12e5dfb3a7811871bef4ba5cc906559ceb371517b7308bd909f17730a + languageName: node + linkType: hard + +"@expo/config-types@npm:^53.0.0": + version: 53.0.5 + resolution: "@expo/config-types@npm:53.0.5" + checksum: 3f4db2d9590c18fb178f7395739ee2200512ad7c253655be0bad7f3f0d948df89c1c69c7e949f202faa98eecbd4bbae3edfcf9264f97f49d4f7087f85c68af5d + languageName: node + linkType: hard + +"@expo/json-file@npm:~9.1.3": + version: 9.1.5 + resolution: "@expo/json-file@npm:9.1.5" + dependencies: + "@babel/code-frame": ~7.10.4 + json5: ^2.2.3 + checksum: beedf9077dcff476acd895219e391e18079d3c375e58bb4902a4147fffe9774e11d4d1607cfc3488f8e9daec2c30e4d7c17c46fb701035ad7aabacaaf6d465b4 + languageName: node + linkType: hard + +"@expo/plist@npm:^0.3.3": + version: 0.3.5 + resolution: "@expo/plist@npm:0.3.5" + dependencies: + "@xmldom/xmldom": ^0.8.8 + base64-js: ^1.2.3 + xmlbuilder: ^15.1.1 + checksum: b78fda216c63ab553b24e75e87021be09155cd16e0fcf666846c1a5ea33defa2d7f631ea504788a8e2c5d67b5f59b35d6a46fa79a244d5890ee26870caffec22 + languageName: node + linkType: hard + +"@expo/sdk-runtime-versions@npm:^1.0.0": + version: 1.0.0 + resolution: "@expo/sdk-runtime-versions@npm:1.0.0" + checksum: 0942d5a356f590e8dc795761456cc48b3e2d6a38ad2a02d6774efcdc5a70424e05623b4e3e5d2fec0cdc30f40dde05c14391c781607eed3971bf8676518bfd9d + languageName: node + linkType: hard + "@gerrit0/mini-shiki@npm:^3.12.0": version: 3.13.1 resolution: "@gerrit0/mini-shiki@npm:3.13.1" @@ -1979,6 +2064,7 @@ __metadata: dependencies: "@commitlint/config-conventional": ^19.6.0 "@evilmartians/lefthook": ^1.5.0 + "@expo/config-plugins": ^9.0.0 "@react-native-community/cli": 18.0.0 "@react-native/babel-preset": 0.79.3 "@react-native/eslint-config": 0.79.3 @@ -1989,6 +2075,7 @@ __metadata: "@testing-library/jest-native": ^5.4.3 "@testing-library/react-native": ^13.3.3 "@types/jest": ^29.5.5 + "@types/node": ^20.0.0 "@types/react": ^19.0.0 "@typescript-eslint/eslint-plugin": ^8.13.0 "@typescript-eslint/parser": ^8.13.0 @@ -2011,6 +2098,7 @@ __metadata: react-native-webview: ^13.14.1 react-test-renderer: 19.0.0 release-it: ^17.10.0 + ts-jest: ^29.1.0 turbo: ^1.10.7 typedoc: ^0.28.13 typedoc-plugin-coverage: ^3.3.0 @@ -2023,6 +2111,8 @@ __metadata: react-native-safe-area-context: "*" react-native-webview: "*" peerDependenciesMeta: + "@expo/config-plugins": + optional: true expo: optional: true languageName: unknown @@ -3601,6 +3691,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.0.0": + version: 20.19.31 + resolution: "@types/node@npm:20.19.31" + dependencies: + undici-types: ~6.21.0 + checksum: 7669526cef4b9f21e771b0f1a4dea662171484a0376a63f8c0165954a7de461acc9d2b20da867fcf15f5ba074ce7e0e81ecfa592df7ddf473ffd9d595fd220a9 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0, @types/normalize-package-data@npm:^2.4.3": version: 2.4.4 resolution: "@types/normalize-package-data@npm:2.4.4" @@ -4068,6 +4167,13 @@ __metadata: languageName: node linkType: hard +"@xmldom/xmldom@npm:^0.8.8": + version: 0.8.11 + resolution: "@xmldom/xmldom@npm:0.8.11" + checksum: 72020f3d5c74b54e25d19f2cd7b2d87484926cc7febdf02347dc3e06364186641d54e9e94baaaaba30e99528e6727adcd1baef6d0809e7460aee3a5be890b132 + languageName: node + linkType: hard + "JSONStream@npm:^1.3.5": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -4265,7 +4371,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.2.0": +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" dependencies: @@ -4688,7 +4794,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": +"base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -4718,6 +4824,13 @@ __metadata: languageName: node linkType: hard +"big-integer@npm:1.6.x": + version: 1.6.52 + resolution: "big-integer@npm:1.6.52" + checksum: 6e86885787a20fed96521958ae9086960e4e4b5e74d04f3ef7513d4d0ad631a9f3bde2730fc8aaa4b00419fc865f6ec573e5320234531ef37505da7da192c40b + languageName: node + linkType: hard + "bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -4765,6 +4878,24 @@ __metadata: languageName: node linkType: hard +"bplist-creator@npm:0.1.1": + version: 0.1.1 + resolution: "bplist-creator@npm:0.1.1" + dependencies: + stream-buffers: 2.2.x + checksum: b0d40d1d1623f1afdbb575cfc8075d742d2c4f0eb458574be809e3857752d1042a39553b3943d2d7f505dde92bcd43e1d7bdac61c9cd44475d696deb79f897ce + languageName: node + linkType: hard + +"bplist-parser@npm:0.3.2": + version: 0.3.2 + resolution: "bplist-parser@npm:0.3.2" + dependencies: + big-integer: 1.6.x + checksum: fad0f6eb155a9b636b4096a1725ce972a0386490d7d38df7be11a3a5645372446b7c44aacbc6626d24d2c17d8b837765361520ebf2960aeffcaf56765811620e + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.12 resolution: "brace-expansion@npm:1.1.12" @@ -4808,6 +4939,15 @@ __metadata: languageName: node linkType: hard +"bs-logger@npm:^0.2.6": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: 2.x + checksum: d34bdaf68c64bd099ab97c3ea608c9ae7d3f5faa1178b3f3f345acd94e852e608b2d4f9103fb2e503f5e69780e98293df41691b84be909b41cf5045374d54606 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -4994,6 +5134,17 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: ^3.2.1 + escape-string-regexp: ^1.0.5 + supports-color: ^5.3.0 + checksum: ec3661d38fe77f681200f878edbd9448821924e0f93a9cefc0e26a33b145f1027a2084bf19967160d11e1f03bfe4eaffcabf5493b89098b2782c3fe0b03d80c2 + languageName: node + linkType: hard + "chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -5741,7 +5892,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.1": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -6841,7 +6992,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb @@ -7246,6 +7397,13 @@ __metadata: languageName: node linkType: hard +"getenv@npm:^1.0.0": + version: 1.0.0 + resolution: "getenv@npm:1.0.0" + checksum: 19ae5cad603a1cf1bcb8fa3bed48e00d062eb0572a4404c02334b67f3b3499f238383082b064bb42515e9e25c2b08aef1a3e3d2b6852347721aa8b174825bd56 + languageName: node + linkType: hard + "git-raw-commits@npm:^4.0.0": version: 4.0.0 resolution: "git-raw-commits@npm:4.0.0" @@ -7336,6 +7494,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.4.2": + version: 10.5.0 + resolution: "glob@npm:10.5.0" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^3.1.2 + minimatch: ^9.0.4 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^1.11.1 + bin: + glob: dist/esm/bin.mjs + checksum: cda96c074878abca9657bd984d2396945cf0d64283f6feeb40d738fe2da642be0010ad5210a1646244a5fc3511b0cab5a374569b3de5a12b8a63d392f18c6043 + languageName: node + linkType: hard + "glob@npm:^7.0.0, glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -7472,7 +7646,7 @@ __metadata: languageName: node linkType: hard -"handlebars@npm:^4.7.7": +"handlebars@npm:^4.7.7, handlebars@npm:^4.7.8": version: 4.7.8 resolution: "handlebars@npm:4.7.8" dependencies: @@ -7513,6 +7687,13 @@ __metadata: languageName: node linkType: hard +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b + languageName: node + linkType: hard + "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -9401,6 +9582,13 @@ __metadata: languageName: node linkType: hard +"lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 9ff3942feeccffa4f1fafa88d32f0d24fdc62fd15ded5a74a5f950ff5f0c6f61916157246744c620173dddf38d37095a92327d5fd3861e2063e736a5c207d089 + languageName: node + linkType: hard + "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -9580,6 +9768,13 @@ __metadata: languageName: node linkType: hard +"make-error@npm:^1.3.6": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 + languageName: node + linkType: hard + "make-fetch-happen@npm:^14.0.3": version: 14.0.3 resolution: "make-fetch-happen@npm:14.0.3" @@ -11151,7 +11346,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.1.1": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -11197,6 +11392,17 @@ __metadata: languageName: node linkType: hard +"plist@npm:^3.0.5": + version: 3.1.0 + resolution: "plist@npm:3.1.0" + dependencies: + "@xmldom/xmldom": ^0.8.8 + base64-js: ^1.5.1 + xmlbuilder: ^15.1.1 + checksum: c8ea013da8646d4c50dff82f9be39488054621cc229957621bb00add42b5d4ce3657cf58d4b10c50f7dea1a81118f825838f838baeb4e6f17fab453ecf91d424 + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0": version: 1.1.0 resolution: "possible-typed-array-names@npm:1.1.0" @@ -12261,6 +12467,13 @@ __metadata: languageName: node linkType: hard +"sax@npm:>=0.6.0": + version: 1.4.4 + resolution: "sax@npm:1.4.4" + checksum: a6082a153b4ab00968894b3751f6fdc431b0b7edc2d086da67ee162a06716f4bc7d0546e875993e950c757039c9e3838747ab77f50578a6ce579f970a6feadaf + languageName: node + linkType: hard + "scheduler@npm:0.25.0, scheduler@npm:^0.25.0": version: 0.25.0 resolution: "scheduler@npm:0.25.0" @@ -12286,7 +12499,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.3, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.6, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3": +"semver@npm:^7.1.3, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.6, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.3": version: 7.7.3 resolution: "semver@npm:7.7.3" bin: @@ -12484,6 +12697,17 @@ __metadata: languageName: node linkType: hard +"simple-plist@npm:^1.1.0": + version: 1.4.0 + resolution: "simple-plist@npm:1.4.0" + dependencies: + bplist-creator: 0.1.1 + bplist-parser: 0.3.2 + plist: ^3.0.5 + checksum: fa8086f6b781c289f1abad21306481dda4af6373b32a5d998a70e53c2b7218a1d21ebb5ae3e736baae704c21d311d3d39d01d0e6a2387eda01b4020b9ebd909e + languageName: node + linkType: hard + "simple-swizzle@npm:^0.2.2": version: 0.2.4 resolution: "simple-swizzle@npm:0.2.4" @@ -12532,6 +12756,13 @@ __metadata: languageName: node linkType: hard +"slugify@npm:^1.6.6": + version: 1.6.6 + resolution: "slugify@npm:1.6.6" + checksum: 04773c2d3b7aea8d2a61fa47cc7e5d29ce04e1a96cbaec409da57139df906acb3a449fac30b167d203212c806e73690abd4ff94fbad0a9a7b7ea109a2a638ae9 + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -12714,6 +12945,13 @@ __metadata: languageName: node linkType: hard +"stream-buffers@npm:2.2.x": + version: 2.2.0 + resolution: "stream-buffers@npm:2.2.0" + checksum: 4587d9e8f050d689fb38b4295e73408401b16de8edecc12026c6f4ae92956705ecfd995ae3845d7fa3ebf19502d5754df9143d91447fd881d86e518f43882c1c + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -12966,6 +13204,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: ^3.0.0 + checksum: 95f6f4ba5afdf92f495b5a912d4abee8dcba766ae719b975c56c084f5004845f6f5a5f7769f52d53f40e21952a6d87411bafe34af4a01e65f9926002e38e1dac + languageName: node + linkType: hard + "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -13150,6 +13397,46 @@ __metadata: languageName: node linkType: hard +"ts-jest@npm:^29.1.0": + version: 29.4.6 + resolution: "ts-jest@npm:29.4.6" + dependencies: + bs-logger: ^0.2.6 + fast-json-stable-stringify: ^2.1.0 + handlebars: ^4.7.8 + json5: ^2.2.3 + lodash.memoize: ^4.1.2 + make-error: ^1.3.6 + semver: ^7.7.3 + type-fest: ^4.41.0 + yargs-parser: ^21.1.1 + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 || ^30.0.0 + "@jest/types": ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: ">=4.3 <6" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + bin: + ts-jest: cli.js + checksum: 07ae4102569565ab57036f095152ea75c85032edf15379043ffc8da2dd0e6e93e84d0c50a24e10a5cddacb5ab773df0f3170f02db6c178edd22a5e485bc57dc7 + languageName: node + linkType: hard + "tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -13297,7 +13584,7 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.18.2, type-fest@npm:^4.21.0, type-fest@npm:^4.39.1, type-fest@npm:^4.6.0": +"type-fest@npm:^4.18.2, type-fest@npm:^4.21.0, type-fest@npm:^4.39.1, type-fest@npm:^4.41.0, type-fest@npm:^4.6.0": version: 4.41.0 resolution: "type-fest@npm:4.41.0" checksum: 7055c0e3eb188425d07403f1d5dc175ca4c4f093556f26871fe22041bc93d137d54bef5851afa320638ca1379106c594f5aa153caa654ac1a7f22c71588a4e80 @@ -13466,6 +13753,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 46331c7d6016bf85b3e8f20c159d62f5ae471aba1eb3dc52fff35a0259d58dcc7d592d4cc4f00c5f9243fa738a11cfa48bd20203040d4a9e6bc25e807fab7ab3 + languageName: node + linkType: hard + "undici-types@npm:~7.14.0": version: 7.14.0 resolution: "undici-types@npm:7.14.0" @@ -13637,6 +13931,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^7.0.3": + version: 7.0.3 + resolution: "uuid@npm:7.0.3" + bin: + uuid: dist/bin/uuid + checksum: f5b7b5cc28accac68d5c083fd51cca64896639ebd4cca88c6cfb363801aaa83aa439c86dfc8446ea250a7a98d17afd2ad9e88d9d4958c79a412eccb93bae29de + languageName: node + linkType: hard + "v8-to-istanbul@npm:^9.0.1": version: 9.3.0 resolution: "v8-to-istanbul@npm:9.3.0" @@ -13942,6 +14245,16 @@ __metadata: languageName: node linkType: hard +"xcode@npm:^3.0.1": + version: 3.0.1 + resolution: "xcode@npm:3.0.1" + dependencies: + simple-plist: ^1.1.0 + uuid: ^7.0.3 + checksum: 908ff85851f81aec6e36ca24427db092e1cc068f052716e14de5e762196858039efabbe053a1abe8920184622501049e74a93618e8692b982f7604a9847db108 + languageName: node + linkType: hard + "xdg-basedir@npm:^5.1.0": version: 5.1.0 resolution: "xdg-basedir@npm:5.1.0" @@ -13949,6 +14262,30 @@ __metadata: languageName: node linkType: hard +"xml2js@npm:0.6.0": + version: 0.6.0 + resolution: "xml2js@npm:0.6.0" + dependencies: + sax: ">=0.6.0" + xmlbuilder: ~11.0.0 + checksum: 437f353fd66d367bf158e9555a0625df9965d944e499728a5c6bc92a54a2763179b144f14b7e1c725040f56bbd22b0fa6cfcb09ec4faf39c45ce01efe631f40b + languageName: node + linkType: hard + +"xmlbuilder@npm:^15.1.1": + version: 15.1.1 + resolution: "xmlbuilder@npm:15.1.1" + checksum: 14f7302402e28d1f32823583d121594a9dca36408d40320b33f598bd589ca5163a352d076489c9c64d2dc1da19a790926a07bf4191275330d4de2b0d85bb1843 + languageName: node + linkType: hard + +"xmlbuilder@npm:~11.0.0": + version: 11.0.1 + resolution: "xmlbuilder@npm:11.0.1" + checksum: 7152695e16f1a9976658215abab27e55d08b1b97bca901d58b048d2b6e106b5af31efccbdecf9b07af37c8377d8e7e821b494af10b3a68b0ff4ae60331b415b0 + languageName: node + linkType: hard + "xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2"