Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/reusable_build_sample_apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ jobs:
- name: Install XCode
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: "15.3"
xcode-version: "16.2"

- name: Install tools from Gemfile (ruby language) used for building our apps with
uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1.229.0
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/validate-plugin-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concurrency:
env:
NODE_VERSION: "20"
JAVA_VERSION: "17"
XCODE_VERSION: "15.3"
XCODE_VERSION: "16.2"
APP_DIR: ci-test-apps
APP_NAME_PREFIX: ExpoPluginTestApp

Expand Down Expand Up @@ -45,6 +45,15 @@ jobs:
- expo-version: 52
platform: ios
ios-push-provider: fcm

- expo-version: 53
platform: android
- expo-version: 53
platform: ios
ios-push-provider: apn
- expo-version: 53
platform: ios
ios-push-provider: fcm

# Running on latest version helps to catch issues early for new versions not listed above
- expo-version: latest
Expand Down
5,714 changes: 2,124 additions & 3,590 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@
"registry": "https://registry.npmjs.org/"
},
"peerDependencies": {
"customerio-reactnative": "4.2.3"
"customerio-reactnative": "4.2.7"
},
"devDependencies": {
"@expo/config-plugins": "^4.1.4",
"@expo/config-types": "^44.0.0",
"@expo/config-plugins": "^7.0.0",
"@expo/config-types": "^49.0.0",
"@types/jest": "^29.5.14",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
Expand Down
42 changes: 41 additions & 1 deletion plugin/app.plugin.js
Original file line number Diff line number Diff line change
@@ -1 +1,41 @@
module.exports = require("./lib/commonjs/index");
// For development testing, we directly require the source files
// This allows us to test changes without needing to rebuild the plugin
try {
module.exports = require("./lib/commonjs/index");
} catch (error) {
// Fallback to src for development
console.log("Using source files directly for testing - attempting to register ts-node");
try {
// Try to register ts-node for TypeScript compilation on the fly
require('ts-node').register({
transpileOnly: true,
compilerOptions: {
module: "commonjs",
target: "es2017",
}
});
module.exports = require("./src/index");
} catch (tsNodeError) {
console.error("Could not load ts-node for TypeScript compilation:", tsNodeError.message);
console.log("Trying to load compiled JavaScript files if they exist...");

// As a last resort, try to find any .js files that may exist
try {
const fs = require('fs');
const path = require('path');

// Check if index.js exists in the src directory (might have been compiled but not moved)
const indexJsPath = path.resolve(__dirname, 'src', 'index.js');
if (fs.existsSync(indexJsPath)) {
console.log(`Found ${indexJsPath}, attempting to load it`);
module.exports = require(indexJsPath);
} else {
console.error("Could not find any suitable module to load. Please rebuild the plugin.");
throw new Error("CustomerIO Expo Plugin failed to load. Please rebuild the plugin with 'npm run build'.");
}
} catch (finalError) {
console.error("Fatal error loading CustomerIO Expo Plugin:", finalError);
throw finalError;
}
}
}
38 changes: 31 additions & 7 deletions plugin/src/helpers/utils/injectCIOPodfileCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { CustomerIOPluginOptionsIOS } from '../../types/cio-types';
import { getRelativePathToRNSDK } from '../constants/ios';
import { injectCodeByRegex } from './codeInjection';
import { FileManagement } from './fileManagement';
import * as fs from 'fs';
import * as path from 'path';

export async function injectCIOPodfileCode(iosPath: string, isFcmPushProvider: boolean) {
const blockStart = '# --- CustomerIO Host App START ---';
Expand All @@ -10,20 +12,42 @@ export async function injectCIOPodfileCode(iosPath: string, isFcmPushProvider: b
const filename = `${iosPath}/Podfile`;
const podfile = await FileManagement.read(filename);
const matches = podfile.match(new RegExp(blockStart));

// Check if this is Expo 53 by looking for Swift-based AppDelegate
const appDelegatePath = path.join(iosPath, 'AppDelegate.swift');
const isExpo53 = fs.existsSync(appDelegatePath);

if (!matches) {
// We need to decide what line of code in the Podfile to insert our native code.
// The "post_install" line is always present in an Expo project Podfile so it's reliable.
// Find that line in the Podfile and then we will insert our code above that line.
const lineInPodfileToInjectSnippetBefore = /post_install do \|installer\|/;

const snippetToInjectInPodfile = `

let snippetToInjectInPodfile = '';

if (isExpo53) {
// For Expo 53, we need to include the ReactAppDependencyProvider source
snippetToInjectInPodfile = `
${blockStart}
# Add source for ReactAppDependencyProvider
source 'https://github.com/react-native-tvos/react-native-tvos-podspecs.git'

pod 'customerio-reactnative/${isFcmPushProvider ? "fcm" : "apn"}', :path => '${getRelativePathToRNSDK(
iosPath
)}'
iosPath
)}'
${blockEnd}
`.trim();
console.log('Adding ReactAppDependencyProvider source for Expo 53');
} else {
// For older Expo versions
snippetToInjectInPodfile = `
${blockStart}
pod 'customerio-reactnative/${isFcmPushProvider ? "fcm" : "apn"}', :path => '${getRelativePathToRNSDK(
iosPath
)}'
${blockEnd}
`.trim();
}

FileManagement.write(
filename,
Expand Down Expand Up @@ -57,12 +81,12 @@ ${blockStart}
target 'NotificationService' do
${useFrameworks === 'static' ? 'use_frameworks! :linkage => :static' : ''}
pod 'customerio-reactnative-richpush/${isFcmPushProvider ? "fcm" : "apn"}', :path => '${getRelativePathToRNSDK(
iosPath
)}'
iosPath
)}'
end
${blockEnd}
`.trim();

FileManagement.append(filename, snippetToInjectInPodfile);
}
}
}
62 changes: 56 additions & 6 deletions plugin/src/ios/withAppDelegateModifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ const addExpoNotificationsHeaderModification = (stringContents: string) => {
#if __has_include(<EXNotifications/EXNotificationCenterDelegate.h>)
#import <EXNotifications/EXNotificationCenterDelegate.h>
#endif

#if __has_include(<ExpoModulesCore/EXModulesProvidersRegistry.h>)
#import <ExpoModulesCore/EXModulesProvidersRegistry.h>
#endif
`
);

Expand Down Expand Up @@ -220,18 +224,60 @@ export const withAppDelegateModifications: ConfigPlugin<
> = (configOuter, props) => {
return withAppDelegate(configOuter, async (config) => {
let stringContents = config.modResults.contents;

// Check if this is a Swift-based AppDelegate (Expo 53+)
const isSwiftAppDelegate = stringContents.includes('import Expo') &&
(stringContents.includes('class AppDelegate: ExpoAppDelegate') ||
stringContents.includes('public class AppDelegate: ExpoAppDelegate'));

if (isSwiftAppDelegate) {
console.log('Detected Swift-based AppDelegate (Expo 53+). Skipping Objective-C modifications.');
// TODO: Add Swift-specific modifications if needed in the future
return config;
}

// For Objective-C AppDelegate (prior to Expo 53)
const regex = new RegExp(
`#import <${config.modRequest.projectName}-Swift.h>`
);
const match = stringContents.match(regex);

if (!match) {
const headerPath = getAppDelegateHeaderFilePath(
config.modRequest.projectRoot
);
let headerContent = await FileManagement.read(headerPath);
headerContent = addAppdelegateHeaderModification(headerContent);
FileManagement.write(headerPath, headerContent);
let headerPath;
try {
headerPath = getAppDelegateHeaderFilePath(
config.modRequest.projectRoot
);
} catch (error) {
// Try fallback paths for older Expo versions
console.log('Trying fallback paths for AppDelegate.h');
const possibleHeaderPaths = [
`${config.modRequest.projectRoot}/ios/${config.modRequest.projectName}/AppDelegate.h`,
`${config.modRequest.projectRoot}/ios/AppDelegate.h`
];

for (const path of possibleHeaderPaths) {
try {
if (FileManagement.exists(path)) {
headerPath = path;
console.log(`Found AppDelegate.h at: ${path}`);
break;
}
} catch (e) {
// Continue trying other paths
}
}

if (!headerPath) {
console.warn("Could not find AppDelegate.h file. Skipping header modifications.");
}
}

if (headerPath) {
let headerContent = await FileManagement.read(headerPath);
headerContent = addAppdelegateHeaderModification(headerContent);
FileManagement.write(headerPath, headerContent);
}

stringContents = addImport(
stringContents,
Expand Down Expand Up @@ -273,7 +319,11 @@ export const withAppDelegateModifications: ConfigPlugin<
function getImportSnippet(appName: string) {
return `
// Add swift bridge imports
#if __has_include(<ExpoModulesCore-Swift.h>)
#import <ExpoModulesCore-Swift.h>
#endif
#if __has_include(<${appName}-Swift.h>)
#import <${appName}-Swift.h>
#endif
`;
}
25 changes: 20 additions & 5 deletions scripts/compatibility/create-test-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,26 @@ function execute() {

// Step 3: Create a new Expo app
logMessage(`\n🔧 Creating new Expo app: ${APP_NAME} (Expo ${EXPO_VERSION})`);
const RESOLVED_EXPO_TEMPLATE =
EXPO_VERSION === "latest" ? EXPO_TEMPLATE : `${EXPO_TEMPLATE}@sdk-${EXPO_VERSION}`;
runCommand(
`cd ${APP_DIRECTORY_PATH} && npx create-expo-app '${APP_NAME}' --template ${RESOLVED_EXPO_TEMPLATE}`,
);
let RESOLVED_EXPO_TEMPLATE;

// Handle Expo 53 specially as it might have different template requirements
if (EXPO_VERSION === "53") {
RESOLVED_EXPO_TEMPLATE = EXPO_TEMPLATE;
runCommand(
`cd ${APP_DIRECTORY_PATH} && npx create-expo-app@latest '${APP_NAME}' --template ${RESOLVED_EXPO_TEMPLATE}`,
);
// Install Expo 53 SDK explicitly
runCommand(
`cd ${APP_DIRECTORY_PATH}/${APP_NAME} && npx expo install expo@~53.0.0`,
);
} else {
RESOLVED_EXPO_TEMPLATE =
EXPO_VERSION === "latest" ? EXPO_TEMPLATE : `${EXPO_TEMPLATE}@sdk-${EXPO_VERSION}`;
runCommand(
`cd ${APP_DIRECTORY_PATH} && npx create-expo-app '${APP_NAME}' --template ${RESOLVED_EXPO_TEMPLATE}`,
);
}

logMessage("✅ Expo app created successfully!", "success");
}

Expand Down
2 changes: 2 additions & 0 deletions scripts/compatibility/run-compatibility-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const APP_NAME = getArgValue("--app-name", {
default: `ExpoTest_V${EXPO_VERSION}`.replace(/\./g, ""),
});
const APP_DIR = getArgValue("--dir-name", { default: "ci-test-apps" });
const CLEAN_FLAG = process.argv.includes("--clean");
const APP_PATH = path.resolve(__dirname, "../..", APP_DIR, APP_NAME);

logMessage(`🚀 Starting local validation for Expo plugin (Expo ${EXPO_VERSION})...`);
Expand All @@ -18,6 +19,7 @@ runScriptWithArgs("compatibility:create-test-app", {
"expo-version": EXPO_VERSION,
"app-name": APP_NAME,
"dir-name": APP_DIR,
...(CLEAN_FLAG ? { "clean": true } : {}),
},
exclude: EXCLUDED_FORWARD_ARGS,
});
Expand Down
Loading
Loading