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
@@ -1,3 +1,16 @@
1
- import type { ConfigPlugin } from '@expo/config-plugins';
1
+ import type { ConfigPlugin, XcodeProject } from '@expo/config-plugins';
2
2
  import type { CustomerIOPluginOptionsIOS } from '../types/cio-types';
3
+ export type CopyGoogleServicePlistOptions = {
4
+ iosPath: string;
5
+ appName: string | undefined;
6
+ googleServicesFile: string | undefined;
7
+ expoIosGoogleServicesFileSet: boolean;
8
+ xcodeProject: XcodeProject;
9
+ };
10
+ /**
11
+ * Copies the FCM GoogleService-Info.plist into the iOS project (when needed) and registers it
12
+ * in the Xcode project's Resources group. Idempotent — no-ops if a plist is already present
13
+ * at either of the two well-known locations Expo / RN Firebase use.
14
+ */
15
+ export declare function copyGoogleServicePlistFile({ iosPath, appName, googleServicesFile, expoIosGoogleServicesFileSet, xcodeProject, }: CopyGoogleServicePlistOptions): void;
3
16
  export declare const withGoogleServicesJsonFile: ConfigPlugin<CustomerIOPluginOptionsIOS>;
@@ -1,3 +1,54 @@
1
- import type { ConfigPlugin } from '@expo/config-plugins';
2
- import type { CustomerIOPluginOptionsIOS } from '../types/cio-types';
1
+ import type { ConfigPlugin, XcodeProject } from '@expo/config-plugins';
2
+ import type { CustomerIOPluginOptionsIOS, RichPushConfig } from '../types/cio-types';
3
3
  export declare const withCioNotificationsXcodeProject: ConfigPlugin<CustomerIOPluginOptionsIOS>;
4
+ export type AddNseTargetToXcodeProjectOptions = {
5
+ appleTeamId?: string;
6
+ bundleIdentifier?: string;
7
+ iosDeploymentTarget?: string;
8
+ appGroupId?: string;
9
+ };
10
+ /**
11
+ * Mutates the parsed XcodeProject to register the rich-push NotificationService
12
+ * extension target: creates a PBXGroup for its files, registers the group under
13
+ * the project's top-level group, adds the app_extension target, wires three
14
+ * build phases (Sources, Resources, Frameworks), configures the target's build
15
+ * settings (DEVELOPMENT_TEAM, IPHONEOS_DEPLOYMENT_TARGET, code-sign style,
16
+ * Swift version, and CODE_SIGN_ENTITLEMENTS when `appGroupId` is set), and
17
+ * stamps the development team attribute on both the new target and the
18
+ * project's main target attributes.
19
+ *
20
+ * Idempotent — returns the project unchanged if a target named
21
+ * `CIO_NOTIFICATION_TARGET_NAME` is already present.
22
+ */
23
+ export declare function addNotificationServiceExtensionToXcodeProject(xcodeProject: XcodeProject, options: AddNseTargetToXcodeProjectOptions): XcodeProject;
24
+ /**
25
+ * Pure string transform: substitutes the `{{BUNDLE_VERSION}}` and
26
+ * `{{BUNDLE_SHORT_VERSION}}` placeholders in the NSE Info.plist template.
27
+ * Either or both may be provided; missing values leave the corresponding
28
+ * placeholder untouched.
29
+ */
30
+ export declare function applyBundleVersionToNsePlist(content: string, payload: {
31
+ bundleVersion?: string;
32
+ bundleShortVersion?: string;
33
+ }): string;
34
+ /**
35
+ * Pure string transform: substitutes the `{{APP_GROUP_ID_BUILDER_LINE}}`
36
+ * placeholder in NotificationService.swift with either the configured
37
+ * appGroupId builder line or an empty string.
38
+ */
39
+ export declare function applyAppGroupIdToNotificationService(content: string, appGroupId?: string): string;
40
+ /**
41
+ * Pure string transform: substitutes the `{{CDP_API_KEY}}` and `{{REGION}}`
42
+ * placeholders in the NSE Env.swift template. Missing or invalid region
43
+ * falls back to `Region.US` and logs a warning.
44
+ */
45
+ export declare function applyRichPushConfigToEnv(content: string, richPushConfig?: RichPushConfig): string;
46
+ /**
47
+ * Pure string transform: substitutes every PushService.swift placeholder
48
+ * (`{{REGISTER_SNIPPET}}`, `{{CDP_API_KEY}}`, `{{REGION}}`,
49
+ * `{{AUTO_TRACK_PUSH_EVENTS}}`, `{{AUTO_FETCH_DEVICE_TOKEN}}`,
50
+ * `{{SHOW_PUSH_APP_IN_FOREGROUND}}`, `{{APP_GROUP_ID_BUILDER_LINE}}`) using
51
+ * the configured push-notification options. Validation of the rich-push
52
+ * config (cdpApiKey/region required) is the wrapper's responsibility.
53
+ */
54
+ export declare function applyConfigToPushFile(content: string, options: CustomerIOPluginOptionsIOS): string;
@@ -0,0 +1,7 @@
1
+ export type ResolvedRNSDK = {
2
+ packageDir: string;
3
+ packageJsonPath: string;
4
+ };
5
+ export declare function tryReadRNVersion(fromDir: string): string | null;
6
+ export declare function tryResolveRNSDK(fromDir: string): ResolvedRNSDK | null;
7
+ export declare function resolveRNSDK(fromDir: string): ResolvedRNSDK;
@@ -0,0 +1,3 @@
1
+ import type { ConfigPlugin } from '@expo/config-plugins';
2
+ export declare function writeExpoVersion(projectRoot: string): void;
3
+ export declare const withExpoVersion: ConfigPlugin;
@@ -9,92 +9,102 @@ import { logger } from '../utils/logger';
9
9
  export const DEFAULT_LOW_PRIORITY = -10;
10
10
 
11
11
 
12
- export const withAndroidManifestUpdates: ConfigPlugin<
13
- CustomerIOPluginOptionsAndroid
14
- > = (configOuter, options) => {
15
- return withAndroidManifest(configOuter, (props) => {
16
- const application = props.modResults.manifest
17
- .application as ManifestApplication[];
18
- const customerIOMessagingpush =
19
- 'io.customer.messagingpush.CustomerIOFirebaseMessagingService';
12
+ export function modifyAndroidManifestApplication(
13
+ application: ManifestApplication[],
14
+ options: CustomerIOPluginOptionsAndroid
15
+ ): ManifestApplication[] {
16
+ const customerIOMessagingpush =
17
+ 'io.customer.messagingpush.CustomerIOFirebaseMessagingService';
20
18
 
21
- if (!application[0].service) {
22
- application[0].service = [];
23
- }
19
+ if (!application[0].service) {
20
+ application[0].service = [];
21
+ }
24
22
 
25
- const existingServiceIndex = application[0].service.findIndex(
26
- (service) => service.$['android:name'] === customerIOMessagingpush
27
- );
23
+ const existingServiceIndex = application[0].service.findIndex(
24
+ (service) => service.$['android:name'] === customerIOMessagingpush
25
+ );
28
26
 
29
- if (existingServiceIndex === -1) {
30
- // Intent filter structure for Firebase messaging service
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- const intentFilter: any = {
33
- action: [
34
- {
35
- $: {
36
- 'android:name': 'com.google.firebase.MESSAGING_EVENT',
37
- },
27
+ if (existingServiceIndex === -1) {
28
+ // Intent filter structure for Firebase messaging service
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ const intentFilter: any = {
31
+ action: [
32
+ {
33
+ $: {
34
+ 'android:name': 'com.google.firebase.MESSAGING_EVENT',
38
35
  },
39
- ],
40
- };
41
-
42
- // Handle priority based on setHighPriorityPushHandler value
43
- if (options.setHighPriorityPushHandler === true) {
44
- // High priority - no priority attribute means default high priority
45
- logger.info(
46
- 'Successfully set CustomerIO push handler as high priority in AndroidManifest.xml'
47
- );
48
- } else if (options.setHighPriorityPushHandler === false) {
49
- // Low priority - set fixed priority
50
- intentFilter.$ = {
51
- 'android:priority': DEFAULT_LOW_PRIORITY.toString(),
52
- };
53
- logger.info(
54
- `Successfully set CustomerIO push handler as low priority (${DEFAULT_LOW_PRIORITY}) in AndroidManifest.xml`
55
- );
56
- }
57
-
58
- application[0].service.push({
59
- '$': {
60
- 'android:name': customerIOMessagingpush,
61
- 'android:exported': 'false',
62
36
  },
63
- 'intent-filter': [intentFilter],
64
- });
65
- } else if (options.setHighPriorityPushHandler === true) {
66
- // Service exists, need to ensure it becomes high priority (remove priority attribute)
67
- const existingService = application[0].service[existingServiceIndex];
37
+ ],
38
+ };
68
39
 
69
- if (existingService['intent-filter'] && existingService['intent-filter'].length > 0) {
70
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
- const intentFilter = existingService['intent-filter'][0] as any;
72
- if (intentFilter.$ && intentFilter.$['android:priority']) {
73
- delete intentFilter.$['android:priority'];
74
- logger.info(
75
- 'Successfully updated existing CustomerIO push handler to high priority in AndroidManifest.xml'
76
- );
77
- }
78
- }
40
+ // Handle priority based on setHighPriorityPushHandler value
41
+ if (options.setHighPriorityPushHandler === true) {
42
+ // High priority - no priority attribute means default high priority
43
+ logger.info(
44
+ 'Successfully set CustomerIO push handler as high priority in AndroidManifest.xml'
45
+ );
79
46
  } else if (options.setHighPriorityPushHandler === false) {
80
- // Service exists, update to low priority
81
- const existingService = application[0].service[existingServiceIndex];
47
+ // Low priority - set fixed priority
48
+ intentFilter.$ = {
49
+ 'android:priority': DEFAULT_LOW_PRIORITY.toString(),
50
+ };
51
+ logger.info(
52
+ `Successfully set CustomerIO push handler as low priority (${DEFAULT_LOW_PRIORITY}) in AndroidManifest.xml`
53
+ );
54
+ }
82
55
 
83
- // Update existing service intent-filter with fixed priority
84
- if (existingService['intent-filter'] && existingService['intent-filter'].length > 0) {
85
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
- const intentFilter = existingService['intent-filter'][0] as any;
87
- if (!intentFilter.$) {
88
- intentFilter.$ = {};
89
- }
90
- intentFilter.$['android:priority'] = DEFAULT_LOW_PRIORITY.toString();
56
+ application[0].service.push({
57
+ '$': {
58
+ 'android:name': customerIOMessagingpush,
59
+ 'android:exported': 'false',
60
+ },
61
+ 'intent-filter': [intentFilter],
62
+ });
63
+ } else if (options.setHighPriorityPushHandler === true) {
64
+ // Service exists, need to ensure it becomes high priority (remove priority attribute)
65
+ const existingService = application[0].service[existingServiceIndex];
66
+
67
+ if (existingService['intent-filter'] && existingService['intent-filter'].length > 0) {
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ const intentFilter = existingService['intent-filter'][0] as any;
70
+ if (intentFilter.$ && intentFilter.$['android:priority']) {
71
+ delete intentFilter.$['android:priority'];
91
72
  logger.info(
92
- `Successfully updated existing CustomerIO push handler to low priority (${DEFAULT_LOW_PRIORITY}) in AndroidManifest.xml`
73
+ 'Successfully updated existing CustomerIO push handler to high priority in AndroidManifest.xml'
93
74
  );
94
75
  }
95
76
  }
77
+ } else if (options.setHighPriorityPushHandler === false) {
78
+ // Service exists, update to low priority
79
+ const existingService = application[0].service[existingServiceIndex];
80
+
81
+ // Update existing service intent-filter with fixed priority
82
+ if (existingService['intent-filter'] && existingService['intent-filter'].length > 0) {
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ const intentFilter = existingService['intent-filter'][0] as any;
85
+ if (!intentFilter.$) {
86
+ intentFilter.$ = {};
87
+ }
88
+ intentFilter.$['android:priority'] = DEFAULT_LOW_PRIORITY.toString();
89
+ logger.info(
90
+ `Successfully updated existing CustomerIO push handler to low priority (${DEFAULT_LOW_PRIORITY}) in AndroidManifest.xml`
91
+ );
92
+ }
93
+ }
96
94
 
97
- props.modResults.manifest.application = application;
95
+ return application;
96
+ }
97
+
98
+ export const withAndroidManifestUpdates: ConfigPlugin<
99
+ CustomerIOPluginOptionsAndroid
100
+ > = (configOuter, options) => {
101
+ return withAndroidManifest(configOuter, (props) => {
102
+ const application = props.modResults.manifest
103
+ .application as ManifestApplication[];
104
+ props.modResults.manifest.application = modifyAndroidManifestApplication(
105
+ application,
106
+ options
107
+ );
98
108
  return props;
99
109
  });
100
110
  };
@@ -8,21 +8,23 @@ import {
8
8
  import type { CustomerIOPluginOptionsAndroid } from '../types/cio-types';
9
9
  import { logger } from '../utils/logger';
10
10
 
11
+ export function modifyAppBuildGradle(contents: string): string {
12
+ const regex = new RegExp(CIO_APP_GOOGLE_SNIPPET);
13
+ if (regex.test(contents)) {
14
+ logger.info('app/build.gradle snippet already exists. Skipping...');
15
+ return contents;
16
+ }
17
+ return contents.replace(
18
+ CIO_APP_APPLY_REGEX,
19
+ `$1\n${CIO_APP_GOOGLE_SNIPPET}`
20
+ );
21
+ }
22
+
11
23
  export const withAppGoogleServices: ConfigPlugin<
12
24
  CustomerIOPluginOptionsAndroid
13
25
  > = (configOuter) => {
14
26
  return withAppBuildGradle(configOuter, (props) => {
15
- const regex = new RegExp(CIO_APP_GOOGLE_SNIPPET);
16
- const match = props.modResults.contents.match(regex);
17
- if (!match) {
18
- props.modResults.contents = props.modResults.contents.replace(
19
- CIO_APP_APPLY_REGEX,
20
- `$1\n${CIO_APP_GOOGLE_SNIPPET}`
21
- );
22
- } else {
23
- logger.info('app/build.gradle snippet already exists. Skipping...');
24
- }
25
-
27
+ props.modResults.contents = modifyAppBuildGradle(props.modResults.contents);
26
28
  return props;
27
29
  });
28
30
  };
@@ -5,38 +5,40 @@ import { logger } from '../utils/logger';
5
5
  import { FileManagement } from './../helpers/utils/fileManagement';
6
6
  import type { CustomerIOPluginOptionsAndroid } from './../types/cio-types';
7
7
 
8
- export const withGoogleServicesJSON: ConfigPlugin<
9
- CustomerIOPluginOptionsAndroid
10
- > = (configOuter, cioProps) => {
11
- return withProjectBuildGradle(configOuter, (props) => {
12
- const options: CustomerIOPluginOptionsAndroid = {
13
- androidPath: props.modRequest.platformProjectRoot,
14
- googleServicesFile: cioProps?.googleServicesFile,
15
- };
16
- const { androidPath, googleServicesFile } = options;
17
- if (!FileManagement.exists(`${androidPath}/app/google-services.json`)) {
18
- if (googleServicesFile && FileManagement.exists(googleServicesFile)) {
19
- try {
20
- FileManagement.copyFile(
21
- googleServicesFile,
22
- `${androidPath}/app/google-services.json`
23
- );
24
- } catch {
25
- logger.info(
26
- `There was an error copying your google-services.json file. You can copy it manually into ${androidPath}/app/google-services.json`
27
- );
28
- }
29
- } else {
30
- logger.info(
31
- `The Google Services file provided in ${googleServicesFile} doesn't seem to exist. You can copy it manually into ${androidPath}/app/google-services.json`
32
- );
33
- }
34
- } else {
8
+ export function copyGoogleServicesFile(
9
+ androidPath: string,
10
+ googleServicesFile: string | undefined
11
+ ): void {
12
+ const destination = `${androidPath}/app/google-services.json`;
13
+
14
+ if (FileManagement.exists(destination)) {
15
+ logger.info(`File already exists: ${destination}. Skipping...`);
16
+ return;
17
+ }
18
+
19
+ if (googleServicesFile && FileManagement.exists(googleServicesFile)) {
20
+ try {
21
+ FileManagement.copyFile(googleServicesFile, destination);
22
+ } catch {
35
23
  logger.info(
36
- `File already exists: ${androidPath}/app/google-services.json. Skipping...`
24
+ `There was an error copying your google-services.json file. You can copy it manually into ${destination}`
37
25
  );
38
26
  }
27
+ } else {
28
+ logger.info(
29
+ `The Google Services file provided in ${googleServicesFile} doesn't seem to exist. You can copy it manually into ${destination}`
30
+ );
31
+ }
32
+ }
39
33
 
34
+ export const withGoogleServicesJSON: ConfigPlugin<
35
+ CustomerIOPluginOptionsAndroid
36
+ > = (configOuter, cioProps) => {
37
+ return withProjectBuildGradle(configOuter, (props) => {
38
+ copyGoogleServicesFile(
39
+ props.modRequest.platformProjectRoot,
40
+ cioProps?.googleServicesFile
41
+ );
40
42
  return props;
41
43
  });
42
44
  };
@@ -6,6 +6,28 @@ import type { CustomerIOPluginLocationOptions } from '../types/cio-types';
6
6
 
7
7
  const CUSTOMERIO_LOCATION_ENABLED_KEY = 'customerio_location_enabled';
8
8
 
9
+ export function modifyGradleProperties(
10
+ items: PropertiesItem[]
11
+ ): PropertiesItem[] {
12
+ const existingIndex = items.findIndex(
13
+ (item) => item.type === 'property' && item.key === CUSTOMERIO_LOCATION_ENABLED_KEY
14
+ );
15
+
16
+ const newItem: PropertiesItem = {
17
+ type: 'property',
18
+ key: CUSTOMERIO_LOCATION_ENABLED_KEY,
19
+ value: 'true',
20
+ };
21
+
22
+ if (existingIndex >= 0) {
23
+ items[existingIndex] = newItem;
24
+ } else {
25
+ items.push(newItem);
26
+ }
27
+
28
+ return items;
29
+ }
30
+
9
31
  /**
10
32
  * Adds or updates customerio_location_enabled in android/gradle.properties when location.enabled is true.
11
33
  * The Customer.io React Native SDK reads this to enable the location native module.
@@ -19,23 +41,7 @@ export const withLocationGradleProperties: ConfigPlugin<{
19
41
 
20
42
  return withGradleProperties(config, (config) => {
21
43
  const items = config.modResults as PropertiesItem[];
22
- const existingIndex = items.findIndex(
23
- (item) => item.type === 'property' && item.key === CUSTOMERIO_LOCATION_ENABLED_KEY
24
- );
25
-
26
- const newItem: PropertiesItem = {
27
- type: 'property',
28
- key: CUSTOMERIO_LOCATION_ENABLED_KEY,
29
- value: 'true',
30
- };
31
-
32
- if (existingIndex >= 0) {
33
- items[existingIndex] = newItem;
34
- } else {
35
- items.push(newItem);
36
- }
37
-
38
- config.modResults = items;
44
+ config.modResults = modifyGradleProperties(items);
39
45
  return config;
40
46
  });
41
47
  };
@@ -36,6 +36,30 @@ const getLocationInitOptions = (
36
36
  trackingMode: sdkConfig?.location?.trackingMode,
37
37
  });
38
38
 
39
+ const SDK_INITIALIZER_CLASS = 'CustomerIOSDKInitializer';
40
+ const SDK_INITIALIZER_PACKAGE = 'io.customer.sdk.expo';
41
+ const SDK_INITIALIZER_FILE = `${SDK_INITIALIZER_CLASS}.kt`;
42
+ const SDK_INITIALIZER_IMPORT = `import ${SDK_INITIALIZER_PACKAGE}.${SDK_INITIALIZER_CLASS}`;
43
+
44
+ /**
45
+ * Pure string transform: given the existing MainApplication contents, returns the contents
46
+ * with the CustomerIOSDKInitializer import and onCreate call injected (idempotent — if the
47
+ * initialize call is already present, the call-injection step is skipped).
48
+ */
49
+ export function injectCustomerIOInitializerIntoMainApplication(
50
+ contents: string
51
+ ): string {
52
+ let next = addImportToFile(contents, SDK_INITIALIZER_IMPORT);
53
+ if (!next.includes(CIO_NATIVE_SDK_INITIALIZE_CALL)) {
54
+ next = addCodeToMethod(
55
+ next,
56
+ CIO_MAINAPPLICATION_ONCREATE_REGEX,
57
+ CIO_NATIVE_SDK_INITIALIZE_SNIPPET
58
+ );
59
+ }
60
+ return next;
61
+ }
62
+
39
63
  /**
40
64
  * Setup CustomerIOSDKInitializer for Android auto initialization
41
65
  */
@@ -44,30 +68,16 @@ const setupCustomerIOSDKInitializer = (
44
68
  sdkConfig: NativeSDKConfig,
45
69
  location?: CustomerIOPluginLocationOptions,
46
70
  ): string => {
47
- const SDK_INITIALIZER_CLASS = 'CustomerIOSDKInitializer';
48
- const SDK_INITIALIZER_PACKAGE = 'io.customer.sdk.expo';
49
-
50
- const SDK_INITIALIZER_FILE = `${SDK_INITIALIZER_CLASS}.kt`;
51
- const SDK_INITIALIZER_IMPORT = `import ${SDK_INITIALIZER_PACKAGE}.${SDK_INITIALIZER_CLASS}`;
52
-
53
71
  const locationOptions = getLocationInitOptions(location, sdkConfig);
54
- let content = config.modResults.contents;
55
72
 
56
73
  try {
57
74
  // Always regenerate the CustomerIOSDKInitializer file to reflect config changes
58
75
  copyTemplateFile(config, SDK_INITIALIZER_FILE, SDK_INITIALIZER_PACKAGE, (content) =>
59
76
  patchNativeSDKInitializer(content, PLATFORM.ANDROID, sdkConfig, locationOptions)
60
77
  );
61
- // Add import if not already present
62
- content = addImportToFile(content, SDK_INITIALIZER_IMPORT);
63
- // Add initialization code to onCreate if not already present
64
- if (!content.includes(CIO_NATIVE_SDK_INITIALIZE_CALL)) {
65
- content = addCodeToMethod(content, CIO_MAINAPPLICATION_ONCREATE_REGEX, CIO_NATIVE_SDK_INITIALIZE_SNIPPET);
66
- }
78
+ return injectCustomerIOInitializerIntoMainApplication(config.modResults.contents);
67
79
  } catch (error) {
68
80
  logger.warn(`Could not setup ${SDK_INITIALIZER_CLASS}:`, error);
69
81
  return config.modResults.contents;
70
82
  }
71
-
72
- return content;
73
83
  };
@@ -7,7 +7,7 @@ import type { CustomerIOPluginOptionsAndroid } from '../types/cio-types';
7
7
  /**
8
8
  * Adds a metadata entry to the Android manifest if it doesn't already exist
9
9
  */
10
- const addMetadataIfNotExists = (
10
+ export const addMetadataIfNotExists = (
11
11
  application: ManifestApplication,
12
12
  name: string,
13
13
  value: string
@@ -23,6 +23,40 @@ function shouldDisableAndroid16Support(
23
23
  return isExpoVersion53OrLower(config);
24
24
  }
25
25
 
26
+ /**
27
+ * Pure string transform: injects an androidx resolution-strategy block into the
28
+ * project-level build.gradle's `allprojects { ... }` section when
29
+ * `disableAndroid16Support` is true. Idempotent — returns input unchanged if the
30
+ * snippet is already present, or if the flag is false.
31
+ */
32
+ export function modifyProjectBuildGradleAndroid16Support(
33
+ contents: string,
34
+ options: { disableAndroid16Support: boolean }
35
+ ): string {
36
+ if (!options.disableAndroid16Support) {
37
+ return contents;
38
+ }
39
+
40
+ if (contents.includes('androidx.core:core-ktx:1.13.1')) {
41
+ return contents;
42
+ }
43
+
44
+ const resolutionStrategy = `
45
+ configurations.all {
46
+ resolutionStrategy {
47
+ // Disable Android 16 support by forcing older androidx versions
48
+ // Compatible with API 35 and AGP 8.8.2 (prevents API 36/AGP 8.9.1+ requirement)
49
+ force 'androidx.core:core-ktx:1.13.1'
50
+ force 'androidx.lifecycle:lifecycle-process:2.8.7'
51
+ }
52
+ }`;
53
+
54
+ return contents.replace(
55
+ /allprojects\s*\{/,
56
+ `allprojects {${resolutionStrategy}`
57
+ );
58
+ }
59
+
26
60
  /**
27
61
  * Adds dependency resolution strategy to force specific androidx versions.
28
62
  * This disables Android 16 support for apps using Expo SDK 53 or older gradle versions.
@@ -38,34 +72,10 @@ export function withProjectBuildGradle(
38
72
  androidOptions?: CustomerIOPluginOptionsAndroid
39
73
  ): ExpoConfig {
40
74
  return withExpoProjectBuildGradle(config, (config) => {
41
- const { modResults } = config;
42
-
43
- // Check if Android 16 support should be disabled
44
- if (!shouldDisableAndroid16Support(config, androidOptions)) {
45
- return config;
46
- }
47
-
48
- // Skip if already applied
49
- if (modResults.contents.includes('androidx.core:core-ktx:1.13.1')) {
50
- return config;
51
- }
52
-
53
- const resolutionStrategy = `
54
- configurations.all {
55
- resolutionStrategy {
56
- // Disable Android 16 support by forcing older androidx versions
57
- // Compatible with API 35 and AGP 8.8.2 (prevents API 36/AGP 8.9.1+ requirement)
58
- force 'androidx.core:core-ktx:1.13.1'
59
- force 'androidx.lifecycle:lifecycle-process:2.8.7'
60
- }
61
- }`;
62
-
63
- // Add resolution strategy inside allprojects block
64
- modResults.contents = modResults.contents.replace(
65
- /allprojects\s*\{/,
66
- `allprojects {${resolutionStrategy}`
75
+ config.modResults.contents = modifyProjectBuildGradleAndroid16Support(
76
+ config.modResults.contents,
77
+ { disableAndroid16Support: shouldDisableAndroid16Support(config, androidOptions) }
67
78
  );
68
-
69
79
  return config;
70
80
  });
71
81
  }
@@ -7,19 +7,24 @@ import {
7
7
  } from './../helpers/constants/android';
8
8
  import type { CustomerIOPluginOptionsAndroid } from './../types/cio-types';
9
9
 
10
+ export function modifyProjectBuildGradleForGoogleServices(contents: string): string {
11
+ const regex = new RegExp(CIO_PROJECT_GOOGLE_SNIPPET);
12
+ if (regex.test(contents)) {
13
+ return contents;
14
+ }
15
+ return contents.replace(
16
+ CIO_PROJECT_BUILDSCRIPTS_REGEX,
17
+ `$1\n${CIO_PROJECT_GOOGLE_SNIPPET}`
18
+ );
19
+ }
20
+
10
21
  export const withProjectGoogleServices: ConfigPlugin<
11
22
  CustomerIOPluginOptionsAndroid
12
23
  > = (configOuter) => {
13
24
  return withProjectBuildGradle(configOuter, (props) => {
14
- const regex = new RegExp(CIO_PROJECT_GOOGLE_SNIPPET);
15
- const match = props.modResults.contents.match(regex);
16
- if (!match) {
17
- props.modResults.contents = props.modResults.contents.replace(
18
- CIO_PROJECT_BUILDSCRIPTS_REGEX,
19
- `$1\n${CIO_PROJECT_GOOGLE_SNIPPET}`
20
- );
21
- }
22
-
25
+ props.modResults.contents = modifyProjectBuildGradleForGoogleServices(
26
+ props.modResults.contents
27
+ );
23
28
  return props;
24
29
  });
25
30
  };