customerio-expo-plugin 2.5.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.
- package/package.json +13 -31
- package/plugin/app.plugin.js +1 -1
- package/plugin/lib/commonjs/android/withAndroidManifestUpdates.js +4 -4
- package/plugin/lib/commonjs/android/withAndroidManifestUpdates.js.map +1 -1
- package/plugin/lib/commonjs/android/withCIOAndroid.js +6 -1
- package/plugin/lib/commonjs/android/withCIOAndroid.js.map +1 -1
- package/plugin/lib/commonjs/android/withGoogleServicesJSON.js +1 -1
- package/plugin/lib/commonjs/android/withGoogleServicesJSON.js.map +1 -1
- package/plugin/lib/commonjs/android/withMainApplicationModifications.js +45 -0
- package/plugin/lib/commonjs/android/withMainApplicationModifications.js.map +1 -0
- package/plugin/lib/commonjs/android/withNotificationChannelMetadata.js +1 -1
- package/plugin/lib/commonjs/android/withNotificationChannelMetadata.js.map +1 -1
- package/plugin/lib/commonjs/android/withProjectStrings.js +14 -7
- package/plugin/lib/commonjs/android/withProjectStrings.js.map +1 -1
- package/plugin/lib/commonjs/helpers/constants/android.js +7 -1
- package/plugin/lib/commonjs/helpers/constants/android.js.map +1 -1
- package/plugin/lib/commonjs/helpers/constants/common.js +18 -0
- package/plugin/lib/commonjs/helpers/constants/common.js.map +1 -0
- package/plugin/lib/commonjs/helpers/constants/ios.js +7 -7
- package/plugin/lib/commonjs/helpers/constants/ios.js.map +1 -1
- package/plugin/lib/commonjs/helpers/native-files/android/CustomerIOSDKInitializer.kt +64 -0
- package/plugin/lib/commonjs/helpers/native-files/ios/CustomerIOSDKInitializer.swift +54 -0
- package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js +2 -2
- package/plugin/lib/commonjs/helpers/utils/injectCIOPodfileCode.js.map +1 -1
- package/plugin/lib/commonjs/helpers/utils/patchPluginNativeCode.js +62 -0
- package/plugin/lib/commonjs/helpers/utils/patchPluginNativeCode.js.map +1 -0
- package/plugin/lib/commonjs/index.js +13 -2
- package/plugin/lib/commonjs/index.js.map +1 -1
- package/plugin/lib/commonjs/ios/utils.js +1 -1
- package/plugin/lib/commonjs/ios/utils.js.map +1 -1
- package/plugin/lib/commonjs/ios/withAppDelegateModifications.js +1 -1
- package/plugin/lib/commonjs/ios/withAppDelegateModifications.js.map +1 -1
- package/plugin/lib/commonjs/ios/withCIOIos.js +17 -11
- package/plugin/lib/commonjs/ios/withCIOIos.js.map +1 -1
- package/plugin/lib/commonjs/ios/withCIOIosSwift.js +133 -49
- package/plugin/lib/commonjs/ios/withCIOIosSwift.js.map +1 -1
- package/plugin/lib/commonjs/ios/withGoogleServicesJsonFile.js +1 -1
- package/plugin/lib/commonjs/ios/withGoogleServicesJsonFile.js.map +1 -1
- package/plugin/lib/commonjs/ios/withNotificationsXcodeProject.js +17 -19
- package/plugin/lib/commonjs/ios/withNotificationsXcodeProject.js.map +1 -1
- package/plugin/lib/commonjs/ios/withXcodeProject.js +1 -1
- package/plugin/lib/commonjs/ios/withXcodeProject.js.map +1 -1
- package/plugin/lib/commonjs/postInstallHelper.js.map +1 -1
- package/plugin/lib/commonjs/types/cio-types.js.map +1 -1
- package/plugin/lib/commonjs/utils/android.js +109 -0
- package/plugin/lib/commonjs/utils/android.js.map +1 -0
- package/plugin/lib/commonjs/utils/config.js +43 -0
- package/plugin/lib/commonjs/utils/config.js.map +1 -0
- package/plugin/lib/commonjs/utils/plugin.js +49 -0
- package/plugin/lib/commonjs/utils/plugin.js.map +1 -0
- package/plugin/lib/commonjs/utils/validation.js +44 -0
- package/plugin/lib/commonjs/utils/validation.js.map +1 -0
- package/plugin/lib/commonjs/utils/xcode.js +67 -0
- package/plugin/lib/commonjs/utils/xcode.js.map +1 -0
- package/plugin/lib/module/android/withAndroidManifestUpdates.js +4 -4
- package/plugin/lib/module/android/withAndroidManifestUpdates.js.map +1 -1
- package/plugin/lib/module/android/withCIOAndroid.js +6 -1
- package/plugin/lib/module/android/withCIOAndroid.js.map +1 -1
- package/plugin/lib/module/android/withGoogleServicesJSON.js +1 -1
- package/plugin/lib/module/android/withGoogleServicesJSON.js.map +1 -1
- package/plugin/lib/module/android/withMainApplicationModifications.js +38 -0
- package/plugin/lib/module/android/withMainApplicationModifications.js.map +1 -0
- package/plugin/lib/module/android/withNotificationChannelMetadata.js +1 -1
- package/plugin/lib/module/android/withNotificationChannelMetadata.js.map +1 -1
- package/plugin/lib/module/android/withProjectStrings.js +13 -6
- package/plugin/lib/module/android/withProjectStrings.js.map +1 -1
- package/plugin/lib/module/helpers/constants/android.js +6 -0
- package/plugin/lib/module/helpers/constants/android.js.map +1 -1
- package/plugin/lib/module/helpers/constants/common.js +12 -0
- package/plugin/lib/module/helpers/constants/common.js.map +1 -0
- package/plugin/lib/module/helpers/constants/ios.js +6 -6
- package/plugin/lib/module/helpers/constants/ios.js.map +1 -1
- package/plugin/lib/module/helpers/native-files/android/CustomerIOSDKInitializer.kt +64 -0
- package/plugin/lib/module/helpers/native-files/ios/CustomerIOSDKInitializer.swift +54 -0
- package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js +2 -2
- package/plugin/lib/module/helpers/utils/injectCIOPodfileCode.js.map +1 -1
- package/plugin/lib/module/helpers/utils/patchPluginNativeCode.js +57 -0
- package/plugin/lib/module/helpers/utils/patchPluginNativeCode.js.map +1 -0
- package/plugin/lib/module/index.js +14 -2
- package/plugin/lib/module/index.js.map +1 -1
- package/plugin/lib/module/ios/utils.js +1 -2
- package/plugin/lib/module/ios/utils.js.map +1 -1
- package/plugin/lib/module/ios/withAppDelegateModifications.js +3 -3
- package/plugin/lib/module/ios/withAppDelegateModifications.js.map +1 -1
- package/plugin/lib/module/ios/withCIOIos.js +17 -11
- package/plugin/lib/module/ios/withCIOIos.js.map +1 -1
- package/plugin/lib/module/ios/withCIOIosSwift.js +134 -50
- package/plugin/lib/module/ios/withCIOIosSwift.js.map +1 -1
- package/plugin/lib/module/ios/withGoogleServicesJsonFile.js +2 -2
- package/plugin/lib/module/ios/withGoogleServicesJsonFile.js.map +1 -1
- package/plugin/lib/module/ios/withNotificationsXcodeProject.js +18 -20
- package/plugin/lib/module/ios/withNotificationsXcodeProject.js.map +1 -1
- package/plugin/lib/module/ios/withXcodeProject.js +1 -1
- package/plugin/lib/module/ios/withXcodeProject.js.map +1 -1
- package/plugin/lib/module/postInstallHelper.js.map +1 -1
- package/plugin/lib/module/types/cio-types.js.map +1 -1
- package/plugin/lib/module/utils/android.js +100 -0
- package/plugin/lib/module/utils/android.js.map +1 -0
- package/plugin/lib/module/utils/config.js +38 -0
- package/plugin/lib/module/utils/config.js.map +1 -0
- package/plugin/lib/module/utils/plugin.js +38 -0
- package/plugin/lib/module/utils/plugin.js.map +1 -0
- package/plugin/lib/module/utils/validation.js +39 -0
- package/plugin/lib/module/utils/validation.js.map +1 -0
- package/plugin/lib/module/utils/xcode.js +60 -0
- package/plugin/lib/module/utils/xcode.js.map +1 -0
- package/plugin/lib/typescript/android/withCIOAndroid.d.ts +2 -2
- package/plugin/lib/typescript/android/withMainApplicationModifications.d.ts +3 -0
- package/plugin/lib/typescript/android/withProjectStrings.d.ts +2 -1
- package/plugin/lib/typescript/helpers/constants/android.d.ts +3 -0
- package/plugin/lib/typescript/helpers/constants/common.d.ts +11 -0
- package/plugin/lib/typescript/helpers/constants/ios.d.ts +3 -1
- package/plugin/lib/typescript/helpers/utils/patchPluginNativeCode.d.ts +7 -0
- package/plugin/lib/typescript/ios/utils.d.ts +2 -2
- package/plugin/lib/typescript/ios/withCIOIos.d.ts +2 -2
- package/plugin/lib/typescript/ios/withCIOIosSwift.d.ts +3 -3
- package/plugin/lib/typescript/types/cio-types.d.ts +46 -6
- package/plugin/lib/typescript/utils/android.d.ts +5 -0
- package/plugin/lib/typescript/utils/config.d.ts +8 -0
- package/plugin/lib/typescript/utils/plugin.d.ts +4 -0
- package/plugin/lib/typescript/utils/validation.d.ts +3 -0
- package/plugin/lib/typescript/utils/xcode.d.ts +28 -0
- package/plugin/src/android/withAndroidManifestUpdates.ts +5 -5
- package/plugin/src/android/withCIOAndroid.ts +7 -1
- package/plugin/src/android/withGoogleServicesJSON.ts +2 -2
- package/plugin/src/android/withMainApplicationModifications.ts +50 -0
- package/plugin/src/android/withNotificationChannelMetadata.ts +7 -3
- package/plugin/src/android/withProjectStrings.ts +20 -10
- package/plugin/src/helpers/constants/android.ts +7 -0
- package/plugin/src/helpers/constants/common.ts +12 -0
- package/plugin/src/helpers/constants/ios.ts +11 -13
- package/plugin/src/helpers/native-files/android/CustomerIOSDKInitializer.kt +64 -0
- package/plugin/src/helpers/native-files/ios/CustomerIOSDKInitializer.swift +54 -0
- package/plugin/src/helpers/utils/injectCIOPodfileCode.ts +8 -7
- package/plugin/src/helpers/utils/patchPluginNativeCode.ts +97 -0
- package/plugin/src/index.ts +18 -2
- package/plugin/src/ios/utils.ts +5 -5
- package/plugin/src/ios/withAppDelegateModifications.ts +11 -8
- package/plugin/src/ios/withCIOIos.ts +19 -11
- package/plugin/src/ios/withCIOIosSwift.ts +195 -73
- package/plugin/src/ios/withGoogleServicesJsonFile.ts +7 -10
- package/plugin/src/ios/withNotificationsXcodeProject.ts +25 -26
- package/plugin/src/ios/withXcodeProject.ts +1 -1
- package/plugin/src/postInstallHelper.js +1 -1
- package/plugin/src/types/cio-types.ts +48 -8
- package/plugin/src/utils/android.ts +112 -0
- package/plugin/src/utils/config.ts +53 -0
- package/plugin/src/utils/plugin.ts +46 -0
- package/plugin/src/utils/validation.ts +54 -0
- package/plugin/src/utils/xcode.ts +74 -0
- package/plugin/lib/commonjs/helpers/constants/globals.d.js +0 -2
- package/plugin/lib/commonjs/helpers/constants/globals.d.js.map +0 -1
- package/plugin/lib/commonjs/helpers/utils/pluginUtils.js +0 -26
- package/plugin/lib/commonjs/helpers/utils/pluginUtils.js.map +0 -1
- package/plugin/lib/module/helpers/constants/globals.d.js +0 -2
- package/plugin/lib/module/helpers/constants/globals.d.js.map +0 -1
- package/plugin/lib/module/helpers/utils/pluginUtils.js +0 -19
- package/plugin/lib/module/helpers/utils/pluginUtils.js.map +0 -1
- package/plugin/lib/typescript/helpers/utils/pluginUtils.d.ts +0 -4
- package/plugin/src/helpers/constants/globals.d.ts +0 -8
- 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['
|
|
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
|
|
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 (
|
|
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 {
|
|
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 = (
|
|
9
|
-
return withStringsXml(
|
|
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
|
-
{
|
|
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:
|
|
35
|
-
stringResources: { name: 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 =
|
|
44
|
-
(item
|
|
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
|
-
|
|
59
|
+
stringArray[existingStringIndex]._ = value;
|
|
50
60
|
} else {
|
|
51
61
|
// Add a new string resource
|
|
52
|
-
|
|
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}`;
|
|
@@ -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(
|
|
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(
|
|
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 ?
|
|
23
|
-
|
|
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 ?
|
|
60
|
-
|
|
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
|
+
}
|
package/plugin/src/index.ts
CHANGED
|
@@ -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;
|
package/plugin/src/ios/utils.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 = (
|
|
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
|
|
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 =
|
|
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 (
|
|
24
|
+
if (platformConfig.pushNotification) {
|
|
22
25
|
if (isSwiftProject) {
|
|
23
|
-
config = withCIOIosSwift(config,
|
|
26
|
+
config = withCIOIosSwift(config, sdkConfig, platformConfig);
|
|
24
27
|
} else {
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
config =
|
|
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
|
-
|