@glomopay/react-native-sdk 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/MIGRATION.md +112 -0
  3. package/README.md +173 -36
  4. package/lib/bridge/file-input-handler.d.ts +25 -0
  5. package/lib/bridge/file-input-handler.d.ts.map +1 -0
  6. package/lib/bridge/file-input-handler.js +38 -0
  7. package/lib/bridge/file-save-handler.d.ts +32 -0
  8. package/lib/bridge/file-save-handler.d.ts.map +1 -0
  9. package/lib/bridge/file-save-handler.js +92 -0
  10. package/lib/bridge/message-router.d.ts +44 -0
  11. package/lib/bridge/message-router.d.ts.map +1 -0
  12. package/lib/bridge/message-router.js +48 -0
  13. package/lib/bridge/payment-payload.d.ts +18 -0
  14. package/lib/bridge/payment-payload.d.ts.map +1 -0
  15. package/lib/bridge/payment-payload.js +31 -0
  16. package/lib/capabilities/file-save.d.ts +40 -0
  17. package/lib/capabilities/file-save.d.ts.map +1 -0
  18. package/lib/capabilities/file-save.js +101 -0
  19. package/lib/capabilities/index.d.ts +22 -0
  20. package/lib/capabilities/index.d.ts.map +1 -0
  21. package/lib/capabilities/index.js +34 -0
  22. package/lib/capabilities/use-capability-reporting.d.ts +24 -0
  23. package/lib/capabilities/use-capability-reporting.d.ts.map +1 -0
  24. package/lib/capabilities/use-capability-reporting.js +44 -0
  25. package/lib/config/base.d.ts +12 -0
  26. package/lib/config/base.d.ts.map +1 -1
  27. package/lib/config/base.js +13 -1
  28. package/lib/glomo-checkout.d.ts +6 -1
  29. package/lib/glomo-checkout.d.ts.map +1 -1
  30. package/lib/glomo-checkout.js +11 -5
  31. package/lib/glomo-lrs-checkout.d.ts.map +1 -1
  32. package/lib/glomo-lrs-checkout.js +33 -10
  33. package/lib/glomo-standard-checkout.d.ts +3 -1
  34. package/lib/glomo-standard-checkout.d.ts.map +1 -1
  35. package/lib/glomo-standard-checkout.js +21 -13
  36. package/lib/glomo-subscriptions-checkout.d.ts.map +1 -1
  37. package/lib/glomo-subscriptions-checkout.js +1 -1
  38. package/lib/index.d.ts +2 -2
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +4 -3
  41. package/lib/injections/index.d.ts +24 -0
  42. package/lib/injections/index.d.ts.map +1 -1
  43. package/lib/injections/index.js +41 -0
  44. package/lib/injections/webview-flow.injection.d.ts.map +1 -1
  45. package/lib/injections/webview-flow.injection.js +76 -0
  46. package/lib/injections/webview-main.injection.d.ts.map +1 -1
  47. package/lib/injections/webview-main.injection.js +31 -0
  48. package/lib/injections/webview-standard.injection.d.ts.map +1 -1
  49. package/lib/injections/webview-standard.injection.js +31 -0
  50. package/lib/services/file-saver.d.ts +109 -0
  51. package/lib/services/file-saver.d.ts.map +1 -0
  52. package/lib/services/file-saver.js +416 -0
  53. package/lib/types/checkout.d.ts +58 -6
  54. package/lib/types/checkout.d.ts.map +1 -1
  55. package/lib/types/checkout.js +24 -2
  56. package/lib/types/standard-checkout.d.ts +6 -6
  57. package/lib/types/standard-checkout.d.ts.map +1 -1
  58. package/lib/types/subscriptions-checkout.d.ts +3 -3
  59. package/lib/types/subscriptions-checkout.d.ts.map +1 -1
  60. package/lib/use-glomo-checkout.js +2 -2
  61. package/lib/use-lrs-checkout.d.ts +9 -5
  62. package/lib/use-lrs-checkout.d.ts.map +1 -1
  63. package/lib/use-lrs-checkout.js +317 -171
  64. package/lib/use-standard-checkout.d.ts +17 -13
  65. package/lib/use-standard-checkout.d.ts.map +1 -1
  66. package/lib/use-standard-checkout.js +281 -207
  67. package/lib/utils/analytics.d.ts +177 -51
  68. package/lib/utils/analytics.d.ts.map +1 -1
  69. package/lib/utils/analytics.js +299 -81
  70. package/lib/utils/device-compliance.d.ts +57 -12
  71. package/lib/utils/device-compliance.d.ts.map +1 -1
  72. package/lib/utils/device-compliance.js +101 -37
  73. package/lib/utils/event-projections.d.ts +56 -0
  74. package/lib/utils/event-projections.d.ts.map +1 -0
  75. package/lib/utils/event-projections.js +42 -0
  76. package/lib/utils/file-metadata.d.ts +52 -0
  77. package/lib/utils/file-metadata.d.ts.map +1 -0
  78. package/lib/utils/file-metadata.js +134 -0
  79. package/lib/utils/use-sdk-session.d.ts +12 -0
  80. package/lib/utils/use-sdk-session.d.ts.map +1 -0
  81. package/lib/utils/use-sdk-session.js +37 -0
  82. package/lib/utils/validation.d.ts +37 -3
  83. package/lib/utils/validation.d.ts.map +1 -1
  84. package/lib/utils/validation.js +64 -8
  85. package/lib/webview/capability-props.d.ts +27 -0
  86. package/lib/webview/capability-props.d.ts.map +1 -0
  87. package/lib/webview/capability-props.js +61 -0
  88. package/lib/webview/use-permission-request.d.ts +33 -0
  89. package/lib/webview/use-permission-request.d.ts.map +1 -0
  90. package/lib/webview/use-permission-request.js +89 -0
  91. package/package.json +10 -2
  92. package/src/bridge/file-input-handler.ts +66 -0
  93. package/src/bridge/file-save-handler.ts +141 -0
  94. package/src/bridge/message-router.ts +92 -0
  95. package/src/bridge/payment-payload.ts +31 -0
  96. package/src/capabilities/file-save.ts +124 -0
  97. package/src/capabilities/index.ts +34 -0
  98. package/src/capabilities/use-capability-reporting.ts +55 -0
  99. package/src/config/base.ts +13 -0
  100. package/src/glomo-checkout.tsx +11 -7
  101. package/src/glomo-lrs-checkout.tsx +41 -26
  102. package/src/glomo-standard-checkout.tsx +31 -34
  103. package/src/glomo-subscriptions-checkout.tsx +1 -0
  104. package/src/index.ts +4 -2
  105. package/src/injections/index.ts +43 -0
  106. package/src/injections/webview-flow.injection.ts +76 -0
  107. package/src/injections/webview-main.injection.ts +31 -0
  108. package/src/injections/webview-standard.injection.ts +31 -0
  109. package/src/services/file-saver.ts +497 -0
  110. package/src/types/checkout.ts +62 -6
  111. package/src/types/standard-checkout.ts +6 -6
  112. package/src/types/subscriptions-checkout.ts +7 -3
  113. package/src/use-glomo-checkout.tsx +2 -2
  114. package/src/use-lrs-checkout.tsx +656 -365
  115. package/src/use-standard-checkout.tsx +728 -466
  116. package/src/utils/analytics.ts +422 -117
  117. package/src/utils/device-compliance.ts +148 -38
  118. package/src/utils/event-projections.ts +74 -0
  119. package/src/utils/file-metadata.ts +140 -0
  120. package/src/utils/use-sdk-session.ts +41 -0
  121. package/src/utils/validation.ts +70 -10
  122. package/src/webview/capability-props.ts +86 -0
  123. package/src/webview/use-permission-request.ts +114 -0
  124. package/src/jail-monkey.d.ts +0 -19
@@ -6,8 +6,9 @@ import { WebView, type WebViewMessageEvent, type WebViewProps } from "react-nati
6
6
 
7
7
  import { useStandardCheckout } from "./use-standard-checkout";
8
8
  import { type GlomoStandardCheckoutRef, type GlomoStandardCheckoutProps } from "./types/standard-checkout";
9
- import { type CheckoutAnalyticsTrackers } from "./utils/analytics";
10
- import { isValidUrl } from "./utils/validation";
9
+ import { type CheckoutAnalyticsTrackers, type FlowType } from "./utils/analytics";
10
+ import { isValidUrl, isMockModeKey } from "./utils/validation";
11
+ import { buildCapabilityProps } from "./webview/capability-props";
11
12
 
12
13
  /**
13
14
  * Internal-only props for GlomoStandardCheckout.
@@ -16,6 +17,9 @@ import { isValidUrl } from "./utils/validation";
16
17
  */
17
18
  export interface GlomoStandardCheckoutInternalProps extends GlomoStandardCheckoutProps {
18
19
  _analyticsTrackers?: CheckoutAnalyticsTrackers;
20
+
21
+ /** Names the flow for telemetry the tracker map does not cover - see UseStandardCheckoutInternalOptions */
22
+ _flowType?: FlowType;
19
23
  _skipOrderIdValidation?: boolean;
20
24
  _testInjectionScript?: string;
21
25
  }
@@ -29,7 +33,9 @@ export interface GlomoStandardCheckoutInternalProps extends GlomoStandardCheckou
29
33
  *
30
34
  * @param onPaymentSuccess - The success callback with payload, for a successful payment
31
35
  * @param onPaymentFailure - The failure callback with payload, for a failed payment
32
- * @param onPaymentTerminate - Optional callback called when the checkout is terminated either from the webpage (back button or close button) or mobile component (user dismissing the modal on iOS or user pressing the back button on Android)
36
+ * @param onUserJourneyCompleted - Optional callback for asynchronous payment journeys - bank transfers and pay via bank. Carries the journey type and the transfer details the page reported, not a confirmed payment. LRS/Cards flows will never trigger this.
37
+ * @param onPaymentTerminate - Optional callback called when the checkout ends without a payment outcome. Receives a payload whose `source` is a TERMINATION_SOURCES value naming how it ended: the page's own close control, the Android back button/gesture, or an iOS swipe-down on the modal. The back chevron over a bank flow is deliberately not a termination - it returns the user to the checkout with the session intact.
38
+ * @param onUserRefusedDevicePermissions - Optional callback invoked when the user refuses a device permission the bank's page asked for - camera today. Upload-related permissions are requested by react-native-webview itself and do not reach this callback. Currently Android only: due to platform limitations on iOS, the WebView raises its own prompt and never reports the answer back, so this cannot fire there.
33
39
  *
34
40
  * @param onConnectionError - Optional callback for connection/network errors
35
41
  * @param onSdkError - Optional callback for handling SDK validation errors and configuration issues
@@ -48,9 +54,10 @@ function GlomoStandardCheckoutComponent(
48
54
  onPaymentTerminate,
49
55
  onSdkError,
50
56
  onUserJourneyCompleted,
51
- onUserRefusedCameraPermissions,
57
+ onUserRefusedDevicePermissions,
52
58
  devMode = false,
53
59
  _analyticsTrackers,
60
+ _flowType,
54
61
  _skipOrderIdValidation,
55
62
  _testInjectionScript,
56
63
  }: GlomoStandardCheckoutInternalProps,
@@ -58,7 +65,7 @@ function GlomoStandardCheckoutComponent(
58
65
  ) {
59
66
  // Computing mockMode based on the publicKey prefix
60
67
  const mockMode = useMemo(() => {
61
- const resolvedMockMode = publicKey?.toLowerCase().startsWith("test_") ?? false;
68
+ const resolvedMockMode = isMockModeKey(publicKey);
62
69
  if (devMode) {
63
70
  console.log(
64
71
  `[Glomo-RN-SDK Standard] ${resolvedMockMode ? "Using" : "Not using"} Mock mode for publicKey: ${publicKey}`
@@ -97,11 +104,11 @@ function GlomoStandardCheckoutComponent(
97
104
  onPaymentTerminate,
98
105
  onSdkError,
99
106
  onUserJourneyCompleted,
100
- onUserRefusedCameraPermissions,
107
+ onUserRefusedDevicePermissions,
101
108
  devMode,
102
109
  },
103
110
  mockMode,
104
- { analyticsTrackers: _analyticsTrackers, _skipOrderIdValidation, _testInjectionScript }
111
+ { analyticsTrackers: _analyticsTrackers, flowType: _flowType, _skipOrderIdValidation, _testInjectionScript }
105
112
  );
106
113
 
107
114
  // Exposing the start() and getStatus() methods via the ref
@@ -110,6 +117,12 @@ function GlomoStandardCheckoutComponent(
110
117
  getStatus: () => status,
111
118
  }));
112
119
 
120
+ // Configuration shared by every WebView the SDK renders, regardless of flow
121
+ const capabilityProps = useMemo(
122
+ () => buildCapabilityProps({ devMode, onPermissionRequest: handlePermissionRequest }),
123
+ [devMode, handlePermissionRequest]
124
+ );
125
+
113
126
  // Making the WebView component
114
127
  const makeWebView = React.useCallback(
115
128
  (
@@ -153,10 +166,8 @@ function GlomoStandardCheckoutComponent(
153
166
 
154
167
  return (
155
168
  <WebView
169
+ {...capabilityProps}
156
170
  ref={ref}
157
- webviewDebuggingEnabled={__DEV__ && devMode}
158
- javaScriptEnabled={true}
159
- domStorageEnabled={true}
160
171
  onMessage={messageHandler}
161
172
  onError={handleError}
162
173
  onHttpError={handleHttpError}
@@ -172,36 +183,14 @@ function GlomoStandardCheckoutComponent(
172
183
  }
173
184
  }}
174
185
  source={{ uri: url }}
175
- javaScriptCanOpenWindowsAutomatically={true}
176
- setSupportMultipleWindows={true}
177
- incognito={true}
178
- cacheEnabled={false}
179
- cacheMode='LOAD_NO_CACHE'
180
- pullToRefreshEnabled={false}
181
- limitsNavigationsToAppBoundDomains={false}
182
- menuItems={[]}
183
186
  injectedJavaScript={injectedJavaScript}
184
187
  injectedJavaScriptBeforeContentLoaded={injectedJavaScriptBeforeContentLoaded}
185
188
  style={styles.webview}
186
- showsHorizontalScrollIndicator={false}
187
- showsVerticalScrollIndicator={false}
188
- mixedContentMode='always'
189
- thirdPartyCookiesEnabled={true}
190
- sharedCookiesEnabled={true}
191
- allowsInlineMediaPlayback={true}
192
- mediaPlaybackRequiresUserAction={false}
193
- mediaCapturePermissionGrantType='prompt'
194
- allowFileAccess={true}
195
- allowUniversalAccessFromFileURLs={true}
196
- allowFileAccessFromFileURLs={true}
197
- androidLayerType='hardware'
198
- // @ts-expect-error onPermissionRequest is an Android-only WebView prop not yet in react-native-webview type definitions
199
- onPermissionRequest={handlePermissionRequest}
200
189
  {...additionalProps}
201
190
  />
202
191
  );
203
192
  },
204
- [handleError, handleHttpError, handleNavigationStateChange, handlePermissionRequest, devMode]
193
+ [handleError, handleHttpError, handleNavigationStateChange, capabilityProps, devMode]
205
194
  );
206
195
 
207
196
  const MainWebViewComponent = useMemo(
@@ -262,6 +251,8 @@ function GlomoStandardCheckoutComponent(
262
251
  <TouchableOpacity
263
252
  onPress={handleFlowBack}
264
253
  style={styles.flowBackButton}
254
+ accessibilityRole='button'
255
+ accessibilityLabel='Go back to checkout'
265
256
  >
266
257
  <Text style={styles.flowBackChevron}>{"\u2039"}</Text>
267
258
  </TouchableOpacity>
@@ -324,8 +315,14 @@ const styles = StyleSheet.create({
324
315
  borderBottomColor: "#e0e0e0",
325
316
  },
326
317
  flowBackButton: {
327
- paddingVertical: 0,
318
+ /**
319
+ * Sized to the 44dp / 44pt minimum touch target both platforms specify. The glyph alone is
320
+ * 42px tall and the hit area used to hug it horizontally, which is the only SDK-drawn
321
+ * control in this package and the one a user reaches mid-payment.
322
+ */
323
+ paddingVertical: 6,
328
324
  paddingLeft: 20,
325
+ paddingRight: 20,
329
326
  },
330
327
  flowBackChevron: {
331
328
  fontSize: 42,
@@ -71,6 +71,7 @@ function GlomoSubscriptionsCheckoutComponent(
71
71
  onSdkError={onSdkError}
72
72
  devMode={devMode}
73
73
  _analyticsTrackers={subscriptionCheckoutTrackers}
74
+ _flowType='subscriptions'
74
75
  _skipOrderIdValidation={true}
75
76
  />
76
77
  );
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- /** The GlomoPay RN SDK v4 exports */
1
+ /** The GlomoPay RN SDK v5 exports */
2
2
 
3
- // v4 public API - unified checkout
3
+ // v5 public API - unified checkout
4
4
  export { GlomoCheckout } from "./glomo-checkout";
5
5
  export {
6
6
  type GlomoCheckoutRef,
@@ -8,7 +8,9 @@ export {
8
8
  type GlomoCheckoutPayload,
9
9
  type CheckoutStatus,
10
10
  type GlomoUserJourneyCompletedPayload,
11
+ type GlomoTerminationPayload,
11
12
  ASYNC_PAYMENT_EVENTS,
13
+ TERMINATION_SOURCES,
12
14
  } from "./types/checkout";
13
15
  export { type GlomoServer } from "./config/base";
14
16
  export { type SdkError } from "./utils/analytics";
@@ -6,6 +6,49 @@ import { injectedScript as flowScript } from "./webview-flow.injection";
6
6
  import { injectedScript as educationCarouselScript } from "./webview-education-carousel.injection";
7
7
  import { injectedScript as standardScript } from "./webview-standard.injection";
8
8
 
9
+ /**
10
+ * Publishes what this SDK build can handle, for the checkout page to gate on.
11
+ *
12
+ * Appends rather than assigns, and de-duplicates, because the array is shared with anything else
13
+ * that writes to it. The page reads it at the moment it needs a capability rather than snapshotting
14
+ * it at load, so a script that runs after the page has mounted still takes effect.
15
+ *
16
+ * Returns an empty string when there is nothing to advertise - the page treats a missing array and
17
+ * an empty one the same way, so there is no reason to inject the assignment.
18
+ */
19
+ export function buildCapabilityAdvertisement(capabilities: string[]): string {
20
+ if (capabilities.length === 0) {
21
+ return "";
22
+ }
23
+
24
+ return `
25
+ (function() {
26
+ window.__glomoSdkCapabilities__ = (window.__glomoSdkCapabilities__ || [])
27
+ .concat(${JSON.stringify(capabilities)})
28
+ .filter(function(cap, i, all) { return all.indexOf(cap) === i; });
29
+ })();
30
+ true;
31
+ `;
32
+ }
33
+
34
+ /**
35
+ * The scripts injected into each WebView, for a build with the given capabilities.
36
+ *
37
+ * Capabilities are advertised on the WebViews that load a checkout or a bank page, and not on the
38
+ * education carousel - that renders our own content, which has nothing to gate. Advertising a
39
+ * capability where nothing can exercise it is noise on a contract the page reads.
40
+ */
41
+ export function buildInjectionScripts(capabilities: string[]) {
42
+ const advertisement = buildCapabilityAdvertisement(capabilities);
43
+
44
+ return {
45
+ main: mainScript + advertisement,
46
+ flow: flowScript + advertisement,
47
+ standard: standardScript + advertisement,
48
+ educationCarousel: educationCarouselScript,
49
+ };
50
+ }
51
+
9
52
  export const InjectionScripts = {
10
53
  main: mainScript,
11
54
  flow: flowScript,
@@ -203,6 +203,82 @@ export const injectedScript: string = `
203
203
  });
204
204
  });
205
205
 
206
+
207
+ /**
208
+ * File-input click detection.
209
+ *
210
+ * Does not open the picker - react-native-webview does that natively through
211
+ * onShowFileChooser, and the SDK never sees the rest of it. This carries the only record
212
+ * that an upload was attempted, and on iOS it is the only file signal at all, since
213
+ * WKWebView runs its own picker and reports nothing back.
214
+ *
215
+ * Capture phase, so a page that stops propagation on its own handler cannot silence it.
216
+ *
217
+ * Posts the shape of the input and nothing that identifies it. The id and name of a KYC
218
+ * upload control are third-party identity, and unconsumed page data sitting on the bridge is
219
+ * how identity ends up spread into a tracker by someone who never read this.
220
+ */
221
+ document.addEventListener('click', function(event) {
222
+ try {
223
+ const target = event.target;
224
+ if (!target || target.tagName !== 'INPUT' || target.type !== 'file') {
225
+ return;
226
+ }
227
+ safePostMessage({
228
+ type: 'file.input',
229
+ accept: target.getAttribute('accept') || '',
230
+ hasCapture: target.hasAttribute('capture')
231
+ });
232
+ } catch (error) {
233
+ // Never let this break a bank's own upload handling
234
+ }
235
+ }, true);
236
+
237
+
238
+ /**
239
+ * getUserMedia observation.
240
+ *
241
+ * Telemetry only, and it must stay that way. A rejection with NotAllowedError IS the user
242
+ * denying camera access - an observation, not an inference - and on iOS it is the only one
243
+ * available, because WKWebView raises its own prompt and never reports the answer back.
244
+ *
245
+ * It deliberately does NOT drive onUserRefusedDevicePermissions. Coverage depends on whether
246
+ * a given bank uses getUserMedia or a capture-enabled file input, which is third-party
247
+ * markup we do not control and are not told about when it changes. A callback that fires for
248
+ * some banks would leave a merchant unable to tell "the user did not deny" from "this bank's
249
+ * flow is not observable". Silence is unambiguous; partial coverage is not.
250
+ *
251
+ * Wrapped thinly and never allowed to throw: this runs inside a bank's page, and a fault in
252
+ * our handler must not be able to break their capture flow.
253
+ */
254
+ (function() {
255
+ try {
256
+ const media = navigator.mediaDevices;
257
+ if (!media || typeof media.getUserMedia !== 'function') {
258
+ return;
259
+ }
260
+ const originalGetUserMedia = media.getUserMedia.bind(media);
261
+ media.getUserMedia = function(constraints) {
262
+ return originalGetUserMedia(constraints).catch(function(error) {
263
+ try {
264
+ if (error && error.name === 'NotAllowedError') {
265
+ safePostMessage({
266
+ type: 'permission.denied',
267
+ resource: (constraints && constraints.video) ? 'camera' : 'microphone',
268
+ errorName: error.name
269
+ });
270
+ }
271
+ } catch (reportingError) {
272
+ // Reporting must never change what the page sees
273
+ }
274
+ throw error;
275
+ });
276
+ };
277
+ } catch (error) {
278
+ // A page that has frozen navigator.mediaDevices keeps working, unobserved
279
+ }
280
+ })();
281
+
206
282
  // Logging that injection is complete
207
283
  console.log(LOG_PREFIX + 'GlomoPay WebView JavaScript injection complete');
208
284
 
@@ -209,6 +209,37 @@ export const injectedScript: string = `
209
209
  });
210
210
  });
211
211
 
212
+
213
+ /**
214
+ * File-input click detection.
215
+ *
216
+ * Does not open the picker - react-native-webview does that natively through
217
+ * onShowFileChooser, and the SDK never sees the rest of it. This carries the only record
218
+ * that an upload was attempted, and on iOS it is the only file signal at all, since
219
+ * WKWebView runs its own picker and reports nothing back.
220
+ *
221
+ * Capture phase, so a page that stops propagation on its own handler cannot silence it.
222
+ *
223
+ * Posts the shape of the input and nothing that identifies it. The id and name of a KYC
224
+ * upload control are third-party identity, and unconsumed page data sitting on the bridge is
225
+ * how identity ends up spread into a tracker by someone who never read this.
226
+ */
227
+ document.addEventListener('click', function(event) {
228
+ try {
229
+ const target = event.target;
230
+ if (!target || target.tagName !== 'INPUT' || target.type !== 'file') {
231
+ return;
232
+ }
233
+ safePostMessage({
234
+ type: 'file.input',
235
+ accept: target.getAttribute('accept') || '',
236
+ hasCapture: target.hasAttribute('capture')
237
+ });
238
+ } catch (error) {
239
+ // Never let this break a bank's own upload handling
240
+ }
241
+ }, true);
242
+
212
243
  // Logging that injection is complete
213
244
  console.log(LOG_PREFIX + 'GlomoPay WebView JavaScript injection complete');
214
245
 
@@ -203,6 +203,37 @@ export const injectedScript: string = `
203
203
  });
204
204
  });
205
205
 
206
+
207
+ /**
208
+ * File-input click detection.
209
+ *
210
+ * Does not open the picker - react-native-webview does that natively through
211
+ * onShowFileChooser, and the SDK never sees the rest of it. This carries the only record
212
+ * that an upload was attempted, and on iOS it is the only file signal at all, since
213
+ * WKWebView runs its own picker and reports nothing back.
214
+ *
215
+ * Capture phase, so a page that stops propagation on its own handler cannot silence it.
216
+ *
217
+ * Posts the shape of the input and nothing that identifies it. The id and name of a KYC
218
+ * upload control are third-party identity, and unconsumed page data sitting on the bridge is
219
+ * how identity ends up spread into a tracker by someone who never read this.
220
+ */
221
+ document.addEventListener('click', function(event) {
222
+ try {
223
+ const target = event.target;
224
+ if (!target || target.tagName !== 'INPUT' || target.type !== 'file') {
225
+ return;
226
+ }
227
+ safePostMessage({
228
+ type: 'file.input',
229
+ accept: target.getAttribute('accept') || '',
230
+ hasCapture: target.hasAttribute('capture')
231
+ });
232
+ } catch (error) {
233
+ // Never let this break a bank's own upload handling
234
+ }
235
+ }, true);
236
+
206
237
  // Logging that injection is complete
207
238
  console.log(LOG_PREFIX + 'GlomoPay WebView JavaScript injection complete');
208
239