react-native-expo-moengage 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE.txt +8 -0
  3. package/README.md +38 -0
  4. package/android/build.gradle +53 -0
  5. package/android/src/main/AndroidManifest.xml +6 -0
  6. package/android/src/main/java/expo/modules/moengage/MoEExpoFireBaseMessagingService.kt +56 -0
  7. package/android/src/main/java/expo/modules/moengage/MoEngageApplicationLifecycleListener.kt +31 -0
  8. package/android/src/main/java/expo/modules/moengage/MoEngagePackage.kt +12 -0
  9. package/android/src/main/java/expo/modules/moengage/internal/Constants.kt +4 -0
  10. package/android/user-agent.gradle +15 -0
  11. package/app.plugin.js +1 -0
  12. package/apple/ExpoAdapterMoEngage/MoEngageAppDelegate.swift +76 -0
  13. package/apple/ExpoAdapterMoEngage/MoEngageExpoPluginInfo.swift +5 -0
  14. package/apple/ExpoAdapterMoEngage.podspec +40 -0
  15. package/apple/PushTemplates/MainInterface.storyboard +29 -0
  16. package/apple/PushTemplates/MoEngageExpoPushTemplates-Info.plist +45 -0
  17. package/apple/PushTemplates/MoEngageExpoPushTemplates.entitlements +14 -0
  18. package/apple/PushTemplates/NotificationViewController.swift +14 -0
  19. package/apple/RichPush/MoEngageExpoRichPush-Info.plist +31 -0
  20. package/apple/RichPush/MoEngageExpoRichPush.entitlements +14 -0
  21. package/apple/RichPush/NotificationService.swift +20 -0
  22. package/build/android/constants.js +47 -0
  23. package/build/android/types.js +2 -0
  24. package/build/android/utils.js +80 -0
  25. package/build/android/withMoEngageAndroid.js +100 -0
  26. package/build/apple/constants.js +47 -0
  27. package/build/apple/index.js +72 -0
  28. package/build/apple/withDangerousMod.js +275 -0
  29. package/build/apple/withEntitlements.js +117 -0
  30. package/build/apple/withInfoPlist.js +91 -0
  31. package/build/apple/withXcodeProject.js +364 -0
  32. package/build/index.js +37 -0
  33. package/build/types.js +2 -0
  34. package/expo-module.config.json +18 -0
  35. package/package.json +69 -0
  36. package/src/android/constants.ts +52 -0
  37. package/src/android/types.ts +1 -0
  38. package/src/android/utils.ts +74 -0
  39. package/src/android/withMoEngageAndroid.ts +127 -0
  40. package/src/apple/constants.ts +48 -0
  41. package/src/apple/index.ts +38 -0
  42. package/src/apple/withDangerousMod.ts +265 -0
  43. package/src/apple/withEntitlements.ts +81 -0
  44. package/src/apple/withInfoPlist.ts +63 -0
  45. package/src/apple/withXcodeProject.ts +418 -0
  46. package/src/index.ts +52 -0
  47. package/src/types.ts +100 -0
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "react-native-expo-moengage",
3
+ "version": "1.0.0",
4
+ "dependencyVersions": {
5
+ "firebaseMessaging": "24.1.0"
6
+ },
7
+ "description": "MoEngage Expo plugin for integrating MoEngage React-Native SDK",
8
+ "main": "build/index.js",
9
+ "files": [
10
+ "android",
11
+ "apple",
12
+ "src",
13
+ "build",
14
+ "CHANGELOG.md",
15
+ "README.md",
16
+ "package.json",
17
+ "app.plugin.js",
18
+ "expo-module.config.json",
19
+ "!**/__tests__",
20
+ "!**/__mocks__"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/moengage/React-Native"
25
+ },
26
+ "keywords": [
27
+ "moengage",
28
+ "push-notification",
29
+ "react-native",
30
+ "ios",
31
+ "tvos",
32
+ "android",
33
+ "device",
34
+ "events",
35
+ "expo"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "postinstall": "tsc",
40
+ "prepack": "tsc",
41
+ "test": "jest",
42
+ "test:watch": "jest --watch",
43
+ "test:coverage": "jest --coverage"
44
+ },
45
+ "author": {
46
+ "name": "MoEngage",
47
+ "email": "mobiledevs@moengage.com"
48
+ },
49
+ "contributors": [
50
+ "MoEngage <mobiledevs@moengage.com>"
51
+ ],
52
+ "license": "SEE LICENSE IN LICENSE.txt",
53
+ "devDependencies": {
54
+ "@expo/config-plugins": "^5.0.0",
55
+ "@expo/config-types": "^53.0.4",
56
+ "@types/jest": "^29.5.12",
57
+ "@types/node": "^22.15.26",
58
+ "jest": "^29.7.0",
59
+ "ts-jest": "^29.1.2",
60
+ "ts-node": "^10.9.2",
61
+ "typescript": "^5.3.3",
62
+ "expo": "^48.0.0",
63
+ "expo-module-scripts": "^3.0.4",
64
+ "expo-notifications": "^0.31.3"
65
+ },
66
+ "peerDependencies": {
67
+ "react-native-moengage": "^12.1.0"
68
+ }
69
+ }
@@ -0,0 +1,52 @@
1
+ import { StringBoolean } from "@expo/config-plugins/build/android/Manifest";
2
+ import { ManifestApplicationKey } from "./types";
3
+
4
+ export const manifestApplicationKeys: ManifestApplicationKey[] = [
5
+ { "android:fullBackupContent": "@xml/com_moengage_backup_descriptor" },
6
+ { "android:dataExtractionRules": "@xml/com_moengage_data_extraction_rules" }
7
+ ];
8
+
9
+ /**
10
+ * Common intent-filter for MoEngage FCM/Expo Notification services
11
+ */
12
+ export const moEngageMessagingIntentFilter = [
13
+ {
14
+ action: [
15
+ {
16
+ $: {
17
+ 'android:name': 'com.google.firebase.MESSAGING_EVENT',
18
+ },
19
+ },
20
+ ],
21
+ },
22
+ ];
23
+
24
+ /**
25
+ * Service entry for MoEngage Push Notification handling with fcm integration
26
+ */
27
+ export const moEngageFCMServiceName = 'com.moengage.firebase.MoEFireBaseMessagingService';
28
+ export const moEngageFCMServiceEntry = {
29
+ $: {
30
+ 'android:name': moEngageFCMServiceName,
31
+ 'android:exported': 'false' as StringBoolean,
32
+ },
33
+ 'intent-filter': moEngageMessagingIntentFilter,
34
+ };
35
+
36
+ /**
37
+ * Service entry for MoEngage Push Notification handling with expo notification integration
38
+ */
39
+ export const moEngageExpoNotificationServiceName = 'expo.modules.moengage.MoEExpoFireBaseMessagingService';
40
+ export const moEngageExpoNotificationServiceEntry = {
41
+ $: {
42
+ 'android:name': moEngageExpoNotificationServiceName,
43
+ 'android:exported': 'false' as StringBoolean,
44
+ },
45
+ 'intent-filter': moEngageMessagingIntentFilter,
46
+ };
47
+
48
+ export const drawableResourcePath = './android/app/src/main/res/drawable';
49
+ export const xmlValuesResourcePath = './android/app/src/main/res/values';
50
+
51
+ export const googleFirebaseMessagingGroup = 'com.google.firebase';
52
+ export const googleFirebaseMessagingModule = 'firebase-messaging';
@@ -0,0 +1 @@
1
+ export type ManifestApplicationKey = { [key: string]: string };
@@ -0,0 +1,74 @@
1
+ import { ManifestApplication } from "@expo/config-plugins/build/android/Manifest";
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ /**
6
+ * Add serviceEntry to the main application manifest if does not already exist.
7
+ */
8
+ export function addServiceToManifestIfNotExist(
9
+ mainApplication: ManifestApplication,
10
+ serviceEntry: any,
11
+ serviceName: string
12
+ ): ManifestApplication {
13
+ console.log('Checking if service exists:', serviceName);
14
+ if (!isServiceExistInManifest(mainApplication, serviceName)) {
15
+ console.log('Adding service to manifest:', serviceName);
16
+ addServiceToManifest(mainApplication, serviceEntry);
17
+ }
18
+ return mainApplication;
19
+ }
20
+
21
+ /**
22
+ * Return true if given serviceName already exists in the main application manifest, else false.
23
+ */
24
+ export function isServiceExistInManifest(mainApplication: ManifestApplication, serviceName: string): boolean {
25
+ const isServiceExist = (mainApplication.service || []).find(
26
+ (service: any) => service.$ && service.$['android:name'] === serviceName
27
+ );
28
+ console.log('Service exists:', serviceName, isServiceExist !== undefined);
29
+ return isServiceExist !== undefined;
30
+ }
31
+
32
+ /**
33
+ * Add serviceEntry to the main application manifest.
34
+ */
35
+ export function addServiceToManifest(mainApplication: ManifestApplication, serviceEntry: any) {
36
+ console.log('Adding service entry:', serviceEntry);
37
+ if (!mainApplication.service) {
38
+ mainApplication.service = [];
39
+ }
40
+ mainApplication.service.push(serviceEntry);
41
+ }
42
+
43
+ /**
44
+ * Copy file from src to dest after creating dest directory if not exist
45
+ */
46
+ export async function copyFile(src: string, dest: string) {
47
+ let destPath = dest;
48
+ if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) {
49
+ destPath = path.join(dest, path.basename(src));
50
+ }
51
+ const destDir = path.dirname(destPath);
52
+ if (!fs.existsSync(destDir)) {
53
+ fs.mkdirSync(destDir, { recursive: true });
54
+ }
55
+ console.log('Copying file from', src, 'to', destPath);
56
+ fs.copyFileSync(src, destPath);
57
+ }
58
+
59
+ /**
60
+ * Add a dependency to the dependencies block in app/build.gradle if not already present.
61
+ */
62
+ export function addDependencyToGradle(contents: string, group: string, module: string, version: string): string {
63
+ const dependency = `implementation("${group}:${module}:${version}")`;
64
+ console.log('Checking for dependency:', group, module, version);
65
+ if (!contents.includes(`${group}:${module}`)) {
66
+ console.log('Adding dependency:', dependency);
67
+ return contents.replace(
68
+ /dependencies\s*\{/,
69
+ match => `${match}\n ${dependency}`
70
+ );
71
+ }
72
+ console.log('Dependency already present:', dependency);
73
+ return contents;
74
+ }
@@ -0,0 +1,127 @@
1
+ import { withAndroidManifest, ConfigPlugin, AndroidConfig, withDangerousMod, withAppBuildGradle } from '@expo/config-plugins';
2
+ import {
3
+ drawableResourcePath,
4
+ manifestApplicationKeys,
5
+ xmlValuesResourcePath,
6
+ moEngageFCMServiceEntry,
7
+ moEngageFCMServiceName,
8
+ moEngageExpoNotificationServiceEntry,
9
+ moEngageExpoNotificationServiceName,
10
+ googleFirebaseMessagingGroup,
11
+ googleFirebaseMessagingModule
12
+ } from './constants';
13
+ import { MoEngagePluginProps } from '../types';
14
+ import { addServiceToManifestIfNotExist, copyFile, addDependencyToGradle } from './utils';
15
+
16
+ const packageJsonFile = require('../../package.json');
17
+
18
+ const withMoEngageAndroid: ConfigPlugin<MoEngagePluginProps> = (config, props) => {
19
+ console.log('Running withMoEngageAndroid');
20
+ config = withMoEngageAndroidManifest(config, props);
21
+ config = withMoEngageInitialisationConfigResource(config, props);
22
+ config = withMoEngageDependencies(config, props);
23
+ return config;
24
+ };
25
+
26
+
27
+ /**
28
+ * Update the AndroidManifest.xml file with MoEngage specific configurations.
29
+ */
30
+ const withMoEngageAndroidManifest: ConfigPlugin<MoEngagePluginProps> = (config, props) => {
31
+ console.log('Running withMoEngageAndroidManifest');
32
+ if (!props.android.disableMoEngageDefaultBackupFile) {
33
+ withAndroidManifest(config, config => {
34
+ const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
35
+ console.log('Adding manifest application keys');
36
+ manifestApplicationKeys.forEach(item => {
37
+ const key = Object.keys(item)[0];
38
+ if (key !== undefined) {
39
+ const value = (item as any)[key];
40
+ if (!mainApplication.$) {
41
+ const androidName = (mainApplication as any)["android:name"] || "";
42
+ mainApplication.$ = { "android:name": androidName };
43
+ }
44
+
45
+ if (!(key in mainApplication.$)) {
46
+ mainApplication.$[key] = value;
47
+ }
48
+ }
49
+ });
50
+
51
+ return config;
52
+ });
53
+ }
54
+
55
+ if (props.android.shouldIncludeMoEngageFirebaseMessagingService) {
56
+ withAndroidManifest(config, config => {
57
+ const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
58
+ if (props.android.isExpoNotificationIntegration) {
59
+ console.log('Adding Expo Notification Service to manifest');
60
+ addServiceToManifestIfNotExist(
61
+ mainApplication,
62
+ moEngageExpoNotificationServiceEntry,
63
+ moEngageExpoNotificationServiceName
64
+ );
65
+ } else {
66
+ console.log('Adding MoEngage FCM Service to manifest');
67
+ addServiceToManifestIfNotExist(
68
+ mainApplication,
69
+ moEngageFCMServiceEntry,
70
+ moEngageFCMServiceName
71
+ );
72
+ }
73
+
74
+ return config;
75
+ });
76
+ }
77
+
78
+ return config;
79
+ };
80
+
81
+
82
+ /**
83
+ * Copy all the required resources for MoEngage initialisation to native Android module.
84
+ */
85
+ const withMoEngageInitialisationConfigResource: ConfigPlugin<MoEngagePluginProps> = (config, props) => {
86
+ console.log('Running withMoEngageInitialisationConfigResource');
87
+ return withDangerousMod(config, [
88
+ 'android',
89
+ async (config) => {
90
+ if (props.android.smallIconPath !== undefined) {
91
+ console.log('Copying small icon:', props.android.smallIconPath);
92
+ await copyFile(props.android.smallIconPath, drawableResourcePath);
93
+ }
94
+ if (props.android.largeIconPath !== undefined) {
95
+ console.log('Copying large icon:', props.android.largeIconPath);
96
+ await copyFile(props.android.largeIconPath, drawableResourcePath);
97
+ }
98
+
99
+ console.log('Copying config file:', props.android.configFilePath);
100
+ await copyFile(props.android.configFilePath, xmlValuesResourcePath);
101
+ return config;
102
+ },
103
+ ]);
104
+ };
105
+
106
+ /**
107
+ * Add required dependencies to app/build.gradle
108
+ */
109
+ const withMoEngageDependencies: ConfigPlugin<MoEngagePluginProps> = (config, props) => {
110
+ if (props.android.includeFirebaseMessagingDependencies) {
111
+ const firebaseMessagingVersion = packageJsonFile.dependencyVersions.firebaseMessaging;
112
+ console.log('Adding Firebase Messaging dependency:', firebaseMessagingVersion);
113
+ return withAppBuildGradle(config, gradleConfig => {
114
+ gradleConfig.modResults.contents = addDependencyToGradle(
115
+ gradleConfig.modResults.contents,
116
+ googleFirebaseMessagingGroup,
117
+ googleFirebaseMessagingModule,
118
+ firebaseMessagingVersion
119
+ );
120
+ return gradleConfig;
121
+ });
122
+ }
123
+
124
+ return config;
125
+ };
126
+
127
+ export default withMoEngageAndroid;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Constants used in the MoEngage iOS implementation
3
+ *
4
+ * This file contains all the constants needed for configuring the MoEngage SDK for iOS,
5
+ * including target names, file lists, and pod dependencies.
6
+ */
7
+
8
+ /**
9
+ * Target names for the extension targets in Xcode
10
+ * These names are used when creating extension targets in the Xcode project
11
+ * and must match the names used in entitlements and Info.plist files
12
+ */
13
+ export const MOENGAGE_IOS_RICH_PUSH_TARGET = 'MoEngageExpoRichPush';
14
+ export const MOENGAGE_IOS_PUSH_TEMPLATE_TARGET = 'MoEngageExpoPushTemplates';
15
+ export const MOENGAGE_IOS_LIVE_ACTIVITY_TARGET = 'MoEngageExpoLiveActivity';
16
+
17
+ /**
18
+ * Files required for the Rich Push Notification Service Extension
19
+ * These files are copied from the plugin's resources to the Xcode project
20
+ * and added to the Rich Push target
21
+ */
22
+ export const MOENGAGE_IOS_RICH_PUSH_FILES = [
23
+ 'NotificationService.swift',
24
+ `${MOENGAGE_IOS_RICH_PUSH_TARGET}-Info.plist`,
25
+ `${MOENGAGE_IOS_RICH_PUSH_TARGET}.entitlements`
26
+ ];
27
+
28
+ /**
29
+ * Files required for the Push Templates Notification Content Extension
30
+ * These files are copied from the plugin's resources to the Xcode project
31
+ * and added to the Push Templates target
32
+ */
33
+ export const MOENGAGE_IOS_PUSH_TEMPLATE_FILES = [
34
+ 'MainInterface.storyboard',
35
+ 'NotificationViewController.swift',
36
+ `${MOENGAGE_IOS_PUSH_TEMPLATE_TARGET}-Info.plist`,
37
+ `${MOENGAGE_IOS_PUSH_TEMPLATE_TARGET}.entitlements`
38
+ ];
39
+
40
+ /**
41
+ * CocoaPods dependencies for each feature
42
+ * These pod specifications are added to the Podfile for each target
43
+ * that requires the functionality
44
+ */
45
+ export const MOENGAGE_IOS_NOTIFICATION_SERVICE_POD = 'MoEngage-iOS-SDK/RichNotification'; // For Rich Push Notification Service Extension
46
+ export const MOENGAGE_IOS_PUSH_TEMPLATE_POD = 'MoEngage-iOS-SDK/RichNotification'; // For Push Templates Notification Content Extension
47
+ export const MOENGAGE_IOS_DEVICE_TRIGGER_POD = 'MoEngage-iOS-SDK/RealTimeTrigger'; // For Device-based campaign triggers
48
+ export const MOENGAGE_IOS_LIVE_ACTIVITY_POD = 'MoEngage-iOS-SDK/LiveActivity'; // For LiveActivity support
@@ -0,0 +1,38 @@
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
+ import { MoEngagePluginProps } from "../types";
3
+ import { withMoEngageInfoPlist } from "./withInfoPlist";
4
+ import { withMoEngageEntitlements } from "./withEntitlements";
5
+ import { withMoEngageXcodeProject } from "./withXcodeProject";
6
+ import { withMoEngageDangerousMod } from "./withDangerousMod";
7
+ import * as constants from './constants';
8
+
9
+ /**
10
+ * The main iOS plugin that applies all modifiers to set up MoEngage iOS integration
11
+ *
12
+ * This is the primary entry point for the MoEngage Expo plugin for iOS.
13
+ * It orchestrates the entire configuration process by applying each of the
14
+ * specialized modifiers in the correct order:
15
+ *
16
+ * 1. withMoEngageInfoPlist - Updates Info.plist with MoEngage configuration
17
+ * 2. withMoEngageEntitlements - Sets up app groups and other entitlements
18
+ * 3. withMoEngageXcodeProject - Configures the Xcode project with extension targets
19
+ * 4. withMoEngageDangerousMod - Performs file operations and updates Podfile
20
+ *
21
+ * @param config - The Expo config object
22
+ * @param props - The MoEngage plugin properties
23
+ * @returns The fully configured Expo config
24
+ */
25
+ export const withMoEngageIos: ConfigPlugin<MoEngagePluginProps> = (config, props) => {
26
+ config = withMoEngageInfoPlist(config, props);
27
+ config = withMoEngageEntitlements(config, props);
28
+ config = withMoEngageXcodeProject(config, props);
29
+ config = withMoEngageDangerousMod(config, props);
30
+ return config;
31
+ };
32
+
33
+ // Export all constants and individual modifiers for advanced usage
34
+ export { constants };
35
+ export { withMoEngageInfoPlist, withMoEngageEntitlements, withMoEngageXcodeProject, withMoEngageDangerousMod };
36
+
37
+ // Default export
38
+ export default withMoEngageIos;
@@ -0,0 +1,265 @@
1
+ import { ConfigPlugin, withDangerousMod } from "@expo/config-plugins";
2
+ import { MoEngagePluginProps } from "../types";
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import {
6
+ MOENGAGE_IOS_RICH_PUSH_TARGET,
7
+ MOENGAGE_IOS_PUSH_TEMPLATE_TARGET,
8
+ MOENGAGE_IOS_LIVE_ACTIVITY_TARGET,
9
+ MOENGAGE_IOS_RICH_PUSH_FILES,
10
+ MOENGAGE_IOS_PUSH_TEMPLATE_FILES,
11
+ MOENGAGE_IOS_NOTIFICATION_SERVICE_POD,
12
+ MOENGAGE_IOS_PUSH_TEMPLATE_POD,
13
+ MOENGAGE_IOS_DEVICE_TRIGGER_POD,
14
+ MOENGAGE_IOS_LIVE_ACTIVITY_POD
15
+ } from './constants';
16
+
17
+ const plist = require('plist');
18
+
19
+ /**
20
+ * MoEngage Expo plugin for iOS - Dangerous modifications
21
+ *
22
+ * This plugin handles file operations that are not directly exposed by the Expo Config Plugin API,
23
+ * including copying Rich Push, Push Templates, and LiveActivity files, and updating the Podfile
24
+ * to include the necessary MoEngage pods for each target.
25
+ *
26
+ * @param config - The Expo config object
27
+ * @param props - The MoEngage plugin properties
28
+ * @returns The updated config object
29
+ */
30
+ export const withMoEngageDangerousMod: ConfigPlugin<MoEngagePluginProps> = (config, props) => {
31
+ return withDangerousMod(config, [
32
+ 'ios',
33
+ (config) => {
34
+ if (process.env['EXPO_TV']) {
35
+ // Skip modifications for tvOS
36
+ console.log(`Skipping extension targets copy for tvOS`);
37
+ return config;
38
+ }
39
+
40
+ const applicationPods: string[] = []
41
+ const projectRoot = config.modRequest.projectRoot;
42
+ const { apple } = props;
43
+ const shouldAddRichPushExtension = apple.richPushNotificationEnabled || apple.pushNotificationImpressionTrackingEnabled || apple.pushTemplatesEnabled;
44
+
45
+ /**
46
+ * Extract the KeychainGroupName from the MoEngage configuration file
47
+ *
48
+ * The keychain group is needed for secure data sharing between the main app
49
+ * and extension targets. If present, it's added to the entitlements files
50
+ * for the extension targets.
51
+ */
52
+ let keychainGroupValue = ''
53
+ try {
54
+ const configFilePath = path.join(config.modRequest.projectRoot, props.apple.configFilePath);
55
+ if (fs.existsSync(configFilePath)) {
56
+ const configPlist = plist.parse(fs.readFileSync(configFilePath, 'utf8')) as { [key: string]: any };
57
+ keychainGroupValue = configPlist['KeychainGroupName'] as string;
58
+ } else {
59
+ const message = `MoEngage configuration does not exist`;
60
+ console.error(message);
61
+ throw new Error(message);
62
+ }
63
+ } catch (e) {
64
+ const message = `Could not import MoEngage configuration: ${e}`;
65
+ console.error(message);
66
+ throw new Error(message);
67
+ }
68
+
69
+ /**
70
+ * Set up the Rich Push Notification Service Extension
71
+ *
72
+ * This extension handles rich push notifications with media attachments and
73
+ * custom notification UI. We:
74
+ * 1. Create the extension directory if it doesn't exist
75
+ * 2. Copy template files from the plugin's resources
76
+ * 3. Update entitlements with keychain access if configured
77
+ * 4. Modify the Podfile to include the MoEngage rich notification dependency
78
+ */
79
+ if (shouldAddRichPushExtension) {
80
+ // Copy Rich Push files to project path.
81
+ const absoluteSource = require.resolve('react-native-expo-moengage/apple/RichPush/NotificationService.swift');
82
+ const sourcePath = path.dirname(absoluteSource);
83
+ const destinationPath = `${projectRoot}/ios/${MOENGAGE_IOS_RICH_PUSH_TARGET}`;
84
+ if (!fs.existsSync(`${destinationPath}`)) {
85
+ fs.mkdirSync(`${destinationPath}`, { recursive: true });
86
+ }
87
+ for (const file of MOENGAGE_IOS_RICH_PUSH_FILES) {
88
+ // Copy keychain group to extension entitlements
89
+ if (keychainGroupValue && keychainGroupValue.length > 0 && file.endsWith('.entitlements')) {
90
+ const entitlements = plist.parse(fs.readFileSync(`${sourcePath}/${file}`, 'utf8'));
91
+ entitlements['keychain-access-groups'] = [keychainGroupValue]
92
+ fs.writeFileSync(`${destinationPath}/${file}`, plist.build(entitlements))
93
+ continue
94
+ }
95
+ fs.copyFileSync(`${sourcePath}/${file}`, `${destinationPath}/${file}`);
96
+ }
97
+
98
+ // Modify Podfile to include `MoEngageRichNotification`.
99
+ const podfilePath = `${projectRoot}/ios/Podfile`;
100
+ if (fs.existsSync(podfilePath)) {
101
+ const podfile = fs.readFileSync(podfilePath, 'utf8');
102
+ if (!podfile.includes(MOENGAGE_IOS_RICH_PUSH_TARGET) || !podfile.includes(MOENGAGE_IOS_NOTIFICATION_SERVICE_POD)) {
103
+ const notificationServiceTarget =
104
+ [
105
+ ``,
106
+ `target '${MOENGAGE_IOS_RICH_PUSH_TARGET}' do`,
107
+ ` use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']`,
108
+ ` use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']`,
109
+ ` pod '${MOENGAGE_IOS_NOTIFICATION_SERVICE_POD}'`,
110
+ `end`
111
+ ].join('\n');
112
+ fs.appendFileSync(podfilePath, notificationServiceTarget);
113
+ }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Set up the Push Templates Notification Content Extension
119
+ *
120
+ * This extension enables custom notification UI templates for push notifications.
121
+ * The setup process includes:
122
+ * 1. Creating the extension directory if it doesn't exist
123
+ * 2. Copying template files from the plugin's resources
124
+ * 3. Updating entitlements with keychain access if configured
125
+ * 4. Modifying the Podfile to include the MoEngage push template dependency
126
+ */
127
+ if (apple.pushTemplatesEnabled) {
128
+ // Copy Push Template files to project path.
129
+ const absoluteSource = require.resolve('react-native-expo-moengage/apple/PushTemplates/NotificationViewController.swift');
130
+ const sourcePath = path.dirname(absoluteSource);
131
+ const destinationPath = `${projectRoot}/ios/${MOENGAGE_IOS_PUSH_TEMPLATE_TARGET}`;
132
+ if (!fs.existsSync(`${destinationPath}`)) {
133
+ fs.mkdirSync(`${destinationPath}`, { recursive: true });
134
+ }
135
+ for (const file of MOENGAGE_IOS_PUSH_TEMPLATE_FILES) {
136
+ // Copy keychain group to extension entitlements
137
+ if (keychainGroupValue && keychainGroupValue.length > 0 && file.endsWith('.entitlements')) {
138
+ const entitlements = plist.parse(fs.readFileSync(`${sourcePath}/${file}`, 'utf8'));
139
+ entitlements['keychain-access-groups'] = [keychainGroupValue]
140
+ fs.writeFileSync(`${destinationPath}/${file}`, plist.build(entitlements))
141
+ continue
142
+ }
143
+ fs.copyFileSync(`${sourcePath}/${file}`, `${destinationPath}/${file}`);
144
+ }
145
+
146
+ // Modify Podfile to include `MoEngageRichNotification`.
147
+ const podfilePath = `${projectRoot}/ios/Podfile`;
148
+ if (fs.existsSync(podfilePath)) {
149
+ const podfile = fs.readFileSync(podfilePath, 'utf8');
150
+ if (!podfile.includes(MOENGAGE_IOS_PUSH_TEMPLATE_TARGET) || !podfile.includes(MOENGAGE_IOS_PUSH_TEMPLATE_POD)) {
151
+ const pushTemplateTarget =
152
+ [
153
+ ``,
154
+ `target '${MOENGAGE_IOS_PUSH_TEMPLATE_TARGET}' do`,
155
+ ` use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']`,
156
+ ` use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']`,
157
+ ` pod '${MOENGAGE_IOS_PUSH_TEMPLATE_POD}'`,
158
+ `end`
159
+ ].join('\n');
160
+ fs.appendFileSync(podfilePath, pushTemplateTarget);
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Set up Live Activity support
167
+ *
168
+ * Live Activities allow apps to display persistent, interactive notifications
169
+ * on the lock screen and in the Dynamic Island on supported devices.
170
+ *
171
+ * This section:
172
+ * 1. Adds the MoEngageLiveActivity pod to the main app target
173
+ * 2. Creates a LiveActivity extension target in the Podfile if needed
174
+ * 3. Configures the pod dependencies for the LiveActivity extension
175
+ */
176
+ if (apple.liveActivityTargetPath && apple.liveActivityTargetPath.length) {
177
+ // Add MoEngageLiveActivity pod to the Podfile
178
+ applicationPods.push(` pod '${MOENGAGE_IOS_LIVE_ACTIVITY_POD}'`)
179
+ const podfilePath = path.join(projectRoot, 'ios', 'Podfile');
180
+ if (fs.existsSync(podfilePath)) {
181
+ const podfile = fs.readFileSync(podfilePath, 'utf8');
182
+
183
+ if (!podfile.includes(MOENGAGE_IOS_LIVE_ACTIVITY_TARGET)) {
184
+ const liveActivityTarget = [
185
+ ``,
186
+ `target '${MOENGAGE_IOS_LIVE_ACTIVITY_TARGET}' do`,
187
+ ` use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']`,
188
+ ` use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']`,
189
+ ` pod '${MOENGAGE_IOS_LIVE_ACTIVITY_POD}'`,
190
+ `end`
191
+ ].join('\n');
192
+
193
+ fs.appendFileSync(podfilePath, liveActivityTarget);
194
+ console.log(`Added MoEngageLiveActivity pod to Podfile for ${MOENGAGE_IOS_LIVE_ACTIVITY_TARGET} target`);
195
+ }
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Set up Device Trigger support
201
+ *
202
+ * Device Triggers allow campaigns to be triggered based on device events,
203
+ * without requiring server communication. This is useful for real-time
204
+ * engagement based on user actions within the app.
205
+ *
206
+ * If enabled, we add the MoEngage RealTimeTrigger pod to the main application.
207
+ */
208
+ if (apple.deviceTriggerEnabled) {
209
+ applicationPods.push(` pod '${MOENGAGE_IOS_DEVICE_TRIGGER_POD}'`)
210
+ }
211
+
212
+ /**
213
+ * Update the main application's Podfile with MoEngage dependencies
214
+ *
215
+ * This section adds any accumulated MoEngage pods to the main application target
216
+ * in the Podfile. It:
217
+ * 1. Finds the main application target section in the Podfile
218
+ * 2. Locates a suitable insertion point before existing dependencies
219
+ * 3. Inserts the MoEngage pod declarations
220
+ */
221
+ if (applicationPods && applicationPods.length) {
222
+ const podfilePath = `${projectRoot}/ios/Podfile`;
223
+ if (fs.existsSync(podfilePath)) {
224
+ const podfile = fs.readFileSync(podfilePath, 'utf8');
225
+ if (!podfile.includes(MOENGAGE_IOS_DEVICE_TRIGGER_POD)) {
226
+ // Find the main target and add the pod there
227
+ const lines = podfile.split('\n');
228
+ let mainTargetIndex = -1;
229
+
230
+ // Find the main application target
231
+ for (let i = 0; i < lines.length; i++) {
232
+ if (lines[i]?.includes('target') && lines[i]?.includes('do') &&
233
+ !lines[i]?.includes(MOENGAGE_IOS_RICH_PUSH_TARGET) &&
234
+ !lines[i]?.includes(MOENGAGE_IOS_PUSH_TEMPLATE_TARGET)) {
235
+ mainTargetIndex = i;
236
+ break;
237
+ }
238
+ }
239
+
240
+ if (mainTargetIndex !== -1) {
241
+ // Find the end of the dependency section
242
+ let dependenciesEndIndex = -1;
243
+ for (let i = mainTargetIndex; i < lines.length; i++) {
244
+ if (lines[i]?.includes('use_native_modules') ||
245
+ lines[i]?.includes('use_react_native') ||
246
+ lines[i]?.includes('post_install')) {
247
+ dependenciesEndIndex = i;
248
+ break;
249
+ }
250
+ }
251
+
252
+ if (dependenciesEndIndex !== -1) {
253
+ // Insert the application pods before the end of the target
254
+ lines.splice(dependenciesEndIndex, 0, ...applicationPods);
255
+ fs.writeFileSync(podfilePath, lines.join('\n'));
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ return config;
263
+ },
264
+ ]);
265
+ };