customerio-expo-plugin 3.3.0 → 3.5.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 (107) hide show
  1. package/package.json +8 -1
  2. package/plugin/lib/commonjs/android/withAndroidManifestUpdates.js +64 -59
  3. package/plugin/lib/commonjs/android/withAndroidManifestUpdates.js.map +1 -1
  4. package/plugin/lib/commonjs/android/withAppGoogleServices.js +10 -7
  5. package/plugin/lib/commonjs/android/withAppGoogleServices.js.map +1 -1
  6. package/plugin/lib/commonjs/android/withGoogleServicesJSON.js +18 -21
  7. package/plugin/lib/commonjs/android/withGoogleServicesJSON.js.map +1 -1
  8. package/plugin/lib/commonjs/android/withLocationGradleProperties.js +16 -12
  9. package/plugin/lib/commonjs/android/withLocationGradleProperties.js.map +1 -1
  10. package/plugin/lib/commonjs/android/withMainApplicationModifications.js +19 -12
  11. package/plugin/lib/commonjs/android/withMainApplicationModifications.js.map +1 -1
  12. package/plugin/lib/commonjs/android/withNotificationChannelMetadata.js +2 -1
  13. package/plugin/lib/commonjs/android/withNotificationChannelMetadata.js.map +1 -1
  14. package/plugin/lib/commonjs/android/withProjectBuildGradle.js +29 -25
  15. package/plugin/lib/commonjs/android/withProjectBuildGradle.js.map +1 -1
  16. package/plugin/lib/commonjs/android/withProjectGoogleServices.js +9 -5
  17. package/plugin/lib/commonjs/android/withProjectGoogleServices.js.map +1 -1
  18. package/plugin/lib/commonjs/helpers/constants/ios.js +76 -8
  19. package/plugin/lib/commonjs/helpers/constants/ios.js.map +1 -1
  20. package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js +76 -31
  21. package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js.map +1 -1
  22. package/plugin/lib/commonjs/index.js +7 -0
  23. package/plugin/lib/commonjs/index.js.map +1 -1
  24. package/plugin/lib/commonjs/ios/withAppDelegateModifications.js +47 -33
  25. package/plugin/lib/commonjs/ios/withAppDelegateModifications.js.map +1 -1
  26. package/plugin/lib/commonjs/ios/withCIOIosSwift.js +44 -54
  27. package/plugin/lib/commonjs/ios/withCIOIosSwift.js.map +1 -1
  28. package/plugin/lib/commonjs/ios/withGoogleServicesJsonFile.js +46 -30
  29. package/plugin/lib/commonjs/ios/withGoogleServicesJsonFile.js.map +1 -1
  30. package/plugin/lib/commonjs/ios/withNotificationsXcodeProject.js +192 -122
  31. package/plugin/lib/commonjs/ios/withNotificationsXcodeProject.js.map +1 -1
  32. package/plugin/lib/commonjs/postInstallHelper.js +58 -11
  33. package/plugin/lib/commonjs/postInstallHelper.js.map +1 -1
  34. package/plugin/lib/commonjs/utils/resolveRNSDK.js +97 -0
  35. package/plugin/lib/commonjs/utils/resolveRNSDK.js.map +1 -0
  36. package/plugin/lib/commonjs/utils/writeExpoVersion.js +56 -0
  37. package/plugin/lib/commonjs/utils/writeExpoVersion.js.map +1 -0
  38. package/plugin/lib/module/android/withAndroidManifestUpdates.js +61 -58
  39. package/plugin/lib/module/android/withAndroidManifestUpdates.js.map +1 -1
  40. package/plugin/lib/module/android/withAppGoogleServices.js +9 -7
  41. package/plugin/lib/module/android/withAppGoogleServices.js.map +1 -1
  42. package/plugin/lib/module/android/withGoogleServicesJSON.js +17 -21
  43. package/plugin/lib/module/android/withGoogleServicesJSON.js.map +1 -1
  44. package/plugin/lib/module/android/withLocationGradleProperties.js +15 -12
  45. package/plugin/lib/module/android/withLocationGradleProperties.js.map +1 -1
  46. package/plugin/lib/module/android/withMainApplicationModifications.js +18 -12
  47. package/plugin/lib/module/android/withMainApplicationModifications.js.map +1 -1
  48. package/plugin/lib/module/android/withNotificationChannelMetadata.js +1 -1
  49. package/plugin/lib/module/android/withNotificationChannelMetadata.js.map +1 -1
  50. package/plugin/lib/module/android/withProjectBuildGradle.js +28 -25
  51. package/plugin/lib/module/android/withProjectBuildGradle.js.map +1 -1
  52. package/plugin/lib/module/android/withProjectGoogleServices.js +8 -5
  53. package/plugin/lib/module/android/withProjectGoogleServices.js.map +1 -1
  54. package/plugin/lib/module/helpers/constants/ios.js +75 -8
  55. package/plugin/lib/module/helpers/constants/ios.js.map +1 -1
  56. package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js +74 -31
  57. package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js.map +1 -1
  58. package/plugin/lib/module/index.js +7 -0
  59. package/plugin/lib/module/index.js.map +1 -1
  60. package/plugin/lib/module/ios/withAppDelegateModifications.js +45 -33
  61. package/plugin/lib/module/ios/withAppDelegateModifications.js.map +1 -1
  62. package/plugin/lib/module/ios/withCIOIosSwift.js +42 -54
  63. package/plugin/lib/module/ios/withCIOIosSwift.js.map +1 -1
  64. package/plugin/lib/module/ios/withGoogleServicesJsonFile.js +45 -30
  65. package/plugin/lib/module/ios/withGoogleServicesJsonFile.js.map +1 -1
  66. package/plugin/lib/module/ios/withNotificationsXcodeProject.js +187 -122
  67. package/plugin/lib/module/ios/withNotificationsXcodeProject.js.map +1 -1
  68. package/plugin/lib/module/postInstallHelper.js +58 -11
  69. package/plugin/lib/module/postInstallHelper.js.map +1 -1
  70. package/plugin/lib/module/utils/resolveRNSDK.js +88 -0
  71. package/plugin/lib/module/utils/resolveRNSDK.js.map +1 -0
  72. package/plugin/lib/module/utils/writeExpoVersion.js +48 -0
  73. package/plugin/lib/module/utils/writeExpoVersion.js.map +1 -0
  74. package/plugin/lib/typescript/android/withAndroidManifestUpdates.d.ts +2 -0
  75. package/plugin/lib/typescript/android/withAppGoogleServices.d.ts +1 -0
  76. package/plugin/lib/typescript/android/withGoogleServicesJSON.d.ts +1 -0
  77. package/plugin/lib/typescript/android/withLocationGradleProperties.d.ts +2 -0
  78. package/plugin/lib/typescript/android/withMainApplicationModifications.d.ts +6 -0
  79. package/plugin/lib/typescript/android/withNotificationChannelMetadata.d.ts +5 -0
  80. package/plugin/lib/typescript/android/withProjectBuildGradle.d.ts +9 -0
  81. package/plugin/lib/typescript/android/withProjectGoogleServices.d.ts +1 -0
  82. package/plugin/lib/typescript/helpers/constants/ios.d.ts +18 -0
  83. package/plugin/lib/typescript/helpers/utils/injectCIOPodfileCode.d.ts +25 -1
  84. package/plugin/lib/typescript/ios/withAppDelegateModifications.d.ts +13 -0
  85. package/plugin/lib/typescript/ios/withCIOIosSwift.d.ts +11 -0
  86. package/plugin/lib/typescript/ios/withGoogleServicesJsonFile.d.ts +14 -1
  87. package/plugin/lib/typescript/ios/withNotificationsXcodeProject.d.ts +53 -2
  88. package/plugin/lib/typescript/utils/resolveRNSDK.d.ts +7 -0
  89. package/plugin/lib/typescript/utils/writeExpoVersion.d.ts +3 -0
  90. package/plugin/src/android/withAndroidManifestUpdates.ts +83 -73
  91. package/plugin/src/android/withAppGoogleServices.ts +13 -11
  92. package/plugin/src/android/withGoogleServicesJSON.ts +30 -28
  93. package/plugin/src/android/withLocationGradleProperties.ts +23 -17
  94. package/plugin/src/android/withMainApplicationModifications.ts +25 -15
  95. package/plugin/src/android/withNotificationChannelMetadata.ts +1 -1
  96. package/plugin/src/android/withProjectBuildGradle.ts +37 -27
  97. package/plugin/src/android/withProjectGoogleServices.ts +14 -9
  98. package/plugin/src/helpers/constants/ios.ts +87 -8
  99. package/plugin/src/helpers/utils/injectCIOPodfileCode.ts +97 -50
  100. package/plugin/src/index.ts +7 -0
  101. package/plugin/src/ios/withAppDelegateModifications.ts +61 -48
  102. package/plugin/src/ios/withCIOIosSwift.ts +58 -62
  103. package/plugin/src/ios/withGoogleServicesJsonFile.ts +66 -48
  104. package/plugin/src/ios/withNotificationsXcodeProject.ts +257 -207
  105. package/plugin/src/postInstallHelper.js +75 -17
  106. package/plugin/src/utils/resolveRNSDK.ts +118 -0
  107. package/plugin/src/utils/writeExpoVersion.ts +62 -0
@@ -18,6 +18,21 @@ import { isExpoVersion53OrHigher, isFcmPushProvider } from './utils';
18
18
  const PLIST_FILENAME = `${CIO_NOTIFICATION_TARGET_NAME}-Info.plist`;
19
19
  const ENV_FILENAME = 'Env.swift';
20
20
 
21
+ // NSE source files registered in the Xcode group AND copied to the target
22
+ // directory. Single source of truth — both `addNotificationServiceExtensionToXcodeProject`
23
+ // (registers them in the PBXGroup) and `addRichPushXcodeProj` (copies them
24
+ // from the plugin's native-files dir) read the same arrays. Keeping these
25
+ // in sync is load-bearing: the Xcode group must reference the same files
26
+ // that exist on disk, or the build fails with "no such file" / unresolved
27
+ // references.
28
+ const NSE_PLATFORM_SPECIFIC_FILES = ['NotificationService.swift'];
29
+ const NSE_COMMON_FILES = [
30
+ PLIST_FILENAME,
31
+ 'NotificationService.h',
32
+ 'NotificationService.m',
33
+ ENV_FILENAME,
34
+ ];
35
+
21
36
  const TARGETED_DEVICE_FAMILY = `"1,2"`;
22
37
 
23
38
  const addNotificationServiceExtension = async (
@@ -101,109 +116,54 @@ export const withCioNotificationsXcodeProject: ConfigPlugin<
101
116
  });
102
117
  };
103
118
 
104
- const addRichPushXcodeProj = async (
105
- options: CustomerIOPluginOptionsIOS,
106
- xcodeProject: XcodeProject,
107
- ) => {
108
- const {
109
- appleTeamId,
110
- bundleIdentifier,
111
- bundleShortVersion,
112
- bundleVersion,
113
- iosPath,
114
- iosDeploymentTarget,
115
- useFrameworks,
116
- } = options;
119
+ const NSE_ENTITLEMENTS_FILENAME = 'NotificationService.entitlements';
117
120
 
118
- const isFcmProvider = isFcmPushProvider(options);
119
-
120
- await injectCIONotificationPodfileCode(iosPath, useFrameworks, isFcmProvider);
121
+ export type AddNseTargetToXcodeProjectOptions = {
122
+ appleTeamId?: string;
123
+ bundleIdentifier?: string;
124
+ iosDeploymentTarget?: string;
125
+ appGroupId?: string;
126
+ };
121
127
 
122
- // Check if `CIO_NOTIFICATION_TARGET_NAME` group already exist in the project
123
- // If true then skip creating a new group to avoid duplicate folders
128
+ /**
129
+ * Mutates the parsed XcodeProject to register the rich-push NotificationService
130
+ * extension target: creates a PBXGroup for its files, registers the group under
131
+ * the project's top-level group, adds the app_extension target, wires three
132
+ * build phases (Sources, Resources, Frameworks), configures the target's build
133
+ * settings (DEVELOPMENT_TEAM, IPHONEOS_DEPLOYMENT_TARGET, code-sign style,
134
+ * Swift version, and CODE_SIGN_ENTITLEMENTS when `appGroupId` is set), and
135
+ * stamps the development team attribute on both the new target and the
136
+ * project's main target attributes.
137
+ *
138
+ * Idempotent — returns the project unchanged if a target named
139
+ * `CIO_NOTIFICATION_TARGET_NAME` is already present.
140
+ */
141
+ export function addNotificationServiceExtensionToXcodeProject(
142
+ xcodeProject: XcodeProject,
143
+ options: AddNseTargetToXcodeProjectOptions,
144
+ ): XcodeProject {
124
145
  if (xcodeProject.pbxTargetByName(CIO_NOTIFICATION_TARGET_NAME)) {
125
146
  logger.warn(
126
- `${CIO_NOTIFICATION_TARGET_NAME} already exists in project. Skipping...`
147
+ `${CIO_NOTIFICATION_TARGET_NAME} already exists in project. Skipping...`,
127
148
  );
128
- return;
129
- }
130
-
131
- const nsePath = `${iosPath}/${CIO_NOTIFICATION_TARGET_NAME}`;
132
- FileManagement.mkdir(nsePath, {
133
- recursive: true,
134
- });
135
-
136
- const platformSpecificFiles = ['NotificationService.swift'];
137
-
138
- const nseEntitlementsFilename = 'NotificationService.entitlements';
139
- const appGroupId = options.pushNotification?.appGroupId;
140
-
141
- // Write NSE entitlements file only when appGroupId is explicitly configured
142
- if (appGroupId) {
143
- const nseEntitlementsContent = `<?xml version="1.0" encoding="UTF-8"?>
144
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
145
- <plist version="1.0">
146
- <dict>
147
- <key>com.apple.security.application-groups</key>
148
- <array>
149
- <string>${appGroupId}</string>
150
- </array>
151
- </dict>
152
- </plist>
153
- `;
154
- FileManagement.writeFile(`${nsePath}/${nseEntitlementsFilename}`, nseEntitlementsContent);
149
+ return xcodeProject;
155
150
  }
156
151
 
157
- const commonFiles = [
158
- PLIST_FILENAME,
159
- 'NotificationService.h',
160
- 'NotificationService.m',
161
- ENV_FILENAME,
162
- ];
163
-
164
- const getTargetFile = (filename: string) => `${nsePath}/${filename}`;
165
- // Copy platform-specific files
166
- platformSpecificFiles.forEach((filename) => {
167
- const targetFile = getTargetFile(filename);
168
- FileManagement.copyFile(
169
- `${getIosNativeFilesPath()}/${isFcmProvider ? 'fcm' : 'apn'
170
- }/${filename}`,
171
- targetFile
172
- );
173
- });
174
-
175
- // Copy common files
176
- commonFiles.forEach((filename) => {
177
- const targetFile = getTargetFile(filename);
178
- FileManagement.copyFile(
179
- `${getIosNativeFilesPath()}/common/${filename}`,
180
- targetFile
181
- );
182
- });
183
-
184
- /* MODIFY COPIED EXTENSION FILES */
185
- const infoPlistTargetFile = getTargetFile(PLIST_FILENAME);
186
- updateNseInfoPlist({
187
- bundleVersion,
188
- bundleShortVersion,
189
- infoPlistTargetFile,
190
- });
191
- updateNseEnv(getTargetFile(ENV_FILENAME), options.pushNotification?.env);
192
- updateNseNotificationService(getTargetFile('NotificationService.swift'), options.pushNotification?.appGroupId);
152
+ const { appleTeamId, bundleIdentifier, iosDeploymentTarget, appGroupId } = options;
193
153
 
194
154
  // The entitlements file is generated (not copied from source), so it's listed separately
195
- // for the Xcode group so it appears in the file navigator
155
+ // for the Xcode group so it appears in the file navigator.
196
156
  const allGroupFiles = [
197
- ...platformSpecificFiles,
198
- ...commonFiles,
199
- ...(appGroupId ? [nseEntitlementsFilename] : []),
157
+ ...NSE_PLATFORM_SPECIFIC_FILES,
158
+ ...NSE_COMMON_FILES,
159
+ ...(appGroupId ? [NSE_ENTITLEMENTS_FILENAME] : []),
200
160
  ];
201
161
 
202
162
  // Create new PBXGroup for the extension
203
163
  const extGroup = xcodeProject.addPbxGroup(
204
164
  allGroupFiles,
205
165
  CIO_NOTIFICATION_TARGET_NAME,
206
- CIO_NOTIFICATION_TARGET_NAME
166
+ CIO_NOTIFICATION_TARGET_NAME,
207
167
  );
208
168
 
209
169
  // Add the new PBXGroup to the top level group. This makes the
@@ -223,20 +183,12 @@ const addRichPushXcodeProj = async (
223
183
  projObjects.PBXTargetDependency = projObjects.PBXTargetDependency || {};
224
184
  projObjects.PBXContainerItemProxy = projObjects.PBXTargetDependency || {};
225
185
 
226
- if (xcodeProject.pbxTargetByName(CIO_NOTIFICATION_TARGET_NAME)) {
227
- logger.warn(
228
- `${CIO_NOTIFICATION_TARGET_NAME} already exists in project. Skipping...`
229
- );
230
- return;
231
- }
232
-
233
- // Add the NSE target
234
- // This also adds PBXTargetDependency and PBXContainerItemProxy
186
+ // Add the NSE target. This also adds PBXTargetDependency and PBXContainerItemProxy.
235
187
  const nseTarget = xcodeProject.addTarget(
236
188
  CIO_NOTIFICATION_TARGET_NAME,
237
189
  'app_extension',
238
190
  CIO_NOTIFICATION_TARGET_NAME,
239
- `${bundleIdentifier}.richpush`
191
+ `${bundleIdentifier}.richpush`,
240
192
  );
241
193
 
242
194
  // Add build phases to the new target
@@ -244,21 +196,10 @@ const addRichPushXcodeProj = async (
244
196
  ['NotificationService.m', 'NotificationService.swift', 'Env.swift'],
245
197
  'PBXSourcesBuildPhase',
246
198
  'Sources',
247
- nseTarget.uuid
248
- );
249
- xcodeProject.addBuildPhase(
250
- [],
251
- 'PBXResourcesBuildPhase',
252
- 'Resources',
253
- nseTarget.uuid
254
- );
255
-
256
- xcodeProject.addBuildPhase(
257
- [],
258
- 'PBXFrameworksBuildPhase',
259
- 'Frameworks',
260
- nseTarget.uuid
199
+ nseTarget.uuid,
261
200
  );
201
+ xcodeProject.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', nseTarget.uuid);
202
+ xcodeProject.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', nseTarget.uuid);
262
203
 
263
204
  // Edit the Deployment info of the target
264
205
  const configurations = xcodeProject.pbxXCBuildConfigurationSection();
@@ -266,17 +207,16 @@ const addRichPushXcodeProj = async (
266
207
  if (
267
208
  typeof configurations[key].buildSettings !== 'undefined' &&
268
209
  configurations[key].buildSettings.PRODUCT_NAME ===
269
- `"${CIO_NOTIFICATION_TARGET_NAME}"`
210
+ `"${CIO_NOTIFICATION_TARGET_NAME}"`
270
211
  ) {
271
212
  const buildSettingsObj = configurations[key].buildSettings;
272
213
  buildSettingsObj.DEVELOPMENT_TEAM = appleTeamId;
273
- buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET =
274
- iosDeploymentTarget || '15.1';
214
+ buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = iosDeploymentTarget || '15.1';
275
215
  buildSettingsObj.TARGETED_DEVICE_FAMILY = TARGETED_DEVICE_FAMILY;
276
216
  buildSettingsObj.CODE_SIGN_STYLE = 'Automatic';
277
217
  buildSettingsObj.SWIFT_VERSION = 4.2;
278
218
  if (appGroupId) {
279
- buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `${CIO_NOTIFICATION_TARGET_NAME}/${nseEntitlementsFilename}`;
219
+ buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `${CIO_NOTIFICATION_TARGET_NAME}/${NSE_ENTITLEMENTS_FILENAME}`;
280
220
  }
281
221
  }
282
222
  }
@@ -284,91 +224,196 @@ const addRichPushXcodeProj = async (
284
224
  // Add development team to the target & the main
285
225
  xcodeProject.addTargetAttribute('DevelopmentTeam', appleTeamId, nseTarget);
286
226
  xcodeProject.addTargetAttribute('DevelopmentTeam', appleTeamId);
287
- };
288
227
 
289
- const updateNseInfoPlist = (payload: {
290
- bundleVersion?: string;
291
- bundleShortVersion?: string;
292
- infoPlistTargetFile: string;
293
- }) => {
294
- const BUNDLE_SHORT_VERSION_RE = /\{\{BUNDLE_SHORT_VERSION\}\}/;
295
- const BUNDLE_VERSION_RE = /\{\{BUNDLE_VERSION\}\}/;
228
+ return xcodeProject;
229
+ }
230
+
231
+ const addRichPushXcodeProj = async (
232
+ options: CustomerIOPluginOptionsIOS,
233
+ xcodeProject: XcodeProject,
234
+ ) => {
235
+ const {
236
+ appleTeamId,
237
+ bundleIdentifier,
238
+ bundleShortVersion,
239
+ bundleVersion,
240
+ iosPath,
241
+ iosDeploymentTarget,
242
+ useFrameworks,
243
+ } = options;
296
244
 
297
- let plistFileString = FileManagement.readFile(payload.infoPlistTargetFile);
245
+ const isFcmProvider = isFcmPushProvider(options);
246
+ const appGroupId = options.pushNotification?.appGroupId;
298
247
 
299
- if (payload.bundleVersion) {
300
- plistFileString = replaceCodeByRegex(
301
- plistFileString,
302
- BUNDLE_VERSION_RE,
303
- payload.bundleVersion
248
+ await injectCIONotificationPodfileCode(iosPath, useFrameworks, isFcmProvider);
249
+
250
+ // Skip the rest of the work if the NSE target is already in place. The pbxproj-mutating
251
+ // helper has its own idempotency check, but bailing out here also avoids redundant file
252
+ // copies and entitlements writes when prebuild re-runs against an already-prepared project.
253
+ if (xcodeProject.pbxTargetByName(CIO_NOTIFICATION_TARGET_NAME)) {
254
+ logger.warn(
255
+ `${CIO_NOTIFICATION_TARGET_NAME} already exists in project. Skipping...`,
304
256
  );
257
+ return;
305
258
  }
306
259
 
307
- if (payload.bundleShortVersion) {
308
- plistFileString = replaceCodeByRegex(
309
- plistFileString,
310
- BUNDLE_SHORT_VERSION_RE,
311
- payload.bundleShortVersion
312
- );
260
+ const nsePath = `${iosPath}/${CIO_NOTIFICATION_TARGET_NAME}`;
261
+ FileManagement.mkdir(nsePath, { recursive: true });
262
+
263
+ // Write NSE entitlements file only when appGroupId is explicitly configured
264
+ if (appGroupId) {
265
+ const nseEntitlementsContent = `<?xml version="1.0" encoding="UTF-8"?>
266
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
267
+ <plist version="1.0">
268
+ <dict>
269
+ <key>com.apple.security.application-groups</key>
270
+ <array>
271
+ <string>${appGroupId}</string>
272
+ </array>
273
+ </dict>
274
+ </plist>
275
+ `;
276
+ FileManagement.writeFile(`${nsePath}/${NSE_ENTITLEMENTS_FILENAME}`, nseEntitlementsContent);
313
277
  }
314
278
 
315
- FileManagement.writeFile(payload.infoPlistTargetFile, plistFileString);
279
+ const getTargetFile = (filename: string) => `${nsePath}/${filename}`;
280
+
281
+ // Copy platform-specific files
282
+ NSE_PLATFORM_SPECIFIC_FILES.forEach((filename) => {
283
+ FileManagement.copyFile(
284
+ `${getIosNativeFilesPath()}/${isFcmProvider ? 'fcm' : 'apn'}/${filename}`,
285
+ getTargetFile(filename),
286
+ );
287
+ });
288
+
289
+ // Copy common files
290
+ NSE_COMMON_FILES.forEach((filename) => {
291
+ FileManagement.copyFile(
292
+ `${getIosNativeFilesPath()}/common/${filename}`,
293
+ getTargetFile(filename),
294
+ );
295
+ });
296
+
297
+ /* MODIFY COPIED EXTENSION FILES */
298
+ updateNseInfoPlist({
299
+ bundleVersion,
300
+ bundleShortVersion,
301
+ infoPlistTargetFile: getTargetFile(PLIST_FILENAME),
302
+ });
303
+ updateNseEnv(getTargetFile(ENV_FILENAME), options.pushNotification?.env);
304
+ updateNseNotificationService(getTargetFile('NotificationService.swift'), appGroupId);
305
+
306
+ // Register the NSE target in the parsed Xcode project
307
+ addNotificationServiceExtensionToXcodeProject(xcodeProject, {
308
+ appleTeamId,
309
+ bundleIdentifier,
310
+ iosDeploymentTarget,
311
+ appGroupId,
312
+ });
316
313
  };
317
314
 
318
- const updateNseNotificationService = (
319
- notificationServiceFile: string,
320
- appGroupId?: string,
321
- ) => {
322
- const APP_GROUP_ID_BUILDER_LINE_RE = /\{\{APP_GROUP_ID_BUILDER_LINE\}\}/;
315
+ /**
316
+ * Pure string transform: substitutes the `{{BUNDLE_VERSION}}` and
317
+ * `{{BUNDLE_SHORT_VERSION}}` placeholders in the NSE Info.plist template.
318
+ * Either or both may be provided; missing values leave the corresponding
319
+ * placeholder untouched.
320
+ */
321
+ export function applyBundleVersionToNsePlist(
322
+ content: string,
323
+ payload: { bundleVersion?: string; bundleShortVersion?: string }
324
+ ): string {
325
+ let next = content;
326
+ if (payload.bundleVersion) {
327
+ next = replaceCodeByRegex(next, /\{\{BUNDLE_VERSION\}\}/, payload.bundleVersion);
328
+ }
329
+ if (payload.bundleShortVersion) {
330
+ next = replaceCodeByRegex(next, /\{\{BUNDLE_SHORT_VERSION\}\}/, payload.bundleShortVersion);
331
+ }
332
+ return next;
333
+ }
334
+
335
+ const updateNseInfoPlist = (payload: {
336
+ bundleVersion?: string;
337
+ bundleShortVersion?: string;
338
+ infoPlistTargetFile: string;
339
+ }) => {
340
+ const next = applyBundleVersionToNsePlist(
341
+ FileManagement.readFile(payload.infoPlistTargetFile),
342
+ payload,
343
+ );
344
+ FileManagement.writeFile(payload.infoPlistTargetFile, next);
345
+ };
323
346
 
324
- let content = FileManagement.readFile(notificationServiceFile);
347
+ /**
348
+ * Pure string transform: substitutes the `{{APP_GROUP_ID_BUILDER_LINE}}`
349
+ * placeholder in NotificationService.swift with either the configured
350
+ * appGroupId builder line or an empty string.
351
+ */
352
+ export function applyAppGroupIdToNotificationService(
353
+ content: string,
354
+ appGroupId?: string
355
+ ): string {
325
356
  const builderLine = appGroupId
326
357
  ? ` .appGroupId(${JSON.stringify(appGroupId)})\n`
327
358
  : '';
328
- content = replaceCodeByRegex(content, APP_GROUP_ID_BUILDER_LINE_RE, builderLine);
329
- FileManagement.writeFile(notificationServiceFile, content);
330
- };
359
+ return replaceCodeByRegex(content, /\{\{APP_GROUP_ID_BUILDER_LINE\}\}/, builderLine);
360
+ }
331
361
 
332
- const updateNseEnv = (
333
- envFileName: string,
334
- richPushConfig?: RichPushConfig
362
+ const updateNseNotificationService = (
363
+ notificationServiceFile: string,
364
+ appGroupId?: string,
335
365
  ) => {
336
- const CDP_API_KEY_RE = /\{\{CDP_API_KEY\}\}/;
337
- const REGION_RE = /\{\{REGION\}\}/;
338
-
339
- let envFileContent = FileManagement.readFile(envFileName);
366
+ const next = applyAppGroupIdToNotificationService(
367
+ FileManagement.readFile(notificationServiceFile),
368
+ appGroupId,
369
+ );
370
+ FileManagement.writeFile(notificationServiceFile, next);
371
+ };
340
372
 
341
- // Use merged config values (config takes precedence over env)
373
+ /**
374
+ * Pure string transform: substitutes the `{{CDP_API_KEY}}` and `{{REGION}}`
375
+ * placeholders in the NSE Env.swift template. Missing or invalid region
376
+ * falls back to `Region.US` and logs a warning.
377
+ */
378
+ export function applyRichPushConfigToEnv(
379
+ content: string,
380
+ richPushConfig?: RichPushConfig,
381
+ ): string {
342
382
  const cdpApiKey = richPushConfig?.cdpApiKey;
343
383
  const region = richPushConfig?.region;
344
384
 
345
- if (!validateRichPushConfig(richPushConfig)) {
346
- return;
347
- }
348
- envFileContent = replaceCodeByRegex(
349
- envFileContent,
350
- CDP_API_KEY_RE,
385
+ let next = replaceCodeByRegex(
386
+ content,
387
+ /\{\{CDP_API_KEY\}\}/,
351
388
  cdpApiKey || 'MISSING_API_KEY',
352
389
  );
353
390
 
354
- // Simplify region mapping with case insensitive keys and fallback for invalid regions
355
391
  const regionKey = region?.toLowerCase() ?? '';
356
- const regionMap = {
357
- us: 'Region.US',
358
- eu: 'Region.EU',
359
- } as const;
392
+ const regionMap = { us: 'Region.US', eu: 'Region.EU' } as const;
360
393
  const mappedRegion = regionMap[regionKey as keyof typeof regionMap];
361
394
  if (!mappedRegion) {
362
395
  logger.warn(
363
396
  `${regionKey} is an invalid region. Please use the values from the docs: https://docs.customer.io/integrations/sdk/expo/getting-started/packages-options/#configuring-the-expo-plugin`
364
397
  );
365
- // Fallback to US if invalid region provided
366
- envFileContent = replaceCodeByRegex(envFileContent, REGION_RE, regionMap.us);
398
+ next = replaceCodeByRegex(next, /\{\{REGION\}\}/, regionMap.us);
367
399
  } else {
368
- envFileContent = replaceCodeByRegex(envFileContent, REGION_RE, mappedRegion);
400
+ next = replaceCodeByRegex(next, /\{\{REGION\}\}/, mappedRegion);
369
401
  }
402
+ return next;
403
+ }
370
404
 
371
- FileManagement.writeFile(envFileName, envFileContent);
405
+ const updateNseEnv = (
406
+ envFileName: string,
407
+ richPushConfig?: RichPushConfig
408
+ ) => {
409
+ if (!validateRichPushConfig(richPushConfig)) {
410
+ return;
411
+ }
412
+ const next = applyRichPushConfigToEnv(
413
+ FileManagement.readFile(envFileName),
414
+ richPushConfig,
415
+ );
416
+ FileManagement.writeFile(envFileName, next);
372
417
  };
373
418
 
374
419
  async function addPushNotificationFile(
@@ -410,79 +455,84 @@ async function addPushNotificationFile(
410
455
  xcodeProject.addSourceFile(`${appName}/${targetFileName}`, null, group);
411
456
  }
412
457
 
413
- const updatePushFile = (
458
+ /**
459
+ * Pure string transform: substitutes every PushService.swift placeholder
460
+ * (`{{REGISTER_SNIPPET}}`, `{{CDP_API_KEY}}`, `{{REGION}}`,
461
+ * `{{AUTO_TRACK_PUSH_EVENTS}}`, `{{AUTO_FETCH_DEVICE_TOKEN}}`,
462
+ * `{{SHOW_PUSH_APP_IN_FOREGROUND}}`, `{{APP_GROUP_ID_BUILDER_LINE}}`) using
463
+ * the configured push-notification options. Validation of the rich-push
464
+ * config (cdpApiKey/region required) is the wrapper's responsibility.
465
+ */
466
+ export function applyConfigToPushFile(
467
+ content: string,
414
468
  options: CustomerIOPluginOptionsIOS,
415
- envFileName: string
416
- ) => {
417
- const REGISTER_RE = /\{\{REGISTER_SNIPPET\}\}/;
418
-
419
- let envFileContent = FileManagement.readFile(envFileName);
420
- const disableNotificationRegistration =
421
- options.pushNotification?.disableNotificationRegistration;
469
+ ): string {
422
470
  const richPushConfig = options.pushNotification?.env;
423
471
  const { cdpApiKey, region } = richPushConfig || {
424
472
  cdpApiKey: 'MISSING_API_KEY',
425
473
  region: undefined,
426
474
  };
427
- if (!validateRichPushConfig(richPushConfig)) {
428
- return;
429
- }
475
+ const disableNotificationRegistration =
476
+ options.pushNotification?.disableNotificationRegistration;
430
477
 
431
- let snippet = '';
432
478
  // unless this property is explicitly set to true, push notification
433
479
  // registration will be added to the AppDelegate
434
- if (disableNotificationRegistration !== true) {
435
- snippet = CIO_REGISTER_PUSHNOTIFICATION_SNIPPET;
436
- }
437
- envFileContent = replaceCodeByRegex(envFileContent, REGISTER_RE, snippet);
480
+ const registerSnippet = disableNotificationRegistration !== true
481
+ ? CIO_REGISTER_PUSHNOTIFICATION_SNIPPET
482
+ : '';
438
483
 
439
- envFileContent = replaceCodeByRegex(
440
- envFileContent,
441
- /\{\{CDP_API_KEY\}\}/,
442
- cdpApiKey,
443
- );
484
+ let next = replaceCodeByRegex(content, /\{\{REGISTER_SNIPPET\}\}/, registerSnippet);
485
+ next = replaceCodeByRegex(next, /\{\{CDP_API_KEY\}\}/, cdpApiKey);
444
486
 
445
487
  if (region) {
446
- envFileContent = replaceCodeByRegex(
447
- envFileContent,
448
- /\{\{REGION\}\}/,
449
- region.toUpperCase()
450
- );
488
+ next = replaceCodeByRegex(next, /\{\{REGION\}\}/, region.toUpperCase());
451
489
  }
452
490
 
453
491
  const autoTrackPushEvents =
454
492
  options.pushNotification?.autoTrackPushEvents !== false;
455
- envFileContent = replaceCodeByRegex(
456
- envFileContent,
493
+ next = replaceCodeByRegex(
494
+ next,
457
495
  /\{\{AUTO_TRACK_PUSH_EVENTS\}\}/,
458
- autoTrackPushEvents.toString()
496
+ autoTrackPushEvents.toString(),
459
497
  );
460
498
 
461
499
  const autoFetchDeviceToken =
462
500
  options.pushNotification?.autoFetchDeviceToken !== false;
463
- envFileContent = replaceCodeByRegex(
464
- envFileContent,
501
+ next = replaceCodeByRegex(
502
+ next,
465
503
  /\{\{AUTO_FETCH_DEVICE_TOKEN\}\}/,
466
- autoFetchDeviceToken.toString()
504
+ autoFetchDeviceToken.toString(),
467
505
  );
468
506
 
469
507
  const showPushAppInForeground =
470
508
  options.pushNotification?.showPushAppInForeground !== false;
471
- envFileContent = replaceCodeByRegex(
472
- envFileContent,
509
+ next = replaceCodeByRegex(
510
+ next,
473
511
  /\{\{SHOW_PUSH_APP_IN_FOREGROUND\}\}/,
474
- showPushAppInForeground.toString()
512
+ showPushAppInForeground.toString(),
475
513
  );
476
514
 
477
515
  const appGroupId = options.pushNotification?.appGroupId;
478
516
  const appGroupIdBuilderLine = appGroupId
479
517
  ? ` .appGroupId(${JSON.stringify(appGroupId)})\n`
480
518
  : '';
481
- envFileContent = replaceCodeByRegex(
482
- envFileContent,
519
+ next = replaceCodeByRegex(
520
+ next,
483
521
  /\{\{APP_GROUP_ID_BUILDER_LINE\}\}/,
484
- appGroupIdBuilderLine
522
+ appGroupIdBuilderLine,
485
523
  );
486
524
 
487
- FileManagement.writeFile(envFileName, envFileContent);
525
+ return next;
526
+ }
527
+
528
+ const updatePushFile = (
529
+ options: CustomerIOPluginOptionsIOS,
530
+ envFileName: string
531
+ ) => {
532
+ const richPushConfig = options.pushNotification?.env;
533
+ if (!validateRichPushConfig(richPushConfig)) {
534
+ return;
535
+ }
536
+ const next = applyConfigToPushFile(FileManagement.readFile(envFileName), options);
537
+ FileManagement.writeFile(envFileName, next);
488
538
  };