From 0a5bd330040e32bbcb2ff90cacd81fd3f27d319e Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Sun, 21 Sep 2025 15:43:33 +0100 Subject: [PATCH] feat(expo): add Expo Config Plugin This config plugin has been migrated from `expo/config-plugins`. --- .gitignore | 1 + app.plugin.js | 1 + package.json | 4 + plugins/expo/index.js | 275 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 281 insertions(+) create mode 100644 app.plugin.js create mode 100644 plugins/expo/index.js diff --git a/.gitignore b/.gitignore index d4d8957e..d2843cae 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,7 @@ temp/ # yarn yarn.lock +node_modules #example example/.yarn diff --git a/app.plugin.js b/app.plugin.js new file mode 100644 index 00000000..6b7f50d5 --- /dev/null +++ b/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugins/expo'); \ No newline at end of file diff --git a/package.json b/package.json index 7fc6d49a..9d824489 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "license": "MIT", "keywords": [ "react-native", + "expo", "ios", "android", "adjust" @@ -18,5 +19,8 @@ "repository": { "type": "git", "url": "git://github.com/adjust/react_native_sdk.git" + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.1" } } diff --git a/plugins/expo/index.js b/plugins/expo/index.js new file mode 100644 index 00000000..4b0f7274 --- /dev/null +++ b/plugins/expo/index.js @@ -0,0 +1,275 @@ +import { mergeContents } from "@expo/config-plugins/build/utils/generateCode"; +import { + AndroidConfig, + createRunOncePlugin, + IOSConfig, + withAppBuildGradle, + withXcodeProject, + withDangerousMod, +} from "@expo/config-plugins"; +import fs from "node:fs"; +import path from "node:path"; + +/** + * @typedef {import('@expo/config-plugins').ConfigPlugin} ConfigPlugin + */ + +/** + * @typedef {Object} SdkSignature + * @property {string} [ios] - iOS SDK signature path + * @property {string} [android] - Android SDK signature path + */ + +/** + * @typedef {Object} Props + * @property {boolean} [targetAndroid12] - Whether to target Android 12 + * @property {SdkSignature} [sdkSignature] - SDK signature configuration + */ + +/** + * @type {ConfigPlugin<{library: string, status?: string}>} + */ +const withXcodeLinkBinaryWithLibraries = (config, { library, status }) => { + return withXcodeProject(config, (config) => { + const options = status === "optional" ? { weak: true } : {}; + + const target = IOSConfig.XcodeUtils.getApplicationNativeTarget({ + project: config.modResults, + projectName: config.modRequest.projectName, + }); + + config.modResults.addFramework(library, { + target: target.uuid, + ...options, + }); + + return config; + }); +}; + +/** + * @param {string} src + * @returns {Object} + */ +const addAndroidPackagingOptions = (src) => { + return mergeContents({ + tag: "react-native-play-services-analytics", + src, + newSrc: ` + implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' + implementation 'com.android.installreferrer:installreferrer:2.2' + `, + anchor: /dependencies(?:\s+)?\{/, + // Inside the dependencies block. + offset: 1, + comment: "//", + }); +}; + +/** + * @param {string} src + * @returns {Object} + */ +const addSdkSignatureDependency = (src) => { + return mergeContents({ + tag: "react-native-adjust arr dependency", + src, + newSrc: "implementation files('libs/adjust-lib.aar')", + anchor: /dependencies(?:\s+)?\{/, + // Inside the dependencies block. + offset: 1, + comment: "//", + }); +}; + +/** + * @param {string} src + * @returns {Object} + */ +const addNdkAbiFilters = (src) => { + return mergeContents({ + tag: "react-native-adjust NDK abi filters", + src, + newSrc: "ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'", + anchor: /defaultConfig(?:\s+)?\{/, + // Inside the dependencies block. + offset: 1, + comment: "//", + }); +}; + +/** + * @type {ConfigPlugin<{isSdkSignatureSupported: boolean}>} + */ +const withGradle = (config, { isSdkSignatureSupported }) => { + return withAppBuildGradle(config, (config) => { + if (config.modResults.language !== "groovy") { + throw new Error( + "Cannot add Play Services maven gradle because the project build.gradle is not groovy", + ); + } + + if (isSdkSignatureSupported) { + config.modResults.contents = addNdkAbiFilters( + config.modResults.contents + ).contents; + + config.modResults.contents = addSdkSignatureDependency( + config.modResults.contents + ).contents; + } + + config.modResults.contents = addAndroidPackagingOptions( + config.modResults.contents + ).contents; + + return config; + }); +}; + +/** + * @type {ConfigPlugin<{sdkSignaturePath: string}>} + */ +const withAndroidSdkSignature = (config, props) => { + return withDangerousMod(config, [ + "android", + async (config) => { + const projectRoot = config.modRequest.projectRoot; + const libPath = path.join(projectRoot, props.sdkSignaturePath); + + if (!fs.existsSync(libPath)) { + throw new Error("Cannot find Adjust Android sdk signature library !"); + } + + const libsDirectoryPath = path.join(projectRoot, "android/app/libs"); + + if (!fs.existsSync(libsDirectoryPath)) { + fs.mkdirSync(libsDirectoryPath); + } + + fs.cpSync(libPath, path.join(libsDirectoryPath, "adjust-lib.aar")); + + return config; + }, + ]); +}; + +/** + * @type {ConfigPlugin<{sdkSignaturePath: string}>} + */ +const withIosSdkSignature = (config, props) => { + return withXcodeProject(config, (config) => { + const projectRoot = config.modRequest.projectRoot; + const libPath = path.join(projectRoot, props.sdkSignaturePath); + + if (!fs.existsSync(libPath)) { + throw new Error("Cannot find Adjust iOS sdk signature library !"); + } + + const newLibPath = path.join( + projectRoot, + "ios", + config.modRequest.projectName, + path.basename(libPath) + ); + const target = IOSConfig.XcodeUtils.getApplicationNativeTarget({ + project: config.modResults, + projectName: config.modRequest.projectName, + }); + + fs.cpSync(libPath, newLibPath, { recursive: true }); + + const embedFrameworksBuildPhase = + config.modResults.pbxEmbedFrameworksBuildPhaseObj(target.uuid); + + if (!embedFrameworksBuildPhase) { + config.modResults.addBuildPhase( + [], + "PBXCopyFilesBuildPhase", + "Embed Frameworks", + target.uuid, + "frameworks" + ); + } + + config.modResults.addFramework(newLibPath, { + target: target.uuid, + embed: true, + sign: true, + customFramework: true, + }); + + return config; + }); +}; + +/** + * Apply react-native-adjust configuration for Expo SDK +44 projects. + * @type {ConfigPlugin} + */ +const withAdjustPlugin = (config, _props) => { + const props = _props || {}; + + const androidSdkSignaturePath = props.sdkSignature?.android ?? ""; + const iosSdkSignaturePath = props.sdkSignature?.ios ?? ""; + const isAndroidSdkSignatureSupported = androidSdkSignaturePath !== ""; + const isIosSdkSignatureSupported = iosSdkSignaturePath !== ""; + + config = withXcodeLinkBinaryWithLibraries(config, { + library: "AdServices.framework", + status: "optional", + }); + + config = withXcodeLinkBinaryWithLibraries(config, { + library: "AdSupport.framework", + status: "optional", + }); + + config = withXcodeLinkBinaryWithLibraries(config, { + library: "StoreKit.framework", + status: "optional", + }); + + config = withXcodeLinkBinaryWithLibraries(config, { + library: "AppTrackingTransparency.framework", + status: "optional", + }); + + if (isIosSdkSignatureSupported) { + config = withIosSdkSignature(config, { + sdkSignaturePath: iosSdkSignaturePath, + }); + } + + if (props.targetAndroid12) { + config = AndroidConfig.Permissions.withPermissions(config, [ + "com.google.android.gms.permission.AD_ID", + ]); + } + + config = withGradle(config, { + isSdkSignatureSupported: isAndroidSdkSignatureSupported, + }); + + if (isAndroidSdkSignatureSupported) { + config = withAndroidSdkSignature(config, { + sdkSignaturePath: androidSdkSignaturePath, + }); + } + + // Return the modified config. + return config; +}; + +const pkg = { + // Prevent this plugin from being run more than once. + // This pattern enables users to safely migrate off of this + // out-of-tree `@config-plugins/react-native-adjust` to a future + // upstream plugin in `react-native-adjust` + name: "react-native-adjust", + // Indicates that this plugin is dangerously linked to a module, + // and might not work with the latest version of that module. + version: "UNVERSIONED", +}; + +export default createRunOncePlugin(withAdjustPlugin, pkg.name, pkg.version);