From 491166829725037b2d87fd74d8d5d120eeec65e2 Mon Sep 17 00:00:00 2001 From: tiagocandido Date: Wed, 19 Aug 2026 15:00:34 +0200 Subject: [PATCH] Complete React Native telemetry opt-out --- platforms/react-native/README.md | 8 ++++++ .../react-native/__mocks__/react-native.ts | 1 + .../RNShopifyCheckoutKit.podspec | 4 +-- .../checkoutkit/ShopifyCheckoutKitModule.java | 5 ++++ .../ios/ShopifyCheckoutKit.swift | 5 ++++ .../checkout-kit-react-native/src/index.d.ts | 6 ++++ .../src/specs/NativeShopifyCheckoutKit.ts | 2 ++ .../tests/context.test.tsx | 1 + .../tests/index.test.ts | 23 +++++++++++++++ .../ShopifyCheckoutKitModuleTest.java | 28 ++++++++++++++++++- .../react-native/sample/ios/Podfile.lock | 6 ++-- platforms/react-native/scripts/lint_swift | 2 +- .../test/rct-integration-app/Podfile.lock | 6 ++-- .../ShopifyCheckoutKitTests.swift | 15 ++++++++++ 14 files changed, 102 insertions(+), 10 deletions(-) diff --git a/platforms/react-native/README.md b/platforms/react-native/README.md index ded999e28..dd76b1829 100644 --- a/platforms/react-native/README.md +++ b/platforms/react-native/README.md @@ -345,10 +345,18 @@ instance of the `ShopifyCheckout` class. | `title` | | `Checkout` | Sets the title of the checkout sheet at runtime on both iOS and Android. For per-locale localization, use the platform resource files. See [Localization](#localization). | | `colorScheme` | | `automatic` | Sets the color scheme for the checkout. | | `preloading` | | `true` | Enable/disable [preloading](#preloading). | +| `telemetry` | | `true` | Sends anonymous diagnostic metrics to Shopify on iOS and Android. Set to `false` to opt out. | | `colors` | | `{}` | An object with `ios` and `android` properties to override the colors for iOS and Android platforms individually. See [`colors`](#colors) for more information. | | `logLevel` | | `error` | Sets the log level for the native SDK. Use `LogLevel.debug` for verbose logging during development, or `LogLevel.error` for production. | | `allowedMessageOrigins` | | `[]` | Extra origins trusted to send incoming checkout messages. See [Incoming message origin validation](#incoming-message-origin-validation). | +Checkout Kit reports bounded counts for checkout errors, protocol decoding +failures, and navigation retries, plus navigation duration histograms. These +diagnostics never include checkout URLs, message payloads, buyer data, or +checkout, order, customer, or shop identifiers. Disabling telemetry stops new +collection and discards measurements that have not already been handed to the +operating system for delivery. + Here's an example of how a fully customized configuration object might look: ```tsx diff --git a/platforms/react-native/__mocks__/react-native.ts b/platforms/react-native/__mocks__/react-native.ts index 36612ac1d..f22bc7a47 100644 --- a/platforms/react-native/__mocks__/react-native.ts +++ b/platforms/react-native/__mocks__/react-native.ts @@ -49,6 +49,7 @@ const exampleConfig = { colorScheme: 'automatic', logLevel: 'error', preloading: true, + telemetry: true, }; const shopifyCheckoutKitEventEmitter = createMockEmitter(); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec b/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec index 6ab11051d..1b5519c9c 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec @@ -26,8 +26,8 @@ Pod::Spec.new do |s| s.dependency "ShopifyCheckoutKit" s.dependency "ShopifyCheckoutKit/AcceleratedCheckouts" else - s.dependency "ShopifyCheckoutKit", "~> #{ios_native_sdk_version}" - s.dependency "ShopifyCheckoutKit/AcceleratedCheckouts", "~> #{ios_native_sdk_version}" + s.dependency "ShopifyCheckoutKit", "= #{ios_native_sdk_version}" + s.dependency "ShopifyCheckoutKit/AcceleratedCheckouts", "= #{ios_native_sdk_version}" end install_modules_dependencies(s) diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java index 3e2aeb9d1..b42635b77 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java @@ -202,6 +202,7 @@ public WritableMap getConfig() { resultConfig.putString("colorScheme", colorSchemeStringFor(checkoutConfig.getAppearance())); resultConfig.putString("logLevel", logLevelStringFor(checkoutConfig.getLogLevel())); resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled()); + resultConfig.putBoolean("telemetry", checkoutConfig.getTelemetry().getEnabled()); resultConfig.putArray("allowedMessageOrigins", Arguments.fromList(new ArrayList<>(checkoutConfig.getAllowedMessageOrigins()))); @@ -223,6 +224,10 @@ public void setConfig(ReadableMap config) { configuration.setAllowedMessageOrigins(toStringSet(config.getArray("allowedMessageOrigins"))); } + if (config.hasKey("telemetry")) { + configuration.setTelemetry(new Telemetry(config.getBoolean("telemetry"))); + } + if (config.hasKey("logLevel")) { LogLevel logLevel = logLevelFor(config.getString("logLevel")); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift index f1251316a..c07d7cd8a 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift @@ -184,6 +184,10 @@ class RCTShopifyCheckoutKit: NSObject { ShopifyCheckoutKit.configuration.allowedMessageOrigins = allowedMessageOrigins } + if let telemetry = configuration["telemetry"] as? Bool { + ShopifyCheckoutKit.configuration.telemetry.enabled = telemetry + } + if let colorScheme = configuration["colorScheme"] as? String, let appearance = appearanceFor(colorScheme) { @@ -216,6 +220,7 @@ class RCTShopifyCheckoutKit: NSObject { "title": ShopifyCheckoutKit.configuration.title, "colorScheme": colorSchemeStringFor(ShopifyCheckoutKit.configuration.appearance), "preloading": ShopifyCheckoutKit.configuration.preloading.enabled, + "telemetry": ShopifyCheckoutKit.configuration.telemetry.enabled, "tintColor": ShopifyCheckoutKit.configuration.tintColor, "backgroundColor": ShopifyCheckoutKit.configuration.backgroundColor, "closeButtonColor": ShopifyCheckoutKit.configuration.closeButtonTintColor, diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.d.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.d.ts index 3035540f7..735c6e901 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.d.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/index.d.ts @@ -129,6 +129,12 @@ interface CommonConfiguration { * @default [] (all origins trusted) */ allowedMessageOrigins?: string[]; + /** + * Sends anonymous diagnostic metrics to Shopify. Set to `false` to opt out. + * + * @default true + */ + telemetry?: boolean; } export type Configuration = CommonConfiguration & { diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts index ef1c22a34..95e8b4b60 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/src/specs/NativeShopifyCheckoutKit.ts @@ -36,6 +36,7 @@ type ConfigurationSpec = { logLevel?: string; preloading?: boolean; allowedMessageOrigins?: string[]; + telemetry?: boolean; colors?: ColorsSpec; }; @@ -43,6 +44,7 @@ type ConfigurationResultSpec = { colorScheme: string; logLevel: string; preloading: boolean; + telemetry: boolean; title?: string; tintColor?: string; backgroundColor?: string; diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/context.test.tsx b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/context.test.tsx index 79a465850..995ecb490 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/context.test.tsx +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/context.test.tsx @@ -384,6 +384,7 @@ describe('useShopifyCheckout', () => { colorScheme: 'automatic', logLevel: 'error', preloading: true, + telemetry: true, }); expect(NativeModules.ShopifyCheckoutKit.getConfig).toHaveBeenCalled(); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts index 6148c9889..f8f63e152 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/tests/index.test.ts @@ -61,6 +61,12 @@ describe('Type contracts', () => { acceptsCustomer({email: 'test@example.com', phoneNumber: '+1234567890'}), ).toEqual({email: 'test@example.com', phoneNumber: '+1234567890'}); + expect( + acceptsConfiguration({ + telemetry: false, + }).telemetry, + ).toBe(false); + expect( acceptsConfiguration({ acceleratedCheckouts: { @@ -139,6 +145,22 @@ describe('Exports', () => { ]); }); }); + + describe('CheckoutErrorCode enum', () => { + it('exports current native checkout error codes', () => { + expect(CheckoutErrorCode.customerAccountRequired).toBe( + 'customer_account_required', + ); + expect(CheckoutErrorCode.networkError).toBe('network_error'); + expect(CheckoutErrorCode.webViewNotSupported).toBe( + 'web_view_not_supported', + ); + expect(CheckoutErrorCode.webContentProcessTerminated).toBe( + 'web_content_process_terminated', + ); + expect(CheckoutErrorCode.sdkError).toBe('sdk_error'); + }); + }); }); type Dispatch = (envelopeJson: string) => void; @@ -777,6 +799,7 @@ describe('ShopifyCheckoutKit', () => { colorScheme: ColorScheme.automatic, logLevel: LogLevel.error, preloading: true, + telemetry: true, }); expect(NativeModule.getConfig).toHaveBeenCalledTimes(1); }); diff --git a/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkit/reactnativedemo/ShopifyCheckoutKitModuleTest.java b/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkit/reactnativedemo/ShopifyCheckoutKitModuleTest.java index adb78e178..e67d77808 100644 --- a/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkit/reactnativedemo/ShopifyCheckoutKitModuleTest.java +++ b/platforms/react-native/sample/android/app/src/test/java/com/shopify/checkoutkit/reactnativedemo/ShopifyCheckoutKitModuleTest.java @@ -14,11 +14,12 @@ import com.shopify.checkoutkit.CheckoutErrorCode; import com.shopify.checkoutkit.CheckoutException; import com.shopify.checkoutkit.CheckoutPreload; -import com.shopify.checkoutkit.ShopifyCheckoutKit; import com.shopify.checkoutkit.LogLevel; import com.shopify.checkoutkit.PreloadState; import com.shopify.checkoutkit.PreloadStateListener; import com.shopify.checkoutkit.Preloading; +import com.shopify.checkoutkit.ShopifyCheckoutKit; +import com.shopify.checkoutkit.Telemetry; import com.shopify.reactnative.checkoutkit.ShopifyCheckoutKitModule; import com.shopify.reactnative.checkoutkit.CustomCheckoutListener; import com.shopify.reactnative.checkoutkit.DispatchCallback; @@ -60,6 +61,7 @@ public class ShopifyCheckoutKitModuleTest { private CheckoutAppearance initialAppearance; private LogLevel initialLogLevel; private Preloading initialPreloading; + private Telemetry initialTelemetry; // Mock for Arguments.createMap() to avoid native library loading private MockedStatic mockedArguments; @@ -104,6 +106,7 @@ public void setup() { initialAppearance = ShopifyCheckoutKitModule.checkoutConfig.getAppearance(); initialLogLevel = ShopifyCheckoutKitModule.checkoutConfig.getLogLevel(); initialPreloading = ShopifyCheckoutKitModule.checkoutConfig.getPreloading(); + initialTelemetry = ShopifyCheckoutKitModule.checkoutConfig.getTelemetry(); } @After @@ -121,6 +124,7 @@ public void tearDown() throws Exception { configuration.setAppearance(initialAppearance); configuration.setLogLevel(initialLogLevel); configuration.setPreloading(initialPreloading); + configuration.setTelemetry(initialTelemetry); ShopifyCheckoutKitModule.checkoutConfig = configuration; }); } @@ -689,6 +693,28 @@ public void testGetConfigIncludesPreloading() { assertThat(result.getBoolean("preloading")).isFalse(); } + @Test + public void testCanDisableTelemetry() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putBoolean("telemetry", false); + + shopifyCheckoutKitModule.setConfig(config); + + assertThat(ShopifyCheckoutKitModule.checkoutConfig.getTelemetry().getEnabled()) + .isFalse(); + } + + @Test + public void testGetConfigIncludesTelemetry() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putBoolean("telemetry", false); + shopifyCheckoutKitModule.setConfig(config); + + WritableMap result = shopifyCheckoutKitModule.getConfig(); + + assertThat(result.getBoolean("telemetry")).isFalse(); + } + @Test public void testGetConfigReturnsDebugForDebugLogLevel() { JavaOnlyMap config = new JavaOnlyMap(); diff --git a/platforms/react-native/sample/ios/Podfile.lock b/platforms/react-native/sample/ios/Podfile.lock index 17a50336d..fd321714d 100644 --- a/platforms/react-native/sample/ios/Podfile.lock +++ b/platforms/react-native/sample/ios/Podfile.lock @@ -2605,8 +2605,8 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - ShopifyCheckoutKit (~> 4.0.0-alpha.5) - - ShopifyCheckoutKit/AcceleratedCheckouts (~> 4.0.0-alpha.5) + - ShopifyCheckoutKit (= 4.0.0-alpha.5) + - ShopifyCheckoutKit/AcceleratedCheckouts (= 4.0.0-alpha.5) - SocketRocket - Yoga - RNVectorIcons (10.3.0): @@ -2996,7 +2996,7 @@ SPEC CHECKSUMS: RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0 RNReanimated: 237d420b7bb4378ef1dacc7d7a5c674fddb4b5d2 RNScreens: 3fc29af06302e1f1c18a7829fe57cbc2c0259912 - RNShopifyCheckoutKit: f7855358ff15db951f458320526ebd9ce2f91405 + RNShopifyCheckoutKit: 01457278b0a89be66df651b9b10d938b8204d5bc RNVectorIcons: be4d047a76ad307ffe54732208fb0498fcb8477f ShopifyCheckoutKit: 7874c8866e6c889d86194398c97e00388111d972 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 diff --git a/platforms/react-native/scripts/lint_swift b/platforms/react-native/scripts/lint_swift index b5da888a2..411342b7a 100755 --- a/platforms/react-native/scripts/lint_swift +++ b/platforms/react-native/scripts/lint_swift @@ -7,7 +7,7 @@ SWIFT_PATHS=( ) MODE="${1:-check}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" SWIFT_TOOLS_DIR="$REPO_ROOT/platforms/swift" if ! "$REPO_ROOT/scripts/check_xcodebuild_package_resolution"; then diff --git a/platforms/react-native/test/rct-integration-app/Podfile.lock b/platforms/react-native/test/rct-integration-app/Podfile.lock index b04055eac..5eed71cca 100644 --- a/platforms/react-native/test/rct-integration-app/Podfile.lock +++ b/platforms/react-native/test/rct-integration-app/Podfile.lock @@ -2149,8 +2149,8 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - ShopifyCheckoutKit (~> 4.0.0-alpha.5) - - ShopifyCheckoutKit/AcceleratedCheckouts (~> 4.0.0-alpha.5) + - ShopifyCheckoutKit (= 4.0.0-alpha.5) + - ShopifyCheckoutKit/AcceleratedCheckouts (= 4.0.0-alpha.5) - SocketRocket - Yoga - ShopifyCheckoutKit (4.0.0-alpha.5): @@ -2464,7 +2464,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 8df342c127fd0c1e30e8b9f71ff814c22414a7c0 ReactCodegen: 3ba2a79bc32ff858814c17ade10931b33b09dcf4 ReactCommon: 592ef441605638b95e533653259254b4bd35ff4f - RNShopifyCheckoutKit: f7855358ff15db951f458320526ebd9ce2f91405 + RNShopifyCheckoutKit: 01457278b0a89be66df651b9b10d938b8204d5bc ShopifyCheckoutKit: 7874c8866e6c889d86194398c97e00388111d972 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498 diff --git a/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift b/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift index aecefa3cb..56ca0c573 100644 --- a/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift +++ b/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift @@ -23,6 +23,7 @@ class ShopifyCheckoutKitTests: XCTestCase { ShopifyCheckoutKit.configuration.logLevel = LogLevel.warn ShopifyCheckoutKit.configuration.preloading.enabled = true ShopifyCheckoutKit.configuration.allowedMessageOrigins = [] + ShopifyCheckoutKit.configuration.telemetry.enabled = true } private func getShopifyCheckoutKit() -> RCTShopifyCheckoutKit { @@ -334,6 +335,20 @@ class ShopifyCheckoutKitTests: XCTestCase { XCTAssertEqual(result["reason"] as? String, "webContentUnavailable") } + func testConfigureCanDisableTelemetry() { + shopifyCheckoutKit.setConfig(["telemetry": false]) + + XCTAssertFalse(ShopifyCheckoutKit.configuration.telemetry.enabled) + } + + func testGetConfigIncludesTelemetry() { + shopifyCheckoutKit.setConfig(["telemetry": false]) + + let result = shopifyCheckoutKit.getConfig() as? [String: Any] + + XCTAssertEqual(result?["telemetry"] as? Bool, false) + } + func testPreloadWithInvalidURLDoesNotRetainCheckoutSheet() { let preloadAttemptCompleted = expectation(description: "preload attempt completed")