From c6bd4894919274b07f7f834828d24479591c6cd5 Mon Sep 17 00:00:00 2001 From: Mahmoud Elmorabea Date: Wed, 22 Apr 2026 23:54:20 +0200 Subject: [PATCH] fix: honor top-level apiHost and cdnHost in CioConfig Previously the React Native SDK silently dropped these values: iOS only read them from an undocumented nested `qa` object, and Android did not read them at any path. Customers following the public proxy-hosting docs had their overrides ignored on both platforms. Also removes the example-app-only `qa` override shim in favor of the now public top-level fields. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../reactnative/sdk/NativeCustomerIOModule.kt | 4 ++ .../customer/reactnative/sdk/constant/Keys.kt | 2 + .../customerio-reactnative.api.md | 2 + example/src/screens/internal-settings.tsx | 32 +++++++------ example/src/services/storage.ts | 45 +++---------------- ios/wrappers/utils/CioConfigUtils.swift | 6 +-- src/types/data-pipelines.ts | 4 ++ 7 files changed, 39 insertions(+), 56 deletions(-) diff --git a/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt b/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt index cfe302e6..3ac1645f 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt @@ -84,6 +84,10 @@ class NativeCustomerIOModule( ?.let { flushInterval(it) } packageConfig.getTypedValue(Keys.Config.TRACK_APP_LIFECYCLE_EVENTS) ?.let { trackApplicationLifecycleEvents(it) } + packageConfig.getTypedValue(Keys.Config.API_HOST) + ?.let { apiHost(it) } + packageConfig.getTypedValue(Keys.Config.CDN_HOST) + ?.let { cdnHost(it) } // Configure push messaging module based on config provided by customer app packageConfig.getTypedValue>(key = "push").let { pushConfig -> diff --git a/android/src/main/java/io/customer/reactnative/sdk/constant/Keys.kt b/android/src/main/java/io/customer/reactnative/sdk/constant/Keys.kt index 5ef358e2..cb14e3cd 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/constant/Keys.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/constant/Keys.kt @@ -12,6 +12,8 @@ internal object Keys { const val FLUSH_AT = "flushAt" const val FLUSH_INTERVAL = "flushInterval" const val SCREEN_VIEW_USE = "screenViewUse" + const val API_HOST = "apiHost" + const val CDN_HOST = "cdnHost" // Push messaging const val PUSH_CLICK_BEHAVIOR = "pushClickBehavior" } diff --git a/api-extractor-output/customerio-reactnative.api.md b/api-extractor-output/customerio-reactnative.api.md index fe28ee10..c17a5e49 100644 --- a/api-extractor-output/customerio-reactnative.api.md +++ b/api-extractor-output/customerio-reactnative.api.md @@ -21,6 +21,8 @@ export type CioConfig = { migrationSiteId?: string; region?: CioRegion; logLevel?: CioLogLevel; + apiHost?: string; + cdnHost?: string; flushAt?: number; flushInterval?: number; screenViewUse?: ScreenView; diff --git a/example/src/screens/internal-settings.tsx b/example/src/screens/internal-settings.tsx index 6378e2cf..ba303d08 100644 --- a/example/src/screens/internal-settings.tsx +++ b/example/src/screens/internal-settings.tsx @@ -4,28 +4,28 @@ import { LargeBoldText, TextField, } from '@components'; -import { InternalSettings, Storage } from '@services'; +import { Storage } from '@services'; import React, { useState } from 'react'; import { ScrollView, StyleSheet, View } from 'react-native'; import { showMessage } from 'react-native-flash-message'; -const validateSettings = (config: InternalSettings) => { - if (!config.apiHost || !config.cdnHost) { +type HostOverrides = { + apiHost?: string; + cdnHost?: string; +}; + +const validateSettings = (overrides: HostOverrides) => { + if (!overrides.apiHost || !overrides.cdnHost) { throw Error('CDN and API host values are missing'); - } else if (!config.cdnHost) { - throw Error('CDB Host value is missing'); - } else if (!config.apiHost) { - throw Error('API Host value is missing'); } }; export const InternalSettingsScreen = () => { - const [config, setConfig] = useState( - Storage.instance.getInternalDevConfig() || { - cdnHost: '', - apiHost: '', - } - ); + const initialConfig = Storage.instance.getCioConfig(); + const [config, setConfig] = useState({ + apiHost: initialConfig?.apiHost ?? '', + cdnHost: initialConfig?.cdnHost ?? '', + }); return ( @@ -56,7 +56,11 @@ export const InternalSettingsScreen = () => { onPress={async () => { try { validateSettings(config); - Storage.instance.setInternalDevConfig(config); + await Storage.instance.setCioConfig({ + ...Storage.instance.getCioConfig(), + apiHost: config.apiHost, + cdnHost: config.cdnHost, + }); showMessage({ message: 'Internal Settings saved successfully', diff --git a/example/src/services/storage.ts b/example/src/services/storage.ts index 14bdb03f..cadc943b 100644 --- a/example/src/services/storage.ts +++ b/example/src/services/storage.ts @@ -5,12 +5,8 @@ import { Env } from '../env'; const USER_STORAGE_KEY = 'user'; const CIO_CONFIG_STORAGE_KEY = 'cioConfig'; -export type InternalSettings = { - cdnHost: string; - apiHost: string; -}; -type Config = Partial & { qa?: InternalSettings }; +type Config = Partial; const createDefaultConfig = (env: Env | null | undefined): Config => { return { @@ -73,11 +69,7 @@ export class Storage { }; readonly setCioConfig = async (cioConfig: CioConfig) => { - const config = cioConfig as Config; - this.config = { - ...config, - qa: config.qa ?? Storage.defaultConfig.qa, - }; + this.config = cioConfig as Config; await AsyncStorage.setItem( CIO_CONFIG_STORAGE_KEY, JSON.stringify(this.config) @@ -93,34 +85,11 @@ export class Storage { }; readonly resetCioConfig = async () => { - if (this.config === null) { - this.config = Storage.defaultConfig; - } else { - this.config = { - ...this.config, - ...Storage.defaultConfig, - qa: this.config.qa, - }; - } - - await AsyncStorage.setItem( - CIO_CONFIG_STORAGE_KEY, - JSON.stringify(this.config) - ); - }; - - readonly getInternalDevConfig = (): InternalSettings | undefined => { - return this.config?.qa ?? Storage.defaultConfig.qa; - }; - - readonly setInternalDevConfig = async ( - internalSettings: InternalSettings - ) => { - if (this.config === null) { - this.config = { ...Storage.defaultConfig, qa: internalSettings }; - } else { - this.config = { ...this.config, qa: internalSettings }; - } + this.config = { + ...Storage.defaultConfig, + apiHost: this.config?.apiHost, + cdnHost: this.config?.cdnHost, + }; await AsyncStorage.setItem( CIO_CONFIG_STORAGE_KEY, diff --git a/ios/wrappers/utils/CioConfigUtils.swift b/ios/wrappers/utils/CioConfigUtils.swift index 5399f89f..12a6ccbc 100644 --- a/ios/wrappers/utils/CioConfigUtils.swift +++ b/ios/wrappers/utils/CioConfigUtils.swift @@ -36,10 +36,8 @@ extension SDKConfigBuilder { Config.screenViewUse.ifNotNil(in: config, thenPassItTo: builder.screenViewUse) { ScreenView.getScreenView($0) } Config.trackApplicationLifecycleEvents.ifNotNil(in: config, thenPassItTo: builder.trackApplicationLifecycleEvents) Config.autoTrackDeviceAttributes.ifNotNil(in: config, thenPassItTo: builder.autoTrackDeviceAttributes) - - let qaConfig = config["qa"] as? [String: Any?] - Config.apiHost.ifNotNil(in: qaConfig, thenPassItTo: builder.apiHost) - Config.cdnHost.ifNotNil(in: qaConfig, thenPassItTo: builder.cdnHost) + Config.apiHost.ifNotNil(in: config, thenPassItTo: builder.apiHost) + Config.cdnHost.ifNotNil(in: config, thenPassItTo: builder.cdnHost) return builder } diff --git a/src/types/data-pipelines.ts b/src/types/data-pipelines.ts index e4e0790b..83648d2a 100644 --- a/src/types/data-pipelines.ts +++ b/src/types/data-pipelines.ts @@ -40,6 +40,8 @@ export interface IdentifyParams { * @param migrationSiteId - Legacy site ID for migrating from tracking API * @param region - Data center region (US or EU) * @param logLevel - SDK logging verbosity level + * @param apiHost - Override the CDP API host. Use to route SDK traffic through a first-party proxy. + * @param cdnHost - Override the CDN host. Use to route SDK traffic through a first-party proxy. * @param flushAt - Number of events to batch before sending * @param flushInterval - Time interval (seconds) to flush events * @param screenViewUse - How to handle screen view tracking @@ -55,6 +57,8 @@ export type CioConfig = { migrationSiteId?: string; region?: CioRegion; logLevel?: CioLogLevel; + apiHost?: string; + cdnHost?: string; flushAt?: number; flushInterval?: number; screenViewUse?: ScreenView;