customerio-expo-plugin 3.1.0 → 3.2.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 (71) hide show
  1. package/package.json +3 -2
  2. package/plugin/lib/commonjs/android/withCIOAndroid.js +13 -2
  3. package/plugin/lib/commonjs/android/withCIOAndroid.js.map +1 -1
  4. package/plugin/lib/commonjs/android/withLocationGradleProperties.js +37 -0
  5. package/plugin/lib/commonjs/android/withLocationGradleProperties.js.map +1 -0
  6. package/plugin/lib/commonjs/android/withMainApplicationModifications.js +21 -5
  7. package/plugin/lib/commonjs/android/withMainApplicationModifications.js.map +1 -1
  8. package/plugin/lib/commonjs/helpers/native-files/android/CustomerIOSDKInitializer.kt +2 -0
  9. package/plugin/lib/commonjs/helpers/native-files/ios/CustomerIOSDKInitializer.swift +2 -0
  10. package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js +19 -2
  11. package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js.map +1 -1
  12. package/plugin/lib/commonjs/helpers/utils/patchLocationCode.js +50 -0
  13. package/plugin/lib/commonjs/helpers/utils/patchLocationCode.js.map +1 -0
  14. package/plugin/lib/commonjs/helpers/utils/patchPluginNativeCode.js +3 -1
  15. package/plugin/lib/commonjs/helpers/utils/patchPluginNativeCode.js.map +1 -1
  16. package/plugin/lib/commonjs/index.js +2 -2
  17. package/plugin/lib/commonjs/index.js.map +1 -1
  18. package/plugin/lib/commonjs/ios/withCIOIos.js +29 -4
  19. package/plugin/lib/commonjs/ios/withCIOIos.js.map +1 -1
  20. package/plugin/lib/commonjs/ios/withCIOIosSwift.js +19 -9
  21. package/plugin/lib/commonjs/ios/withCIOIosSwift.js.map +1 -1
  22. package/plugin/lib/commonjs/ios/withXcodeProject.js +4 -1
  23. package/plugin/lib/commonjs/ios/withXcodeProject.js.map +1 -1
  24. package/plugin/lib/commonjs/types/cio-types.js.map +1 -1
  25. package/plugin/lib/module/android/withCIOAndroid.js +13 -2
  26. package/plugin/lib/module/android/withCIOAndroid.js.map +1 -1
  27. package/plugin/lib/module/android/withLocationGradleProperties.js +30 -0
  28. package/plugin/lib/module/android/withLocationGradleProperties.js.map +1 -0
  29. package/plugin/lib/module/android/withMainApplicationModifications.js +20 -4
  30. package/plugin/lib/module/android/withMainApplicationModifications.js.map +1 -1
  31. package/plugin/lib/module/helpers/native-files/android/CustomerIOSDKInitializer.kt +2 -0
  32. package/plugin/lib/module/helpers/native-files/ios/CustomerIOSDKInitializer.swift +2 -0
  33. package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js +18 -2
  34. package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js.map +1 -1
  35. package/plugin/lib/module/helpers/utils/patchLocationCode.js +44 -0
  36. package/plugin/lib/module/helpers/utils/patchLocationCode.js.map +1 -0
  37. package/plugin/lib/module/helpers/utils/patchPluginNativeCode.js +3 -2
  38. package/plugin/lib/module/helpers/utils/patchPluginNativeCode.js.map +1 -1
  39. package/plugin/lib/module/index.js +2 -2
  40. package/plugin/lib/module/index.js.map +1 -1
  41. package/plugin/lib/module/ios/withCIOIos.js +29 -4
  42. package/plugin/lib/module/ios/withCIOIos.js.map +1 -1
  43. package/plugin/lib/module/ios/withCIOIosSwift.js +19 -9
  44. package/plugin/lib/module/ios/withCIOIosSwift.js.map +1 -1
  45. package/plugin/lib/module/ios/withXcodeProject.js +5 -1
  46. package/plugin/lib/module/ios/withXcodeProject.js.map +1 -1
  47. package/plugin/lib/module/types/cio-types.js.map +1 -1
  48. package/plugin/lib/typescript/android/withCIOAndroid.d.ts +2 -2
  49. package/plugin/lib/typescript/android/withLocationGradleProperties.d.ts +9 -0
  50. package/plugin/lib/typescript/android/withMainApplicationModifications.d.ts +7 -2
  51. package/plugin/lib/typescript/helpers/utils/injectCIOPodfileCode.d.ts +9 -1
  52. package/plugin/lib/typescript/helpers/utils/patchLocationCode.d.ts +12 -0
  53. package/plugin/lib/typescript/helpers/utils/patchPluginNativeCode.d.ts +3 -1
  54. package/plugin/lib/typescript/index.d.ts +2 -1
  55. package/plugin/lib/typescript/ios/withCIOIos.d.ts +2 -2
  56. package/plugin/lib/typescript/ios/withCIOIosSwift.d.ts +2 -2
  57. package/plugin/lib/typescript/ios/withXcodeProject.d.ts +8 -1
  58. package/plugin/lib/typescript/types/cio-types.d.ts +28 -0
  59. package/plugin/src/android/withCIOAndroid.ts +13 -2
  60. package/plugin/src/android/withLocationGradleProperties.ts +41 -0
  61. package/plugin/src/android/withMainApplicationModifications.ts +26 -4
  62. package/plugin/src/helpers/native-files/android/CustomerIOSDKInitializer.kt +2 -0
  63. package/plugin/src/helpers/native-files/ios/CustomerIOSDKInitializer.swift +2 -0
  64. package/plugin/src/helpers/utils/injectCIOPodfileCode.ts +35 -3
  65. package/plugin/src/helpers/utils/patchLocationCode.ts +80 -0
  66. package/plugin/src/helpers/utils/patchPluginNativeCode.ts +10 -1
  67. package/plugin/src/index.ts +9 -3
  68. package/plugin/src/ios/withCIOIos.ts +24 -3
  69. package/plugin/src/ios/withCIOIosSwift.ts +20 -4
  70. package/plugin/src/ios/withXcodeProject.ts +20 -3
  71. package/plugin/src/types/cio-types.ts +30 -0
@@ -1,4 +1,5 @@
1
1
  import type { ExpoConfig } from '@expo/config-types';
2
- import type { CustomerIOPluginOptions } from './types/cio-types';
2
+ import type { CustomerIOPluginOptions, LocationTrackingMode, NativeSDKConfig } from './types/cio-types';
3
+ export type { LocationTrackingMode, NativeSDKConfig };
3
4
  declare function withCustomerIOPlugin(config: ExpoConfig, props: CustomerIOPluginOptions): ExpoConfig;
4
5
  export default withCustomerIOPlugin;
@@ -1,3 +1,3 @@
1
1
  import type { ExpoConfig } from '@expo/config-types';
2
- import type { CustomerIOPluginOptionsIOS, NativeSDKConfig } from '../types/cio-types';
3
- export declare function withCIOIos(config: ExpoConfig, sdkConfig?: NativeSDKConfig, props?: CustomerIOPluginOptionsIOS): ExpoConfig;
2
+ import type { CustomerIOPluginOptionsIOS, CustomerIOPluginLocationOptions, NativeSDKConfig } from '../types/cio-types';
3
+ export declare function withCIOIos(config: ExpoConfig, sdkConfig?: NativeSDKConfig, props?: CustomerIOPluginOptionsIOS, location?: CustomerIOPluginLocationOptions): ExpoConfig;
@@ -1,3 +1,3 @@
1
1
  import type { ExpoConfig } from '@expo/config-types';
2
- import type { CustomerIOPluginOptionsIOS, NativeSDKConfig } from '../types/cio-types';
3
- export declare const withCIOIosSwift: (configOuter: ExpoConfig, sdkConfig?: NativeSDKConfig, props?: CustomerIOPluginOptionsIOS) => ExpoConfig;
2
+ import type { CustomerIOPluginOptionsIOS, CustomerIOPluginLocationOptions, NativeSDKConfig } from '../types/cio-types';
3
+ export declare const withCIOIosSwift: (configOuter: ExpoConfig, sdkConfig?: NativeSDKConfig, props?: CustomerIOPluginOptionsIOS, location?: CustomerIOPluginLocationOptions) => ExpoConfig;
@@ -1,3 +1,10 @@
1
1
  import type { ConfigPlugin } from '@expo/config-plugins';
2
+ import { type InjectCIOPodfileOptions } from '../helpers/utils/injectCIOPodfileCode';
2
3
  import type { CustomerIOPluginOptionsIOS } from '../types/cio-types';
3
- export declare const withCioXcodeProject: ConfigPlugin<CustomerIOPluginOptionsIOS>;
4
+ export type WithCioXcodeProjectOptions = {
5
+ /** Options for Podfile host app snippet (location subspec, etc.) */
6
+ podfileOptions?: InjectCIOPodfileOptions;
7
+ };
8
+ /** Props for the CIO Xcode project mod; push options are optional when only location is enabled. */
9
+ export type WithCioXcodeProjectProps = Partial<CustomerIOPluginOptionsIOS> & WithCioXcodeProjectOptions;
10
+ export declare const withCioXcodeProject: ConfigPlugin<WithCioXcodeProjectProps>;
@@ -76,6 +76,12 @@ export type CustomerIOPluginOptionsAndroid = {
76
76
  */
77
77
  disableAndroid16Support?: boolean;
78
78
  };
79
+ /**
80
+ * Location tracking mode for the Customer.io SDK location module.
81
+ * Location is off by default. Only used when location is enabled (plugin option location.enabled: true).
82
+ * @public
83
+ */
84
+ export type LocationTrackingMode = 'OFF' | 'MANUAL' | 'ON_APP_START';
79
85
  /**
80
86
  * SDK configuration options for auto initialization
81
87
  * @public
@@ -89,6 +95,23 @@ export type NativeSDKConfig = {
89
95
  logLevel?: 'none' | 'error' | 'info' | 'debug';
90
96
  siteId?: string;
91
97
  migrationSiteId?: string;
98
+ /**
99
+ * Location module config. Location is off by default; only applied when plugin option location.enabled is true.
100
+ * trackingMode: 'MANUAL' (host app controls when location is captured, default),
101
+ * 'ON_APP_START' (SDK captures once per launch when app becomes active), or 'OFF'.
102
+ */
103
+ location?: {
104
+ trackingMode?: LocationTrackingMode;
105
+ };
106
+ };
107
+ /**
108
+ * Location is off by default. When true, enables the Customer.io SDK location native module (iOS Podfile location subspec,
109
+ * Android gradle.properties flag). Permissions and privacy keys (Info.plist, AndroidManifest)
110
+ * remain the host app's responsibility.
111
+ * @public
112
+ */
113
+ export type CustomerIOPluginLocationOptions = {
114
+ enabled?: boolean;
92
115
  };
93
116
  /**
94
117
  * Combined plugin options for both iOS and Android platforms
@@ -98,6 +121,11 @@ export type CustomerIOPluginOptions = {
98
121
  config?: NativeSDKConfig;
99
122
  android: CustomerIOPluginOptionsAndroid;
100
123
  ios: CustomerIOPluginOptionsIOS;
124
+ /**
125
+ * Location is off by default. When location.enabled is true, the plugin adds SDK build-time setup (Podfile location subspec,
126
+ * gradle.properties). Host apps must add their own location permissions and privacy usage strings.
127
+ */
128
+ location?: CustomerIOPluginLocationOptions;
101
129
  };
102
130
  /**
103
131
  * Rich push configuration used to initialize Notification Service Extension (NSE) on the native side
@@ -1,9 +1,14 @@
1
1
  import type { ExpoConfig } from '@expo/config-types';
2
2
 
3
- import type { CustomerIOPluginOptionsAndroid, NativeSDKConfig } from '../types/cio-types';
3
+ import type {
4
+ CustomerIOPluginOptionsAndroid,
5
+ CustomerIOPluginLocationOptions,
6
+ NativeSDKConfig,
7
+ } from '../types/cio-types';
4
8
  import { withAndroidManifestUpdates } from './withAndroidManifestUpdates';
5
9
  import { withAppGoogleServices } from './withAppGoogleServices';
6
10
  import { withGoogleServicesJSON } from './withGoogleServicesJSON';
11
+ import { withLocationGradleProperties } from './withLocationGradleProperties';
7
12
  import { withMainApplicationModifications } from './withMainApplicationModifications';
8
13
  import { withNotificationChannelMetadata } from './withNotificationChannelMetadata';
9
14
  import { withProjectBuildGradle } from './withProjectBuildGradle';
@@ -14,6 +19,7 @@ export function withCIOAndroid(
14
19
  config: ExpoConfig,
15
20
  sdkConfig?: NativeSDKConfig,
16
21
  props?: CustomerIOPluginOptionsAndroid,
22
+ location?: CustomerIOPluginLocationOptions,
17
23
  ): ExpoConfig {
18
24
  // Only run notification setup if props are provided
19
25
  if (props) {
@@ -30,7 +36,7 @@ export function withCIOAndroid(
30
36
 
31
37
  // Add auto initialization if sdkConfig is provided
32
38
  if (sdkConfig) {
33
- config = withMainApplicationModifications(config, sdkConfig);
39
+ config = withMainApplicationModifications(config, { sdkConfig, location });
34
40
  }
35
41
 
36
42
  // Update project strings for user agent metadata
@@ -40,5 +46,10 @@ export function withCIOAndroid(
40
46
  // This prevents androidx versions that require API 36 from being pulled in
41
47
  config = withProjectBuildGradle(config, props);
42
48
 
49
+ // Enable SDK location module when location.enabled is true
50
+ if (location?.enabled === true) {
51
+ config = withLocationGradleProperties(config, { location });
52
+ }
53
+
43
54
  return config;
44
55
  }
@@ -0,0 +1,41 @@
1
+ import type { ConfigPlugin } from '@expo/config-plugins';
2
+ import { withGradleProperties } from '@expo/config-plugins';
3
+ import type { PropertiesItem } from '@expo/config-plugins/build/android/Properties';
4
+
5
+ import type { CustomerIOPluginLocationOptions } from '../types/cio-types';
6
+
7
+ const CUSTOMERIO_LOCATION_ENABLED_KEY = 'customerio_location_enabled';
8
+
9
+ /**
10
+ * Adds or updates customerio_location_enabled in android/gradle.properties when location.enabled is true.
11
+ * The Customer.io React Native SDK reads this to enable the location native module.
12
+ */
13
+ export const withLocationGradleProperties: ConfigPlugin<{
14
+ location?: CustomerIOPluginLocationOptions;
15
+ }> = (config, props) => {
16
+ if (props?.location?.enabled !== true) {
17
+ return config;
18
+ }
19
+
20
+ return withGradleProperties(config, (config) => {
21
+ 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;
39
+ return config;
40
+ });
41
+ };
@@ -4,24 +4,45 @@ import type { ApplicationProjectFile } from '@expo/config-plugins/build/android/
4
4
  import { CIO_MAINAPPLICATION_ONCREATE_REGEX, CIO_NATIVE_SDK_INITIALIZE_CALL, CIO_NATIVE_SDK_INITIALIZE_SNIPPET } from '../helpers/constants/android';
5
5
  import { PLATFORM } from '../helpers/constants/common';
6
6
  import { patchNativeSDKInitializer } from '../helpers/utils/patchPluginNativeCode';
7
- import type { NativeSDKConfig } from '../types/cio-types';
7
+ import type {
8
+ CustomerIOPluginLocationOptions,
9
+ NativeSDKConfig,
10
+ } from '../types/cio-types';
8
11
  import { addCodeToMethod, addImportToFile, copyTemplateFile } from '../utils/android';
9
12
  import { logger } from '../utils/logger';
10
13
 
11
- export const withMainApplicationModifications: ConfigPlugin<NativeSDKConfig> = (configOuter, sdkConfig) => {
14
+ type MainApplicationModParams = {
15
+ sdkConfig: NativeSDKConfig;
16
+ location?: CustomerIOPluginLocationOptions;
17
+ };
18
+
19
+ export const withMainApplicationModifications: ConfigPlugin<MainApplicationModParams> = (configOuter, { sdkConfig, location }) => {
12
20
  return withMainApplication(configOuter, async (config) => {
13
- const content = setupCustomerIOSDKInitializer(config, sdkConfig);
21
+ const content = setupCustomerIOSDKInitializer(config, sdkConfig, location);
14
22
  config.modResults.contents = content;
15
23
  return config;
16
24
  });
17
25
  };
18
26
 
27
+ /**
28
+ * Build location options for native initializer from plugin config.
29
+ * trackingMode comes from config.location.trackingMode (only used when location.enabled is true).
30
+ */
31
+ const getLocationInitOptions = (
32
+ location?: CustomerIOPluginLocationOptions,
33
+ sdkConfig?: NativeSDKConfig
34
+ ) => ({
35
+ enabled: location?.enabled === true,
36
+ trackingMode: sdkConfig?.location?.trackingMode,
37
+ });
38
+
19
39
  /**
20
40
  * Setup CustomerIOSDKInitializer for Android auto initialization
21
41
  */
22
42
  const setupCustomerIOSDKInitializer = (
23
43
  config: ExportedConfigWithProps<ApplicationProjectFile>,
24
44
  sdkConfig: NativeSDKConfig,
45
+ location?: CustomerIOPluginLocationOptions,
25
46
  ): string => {
26
47
  const SDK_INITIALIZER_CLASS = 'CustomerIOSDKInitializer';
27
48
  const SDK_INITIALIZER_PACKAGE = 'io.customer.sdk.expo';
@@ -29,12 +50,13 @@ const setupCustomerIOSDKInitializer = (
29
50
  const SDK_INITIALIZER_FILE = `${SDK_INITIALIZER_CLASS}.kt`;
30
51
  const SDK_INITIALIZER_IMPORT = `import ${SDK_INITIALIZER_PACKAGE}.${SDK_INITIALIZER_CLASS}`;
31
52
 
53
+ const locationOptions = getLocationInitOptions(location, sdkConfig);
32
54
  let content = config.modResults.contents;
33
55
 
34
56
  try {
35
57
  // Always regenerate the CustomerIOSDKInitializer file to reflect config changes
36
58
  copyTemplateFile(config, SDK_INITIALIZER_FILE, SDK_INITIALIZER_PACKAGE, (content) =>
37
- patchNativeSDKInitializer(content, PLATFORM.ANDROID, sdkConfig)
59
+ patchNativeSDKInitializer(content, PLATFORM.ANDROID, sdkConfig, locationOptions)
38
60
  );
39
61
  // Add import if not already present
40
62
  content = addImportToFile(content, SDK_INITIALIZER_IMPORT);
@@ -7,6 +7,7 @@ import io.customer.messaginginapp.ModuleMessagingInApp
7
7
  import io.customer.messagingpush.MessagingPushModuleConfig
8
8
  import io.customer.messagingpush.ModuleMessagingPushFCM
9
9
  import io.customer.reactnative.sdk.messaginginapp.ReactInAppEventListener
10
+ {{LOCATION_MODULE_IMPORT}}
10
11
  import io.customer.sdk.CustomerIOBuilder
11
12
  import io.customer.sdk.core.util.CioLogLevel
12
13
  import io.customer.sdk.data.model.Region
@@ -41,6 +42,7 @@ object CustomerIOSDKInitializer {
41
42
  MessagingPushModuleConfig.Builder().build()
42
43
  )
43
44
  )
45
+ {{LOCATION_MODULE_INIT}}
44
46
 
45
47
  build()
46
48
  }
@@ -1,6 +1,7 @@
1
1
  import CioDataPipelines
2
2
  import CioInternalCommon
3
3
  import CioMessagingInApp
4
+ {{LOCATION_MODULE_IMPORT}}
4
5
 
5
6
  class CustomerIOSDKInitializer {
6
7
  static func initialize() {
@@ -23,6 +24,7 @@ class CustomerIOSDKInitializer {
23
24
  setIfDefined(value: {{SCREEN_VIEW_USE}}, thenPassItTo: builder.screenViewUse) { ScreenView.getScreenView($0) }
24
25
  setIfDefined(value: {{MIGRATION_SITE_ID}}, thenPassItTo: builder.migrationSiteId)
25
26
 
27
+ {{LOCATION_MODULE_INIT}}
26
28
  CustomerIO.initialize(withConfig: builder.build())
27
29
 
28
30
  if let siteId = siteId {
@@ -4,9 +4,40 @@ import { getRelativePathToRNSDK } from '../constants/ios';
4
4
  import { injectCodeByRegex } from './codeInjection';
5
5
  import { FileManagement } from './fileManagement';
6
6
 
7
+ export type InjectCIOPodfileOptions = {
8
+ /** When true, add the location subspec. When false/omit, use single push subspec only. */
9
+ locationEnabled?: boolean;
10
+ /** When false and locationEnabled, inject only :subspecs => ['location']. When true, use push + location. */
11
+ hasPush?: boolean;
12
+ };
13
+
14
+ /** Builds the host app pod line for the Podfile (single subspec or :subspecs with location). Exported for tests. */
15
+ export function buildHostAppPodSnippet(
16
+ iosPath: string,
17
+ isFcmPushProvider: boolean,
18
+ options?: InjectCIOPodfileOptions
19
+ ): string {
20
+ const path = getRelativePathToRNSDK(iosPath);
21
+ const locationEnabled = options?.locationEnabled === true;
22
+ const hasPush = options?.hasPush !== false;
23
+
24
+ if (!locationEnabled) {
25
+ const subspec = isFcmPushProvider ? 'fcm' : 'apn';
26
+ return `pod 'customerio-reactnative/${subspec}', :path => '${path}'`;
27
+ }
28
+
29
+ if (!hasPush) {
30
+ return `pod 'customerio-reactnative', :subspecs => ['location'], :path => '${path}'`;
31
+ }
32
+
33
+ const pushSubspec = isFcmPushProvider ? 'fcm' : 'apn';
34
+ return `pod 'customerio-reactnative', :subspecs => ['${pushSubspec}', 'location'], :path => '${path}'`;
35
+ }
36
+
7
37
  export async function injectCIOPodfileCode(
8
38
  iosPath: string,
9
- isFcmPushProvider: boolean
39
+ isFcmPushProvider: boolean,
40
+ options?: InjectCIOPodfileOptions
10
41
  ) {
11
42
  const blockStart = '# --- CustomerIO Host App START ---';
12
43
  const blockEnd = '# --- CustomerIO Host App END ---';
@@ -21,10 +52,11 @@ export async function injectCIOPodfileCode(
21
52
  // Find that line in the Podfile and then we will insert our code above that line.
22
53
  const lineInPodfileToInjectSnippetBefore = /post_install do \|installer\|/;
23
54
 
55
+ const podLine = buildHostAppPodSnippet(iosPath, isFcmPushProvider, options);
56
+
24
57
  const snippetToInjectInPodfile = `
25
58
  ${blockStart}
26
- pod 'customerio-reactnative/${isFcmPushProvider ? 'fcm' : 'apn'
27
- }', :path => '${getRelativePathToRNSDK(iosPath)}'
59
+ ${podLine}
28
60
  ${blockEnd}
29
61
  `.trim();
30
62
 
@@ -0,0 +1,80 @@
1
+ import type { LocationTrackingMode } from '../../types/cio-types';
2
+ import { PLATFORM, type Platform } from '../constants/common';
3
+
4
+ const VALID_TRACKING_MODES: LocationTrackingMode[] = ['OFF', 'MANUAL', 'ON_APP_START'];
5
+
6
+ /** Options for location module in generated native initializer */
7
+ export type LocationInitOptions = {
8
+ enabled: boolean;
9
+ trackingMode?: LocationTrackingMode;
10
+ };
11
+
12
+ function normalizeTrackingMode(
13
+ rawMode: string | undefined
14
+ ): LocationTrackingMode {
15
+ const upper = rawMode?.toUpperCase();
16
+ return upper && VALID_TRACKING_MODES.includes(upper as LocationTrackingMode)
17
+ ? (upper as LocationTrackingMode)
18
+ : 'MANUAL';
19
+ }
20
+
21
+ /**
22
+ * Replaces {{LOCATION_MODULE_IMPORT}} and {{LOCATION_MODULE_INIT}} placeholders
23
+ * in SDK initializer template content for the given platform.
24
+ */
25
+ export function patchLocationPlaceholders(
26
+ content: string,
27
+ platform: Platform,
28
+ locationOptions?: LocationInitOptions
29
+ ): string {
30
+ const locationEnabled = locationOptions?.enabled === true;
31
+ const trackingMode = normalizeTrackingMode(locationOptions?.trackingMode);
32
+
33
+ if (platform === PLATFORM.ANDROID) {
34
+ if (locationEnabled) {
35
+ return content
36
+ .replace(
37
+ /\{\{LOCATION_MODULE_IMPORT\}\}/g,
38
+ `import io.customer.location.LocationModuleConfig
39
+ import io.customer.location.LocationTrackingMode
40
+ import io.customer.location.ModuleLocation
41
+ `
42
+ )
43
+ .replace(
44
+ /\{\{LOCATION_MODULE_INIT\}\}/g,
45
+ `if (io.customer.reactnative.sdk.BuildConfig.CIO_LOCATION_ENABLED) {
46
+ addCustomerIOModule(
47
+ ModuleLocation(
48
+ LocationModuleConfig.Builder()
49
+ .setLocationTrackingMode(LocationTrackingMode.${trackingMode})
50
+ .build()
51
+ )
52
+ )
53
+ }
54
+ `
55
+ );
56
+ }
57
+ return content
58
+ .replace(/\n\{\{LOCATION_MODULE_IMPORT\}\}\n/g, '\n')
59
+ .replace(/\n\s*\{\{LOCATION_MODULE_INIT\}\}\n/g, '\n');
60
+ }
61
+
62
+ // iOS
63
+ if (locationEnabled) {
64
+ const modeSwift =
65
+ trackingMode === 'OFF'
66
+ ? '.off'
67
+ : trackingMode === 'ON_APP_START'
68
+ ? '.onAppStart'
69
+ : '.manual';
70
+ return content
71
+ .replace(/\{\{LOCATION_MODULE_IMPORT\}\}/g, 'import CioLocation\n')
72
+ .replace(
73
+ /\{\{LOCATION_MODULE_INIT\}\}/g,
74
+ `_ = builder.addModule(LocationModule(config: LocationConfig(mode: ${modeSwift})))`
75
+ );
76
+ }
77
+ return content
78
+ .replace(/\n\{\{LOCATION_MODULE_IMPORT\}\}\n/g, '\n')
79
+ .replace(/\n\s*\{\{LOCATION_MODULE_INIT\}\}\n/g, '\n\n');
80
+ }
@@ -2,6 +2,12 @@ import type { NativeSDKConfig } from '../../types/cio-types';
2
2
  import { getPluginVersion } from '../../utils/plugin';
3
3
  import { validateNativeSDKConfig } from '../../utils/validation';
4
4
  import { PLATFORM, type Platform } from '../constants/common';
5
+ import {
6
+ type LocationInitOptions,
7
+ patchLocationPlaceholders,
8
+ } from './patchLocationCode';
9
+
10
+ export type { LocationInitOptions };
5
11
 
6
12
  /**
7
13
  * Shared utility function to perform common SDK config replacements
@@ -10,7 +16,8 @@ import { PLATFORM, type Platform } from '../constants/common';
10
16
  export function patchNativeSDKInitializer(
11
17
  rawContent: string,
12
18
  platform: Platform,
13
- sdkConfig: NativeSDKConfig
19
+ sdkConfig: NativeSDKConfig,
20
+ locationOptions?: LocationInitOptions
14
21
  ): string {
15
22
  // Validate SDK configuration to ensure all fields are present and
16
23
  // correct at the time of patching in prebuild
@@ -98,5 +105,7 @@ export function patchNativeSDKInitializer(
98
105
  (configValue) => `"${configValue}"`
99
106
  );
100
107
 
108
+ content = patchLocationPlaceholders(content, platform, locationOptions);
109
+
101
110
  return content;
102
111
  }
@@ -3,7 +3,13 @@ import type { ExpoConfig } from '@expo/config-types';
3
3
  import { withCIOAndroid } from './android/withCIOAndroid';
4
4
  import { isExpoVersion53OrHigher } from './ios/utils';
5
5
  import { withCIOIos } from './ios/withCIOIos';
6
- import type { CustomerIOPluginOptions } from './types/cio-types';
6
+ import type {
7
+ CustomerIOPluginOptions,
8
+ LocationTrackingMode,
9
+ NativeSDKConfig,
10
+ } from './types/cio-types';
11
+
12
+ export type { LocationTrackingMode, NativeSDKConfig };
7
13
 
8
14
  // Entry point for config plugin
9
15
  function withCustomerIOPlugin(
@@ -20,8 +26,8 @@ function withCustomerIOPlugin(
20
26
  }
21
27
 
22
28
  // Apply platform specific modifications
23
- config = withCIOIos(config, props.config, props.ios);
24
- config = withCIOAndroid(config, props.config, props.android);
29
+ config = withCIOIos(config, props.config, props.ios, props.location);
30
+ config = withCIOAndroid(config, props.config, props.android, props.location);
25
31
 
26
32
  return config;
27
33
  }
@@ -2,6 +2,7 @@ import type { ExpoConfig } from '@expo/config-types';
2
2
  import type {
3
3
  CustomerIOPluginOptionsIOS,
4
4
  CustomerIOPluginPushNotificationOptions,
5
+ CustomerIOPluginLocationOptions,
5
6
  NativeSDKConfig,
6
7
  } from '../types/cio-types';
7
8
  import { mergeConfigWithEnvValues } from '../utils/config';
@@ -17,13 +18,15 @@ export function withCIOIos(
17
18
  config: ExpoConfig,
18
19
  sdkConfig?: NativeSDKConfig,
19
20
  props?: CustomerIOPluginOptionsIOS,
21
+ location?: CustomerIOPluginLocationOptions,
20
22
  ) {
21
23
  const isSwiftProject = isExpoVersion53OrHigher(config);
22
24
  const platformConfig = mergeDeprecatedPropertiesAndLogWarnings(props);
25
+ const locationEnabled = location?.enabled === true;
23
26
 
24
27
  if (platformConfig?.pushNotification) {
25
28
  if (isSwiftProject) {
26
- config = withCIOIosSwift(config, sdkConfig, platformConfig);
29
+ config = withCIOIosSwift(config, sdkConfig, platformConfig, location);
27
30
  } else {
28
31
  // Auto initialization is only supported in Swift projects (Expo SDK 53+)
29
32
  // Legacy Objective-C projects only support push notifications
@@ -33,10 +36,28 @@ export function withCIOIos(
33
36
  platformConfig.pushNotification.env = platformConfig.pushNotification.env
34
37
  || mergeConfigWithEnvValues(platformConfig, sdkConfig);
35
38
  config = withCioNotificationsXcodeProject(config, platformConfig);
36
- config = withCioXcodeProject(config, platformConfig);
39
+ config = withCioXcodeProject(config, {
40
+ ...platformConfig,
41
+ podfileOptions: {
42
+ locationEnabled,
43
+ hasPush: true,
44
+ },
45
+ });
37
46
  config = withGoogleServicesJsonFile(config, platformConfig);
38
47
  } else if (sdkConfig && isSwiftProject) {
39
- config = withCIOIosSwift(config, sdkConfig, platformConfig);
48
+ config = withCIOIosSwift(config, sdkConfig, platformConfig, location);
49
+ if (locationEnabled) {
50
+ config = withCioXcodeProject(config, {
51
+ ...platformConfig,
52
+ podfileOptions: { locationEnabled: true, hasPush: false },
53
+ });
54
+ }
55
+ } else if (locationEnabled) {
56
+ // Location-only: no push, no config. Still add Podfile location subspec so CIO_LOCATION_ENABLED is set and native location code is included.
57
+ config = withCioXcodeProject(config, {
58
+ ...platformConfig,
59
+ podfileOptions: { locationEnabled: true, hasPush: false },
60
+ });
40
61
  }
41
62
 
42
63
  return config;
@@ -17,7 +17,11 @@ import {
17
17
  import { replaceCodeByRegex } from '../helpers/utils/codeInjection';
18
18
  import { FileManagement } from '../helpers/utils/fileManagement';
19
19
  import { patchNativeSDKInitializer } from '../helpers/utils/patchPluginNativeCode';
20
- import type { CustomerIOPluginOptionsIOS, NativeSDKConfig } from '../types/cio-types';
20
+ import type {
21
+ CustomerIOPluginOptionsIOS,
22
+ CustomerIOPluginLocationOptions,
23
+ NativeSDKConfig,
24
+ } from '../types/cio-types';
21
25
  import { logger } from '../utils/logger';
22
26
  import { getIosNativeFilesPath } from '../utils/plugin';
23
27
  import { copyFileToXcode, getOrCreateCustomerIOGroup } from '../utils/xcode';
@@ -34,6 +38,7 @@ const copyAndConfigureAppDelegateHandler = (
34
38
  config: ExportedConfigWithProps<XcodeProject>,
35
39
  sdkConfig?: NativeSDKConfig,
36
40
  props?: CustomerIOPluginOptionsIOS,
41
+ location?: CustomerIOPluginLocationOptions,
37
42
  ): ExportedConfigWithProps<XcodeProject> => {
38
43
  // Destination path in the iOS project
39
44
  const projectName = config.modRequest.projectName || '';
@@ -59,6 +64,7 @@ const copyAndConfigureAppDelegateHandler = (
59
64
  projectName,
60
65
  sdkConfig,
61
66
  props,
67
+ location,
62
68
  });
63
69
  } else if (sdkConfig) {
64
70
  // Copy only CustomerIOSDKInitializer.swift for auto-init without push notifications
@@ -68,6 +74,7 @@ const copyAndConfigureAppDelegateHandler = (
68
74
  iosProjectRoot,
69
75
  projectName,
70
76
  sdkConfig,
77
+ location,
71
78
  });
72
79
  }
73
80
 
@@ -81,6 +88,7 @@ const copyAndConfigurePushAppDelegateHandler = ({
81
88
  projectName,
82
89
  sdkConfig,
83
90
  props,
91
+ location,
84
92
  }: {
85
93
  xcodeProject: XcodeProject;
86
94
  group: XcodeProject['pbxCreateGroup'];
@@ -88,6 +96,7 @@ const copyAndConfigurePushAppDelegateHandler = ({
88
96
  projectName: string;
89
97
  sdkConfig: NativeSDKConfig | undefined;
90
98
  props: CustomerIOPluginOptionsIOS;
99
+ location?: CustomerIOPluginLocationOptions;
91
100
  }) => {
92
101
  const useFcm = isFcmPushProvider(props);
93
102
 
@@ -156,7 +165,7 @@ const copyAndConfigurePushAppDelegateHandler = ({
156
165
  // Add auto initialization if sdkConfig is provided
157
166
  if (sdkConfig) {
158
167
  // Also copy CustomerIOSDKInitializer.swift for auto-initialization
159
- copyAndConfigureNativeSDKInitializer({ xcodeProject, group, iosProjectRoot, projectName, sdkConfig });
168
+ copyAndConfigureNativeSDKInitializer({ xcodeProject, group, iosProjectRoot, projectName, sdkConfig, location });
160
169
 
161
170
  // Inject auto initialization call before MessagingPush initialization
162
171
  handlerFileContent = handlerFileContent.replace(CIO_MESSAGING_PUSH_APP_DELEGATE_INIT_REGEX, CIO_NATIVE_SDK_INITIALIZE_SNIPPET + '$1');
@@ -171,13 +180,18 @@ const copyAndConfigureNativeSDKInitializer = ({
171
180
  iosProjectRoot,
172
181
  projectName,
173
182
  sdkConfig,
183
+ location,
174
184
  }: {
175
185
  xcodeProject: XcodeProject;
176
186
  group: XcodeProject['pbxCreateGroup'];
177
187
  iosProjectRoot: string;
178
188
  projectName: string;
179
189
  sdkConfig: NativeSDKConfig;
190
+ location?: CustomerIOPluginLocationOptions;
180
191
  }) => {
192
+ const locationOptions = location
193
+ ? { enabled: location.enabled === true, trackingMode: sdkConfig?.location?.trackingMode }
194
+ : undefined;
181
195
  const filename = 'CustomerIOSDKInitializer.swift';
182
196
  const sourcePath = path.join(getIosNativeFilesPath(), filename);
183
197
  // Add the CustomerIOSDKInitializer.swift file to the same Xcode group as CioSdkAppDelegateHandler
@@ -187,7 +201,8 @@ const copyAndConfigureNativeSDKInitializer = ({
187
201
  projectName,
188
202
  sourceFilePath: sourcePath,
189
203
  targetFileName: filename,
190
- transform: (content) => patchNativeSDKInitializer(content, PLATFORM.IOS, sdkConfig),
204
+ transform: (content) =>
205
+ patchNativeSDKInitializer(content, PLATFORM.IOS, sdkConfig, locationOptions),
191
206
  customerIOGroup: group,
192
207
  });
193
208
  };
@@ -196,10 +211,11 @@ export const withCIOIosSwift = (
196
211
  configOuter: ExpoConfig,
197
212
  sdkConfig?: NativeSDKConfig,
198
213
  props?: CustomerIOPluginOptionsIOS,
214
+ location?: CustomerIOPluginLocationOptions,
199
215
  ) => {
200
216
  // First, copy required swift files to iOS folder and add it to Xcode project
201
217
  configOuter = withXcodeProject(configOuter, async (config) => {
202
- return copyAndConfigureAppDelegateHandler(config, sdkConfig, props);
218
+ return copyAndConfigureAppDelegateHandler(config, sdkConfig, props, location);
203
219
  });
204
220
 
205
221
  // Modify the AppDelegate based on configuration
@@ -1,18 +1,35 @@
1
1
  import type { ConfigPlugin } from '@expo/config-plugins';
2
2
  import { withXcodeProject } from '@expo/config-plugins';
3
3
 
4
- import { injectCIOPodfileCode } from '../helpers/utils/injectCIOPodfileCode';
4
+ import {
5
+ injectCIOPodfileCode,
6
+ type InjectCIOPodfileOptions,
7
+ } from '../helpers/utils/injectCIOPodfileCode';
5
8
  import type { CustomerIOPluginOptionsIOS } from '../types/cio-types';
6
9
  import { isFcmPushProvider } from './utils';
7
10
 
8
- export const withCioXcodeProject: ConfigPlugin<CustomerIOPluginOptionsIOS> = (
11
+ export type WithCioXcodeProjectOptions = {
12
+ /** Options for Podfile host app snippet (location subspec, etc.) */
13
+ podfileOptions?: InjectCIOPodfileOptions;
14
+ };
15
+
16
+ /** Props for the CIO Xcode project mod; push options are optional when only location is enabled. */
17
+ export type WithCioXcodeProjectProps = Partial<CustomerIOPluginOptionsIOS> &
18
+ WithCioXcodeProjectOptions;
19
+
20
+ export const withCioXcodeProject: ConfigPlugin<WithCioXcodeProjectProps> = (
9
21
  config,
10
22
  cioProps
11
23
  ) => {
12
24
  return withXcodeProject(config, async (props) => {
13
25
  const iosPath = props.modRequest.platformProjectRoot;
26
+ const podfileOptions = cioProps?.podfileOptions;
14
27
 
15
- await injectCIOPodfileCode(iosPath, isFcmPushProvider(cioProps));
28
+ await injectCIOPodfileCode(
29
+ iosPath,
30
+ isFcmPushProvider(cioProps as CustomerIOPluginOptionsIOS | undefined),
31
+ podfileOptions
32
+ );
16
33
 
17
34
  return props;
18
35
  });