customerio-expo-plugin 2.4.0 → 2.6.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 (161) hide show
  1. package/package.json +13 -31
  2. package/plugin/app.plugin.js +1 -1
  3. package/plugin/lib/commonjs/android/withAndroidManifestUpdates.js +4 -4
  4. package/plugin/lib/commonjs/android/withAndroidManifestUpdates.js.map +1 -1
  5. package/plugin/lib/commonjs/android/withCIOAndroid.js +6 -1
  6. package/plugin/lib/commonjs/android/withCIOAndroid.js.map +1 -1
  7. package/plugin/lib/commonjs/android/withGoogleServicesJSON.js +1 -1
  8. package/plugin/lib/commonjs/android/withGoogleServicesJSON.js.map +1 -1
  9. package/plugin/lib/commonjs/android/withMainApplicationModifications.js +45 -0
  10. package/plugin/lib/commonjs/android/withMainApplicationModifications.js.map +1 -0
  11. package/plugin/lib/commonjs/android/withNotificationChannelMetadata.js +1 -1
  12. package/plugin/lib/commonjs/android/withNotificationChannelMetadata.js.map +1 -1
  13. package/plugin/lib/commonjs/android/withProjectStrings.js +14 -7
  14. package/plugin/lib/commonjs/android/withProjectStrings.js.map +1 -1
  15. package/plugin/lib/commonjs/helpers/constants/android.js +7 -1
  16. package/plugin/lib/commonjs/helpers/constants/android.js.map +1 -1
  17. package/plugin/lib/commonjs/helpers/constants/common.js +18 -0
  18. package/plugin/lib/commonjs/helpers/constants/common.js.map +1 -0
  19. package/plugin/lib/commonjs/helpers/constants/ios.js +7 -7
  20. package/plugin/lib/commonjs/helpers/constants/ios.js.map +1 -1
  21. package/plugin/lib/commonjs/helpers/native-files/android/CustomerIOSDKInitializer.kt +64 -0
  22. package/plugin/lib/commonjs/helpers/native-files/ios/CustomerIOSDKInitializer.swift +54 -0
  23. package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js +2 -2
  24. package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js.map +1 -1
  25. package/plugin/lib/commonjs/helpers/utils/patchPluginNativeCode.js +62 -0
  26. package/plugin/lib/commonjs/helpers/utils/patchPluginNativeCode.js.map +1 -0
  27. package/plugin/lib/commonjs/index.js +13 -2
  28. package/plugin/lib/commonjs/index.js.map +1 -1
  29. package/plugin/lib/commonjs/ios/utils.js +1 -1
  30. package/plugin/lib/commonjs/ios/utils.js.map +1 -1
  31. package/plugin/lib/commonjs/ios/withAppDelegateModifications.js +1 -1
  32. package/plugin/lib/commonjs/ios/withAppDelegateModifications.js.map +1 -1
  33. package/plugin/lib/commonjs/ios/withCIOIos.js +17 -11
  34. package/plugin/lib/commonjs/ios/withCIOIos.js.map +1 -1
  35. package/plugin/lib/commonjs/ios/withCIOIosSwift.js +133 -49
  36. package/plugin/lib/commonjs/ios/withCIOIosSwift.js.map +1 -1
  37. package/plugin/lib/commonjs/ios/withGoogleServicesJsonFile.js +1 -1
  38. package/plugin/lib/commonjs/ios/withGoogleServicesJsonFile.js.map +1 -1
  39. package/plugin/lib/commonjs/ios/withNotificationsXcodeProject.js +17 -19
  40. package/plugin/lib/commonjs/ios/withNotificationsXcodeProject.js.map +1 -1
  41. package/plugin/lib/commonjs/ios/withXcodeProject.js +1 -1
  42. package/plugin/lib/commonjs/ios/withXcodeProject.js.map +1 -1
  43. package/plugin/lib/commonjs/postInstallHelper.js.map +1 -1
  44. package/plugin/lib/commonjs/types/cio-types.js.map +1 -1
  45. package/plugin/lib/commonjs/utils/android.js +109 -0
  46. package/plugin/lib/commonjs/utils/android.js.map +1 -0
  47. package/plugin/lib/commonjs/utils/config.js +43 -0
  48. package/plugin/lib/commonjs/utils/config.js.map +1 -0
  49. package/plugin/lib/commonjs/utils/plugin.js +49 -0
  50. package/plugin/lib/commonjs/utils/plugin.js.map +1 -0
  51. package/plugin/lib/commonjs/utils/validation.js +44 -0
  52. package/plugin/lib/commonjs/utils/validation.js.map +1 -0
  53. package/plugin/lib/commonjs/utils/xcode.js +67 -0
  54. package/plugin/lib/commonjs/utils/xcode.js.map +1 -0
  55. package/plugin/lib/module/android/withAndroidManifestUpdates.js +4 -4
  56. package/plugin/lib/module/android/withAndroidManifestUpdates.js.map +1 -1
  57. package/plugin/lib/module/android/withCIOAndroid.js +6 -1
  58. package/plugin/lib/module/android/withCIOAndroid.js.map +1 -1
  59. package/plugin/lib/module/android/withGoogleServicesJSON.js +1 -1
  60. package/plugin/lib/module/android/withGoogleServicesJSON.js.map +1 -1
  61. package/plugin/lib/module/android/withMainApplicationModifications.js +38 -0
  62. package/plugin/lib/module/android/withMainApplicationModifications.js.map +1 -0
  63. package/plugin/lib/module/android/withNotificationChannelMetadata.js +1 -1
  64. package/plugin/lib/module/android/withNotificationChannelMetadata.js.map +1 -1
  65. package/plugin/lib/module/android/withProjectStrings.js +13 -6
  66. package/plugin/lib/module/android/withProjectStrings.js.map +1 -1
  67. package/plugin/lib/module/helpers/constants/android.js +6 -0
  68. package/plugin/lib/module/helpers/constants/android.js.map +1 -1
  69. package/plugin/lib/module/helpers/constants/common.js +12 -0
  70. package/plugin/lib/module/helpers/constants/common.js.map +1 -0
  71. package/plugin/lib/module/helpers/constants/ios.js +6 -6
  72. package/plugin/lib/module/helpers/constants/ios.js.map +1 -1
  73. package/plugin/lib/module/helpers/native-files/android/CustomerIOSDKInitializer.kt +64 -0
  74. package/plugin/lib/module/helpers/native-files/ios/CustomerIOSDKInitializer.swift +54 -0
  75. package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js +2 -2
  76. package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js.map +1 -1
  77. package/plugin/lib/module/helpers/utils/patchPluginNativeCode.js +57 -0
  78. package/plugin/lib/module/helpers/utils/patchPluginNativeCode.js.map +1 -0
  79. package/plugin/lib/module/index.js +14 -2
  80. package/plugin/lib/module/index.js.map +1 -1
  81. package/plugin/lib/module/ios/utils.js +1 -2
  82. package/plugin/lib/module/ios/utils.js.map +1 -1
  83. package/plugin/lib/module/ios/withAppDelegateModifications.js +3 -3
  84. package/plugin/lib/module/ios/withAppDelegateModifications.js.map +1 -1
  85. package/plugin/lib/module/ios/withCIOIos.js +17 -11
  86. package/plugin/lib/module/ios/withCIOIos.js.map +1 -1
  87. package/plugin/lib/module/ios/withCIOIosSwift.js +134 -50
  88. package/plugin/lib/module/ios/withCIOIosSwift.js.map +1 -1
  89. package/plugin/lib/module/ios/withGoogleServicesJsonFile.js +2 -2
  90. package/plugin/lib/module/ios/withGoogleServicesJsonFile.js.map +1 -1
  91. package/plugin/lib/module/ios/withNotificationsXcodeProject.js +18 -20
  92. package/plugin/lib/module/ios/withNotificationsXcodeProject.js.map +1 -1
  93. package/plugin/lib/module/ios/withXcodeProject.js +1 -1
  94. package/plugin/lib/module/ios/withXcodeProject.js.map +1 -1
  95. package/plugin/lib/module/postInstallHelper.js.map +1 -1
  96. package/plugin/lib/module/types/cio-types.js.map +1 -1
  97. package/plugin/lib/module/utils/android.js +100 -0
  98. package/plugin/lib/module/utils/android.js.map +1 -0
  99. package/plugin/lib/module/utils/config.js +38 -0
  100. package/plugin/lib/module/utils/config.js.map +1 -0
  101. package/plugin/lib/module/utils/plugin.js +38 -0
  102. package/plugin/lib/module/utils/plugin.js.map +1 -0
  103. package/plugin/lib/module/utils/validation.js +39 -0
  104. package/plugin/lib/module/utils/validation.js.map +1 -0
  105. package/plugin/lib/module/utils/xcode.js +60 -0
  106. package/plugin/lib/module/utils/xcode.js.map +1 -0
  107. package/plugin/lib/typescript/android/withCIOAndroid.d.ts +2 -2
  108. package/plugin/lib/typescript/android/withMainApplicationModifications.d.ts +3 -0
  109. package/plugin/lib/typescript/android/withProjectStrings.d.ts +2 -1
  110. package/plugin/lib/typescript/helpers/constants/android.d.ts +3 -0
  111. package/plugin/lib/typescript/helpers/constants/common.d.ts +11 -0
  112. package/plugin/lib/typescript/helpers/constants/ios.d.ts +3 -1
  113. package/plugin/lib/typescript/helpers/utils/patchPluginNativeCode.d.ts +7 -0
  114. package/plugin/lib/typescript/ios/utils.d.ts +2 -2
  115. package/plugin/lib/typescript/ios/withCIOIos.d.ts +2 -2
  116. package/plugin/lib/typescript/ios/withCIOIosSwift.d.ts +3 -3
  117. package/plugin/lib/typescript/types/cio-types.d.ts +46 -6
  118. package/plugin/lib/typescript/utils/android.d.ts +5 -0
  119. package/plugin/lib/typescript/utils/config.d.ts +8 -0
  120. package/plugin/lib/typescript/utils/plugin.d.ts +4 -0
  121. package/plugin/lib/typescript/utils/validation.d.ts +3 -0
  122. package/plugin/lib/typescript/utils/xcode.d.ts +28 -0
  123. package/plugin/src/android/withAndroidManifestUpdates.ts +5 -5
  124. package/plugin/src/android/withCIOAndroid.ts +7 -1
  125. package/plugin/src/android/withGoogleServicesJSON.ts +2 -2
  126. package/plugin/src/android/withMainApplicationModifications.ts +50 -0
  127. package/plugin/src/android/withNotificationChannelMetadata.ts +7 -3
  128. package/plugin/src/android/withProjectStrings.ts +20 -10
  129. package/plugin/src/helpers/constants/android.ts +7 -0
  130. package/plugin/src/helpers/constants/common.ts +12 -0
  131. package/plugin/src/helpers/constants/ios.ts +11 -13
  132. package/plugin/src/helpers/native-files/android/CustomerIOSDKInitializer.kt +64 -0
  133. package/plugin/src/helpers/native-files/ios/CustomerIOSDKInitializer.swift +54 -0
  134. package/plugin/src/helpers/utils/injectCIOPodfileCode.ts +8 -7
  135. package/plugin/src/helpers/utils/patchPluginNativeCode.ts +97 -0
  136. package/plugin/src/index.ts +18 -2
  137. package/plugin/src/ios/utils.ts +5 -5
  138. package/plugin/src/ios/withAppDelegateModifications.ts +11 -8
  139. package/plugin/src/ios/withCIOIos.ts +19 -11
  140. package/plugin/src/ios/withCIOIosSwift.ts +195 -73
  141. package/plugin/src/ios/withGoogleServicesJsonFile.ts +7 -10
  142. package/plugin/src/ios/withNotificationsXcodeProject.ts +25 -26
  143. package/plugin/src/ios/withXcodeProject.ts +1 -1
  144. package/plugin/src/postInstallHelper.js +1 -1
  145. package/plugin/src/types/cio-types.ts +48 -8
  146. package/plugin/src/utils/android.ts +112 -0
  147. package/plugin/src/utils/config.ts +53 -0
  148. package/plugin/src/utils/plugin.ts +46 -0
  149. package/plugin/src/utils/validation.ts +54 -0
  150. package/plugin/src/utils/xcode.ts +74 -0
  151. package/plugin/lib/commonjs/helpers/constants/globals.d.js +0 -2
  152. package/plugin/lib/commonjs/helpers/constants/globals.d.js.map +0 -1
  153. package/plugin/lib/commonjs/helpers/utils/pluginUtils.js +0 -26
  154. package/plugin/lib/commonjs/helpers/utils/pluginUtils.js.map +0 -1
  155. package/plugin/lib/module/helpers/constants/globals.d.js +0 -2
  156. package/plugin/lib/module/helpers/constants/globals.d.js.map +0 -1
  157. package/plugin/lib/module/helpers/utils/pluginUtils.js +0 -19
  158. package/plugin/lib/module/helpers/utils/pluginUtils.js.map +0 -1
  159. package/plugin/lib/typescript/helpers/utils/pluginUtils.d.ts +0 -4
  160. package/plugin/src/helpers/constants/globals.d.ts +0 -8
  161. package/plugin/src/helpers/utils/pluginUtils.ts +0 -22
@@ -19,7 +19,7 @@ const addMetadataIfNotExists = (
19
19
 
20
20
  // Check if metadata already exists
21
21
  const hasMetadata = application['meta-data'].some(
22
- (metadata) => metadata['$']['android:name'] === name
22
+ (metadata) => metadata.$['android:name'] === name
23
23
  );
24
24
 
25
25
  // Add metadata if it doesn't exist
@@ -37,11 +37,15 @@ export const withNotificationChannelMetadata: ConfigPlugin<
37
37
  CustomerIOPluginOptionsAndroid
38
38
  > = (config, props) => {
39
39
  return withAndroidManifest(config, (manifestProps) => {
40
- const application = manifestProps.modResults.manifest.application as ManifestApplication[];
40
+ const application = manifestProps.modResults.manifest
41
+ .application as ManifestApplication[];
41
42
  const channel = props.pushNotification?.channel;
42
43
 
43
44
  // Only proceed if channel configuration exists
44
- if (channel && (channel.id || channel.name || channel.importance !== undefined)) {
45
+ if (
46
+ channel &&
47
+ (channel.id || channel.name || channel.importance !== undefined)
48
+ ) {
45
49
  if (channel.id) {
46
50
  addMetadataIfNotExists(
47
51
  application[0],
@@ -1,12 +1,13 @@
1
1
  import type { ConfigPlugin } from '@expo/config-plugins';
2
2
  import { withStringsXml } from '@expo/config-plugins';
3
- import { getPluginVersion } from '../helpers/utils/pluginUtils';
3
+ import type { ResourceXML } from '@expo/config-plugins/build/android/Resources';
4
+ import { getPluginVersion } from '../utils/plugin';
4
5
 
5
6
  /**
6
7
  * Adds or updates string resources in Android's strings.xml required by the plugin
7
8
  */
8
- export const withProjectStrings: ConfigPlugin = (config) => {
9
- return withStringsXml(config, (config) => {
9
+ export const withProjectStrings: ConfigPlugin = (configOuter) => {
10
+ return withStringsXml(configOuter, (config) => {
10
11
  const stringsXml = config.modResults;
11
12
  const pluginVersion = getPluginVersion();
12
13
 
@@ -17,7 +18,10 @@ export const withProjectStrings: ConfigPlugin = (config) => {
17
18
  // can be generated correctly for Expo apps
18
19
  addStringsToXml(stringsXml, [
19
20
  { name: 'customer_io_react_native_sdk_client_source', value: 'Expo' },
20
- { name: 'customer_io_react_native_sdk_client_version', value: pluginVersion },
21
+ {
22
+ name: 'customer_io_react_native_sdk_client_version',
23
+ value: pluginVersion,
24
+ },
21
25
  ]);
22
26
 
23
27
  return config;
@@ -31,25 +35,31 @@ export const withProjectStrings: ConfigPlugin = (config) => {
31
35
  * @returns Updated strings.xml object
32
36
  */
33
37
  export function addStringsToXml(
34
- stringsXml: any,
35
- stringResources: { name: string, value: string }[]
38
+ stringsXml: ResourceXML,
39
+ stringResources: { name: string; value: string }[]
36
40
  ) {
37
41
  // Ensure the resource exists
38
42
  if (!stringsXml.resources) {
39
43
  stringsXml.resources = { string: [] };
40
44
  }
45
+ // Ensure the string array exists
46
+ if (!stringsXml.resources.string) {
47
+ stringsXml.resources.string = [];
48
+ }
41
49
 
50
+ // Get a reference to the string array after ensuring it exists
51
+ const stringArray = stringsXml.resources.string;
42
52
  stringResources.forEach(({ name, value }) => {
43
- const existingStringIndex = stringsXml.resources.string.findIndex(
44
- (item: { $: { name: string } }) => item.$?.name === name
53
+ const existingStringIndex = stringArray.findIndex(
54
+ (item) => item.$?.name === name
45
55
  );
46
56
 
47
57
  if (existingStringIndex !== -1) {
48
58
  // Update the existing string
49
- stringsXml.resources.string[existingStringIndex]._ = value;
59
+ stringArray[existingStringIndex]._ = value;
50
60
  } else {
51
61
  // Add a new string resource
52
- stringsXml.resources.string.push({
62
+ stringArray.push({
53
63
  $: { name },
54
64
  _: value,
55
65
  });
@@ -12,3 +12,10 @@ export const CIO_APP_GOOGLE_SNIPPET =
12
12
  'apply plugin: "com.google.gms.google-services" // Google Services plugin';
13
13
  export const CIO_PROJECT_GOOGLE_SNIPPET =
14
14
  ' classpath "com.google.gms:google-services:4.3.13" // Google Services plugin';
15
+
16
+ export const CIO_MAINAPPLICATION_ONCREATE_REGEX = /override\s+fun\s+onCreate\s*\(\s*\)\s*\{[\s\S]*?\}/;
17
+ // Actual method call, also used to detect if Customer.io auto initialization is already present
18
+ export const CIO_NATIVE_SDK_INITIALIZE_CALL = 'CustomerIOSDKInitializer.initialize(this)';
19
+ // Complete code snippet to inject into MainActivity.onCreate()
20
+ export const CIO_NATIVE_SDK_INITIALIZE_SNIPPET = `// Auto Initialize Native Customer.io SDK
21
+ ${CIO_NATIVE_SDK_INITIALIZE_CALL}`;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Platform constants for native SDK initialization
3
+ */
4
+ export const PLATFORM = {
5
+ IOS: 'ios',
6
+ ANDROID: 'android',
7
+ } as const;
8
+
9
+ /**
10
+ * Platform type definition
11
+ */
12
+ export type Platform = typeof PLATFORM[keyof typeof PLATFORM];
@@ -1,23 +1,15 @@
1
- const finder = require('find-package-json');
2
1
  const path = require('path');
3
2
  const resolveFrom = require('resolve-from');
4
3
 
5
- const f = finder(__dirname);
6
- let pluginPackageRoot = f.next().filename;
7
- // This is the path to the root of the customerio-expo-plugin package
8
- pluginPackageRoot = path.dirname(pluginPackageRoot);
9
-
10
- export const LOCAL_PATH_TO_CIO_NSE_FILES = path.join(
11
- pluginPackageRoot,
12
- 'plugin/src/helpers/native-files/ios'
13
- );
14
-
15
4
  export function getRelativePathToRNSDK(iosPath: string) {
16
5
  // Root path of the Expo project
17
6
  const rootAppPath = path.dirname(iosPath);
18
7
 
19
8
  // Path of the cio RN package.json file. Example: test-app/node_modules/customerio-reactnative/package.json
20
- const pluginPackageJsonPath = resolveFrom.silent(rootAppPath, `customerio-reactnative/package.json`);
9
+ const pluginPackageJsonPath = resolveFrom.silent(
10
+ rootAppPath,
11
+ `customerio-reactnative/package.json`
12
+ );
21
13
 
22
14
  // Example: ../node_modules/customerio-reactnative
23
15
  return path.relative(iosPath, path.dirname(pluginPackageJsonPath));
@@ -170,4 +162,10 @@ export const CIO_REGISTER_PUSHNOTIFICATION_SNIPPET_v2 = `
170
162
  }
171
163
  }`;
172
164
 
173
- export const CIO_REGISTER_PUSH_NOTIFICATION_PLACEHOLDER = /\{\{REGISTER_SNIPPET\}\}/;
165
+ export const CIO_REGISTER_PUSH_NOTIFICATION_PLACEHOLDER = /\{\{REGISTER_SNIPPET\}\}/;
166
+ // Regex to match MessagingPush initialization in AppDelegate (different from NSE initialization)
167
+ export const CIO_MESSAGING_PUSH_APP_DELEGATE_INIT_REGEX = /(MessagingPush(?:APN|FCM)\.initialize)/;
168
+ export const CIO_NATIVE_SDK_INITIALIZE_CALL = 'CustomerIOSDKInitializer.initialize()';
169
+ export const CIO_NATIVE_SDK_INITIALIZE_SNIPPET = `// Auto Initialize Native Customer.io SDK
170
+ ${CIO_NATIVE_SDK_INITIALIZE_CALL}
171
+ `;
@@ -0,0 +1,64 @@
1
+ package io.customer.sdk.expo
2
+
3
+ import android.app.Application
4
+ import io.customer.datapipelines.config.ScreenView
5
+ import io.customer.messaginginapp.MessagingInAppModuleConfig
6
+ import io.customer.messaginginapp.ModuleMessagingInApp
7
+ import io.customer.messagingpush.MessagingPushModuleConfig
8
+ import io.customer.messagingpush.ModuleMessagingPushFCM
9
+ import io.customer.reactnative.sdk.messaginginapp.ReactInAppEventListener
10
+ import io.customer.sdk.CustomerIOBuilder
11
+ import io.customer.sdk.core.util.CioLogLevel
12
+ import io.customer.sdk.data.model.Region
13
+
14
+ object CustomerIOSDKInitializer {
15
+ fun initialize(application: Application) = with(
16
+ CustomerIOBuilder(application, "{{CDP_API_KEY}}")
17
+ ) {
18
+ val siteId: String? = {{SITE_ID}}
19
+ val migrationSiteId: String? = {{MIGRATION_SITE_ID}}
20
+ val region = Region.getRegion({{REGION}})
21
+
22
+ setIfDefined({{LOG_LEVEL}}, CustomerIOBuilder::logLevel) { CioLogLevel.getLogLevel(it) }
23
+ setIfDefined(region, CustomerIOBuilder::region)
24
+ setIfDefined({{AUTO_TRACK_DEVICE_ATTRIBUTES}}, CustomerIOBuilder::autoTrackDeviceAttributes)
25
+ setIfDefined({{TRACK_APPLICATION_LIFECYCLE_EVENTS}}, CustomerIOBuilder::trackApplicationLifecycleEvents)
26
+ setIfDefined({{SCREEN_VIEW_USE}}, CustomerIOBuilder::screenViewUse) { ScreenView.getScreenView(it) }
27
+ setIfDefined(migrationSiteId, CustomerIOBuilder::migrationSiteId)
28
+
29
+ // Add messaging modules if siteId is provided
30
+ if (!(siteId.isNullOrBlank())) {
31
+ addCustomerIOModule(
32
+ ModuleMessagingInApp(
33
+ MessagingInAppModuleConfig.Builder(siteId, region)
34
+ .setEventListener(ReactInAppEventListener())
35
+ .build()
36
+ )
37
+ )
38
+ }
39
+ addCustomerIOModule(
40
+ ModuleMessagingPushFCM(
41
+ MessagingPushModuleConfig.Builder().build()
42
+ )
43
+ )
44
+
45
+ build()
46
+ }
47
+ }
48
+
49
+ // Apply a value after transforming it, only if both the original and transformed values are non-nil
50
+ private inline fun <R, T> CustomerIOBuilder.setIfDefined(
51
+ value: R?,
52
+ block: CustomerIOBuilder.(T) -> CustomerIOBuilder,
53
+ transform: (R) -> T,
54
+ ): CustomerIOBuilder = value?.let { block(transform(it)) } ?: this
55
+
56
+ // Apply a value to a setter only if it's non-nil
57
+ private inline fun <T> CustomerIOBuilder.setIfDefined(
58
+ value: T?,
59
+ block: CustomerIOBuilder.(T) -> CustomerIOBuilder,
60
+ ): CustomerIOBuilder = setIfDefined(
61
+ value = value,
62
+ block = block,
63
+ transform = { it },
64
+ )
@@ -0,0 +1,54 @@
1
+ import CioDataPipelines
2
+ import CioInternalCommon
3
+ import CioMessagingInApp
4
+ import customerio_reactnative
5
+
6
+ class CustomerIOSDKInitializer {
7
+ static func initialize() {
8
+ // Override SDK client info to include Expo metadata in user agent
9
+ let pluginVersion = "{{EXPO_PLUGIN_VERSION}}"
10
+ DIGraphShared.shared.override(
11
+ value: CustomerIOSdkClient(source: "Expo", sdkVersion: pluginVersion),
12
+ forType: SdkClient.self
13
+ )
14
+
15
+ let cdpApiKey = "{{CDP_API_KEY}}"
16
+ let siteId: String? = {{SITE_ID}}
17
+ let region = CioInternalCommon.Region.getRegion(from: {{REGION}})
18
+
19
+ let builder = SDKConfigBuilder(cdpApiKey: cdpApiKey)
20
+ setIfDefined(value: {{LOG_LEVEL}}, thenPassItTo: builder.logLevel, transformingBy: CioLogLevel.getLogLevel)
21
+ setIfDefined(value: region, thenPassItTo: builder.region)
22
+ setIfDefined(value: {{AUTO_TRACK_DEVICE_ATTRIBUTES}}, thenPassItTo: builder.autoTrackDeviceAttributes)
23
+ setIfDefined(value: {{TRACK_APPLICATION_LIFECYCLE_EVENTS}}, thenPassItTo: builder.trackApplicationLifecycleEvents)
24
+ setIfDefined(value: {{SCREEN_VIEW_USE}}, thenPassItTo: builder.screenViewUse) { ScreenView.getScreenView($0) }
25
+ setIfDefined(value: {{MIGRATION_SITE_ID}}, thenPassItTo: builder.migrationSiteId)
26
+
27
+ CustomerIO.initialize(withConfig: builder.build())
28
+
29
+ if let siteId = siteId {
30
+ let inAppConfig = MessagingInAppConfigBuilder(siteId: siteId, region: region).build()
31
+ MessagingInApp.initialize(withConfig: inAppConfig)
32
+ MessagingInApp.shared.setEventListener(ReactInAppEventListener.shared)
33
+ }
34
+ }
35
+
36
+ /// Apply a value to a setter only if it's non-nil
37
+ private static func setIfDefined<Raw>(
38
+ value rawValue: Raw?,
39
+ thenPassItTo handler: (Raw) -> Any
40
+ ) {
41
+ setIfDefined(value: rawValue, thenPassItTo: handler) { $0 }
42
+ }
43
+
44
+ /// Apply a value after transforming it, only if both the original and transformed values are non-nil
45
+ private static func setIfDefined<Raw, Transformed>(
46
+ value rawValue: Raw?,
47
+ thenPassItTo handler: (Transformed) -> Any,
48
+ transformingBy transform: (Raw) -> Transformed?
49
+ ) {
50
+ if let value = rawValue, let result = transform(value) {
51
+ _ = handler(result)
52
+ }
53
+ }
54
+ }
@@ -3,7 +3,10 @@ import { getRelativePathToRNSDK } from '../constants/ios';
3
3
  import { injectCodeByRegex } from './codeInjection';
4
4
  import { FileManagement } from './fileManagement';
5
5
 
6
- export async function injectCIOPodfileCode(iosPath: string, isFcmPushProvider: boolean) {
6
+ export async function injectCIOPodfileCode(
7
+ iosPath: string,
8
+ isFcmPushProvider: boolean
9
+ ) {
7
10
  const blockStart = '# --- CustomerIO Host App START ---';
8
11
  const blockEnd = '# --- CustomerIO Host App END ---';
9
12
 
@@ -19,9 +22,8 @@ export async function injectCIOPodfileCode(iosPath: string, isFcmPushProvider: b
19
22
 
20
23
  const snippetToInjectInPodfile = `
21
24
  ${blockStart}
22
- pod 'customerio-reactnative/${isFcmPushProvider ? "fcm" : "apn"}', :path => '${getRelativePathToRNSDK(
23
- iosPath
24
- )}'
25
+ pod 'customerio-reactnative/${isFcmPushProvider ? 'fcm' : 'apn'
26
+ }', :path => '${getRelativePathToRNSDK(iosPath)}'
25
27
  ${blockEnd}
26
28
  `.trim();
27
29
 
@@ -56,9 +58,8 @@ export async function injectCIONotificationPodfileCode(
56
58
  ${blockStart}
57
59
  target 'NotificationService' do
58
60
  ${useFrameworks === 'static' ? 'use_frameworks! :linkage => :static' : ''}
59
- pod 'customerio-reactnative-richpush/${isFcmPushProvider ? "fcm" : "apn"}', :path => '${getRelativePathToRNSDK(
60
- iosPath
61
- )}'
61
+ pod 'customerio-reactnative-richpush/${isFcmPushProvider ? 'fcm' : 'apn'
62
+ }', :path => '${getRelativePathToRNSDK(iosPath)}'
62
63
  end
63
64
  ${blockEnd}
64
65
  `.trim();
@@ -0,0 +1,97 @@
1
+ import type { NativeSDKConfig } from '../../types/cio-types';
2
+ import { PLATFORM, type Platform } from '../constants/common';
3
+ import { getPluginVersion } from '../../utils/plugin';
4
+
5
+ /**
6
+ * Shared utility function to perform common SDK config replacements
7
+ * for both iOS and Android template files
8
+ */
9
+ export function patchNativeSDKInitializer(
10
+ rawContent: string,
11
+ platform: Platform,
12
+ sdkConfig: NativeSDKConfig
13
+ ): string {
14
+ let content = rawContent;
15
+
16
+ // Helper function to replace placeholders with platform-specific fallback values
17
+ const replaceValue = <T>(
18
+ placeholder: RegExp,
19
+ value: T | undefined,
20
+ transform: (configValue: T) => string,
21
+ fallback: string = platform === PLATFORM.ANDROID ? 'null' : 'nil'
22
+ ) => {
23
+ if (value !== undefined && value !== null) {
24
+ content = content.replace(placeholder, transform(value));
25
+ } else {
26
+ content = content.replace(placeholder, fallback);
27
+ }
28
+ };
29
+
30
+ // Replace EXPO_PLUGIN_VERSION with actual plugin version
31
+ const pluginVersion = getPluginVersion();
32
+ content = content.replace(/\{\{EXPO_PLUGIN_VERSION\}\}/g, pluginVersion);
33
+
34
+ // Replace CDP API Key (required field)
35
+ content = content.replace(/\{\{CDP_API_KEY\}\}/g, sdkConfig.cdpApiKey);
36
+
37
+ // Handle region - use empty string as fallback (nil not supported for region)
38
+ replaceValue(
39
+ /\{\{REGION\}\}/g,
40
+ sdkConfig.region,
41
+ (configValue) => `"${configValue}"`,
42
+ '""'
43
+ );
44
+
45
+ // Handle logLevel - use nil/null as fallback
46
+ replaceValue(
47
+ /\{\{LOG_LEVEL\}\}/g,
48
+ sdkConfig.logLevel,
49
+ (configValue) => `"${configValue}"`
50
+ );
51
+
52
+ // Handle optional boolean configurations
53
+ replaceValue(
54
+ /\{\{AUTO_TRACK_DEVICE_ATTRIBUTES\}\}/g,
55
+ sdkConfig.autoTrackDeviceAttributes,
56
+ (configValue) => configValue.toString()
57
+ );
58
+
59
+ replaceValue(
60
+ /\{\{TRACK_APPLICATION_LIFECYCLE_EVENTS\}\}/g,
61
+ sdkConfig.trackApplicationLifecycleEvents,
62
+ (configValue) => configValue.toString()
63
+ );
64
+
65
+ // Handle screenViewUse - use nil/null as fallback
66
+ replaceValue(
67
+ /\{\{SCREEN_VIEW_USE\}\}/g,
68
+ sdkConfig.screenViewUse,
69
+ (configValue) => `"${configValue}"`
70
+ );
71
+
72
+ // Handle siteId/migrationSiteId business logic
73
+ let siteId = sdkConfig.siteId;
74
+ let migrationSiteId = sdkConfig.migrationSiteId;
75
+
76
+ // Business rule: if only siteId provided, copy to migrationSiteId; if only migrationSiteId provided, set siteId to undefined
77
+ if (siteId && !migrationSiteId) {
78
+ migrationSiteId = siteId;
79
+ } else if (migrationSiteId && !siteId) {
80
+ siteId = undefined;
81
+ }
82
+
83
+ // Replace siteId and migrationSiteId placeholders (trim whitespace and handle empty strings)
84
+ replaceValue(
85
+ /\{\{SITE_ID\}\}/g,
86
+ siteId?.trim() || undefined,
87
+ (configValue) => `"${configValue}"`
88
+ );
89
+
90
+ replaceValue(
91
+ /\{\{MIGRATION_SITE_ID\}\}/g,
92
+ migrationSiteId?.trim() || undefined,
93
+ (configValue) => `"${configValue}"`
94
+ );
95
+
96
+ return content;
97
+ }
@@ -1,20 +1,36 @@
1
1
  import type { ExpoConfig } from '@expo/config-types';
2
2
 
3
3
  import { withCIOAndroid } from './android/withCIOAndroid';
4
+ import { isExpoVersion53OrHigher } from './ios/utils';
4
5
  import { withCIOIos } from './ios/withCIOIos';
5
6
  import type { CustomerIOPluginOptions } from './types/cio-types';
7
+ import { validateNativeSDKConfig } from './utils/validation';
6
8
 
7
9
  // Entry point for config plugin
8
10
  function withCustomerIOPlugin(
9
11
  config: ExpoConfig,
10
12
  props: CustomerIOPluginOptions
11
13
  ) {
14
+ // Check if config is being used with unsupported Expo version
15
+ if (props.config && !isExpoVersion53OrHigher(config)) {
16
+ throw new Error(
17
+ 'CustomerIO auto initialization (config property) requires Expo SDK 53 or higher. ' +
18
+ 'Please upgrade to Expo SDK 53+ or use manual initialization instead. ' +
19
+ 'See documentation for manual setup instructions.'
20
+ );
21
+ }
22
+
23
+ // Validate SDK config if provided
24
+ if (props.config) {
25
+ validateNativeSDKConfig(props.config);
26
+ }
27
+
12
28
  if (props.ios) {
13
- config = withCIOIos(config, props.ios);
29
+ config = withCIOIos(config, props.config, props.ios);
14
30
  }
15
31
 
16
32
  if (props.android) {
17
- config = withCIOAndroid(config, props.android);
33
+ config = withCIOAndroid(config, props.config, props.android);
18
34
  }
19
35
 
20
36
  return config;
@@ -1,9 +1,9 @@
1
- import type { CustomerIOPluginOptionsIOS } from '../types/cio-types';
2
1
  import type { ExpoConfig } from '@expo/config-types';
3
2
  import * as semver from 'semver';
3
+ import type { CustomerIOPluginOptionsIOS } from '../types/cio-types';
4
4
 
5
5
  /**
6
- * Returns t
6
+ * Returns true if FCM is configured to be used as push provider
7
7
  * @param iosOptions The plugin iOS configuration options
8
8
  * @returns true if FCM is configured to be used as push provider
9
9
  */
@@ -15,13 +15,13 @@ export const isFcmPushProvider = (
15
15
 
16
16
  export const isExpoVersion53OrHigher = (config: ExpoConfig): boolean => {
17
17
  const sdkVersion = config.sdkVersion || '';
18
-
18
+
19
19
  // If sdkVersion is not a valid semver, coerce it to a valid one if possible
20
20
  const validVersion = semver.valid(sdkVersion) || semver.coerce(sdkVersion);
21
-
21
+
22
22
  // If we couldn't get a valid version, return false
23
23
  if (!validVersion) return false;
24
-
24
+
25
25
  // Check if the version is greater than or equal to 53.0.0
26
26
  return semver.gte(validVersion, '53.0.0');
27
27
  };
@@ -9,26 +9,26 @@ import {
9
9
  CIO_APPDELEGATEHEADER_USER_NOTIFICATION_CENTER_SNIPPET,
10
10
  CIO_CONFIGURECIOSDKPUSHNOTIFICATION_SNIPPET,
11
11
  CIO_CONFIGUREDEEPLINK_KILLEDSTATE_SNIPPET,
12
- CIO_RCTBRIDGE_DEEPLINK_MODIFIEDOPTIONS_REGEX,
12
+ CIO_DEEPLINK_COMMENT_REGEX,
13
13
  CIO_DIDFAILTOREGISTERFORREMOTENOTIFICATIONSWITHERROR_REGEX,
14
14
  CIO_DIDFAILTOREGISTERFORREMOTENOTIFICATIONSWITHERROR_SNIPPET,
15
15
  CIO_DIDFINISHLAUNCHINGMETHOD_REGEX,
16
16
  CIO_DIDREGISTERFORREMOTENOTIFICATIONSWITHDEVICETOKEN_REGEX,
17
17
  CIO_DIDREGISTERFORREMOTENOTIFICATIONSWITHDEVICETOKEN_SNIPPET,
18
+ CIO_INITIALIZECIOSDK_SNIPPET,
18
19
  CIO_LAUNCHOPTIONS_DEEPLINK_MODIFIEDOPTIONS_REGEX,
19
- CIO_PUSHNOTIFICATIONHANDLERDECLARATION_SNIPPET,
20
20
  CIO_LAUNCHOPTIONS_MODIFIEDOPTIONS_SNIPPET,
21
+ CIO_PUSHNOTIFICATIONHANDLERDECLARATION_SNIPPET,
22
+ CIO_RCTBRIDGE_DEEPLINK_MODIFIEDOPTIONS_REGEX,
21
23
  CIO_RCTBRIDGE_DEEPLINK_MODIFIEDOPTIONS_SNIPPET,
22
- CIO_DEEPLINK_COMMENT_REGEX,
23
- CIO_INITIALIZECIOSDK_SNIPPET,
24
24
  } from '../helpers/constants/ios';
25
25
  import {
26
26
  injectCodeBeforeMultiLineRegex,
27
27
  injectCodeByLineNumber,
28
28
  injectCodeByMultiLineRegex,
29
29
  injectCodeByMultiLineRegexAndReplaceLine,
30
- replaceCodeByRegex,
31
30
  matchRegexExists,
31
+ replaceCodeByRegex,
32
32
  } from '../helpers/utils/codeInjection';
33
33
  import { FileManagement } from '../helpers/utils/fileManagement';
34
34
  import type { CustomerIOPluginOptionsIOS } from '../types/cio-types';
@@ -140,7 +140,9 @@ const addExpoNotificationsHeaderModification = (stringContents: string) => {
140
140
  return stringContents;
141
141
  };
142
142
 
143
- const addFirebaseDelegateForwardDeclarationIfNeeded = (stringContents: string) => {
143
+ const addFirebaseDelegateForwardDeclarationIfNeeded = (
144
+ stringContents: string
145
+ ) => {
144
146
  stringContents = injectCodeByLineNumber(
145
147
  stringContents,
146
148
  0,
@@ -187,7 +189,7 @@ const addHandleDeeplinkInKilledState = (stringContents: string) => {
187
189
  }
188
190
 
189
191
  // Check if the app delegate is using RCTBridge or LaunchOptions
190
- let snippet = undefined;
192
+ let snippet;
191
193
  let regex = CIO_LAUNCHOPTIONS_DEEPLINK_MODIFIEDOPTIONS_REGEX;
192
194
  if (
193
195
  matchRegexExists(
@@ -258,7 +260,8 @@ export const withAppDelegateModifications: ConfigPlugin<
258
260
  addDidRegisterForRemoteNotificationsWithDeviceToken(stringContents);
259
261
 
260
262
  if (isFcmPushProvider(props)) {
261
- stringContents = addFirebaseDelegateForwardDeclarationIfNeeded(stringContents);
263
+ stringContents =
264
+ addFirebaseDelegateForwardDeclarationIfNeeded(stringContents);
262
265
  }
263
266
 
264
267
  stringContents = addExpoNotificationsHeaderModification(stringContents);
@@ -3,31 +3,40 @@ import type { ExpoConfig } from '@expo/config-types';
3
3
  import type {
4
4
  CustomerIOPluginOptionsIOS,
5
5
  CustomerIOPluginPushNotificationOptions,
6
+ NativeSDKConfig,
6
7
  } from '../types/cio-types';
8
+ import { mergeConfigWithEnvValues } from '../utils/config';
9
+ import { isExpoVersion53OrHigher } from './utils';
7
10
  import { withAppDelegateModifications } from './withAppDelegateModifications';
11
+ import { withCIOIosSwift } from './withCIOIosSwift';
12
+ import { withGoogleServicesJsonFile } from './withGoogleServicesJsonFile';
8
13
  import { withCioNotificationsXcodeProject } from './withNotificationsXcodeProject';
9
14
  import { withCioXcodeProject } from './withXcodeProject';
10
- import { withGoogleServicesJsonFile } from './withGoogleServicesJsonFile';
11
- import { withCIOIosSwift } from './withCIOIosSwift';
12
- import { isExpoVersion53OrHigher } from './utils';
13
15
 
14
16
  export function withCIOIos(
15
17
  config: ExpoConfig,
18
+ sdkConfig: NativeSDKConfig | undefined,
16
19
  props: CustomerIOPluginOptionsIOS
17
20
  ) {
18
- const cioProps = mergeDeprecatedPropertiesAndLogWarnings(props);
19
21
  const isSwiftProject = isExpoVersion53OrHigher(config);
22
+ const platformConfig = mergeDeprecatedPropertiesAndLogWarnings(props);
20
23
 
21
- if (cioProps.pushNotification) {
24
+ if (platformConfig.pushNotification) {
22
25
  if (isSwiftProject) {
23
- config = withCIOIosSwift(config, cioProps);
26
+ config = withCIOIosSwift(config, sdkConfig, platformConfig);
24
27
  } else {
25
- config = withAppDelegateModifications(config, cioProps);
28
+ // Auto initialization is only supported in Swift projects (Expo SDK 53+)
29
+ // Legacy Objective-C projects only support push notifications
30
+ config = withAppDelegateModifications(config, platformConfig);
26
31
  }
27
32
 
28
- config = withCioNotificationsXcodeProject(config, cioProps);
29
- config = withCioXcodeProject(config, cioProps);
30
- config = withGoogleServicesJsonFile(config, cioProps);
33
+ platformConfig.pushNotification.env = platformConfig.pushNotification.env
34
+ || mergeConfigWithEnvValues(platformConfig, sdkConfig);
35
+ config = withCioNotificationsXcodeProject(config, platformConfig);
36
+ config = withCioXcodeProject(config, platformConfig);
37
+ config = withGoogleServicesJsonFile(config, platformConfig);
38
+ } else if (sdkConfig && isSwiftProject) {
39
+ config = withCIOIosSwift(config, sdkConfig, platformConfig);
31
40
  }
32
41
 
33
42
  return config;
@@ -82,4 +91,3 @@ const mergeDeprecatedPropertiesAndLogWarnings = (
82
91
 
83
92
  return props;
84
93
  };
85
-