@stripe/stripe-react-native 0.57.3 → 0.59.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/android/build.gradle +2 -0
- package/android/gradle.properties +1 -1
- package/android/src/main/AndroidManifest.xml +27 -1
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +0 -3
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +7 -3
- package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +8 -0
- package/android/src/main/java/com/reactnativestripesdk/NavigationBarView.kt +12 -1
- package/android/src/main/java/com/reactnativestripesdk/PaymentElementConfig.kt +26 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfig.kt +147 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt +164 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt +65 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +1 -1
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +60 -31
- package/android/src/main/java/com/reactnativestripesdk/StripeAbstractComposeView.kt +17 -5
- package/android/src/main/java/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.kt +77 -0
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +334 -24
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt +1 -0
- package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +3 -0
- package/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt +8 -0
- package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +0 -2
- package/android/src/main/res/xml/file_paths.xml +4 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerDelegate.java +36 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerInterface.java +18 -0
- package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +20 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentElementConfigTest.kt +175 -1
- package/android/src/test/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfigTest.kt +543 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentSheetManagerTest.kt +70 -0
- package/ios/ConnectAccountOnboarding/ConnectAccountOnboardingView.swift +13 -19
- package/ios/CustomerSheet/CustomerSheetUtils.swift +4 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.h +2 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.m +13 -1
- package/ios/PaymentMethodMessagingElementConfig.swift +116 -0
- package/ios/PaymentMethodMessagingElementHandler.m +9 -0
- package/ios/PaymentMethodMessagingElementView.swift +139 -0
- package/ios/StripeSdk.mm +40 -0
- package/ios/StripeSdkEmitter.swift +2 -0
- package/ios/StripeSdkImpl+CustomerSheet.swift +1 -0
- package/ios/StripeSdkImpl+Embedded.swift +8 -1
- package/ios/StripeSdkImpl+PaymentSheet.swift +44 -1
- package/ios/StripeSdkImpl.swift +158 -2
- package/jest/mock.js +26 -0
- package/jest/setup.js +30 -0
- package/lib/commonjs/components/AddToWalletButton.js +1 -1
- package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
- package/lib/commonjs/components/AddressSheet.js +1 -1
- package/lib/commonjs/components/AddressSheet.js.map +1 -1
- package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
- package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
- package/lib/commonjs/components/CardField.js +1 -1
- package/lib/commonjs/components/CardField.js.map +1 -1
- package/lib/commonjs/components/CardForm.js +1 -1
- package/lib/commonjs/components/CardForm.js.map +1 -1
- package/lib/commonjs/components/PlatformPayButton.js +1 -1
- package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
- package/lib/commonjs/components/StripeContainer.js +1 -1
- package/lib/commonjs/components/StripeContainer.js.map +1 -1
- package/lib/commonjs/connect/Components.js +1 -1
- package/lib/commonjs/connect/Components.js.map +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/commonjs/connect/EmbeddedComponent.js +10 -5
- package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
- package/lib/commonjs/connect/ModalCloseButton.js +1 -1
- package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
- package/lib/commonjs/connect/NavigationBar.js +1 -1
- package/lib/commonjs/connect/NavigationBar.js.map +1 -1
- package/lib/commonjs/connect/analytics/AnalyticsClient.js +2 -0
- package/lib/commonjs/connect/analytics/AnalyticsClient.js.map +1 -0
- package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js +2 -0
- package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
- package/lib/commonjs/connect/analytics/events.js +2 -0
- package/lib/commonjs/connect/analytics/events.js.map +1 -0
- package/lib/commonjs/connect/testUtils.js +2 -0
- package/lib/commonjs/connect/testUtils.js.map +1 -0
- package/lib/commonjs/events.js.map +1 -1
- package/lib/commonjs/functions.js +1 -1
- package/lib/commonjs/functions.js.map +1 -1
- package/lib/commonjs/helpers.js +1 -1
- package/lib/commonjs/hooks/useStripe.js +1 -1
- package/lib/commonjs/hooks/useStripe.js.map +1 -1
- package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
- package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
- package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
- package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
- package/lib/commonjs/specs/NativeCardField.js +1 -1
- package/lib/commonjs/specs/NativeCardField.js.map +1 -1
- package/lib/commonjs/specs/NativeCardForm.js +1 -1
- package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
- package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
- package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
- package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
- package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
- package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
- package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +2 -0
- package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js.map +1 -0
- package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
- package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/commonjs/types/Errors.js +1 -1
- package/lib/commonjs/types/Errors.js.map +1 -1
- package/lib/commonjs/types/FinancialConnections.js.map +1 -1
- package/lib/commonjs/types/PaymentSheet.js +1 -1
- package/lib/commonjs/types/PaymentSheet.js.map +1 -1
- package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js +2 -0
- package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
- package/lib/commonjs/types/index.js.map +1 -1
- package/lib/module/components/AddToWalletButton.js +1 -1
- package/lib/module/components/AddToWalletButton.js.map +1 -1
- package/lib/module/components/AddressSheet.js +1 -1
- package/lib/module/components/AddressSheet.js.map +1 -1
- package/lib/module/components/AuBECSDebitForm.js +1 -1
- package/lib/module/components/AuBECSDebitForm.js.map +1 -1
- package/lib/module/components/CardField.js +1 -1
- package/lib/module/components/CardField.js.map +1 -1
- package/lib/module/components/CardForm.js +1 -1
- package/lib/module/components/CardForm.js.map +1 -1
- package/lib/module/components/PlatformPayButton.js +1 -1
- package/lib/module/components/PlatformPayButton.js.map +1 -1
- package/lib/module/components/StripeContainer.js +1 -1
- package/lib/module/components/StripeContainer.js.map +1 -1
- package/lib/module/connect/Components.js +1 -1
- package/lib/module/connect/Components.js.map +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/module/connect/EmbeddedComponent.js +10 -5
- package/lib/module/connect/EmbeddedComponent.js.map +1 -1
- package/lib/module/connect/ModalCloseButton.js +1 -1
- package/lib/module/connect/ModalCloseButton.js.map +1 -1
- package/lib/module/connect/NavigationBar.js +1 -1
- package/lib/module/connect/NavigationBar.js.map +1 -1
- package/lib/module/connect/analytics/AnalyticsClient.js +2 -0
- package/lib/module/connect/analytics/AnalyticsClient.js.map +1 -0
- package/lib/module/connect/analytics/ComponentAnalyticsClient.js +2 -0
- package/lib/module/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
- package/lib/module/connect/analytics/events.js +2 -0
- package/lib/module/connect/analytics/events.js.map +1 -0
- package/lib/module/connect/testUtils.js +2 -0
- package/lib/module/connect/testUtils.js.map +1 -0
- package/lib/module/events.js.map +1 -1
- package/lib/module/functions.js +1 -1
- package/lib/module/functions.js.map +1 -1
- package/lib/module/helpers.js +1 -1
- package/lib/module/hooks/useStripe.js +1 -1
- package/lib/module/hooks/useStripe.js.map +1 -1
- package/lib/module/specs/NativeAddToWalletButton.js +1 -1
- package/lib/module/specs/NativeAddressSheet.js +1 -1
- package/lib/module/specs/NativeApplePayButton.js +1 -1
- package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
- package/lib/module/specs/NativeCardField.js +1 -1
- package/lib/module/specs/NativeCardField.js.map +1 -1
- package/lib/module/specs/NativeCardForm.js +1 -1
- package/lib/module/specs/NativeCardForm.js.map +1 -1
- package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
- package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
- package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
- package/lib/module/specs/NativeGooglePayButton.js +1 -1
- package/lib/module/specs/NativeNavigationBar.js +1 -1
- package/lib/module/specs/NativePaymentMethodMessagingElement.js +2 -0
- package/lib/module/specs/NativePaymentMethodMessagingElement.js.map +1 -0
- package/lib/module/specs/NativeStripeContainer.js +1 -1
- package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/module/types/Errors.js +1 -1
- package/lib/module/types/Errors.js.map +1 -1
- package/lib/module/types/FinancialConnections.js.map +1 -1
- package/lib/module/types/PaymentSheet.js +1 -1
- package/lib/module/types/PaymentSheet.js.map +1 -1
- package/lib/module/types/components/PaymentMethodMessagingElementComponent.js +2 -0
- package/lib/module/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
- package/lib/module/types/index.js.map +1 -1
- package/lib/typescript/src/connect/Components.d.ts +91 -0
- package/lib/typescript/src/connect/Components.d.ts.map +1 -1
- package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts +61 -0
- package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts.map +1 -1
- package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
- package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts +32 -0
- package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts.map +1 -0
- package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts +94 -0
- package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts.map +1 -0
- package/lib/typescript/src/connect/analytics/events.d.ts +215 -0
- package/lib/typescript/src/connect/analytics/events.d.ts.map +1 -0
- package/lib/typescript/src/connect/connectTypes.d.ts +5 -1
- package/lib/typescript/src/connect/connectTypes.d.ts.map +1 -1
- package/lib/typescript/src/connect/testUtils.d.ts +45 -0
- package/lib/typescript/src/connect/testUtils.d.ts.map +1 -0
- package/lib/typescript/src/events.d.ts +2 -0
- package/lib/typescript/src/events.d.ts.map +1 -1
- package/lib/typescript/src/functions.d.ts +13 -1
- package/lib/typescript/src/functions.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useStripe.d.ts +2 -1
- package/lib/typescript/src/hooks/useStripe.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts +16 -0
- package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +16 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
- package/lib/typescript/src/types/CustomerSheet.d.ts +5 -0
- package/lib/typescript/src/types/CustomerSheet.d.ts.map +1 -1
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +11 -1
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
- package/lib/typescript/src/types/Errors.d.ts +4 -0
- package/lib/typescript/src/types/Errors.d.ts.map +1 -1
- package/lib/typescript/src/types/FinancialConnections.d.ts +2 -0
- package/lib/typescript/src/types/FinancialConnections.d.ts.map +1 -1
- package/lib/typescript/src/types/PaymentSheet.d.ts +35 -0
- package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
- package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts +69 -0
- package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +8 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/connect/Components.tsx +109 -11
- package/src/connect/ConnectComponentsProvider.tsx +69 -2
- package/src/connect/EmbeddedComponent.tsx +458 -23
- package/src/connect/analytics/AnalyticsClient.ts +75 -0
- package/src/connect/analytics/ComponentAnalyticsClient.ts +315 -0
- package/src/connect/analytics/events.ts +253 -0
- package/src/connect/connectTypes.ts +5 -1
- package/src/connect/testUtils.ts +37 -0
- package/src/events.ts +2 -0
- package/src/functions.ts +10 -0
- package/src/hooks/useStripe.tsx +8 -0
- package/src/specs/NativePaymentMethodMessagingElement.ts +25 -0
- package/src/specs/NativeStripeSdkModule.ts +21 -1
- package/src/types/CustomerSheet.ts +5 -0
- package/src/types/EmbeddedPaymentElement.tsx +11 -1
- package/src/types/Errors.ts +5 -0
- package/src/types/FinancialConnections.ts +2 -0
- package/src/types/PaymentSheet.ts +38 -1
- package/src/types/components/PaymentMethodMessagingElementComponent.tsx +74 -0
- package/src/types/index.ts +11 -0
- package/stripe-react-native.podspec +1 -1
|
@@ -8,19 +8,25 @@ import React, {
|
|
|
8
8
|
import {
|
|
9
9
|
AppState,
|
|
10
10
|
AppStateStatus,
|
|
11
|
+
Linking,
|
|
11
12
|
Platform,
|
|
12
13
|
StyleProp,
|
|
13
14
|
ViewStyle,
|
|
14
15
|
} from 'react-native';
|
|
15
16
|
import type { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
17
|
+
import type { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes';
|
|
18
|
+
import type { EventSubscription } from 'react-native';
|
|
16
19
|
import pjson from '../../package.json';
|
|
17
20
|
import NativeStripeSdk from '../specs/NativeStripeSdkModule';
|
|
21
|
+
import { addListener } from '../events';
|
|
18
22
|
import { useConnectComponents } from './ConnectComponentsProvider';
|
|
19
23
|
import type {
|
|
20
24
|
LoadError,
|
|
21
25
|
LoaderStart,
|
|
22
26
|
StripeConnectInitParams,
|
|
23
27
|
} from './connectTypes';
|
|
28
|
+
import type { FinancialConnections } from '../types';
|
|
29
|
+
import { ComponentAnalyticsClient } from './analytics/ComponentAnalyticsClient';
|
|
24
30
|
|
|
25
31
|
const DEVELOPMENT_MODE = false;
|
|
26
32
|
const DEVELOPMENT_URL =
|
|
@@ -30,6 +36,12 @@ const BASE_URL = DEVELOPMENT_MODE ? DEVELOPMENT_URL : PRODUCTION_URL;
|
|
|
30
36
|
|
|
31
37
|
const sdkVersion = pjson.version;
|
|
32
38
|
|
|
39
|
+
// Android deep link polling configuration
|
|
40
|
+
// These constants control the polling mechanism that prevents Expo Router from dismissing screens
|
|
41
|
+
const POLLING_INTERVAL_MS = 500; // How often to check for pending deep links
|
|
42
|
+
const URL_DEDUPLICATION_TIMEOUT_MS = 1000; // How long to remember handled URLs
|
|
43
|
+
const DEEP_LINK_GRACE_PERIOD_MS = 500; // Time to wait for deep link after app resumes
|
|
44
|
+
|
|
33
45
|
// react-native-webview.html will only load versions in the format X.Y.Z
|
|
34
46
|
if (!/^\d+\.\d+\.\d+$/.test(sdkVersion)) {
|
|
35
47
|
throw new Error(
|
|
@@ -37,12 +49,26 @@ if (!/^\d+\.\d+\.\d+$/.test(sdkVersion)) {
|
|
|
37
49
|
);
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
// Required for ua-parser-js to detect mobile platforms correctly
|
|
53
|
+
const platformPrefix = Platform.select({
|
|
54
|
+
ios: 'iPhone',
|
|
55
|
+
android: 'Android',
|
|
56
|
+
default: 'Mobile',
|
|
57
|
+
});
|
|
40
58
|
const userAgent = [
|
|
41
|
-
|
|
59
|
+
platformPrefix,
|
|
42
60
|
`Stripe ReactNative SDK ${Platform.OS}/${Platform.Version}`,
|
|
43
61
|
`stripe-react_native/${sdkVersion}`,
|
|
44
62
|
].join(' - ');
|
|
45
63
|
|
|
64
|
+
// Allowed domains for in-WebView navigation (matching iOS SDK behavior)
|
|
65
|
+
const ALLOWED_STRIPE_HOSTS = [
|
|
66
|
+
'connect-js.stripe.com',
|
|
67
|
+
'connect.stripe.com',
|
|
68
|
+
'verify.stripe.com',
|
|
69
|
+
...(DEVELOPMENT_MODE ? ['10.0.2.2:3001', 'localhost:3001'] : []),
|
|
70
|
+
];
|
|
71
|
+
|
|
46
72
|
export interface CommonComponentProps {
|
|
47
73
|
onLoaderStart?: ({ elementTagName }: LoaderStart) => void;
|
|
48
74
|
onLoadError?: ({ error, elementTagName }: LoadError) => void;
|
|
@@ -120,12 +146,29 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
120
146
|
WebView: typeof WebView | null;
|
|
121
147
|
} | null>(null);
|
|
122
148
|
|
|
123
|
-
// Store pending authenticated webview
|
|
124
|
-
|
|
149
|
+
// Store pending authenticated webview promises (for Android Custom Tabs)
|
|
150
|
+
// Uses a Map keyed by auth session ID to support multiple auth flows.
|
|
151
|
+
// Currently processes promises in FIFO order (first entry resolved first).
|
|
152
|
+
const pendingAuthWebViewPromises = useRef<
|
|
153
|
+
Map<
|
|
154
|
+
string,
|
|
155
|
+
{
|
|
156
|
+
callback: (id: string, url: string | null) => void;
|
|
157
|
+
timeoutId?: NodeJS.Timeout;
|
|
158
|
+
}
|
|
159
|
+
>
|
|
160
|
+
>(new Map());
|
|
161
|
+
|
|
162
|
+
// Store pending Financial Connections promise
|
|
163
|
+
const pendingFinancialConnectionsPromise = useRef<{
|
|
125
164
|
id: string;
|
|
126
|
-
|
|
165
|
+
cleanup: () => void;
|
|
127
166
|
} | null>(null);
|
|
128
167
|
|
|
168
|
+
// Track recently handled URLs to prevent duplicate processing
|
|
169
|
+
// This is needed because the SDK uses dual delivery paths (setIntent + direct emit)
|
|
170
|
+
const recentlyHandledUrls = useRef<Set<string>>(new Set());
|
|
171
|
+
|
|
129
172
|
const loadWebViewComponent = useCallback(async () => {
|
|
130
173
|
if (dynamicWebview) return;
|
|
131
174
|
|
|
@@ -145,16 +188,106 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
145
188
|
|
|
146
189
|
const appState = useRef<AppStateStatus>(AppState.currentState);
|
|
147
190
|
|
|
191
|
+
// Android deep link polling mechanism (Android-only)
|
|
192
|
+
//
|
|
193
|
+
// PROBLEM: On Android, completing auth in Custom Tabs causes a stripe-connect:// deep link
|
|
194
|
+
// that gets broadcast to React Native's Linking module, which triggers Expo Router and
|
|
195
|
+
// dismisses the current screen.
|
|
196
|
+
//
|
|
197
|
+
// SOLUTION: This polling mechanism intercepts the deep link before Expo Router receives it:
|
|
198
|
+
// 1. MainActivity.onNewIntent() captures stripe-connect:// URLs and stores them
|
|
199
|
+
// 2. This effect polls for pending URLs while auth is active
|
|
200
|
+
// 3. URLs are processed directly and never broadcast to Expo Router
|
|
201
|
+
// 4. Deduplication prevents the same URL from being processed twice
|
|
202
|
+
//
|
|
203
|
+
// This preserves the navigation stack and prevents unwanted screen dismissals.
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (Platform.OS !== 'android') return;
|
|
206
|
+
|
|
207
|
+
const pollInterval = setInterval(async () => {
|
|
208
|
+
if (pendingAuthWebViewPromises.current.size === 0) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const pendingUrls =
|
|
214
|
+
await NativeStripeSdk.pollAndClearPendingStripeConnectUrls();
|
|
215
|
+
|
|
216
|
+
if (pendingUrls && pendingUrls.length > 0) {
|
|
217
|
+
pendingUrls.forEach((url: string) => {
|
|
218
|
+
if (url.startsWith('stripe-connect://')) {
|
|
219
|
+
// Deduplication: Skip if we've handled this exact URL
|
|
220
|
+
if (recentlyHandledUrls.current.has(url)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Mark as handled
|
|
225
|
+
recentlyHandledUrls.current.add(url);
|
|
226
|
+
|
|
227
|
+
// Clear from the set after deduplication timeout
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
recentlyHandledUrls.current.delete(url);
|
|
230
|
+
}, URL_DEDUPLICATION_TIMEOUT_MS);
|
|
231
|
+
|
|
232
|
+
const firstEntry = pendingAuthWebViewPromises.current
|
|
233
|
+
.entries()
|
|
234
|
+
.next().value;
|
|
235
|
+
|
|
236
|
+
if (firstEntry) {
|
|
237
|
+
const [id, promiseData] = firstEntry;
|
|
238
|
+
|
|
239
|
+
// Clear the timeout if it exists
|
|
240
|
+
if (promiseData.timeoutId) {
|
|
241
|
+
clearTimeout(promiseData.timeoutId);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Remove from Map and invoke callback
|
|
245
|
+
pendingAuthWebViewPromises.current.delete(id);
|
|
246
|
+
promiseData.callback(id, url);
|
|
247
|
+
|
|
248
|
+
// Reset the Android flag
|
|
249
|
+
NativeStripeSdk.authWebViewDeepLinkHandled(id).catch(() => {
|
|
250
|
+
// Intentionally silent - flag reset is not critical
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
} catch (_error) {
|
|
257
|
+
// Intentionally silent - polling will retry on next interval
|
|
258
|
+
}
|
|
259
|
+
}, POLLING_INTERVAL_MS);
|
|
260
|
+
|
|
261
|
+
return () => {
|
|
262
|
+
clearInterval(pollInterval);
|
|
263
|
+
};
|
|
264
|
+
}, []);
|
|
265
|
+
|
|
266
|
+
// Handle app state changes to detect when user returns from auth flow
|
|
148
267
|
useEffect(() => {
|
|
149
268
|
const subscription = AppState.addEventListener('change', (nextAppState) => {
|
|
150
269
|
if (
|
|
151
270
|
appState.current.match(/inactive|background/) &&
|
|
152
271
|
nextAppState === 'active'
|
|
153
272
|
) {
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
273
|
+
if (pendingAuthWebViewPromises.current.size > 0) {
|
|
274
|
+
// Give the deep link handler time to process the URL
|
|
275
|
+
// If no deep link arrives within the grace period, assume the user cancelled
|
|
276
|
+
pendingAuthWebViewPromises.current.forEach((promiseData, id) => {
|
|
277
|
+
// Only set timeout if one doesn't already exist
|
|
278
|
+
if (!promiseData.timeoutId) {
|
|
279
|
+
const timeoutId = setTimeout(() => {
|
|
280
|
+
const stillPending = pendingAuthWebViewPromises.current.get(id);
|
|
281
|
+
if (stillPending) {
|
|
282
|
+
pendingAuthWebViewPromises.current.delete(id);
|
|
283
|
+
stillPending.callback(id, null);
|
|
284
|
+
}
|
|
285
|
+
}, DEEP_LINK_GRACE_PERIOD_MS);
|
|
286
|
+
|
|
287
|
+
// Store the timeout ID so we can clear it later if needed
|
|
288
|
+
promiseData.timeoutId = timeoutId;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
158
291
|
}
|
|
159
292
|
}
|
|
160
293
|
|
|
@@ -162,12 +295,23 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
162
295
|
});
|
|
163
296
|
|
|
164
297
|
return () => {
|
|
165
|
-
|
|
298
|
+
// Clear all pending timeouts and promises
|
|
299
|
+
// We intentionally want the latest ref value at cleanup time
|
|
300
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
301
|
+
const promises = pendingAuthWebViewPromises.current;
|
|
302
|
+
promises.forEach((promiseData, _id) => {
|
|
303
|
+
if (promiseData.timeoutId) {
|
|
304
|
+
clearTimeout(promiseData.timeoutId);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
promises.clear();
|
|
308
|
+
pendingFinancialConnectionsPromise.current?.cleanup();
|
|
166
309
|
subscription.remove();
|
|
167
310
|
};
|
|
168
311
|
}, []);
|
|
169
312
|
|
|
170
|
-
const { connectInstance, appearance, locale } =
|
|
313
|
+
const { connectInstance, appearance, locale, analyticsClient } =
|
|
314
|
+
useConnectComponents();
|
|
171
315
|
const { fonts, publishableKey, fetchClientSecret, overrides } =
|
|
172
316
|
connectInstance.initParams as StripeConnectInitParamsInternal;
|
|
173
317
|
|
|
@@ -181,6 +325,22 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
181
325
|
style,
|
|
182
326
|
} = props;
|
|
183
327
|
|
|
328
|
+
// Initialize component analytics client
|
|
329
|
+
const componentAnalytics = useMemo(
|
|
330
|
+
() =>
|
|
331
|
+
new ComponentAnalyticsClient(analyticsClient, {
|
|
332
|
+
publishableKey,
|
|
333
|
+
platformId: overrides?.platformId,
|
|
334
|
+
merchantId: overrides?.merchantId,
|
|
335
|
+
livemode:
|
|
336
|
+
typeof overrides?.livemode === 'boolean'
|
|
337
|
+
? overrides.livemode
|
|
338
|
+
: publishableKey?.startsWith('pk_live_'),
|
|
339
|
+
component,
|
|
340
|
+
}),
|
|
341
|
+
[analyticsClient, publishableKey, overrides, component]
|
|
342
|
+
);
|
|
343
|
+
|
|
184
344
|
const hashParams = {
|
|
185
345
|
component,
|
|
186
346
|
publicKey: publishableKey,
|
|
@@ -202,6 +362,7 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
202
362
|
const source = useMemo(() => ({ uri: connectURL }), [connectURL]);
|
|
203
363
|
|
|
204
364
|
const ref = useRef<WebView>(null);
|
|
365
|
+
const hasTriedSourceReload = useRef(false);
|
|
205
366
|
|
|
206
367
|
const [prevAppearance, setPrevAppearance] = useState(appearance);
|
|
207
368
|
const [prevLocale, setPrevLocale] = useState(locale);
|
|
@@ -244,6 +405,42 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
244
405
|
|
|
245
406
|
const WebViewComponent = dynamicWebview?.WebView;
|
|
246
407
|
|
|
408
|
+
// Workaround for react-native-webview new architecture bug on iOS
|
|
409
|
+
// https://github.com/react-native-webview/react-native-webview/pull/3880
|
|
410
|
+
// The source prop doesn't get set properly on iOS with new architecture,
|
|
411
|
+
// so we force reload after the component mounts
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
if (
|
|
414
|
+
Platform.OS === 'ios' &&
|
|
415
|
+
!hasTriedSourceReload.current &&
|
|
416
|
+
WebViewComponent &&
|
|
417
|
+
ref.current
|
|
418
|
+
) {
|
|
419
|
+
hasTriedSourceReload.current = true;
|
|
420
|
+
// Force reload after mount to ensure source is set
|
|
421
|
+
const timer = setTimeout(() => {
|
|
422
|
+
ref.current?.reload();
|
|
423
|
+
}, 100);
|
|
424
|
+
return () => clearTimeout(timer);
|
|
425
|
+
}
|
|
426
|
+
return undefined;
|
|
427
|
+
}, [WebViewComponent]);
|
|
428
|
+
|
|
429
|
+
// Track component lifecycle events
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
// Log component created
|
|
432
|
+
componentAnalytics.logComponentCreated();
|
|
433
|
+
}, [componentAnalytics]);
|
|
434
|
+
|
|
435
|
+
// Track component viewed (when web view is visible)
|
|
436
|
+
const [hasBeenViewed, setHasBeenViewed] = useState(false);
|
|
437
|
+
const handleLayout = useCallback(() => {
|
|
438
|
+
if (!hasBeenViewed) {
|
|
439
|
+
setHasBeenViewed(true);
|
|
440
|
+
componentAnalytics.logComponentViewed();
|
|
441
|
+
}
|
|
442
|
+
}, [hasBeenViewed, componentAnalytics]);
|
|
443
|
+
|
|
247
444
|
const handleAuthWebViewResult = (id: string, resultUrl: string | null) => {
|
|
248
445
|
ref.current?.injectJavaScript(`
|
|
249
446
|
(function() {
|
|
@@ -256,12 +453,51 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
256
453
|
`);
|
|
257
454
|
};
|
|
258
455
|
|
|
456
|
+
const handleFinancialConnectionsResult = (
|
|
457
|
+
id: string,
|
|
458
|
+
result: {
|
|
459
|
+
session?: FinancialConnections.Session;
|
|
460
|
+
token?: FinancialConnections.BankAccountToken;
|
|
461
|
+
error?: {
|
|
462
|
+
code: string;
|
|
463
|
+
message: string;
|
|
464
|
+
localizedMessage?: string;
|
|
465
|
+
type?: string;
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
) => {
|
|
469
|
+
ref.current?.injectJavaScript(`
|
|
470
|
+
(function() {
|
|
471
|
+
window.callSetterWithSerializableValue(${JSON.stringify({
|
|
472
|
+
setter: 'setCollectMobileFinancialConnectionsResult',
|
|
473
|
+
value: {
|
|
474
|
+
id: id,
|
|
475
|
+
financialConnectionsSession: result.session
|
|
476
|
+
? {
|
|
477
|
+
accounts: result.session.accounts,
|
|
478
|
+
}
|
|
479
|
+
: null,
|
|
480
|
+
token: result.token ?? null,
|
|
481
|
+
error: result.error ?? null,
|
|
482
|
+
},
|
|
483
|
+
})});
|
|
484
|
+
true;
|
|
485
|
+
})();
|
|
486
|
+
`);
|
|
487
|
+
};
|
|
488
|
+
|
|
259
489
|
const onMessageCallback = useCallback(
|
|
260
490
|
async (event: WebViewMessageEvent) => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
data
|
|
264
|
-
}
|
|
491
|
+
let message: { type: string; data?: unknown };
|
|
492
|
+
try {
|
|
493
|
+
message = JSON.parse(event.nativeEvent.data);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
componentAnalytics.logDeserializeMessageError(
|
|
496
|
+
'unknown',
|
|
497
|
+
error instanceof Error ? error : new Error(String(error))
|
|
498
|
+
);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
265
501
|
|
|
266
502
|
if (message.type === 'fetchClientSecret') {
|
|
267
503
|
const clientSecret = await fetchClientSecret().catch((error) => {
|
|
@@ -279,11 +515,121 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
279
515
|
// message.data is of type string
|
|
280
516
|
console.debug(`[EmbeddedComponent ${component}]: ${message.data}`);
|
|
281
517
|
} else if (message.type === 'pageDidLoad') {
|
|
518
|
+
const pageViewId = (message.data as { pageViewId?: string })
|
|
519
|
+
?.pageViewId;
|
|
520
|
+
componentAnalytics.logComponentWebPageLoaded(pageViewId);
|
|
282
521
|
onPageDidLoad?.();
|
|
522
|
+
} else if (message.type === 'componentLoaded') {
|
|
523
|
+
// Connect JS fully initialized
|
|
524
|
+
componentAnalytics.logComponentLoaded();
|
|
283
525
|
} else if (message.type === 'accountSessionClaimed') {
|
|
284
526
|
// message.data is of type {elementTagName: string, merchantId: string}
|
|
285
527
|
} else if (message.type === 'openFinancialConnections') {
|
|
286
|
-
|
|
528
|
+
const messageData = message.data as {
|
|
529
|
+
clientSecret: string;
|
|
530
|
+
id: string;
|
|
531
|
+
connectedAccountId: string;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const { clientSecret, id, connectedAccountId } = messageData;
|
|
535
|
+
|
|
536
|
+
// Validate client secret
|
|
537
|
+
if (!clientSecret || typeof clientSecret !== 'string') {
|
|
538
|
+
handleFinancialConnectionsResult(id, {
|
|
539
|
+
error: {
|
|
540
|
+
code: 'InvalidClientSecret',
|
|
541
|
+
message: 'Invalid or missing clientSecret parameter',
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Prevent multiple simultaneous flows
|
|
548
|
+
if (pendingFinancialConnectionsPromise.current) {
|
|
549
|
+
handleFinancialConnectionsResult(id, {
|
|
550
|
+
error: {
|
|
551
|
+
code: 'AlreadyInProgress',
|
|
552
|
+
message: 'Financial Connections flow already in progress',
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Setup event listener for debugging
|
|
559
|
+
let eventListener: EventSubscription | null = null;
|
|
560
|
+
if (__DEV__) {
|
|
561
|
+
eventListener = addListener(
|
|
562
|
+
'onFinancialConnectionsEvent',
|
|
563
|
+
(fcEvent: FinancialConnections.FinancialConnectionsEvent) => {
|
|
564
|
+
console.debug(
|
|
565
|
+
`[FinancialConnections ${component}]: ${fcEvent.name}`,
|
|
566
|
+
fcEvent.metadata
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Store cleanup function
|
|
573
|
+
const cleanup = () => {
|
|
574
|
+
eventListener?.remove();
|
|
575
|
+
pendingFinancialConnectionsPromise.current = null;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
pendingFinancialConnectionsPromise.current = {
|
|
579
|
+
id,
|
|
580
|
+
cleanup,
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// Call native Financial Connections
|
|
584
|
+
NativeStripeSdk.collectFinancialConnectionsAccounts(clientSecret, {
|
|
585
|
+
connectedAccountId,
|
|
586
|
+
})
|
|
587
|
+
.then(({ session, error }) => {
|
|
588
|
+
cleanup();
|
|
589
|
+
|
|
590
|
+
if (error) {
|
|
591
|
+
handleFinancialConnectionsResult(id, {
|
|
592
|
+
session: undefined,
|
|
593
|
+
token: undefined,
|
|
594
|
+
error: {
|
|
595
|
+
code: error.code,
|
|
596
|
+
message: error.message,
|
|
597
|
+
localizedMessage: error.localizedMessage,
|
|
598
|
+
type: error.type,
|
|
599
|
+
},
|
|
600
|
+
});
|
|
601
|
+
} else if (session) {
|
|
602
|
+
// Note: collectFinancialConnectionsAccounts doesn't return a token
|
|
603
|
+
// Only collectBankAccountToken returns both session and token
|
|
604
|
+
handleFinancialConnectionsResult(id, {
|
|
605
|
+
session,
|
|
606
|
+
token: undefined,
|
|
607
|
+
error: undefined,
|
|
608
|
+
});
|
|
609
|
+
} else {
|
|
610
|
+
// Defensive: should never happen
|
|
611
|
+
handleFinancialConnectionsResult(id, {
|
|
612
|
+
error: {
|
|
613
|
+
code: 'UnexpectedError',
|
|
614
|
+
message:
|
|
615
|
+
'No session or error returned from Financial Connections',
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
})
|
|
620
|
+
.catch((unexpectedError) => {
|
|
621
|
+
cleanup();
|
|
622
|
+
handleUnexpectedError(unexpectedError);
|
|
623
|
+
handleFinancialConnectionsResult(id, {
|
|
624
|
+
error: {
|
|
625
|
+
code: 'UnexpectedError',
|
|
626
|
+
message:
|
|
627
|
+
unexpectedError instanceof Error
|
|
628
|
+
? unexpectedError.message
|
|
629
|
+
: 'An unexpected error occurred during Financial Connections',
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
});
|
|
287
633
|
} else if (message.type === 'closeWebView') {
|
|
288
634
|
// message.data is empty
|
|
289
635
|
callbacks?.onCloseWebView?.({});
|
|
@@ -303,7 +649,12 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
303
649
|
// remove the 'set' prefix and lowercase the first letter
|
|
304
650
|
const functionName =
|
|
305
651
|
setter.charAt(3).toLowerCase() + setter.substring(4);
|
|
306
|
-
callbacks?.[functionName]
|
|
652
|
+
if (callbacks?.[functionName]) {
|
|
653
|
+
callbacks[functionName](value);
|
|
654
|
+
} else {
|
|
655
|
+
// Unrecognized setter function
|
|
656
|
+
componentAnalytics.logUnrecognizedSetter(setter);
|
|
657
|
+
}
|
|
307
658
|
}
|
|
308
659
|
} else if (message.type === 'openAuthenticatedWebView') {
|
|
309
660
|
const { url, id } = message.data as { id: string; url: string };
|
|
@@ -316,22 +667,51 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
316
667
|
return;
|
|
317
668
|
}
|
|
318
669
|
|
|
670
|
+
// Log authenticated web view opened
|
|
671
|
+
componentAnalytics.logAuthenticatedWebViewOpened(id);
|
|
672
|
+
|
|
319
673
|
// On Android, we need to wait for the deep link callback
|
|
320
674
|
// On iOS, the promise resolves with the redirect URL
|
|
321
675
|
NativeStripeSdk.openAuthenticatedWebView(id, url)
|
|
322
676
|
.then((result) => {
|
|
323
677
|
if (Platform.OS === 'ios') {
|
|
324
678
|
// iOS returns the redirect URL directly
|
|
325
|
-
|
|
679
|
+
const resultUrl = result?.url ?? null;
|
|
680
|
+
if (resultUrl) {
|
|
681
|
+
componentAnalytics.logAuthenticatedWebViewRedirected(id);
|
|
682
|
+
} else {
|
|
683
|
+
componentAnalytics.logAuthenticatedWebViewCanceled(id);
|
|
684
|
+
}
|
|
685
|
+
handleAuthWebViewResult(id, resultUrl);
|
|
326
686
|
} else {
|
|
327
|
-
// Android: Store promise to be resolved by deep link listener
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
687
|
+
// Android: Store promise in Map to be resolved by deep link listener
|
|
688
|
+
pendingAuthWebViewPromises.current.set(id, {
|
|
689
|
+
callback: (authId: string, resultUrl: string | null) => {
|
|
690
|
+
if (resultUrl) {
|
|
691
|
+
componentAnalytics.logAuthenticatedWebViewRedirected(
|
|
692
|
+
authId
|
|
693
|
+
);
|
|
694
|
+
} else {
|
|
695
|
+
componentAnalytics.logAuthenticatedWebViewCanceled(authId);
|
|
696
|
+
}
|
|
697
|
+
handleAuthWebViewResult(authId, resultUrl);
|
|
698
|
+
},
|
|
699
|
+
});
|
|
332
700
|
}
|
|
333
701
|
})
|
|
334
|
-
.catch(
|
|
702
|
+
.catch((error) => {
|
|
703
|
+
if (__DEV__) {
|
|
704
|
+
console.error(
|
|
705
|
+
`[EmbeddedComponent] Error opening authenticated webview:`,
|
|
706
|
+
error
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
componentAnalytics.logAuthenticatedWebViewError(
|
|
710
|
+
id,
|
|
711
|
+
error instanceof Error ? error : new Error(String(error))
|
|
712
|
+
);
|
|
713
|
+
handleUnexpectedError(error);
|
|
714
|
+
});
|
|
335
715
|
} else {
|
|
336
716
|
// unhandled message
|
|
337
717
|
}
|
|
@@ -339,6 +719,7 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
339
719
|
[
|
|
340
720
|
callbacks,
|
|
341
721
|
component,
|
|
722
|
+
componentAnalytics,
|
|
342
723
|
fetchClientSecret,
|
|
343
724
|
handleUnexpectedError,
|
|
344
725
|
onLoadError,
|
|
@@ -347,6 +728,41 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
347
728
|
]
|
|
348
729
|
);
|
|
349
730
|
|
|
731
|
+
const onShouldStartLoadWithRequest = useCallback(
|
|
732
|
+
(event: ShouldStartLoadRequest) => {
|
|
733
|
+
const { url, navigationType } = event;
|
|
734
|
+
|
|
735
|
+
// Handle CSV export downloads
|
|
736
|
+
if (isCsvExportUrl(url)) {
|
|
737
|
+
NativeStripeSdk.downloadAndShareFile(url, null)
|
|
738
|
+
.then((result) => {
|
|
739
|
+
if (!result.success) {
|
|
740
|
+
console.warn('CSV export share failed:', result.error);
|
|
741
|
+
}
|
|
742
|
+
})
|
|
743
|
+
.catch((error) => {
|
|
744
|
+
handleUnexpectedError(error);
|
|
745
|
+
});
|
|
746
|
+
return false; // Block WebView navigation
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (navigationType !== 'click') return true;
|
|
750
|
+
|
|
751
|
+
// Allow navigation within allowed Stripe domains (matching iOS SDK behavior)
|
|
752
|
+
if (ALLOWED_STRIPE_HOSTS.some((host) => url.includes(host))) {
|
|
753
|
+
return true; // Allow in-WebView navigation
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Open external links in system browser
|
|
757
|
+
Linking.openURL(url).catch((error) => {
|
|
758
|
+
handleUnexpectedError(error);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
return false; // Block in-WebView navigation for external links
|
|
762
|
+
},
|
|
763
|
+
[handleUnexpectedError]
|
|
764
|
+
);
|
|
765
|
+
|
|
350
766
|
const backgroundColor = appearance?.variables?.colorBackground || '#FFFFFF';
|
|
351
767
|
|
|
352
768
|
const mergedStyle = useMemo(
|
|
@@ -375,6 +791,12 @@ export function EmbeddedComponent(props: EmbeddedComponentProps) {
|
|
|
375
791
|
// Fixes injectedJavaScriptObject in Android https://github.com/react-native-webview/react-native-webview/issues/3326#issuecomment-3048111789
|
|
376
792
|
injectedJavaScriptBeforeContentLoaded={'(function() {})();'}
|
|
377
793
|
onMessage={onMessageCallback}
|
|
794
|
+
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
|
795
|
+
onLayout={handleLayout}
|
|
796
|
+
// Camera/Media Permissions - matches iOS SDK behavior
|
|
797
|
+
mediaCapturePermissionGrantType="grantIfSameHostElsePrompt"
|
|
798
|
+
allowsInlineMediaPlayback={true}
|
|
799
|
+
mediaPlaybackRequiresUserAction={false}
|
|
378
800
|
/>
|
|
379
801
|
);
|
|
380
802
|
}
|
|
@@ -405,3 +827,16 @@ function isValidUrl(url: string): boolean {
|
|
|
405
827
|
return false;
|
|
406
828
|
}
|
|
407
829
|
}
|
|
830
|
+
|
|
831
|
+
// Detects Stripe CSV export URLs
|
|
832
|
+
function isCsvExportUrl(url: string): boolean {
|
|
833
|
+
try {
|
|
834
|
+
const parsedUrl = new URL(url);
|
|
835
|
+
return (
|
|
836
|
+
parsedUrl.hostname.includes('stripe-data-exports') ||
|
|
837
|
+
parsedUrl.pathname.includes('stripe-data-exports')
|
|
838
|
+
);
|
|
839
|
+
} catch {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
}
|