@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
@@ -7,6 +7,12 @@
7
7
  * @returns true if valid, false otherwise
8
8
  */
9
9
  export declare function isValidPublicKey(publicKey: string): boolean;
10
+ /**
11
+ * Whether this public key should run in mock mode.
12
+ *
13
+ * Case-insensitive, matching what the call sites already did.
14
+ */
15
+ export declare function isMockModeKey(publicKey?: string): boolean;
10
16
  /**
11
17
  * Validates that an order ID has the correct format
12
18
  * @param orderId - The order ID to validate
@@ -21,14 +27,42 @@ export declare function isValidOrderId(orderId: string): boolean;
21
27
  export declare function isValidUrl(url: string): boolean;
22
28
  /**
23
29
  * Validates the payment payload structure
30
+ *
31
+ * `orderId` is the only field the SDK can route on and the only one required. The other two are
32
+ * reported when present and named in telemetry when absent - see missingPaymentFields.
33
+ *
34
+ * Relaxed in v5. Before this, a payload missing either optional field failed here and the whole
35
+ * outcome was dropped behind a devMode-only log: no callback, no telemetry, no trace in a release
36
+ * build. `strict: true` in tsconfig is what makes the looser shape safe for merchants - an optional
37
+ * field cannot reach anything expecting a `string` without a narrowing check, so this lands as a
38
+ * compile error rather than a runtime surprise.
39
+ *
40
+ * An empty `orderId` is accepted, and must stay accepted. Open banking and pay-via-bank post
41
+ * `payment.success` with empty fields when the payment completes on the bank's side before the
42
+ * checkout backend has resolved ids - so rejecting `""` here would drop a payment that actually
43
+ * succeeded, which is the exact failure the widening above exists to remove. v4 carried the same
44
+ * rule in a comment; a length check briefly reintroduced the bug in v5 before review caught it.
45
+ * Type is the only thing checked: a non-string `orderId` is malformed, an empty one is early.
46
+ *
24
47
  * @param payload - The payload to validate
25
- * @returns true if valid, false otherwise
48
+ * @returns true if the payload carries a usable orderId
26
49
  */
27
50
  export declare function isValidPaymentPayload(payload: unknown): payload is {
28
51
  orderId: string;
29
- paymentId: string;
30
- signature: string;
52
+ paymentId?: string;
53
+ signature?: string;
31
54
  };
55
+ /**
56
+ * Which optional fields a valid payload did not carry.
57
+ *
58
+ * Empty for a complete payload. Treats the empty string as missing: the page sends `""` rather than
59
+ * omitting the key when the backend has not resolved an id, and the two mean the same thing to
60
+ * anyone reading the event.
61
+ */
62
+ export declare function missingPaymentFields(payload: {
63
+ paymentId?: string;
64
+ signature?: string;
65
+ }): string[];
32
66
  /**
33
67
  * Safely executes a callback with error handling
34
68
  *
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAU3D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAOvD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAM/C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB,CAYA;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,EAC3D,QAAQ,EAAE,CAAC,GAAG,SAAS,EACvB,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe,GACzB,IAAI,CAWN"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAU3D;AAWD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAMzD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAOvD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAM/C;AAYD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB,CAYA;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,EAAE,CAElG;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,EAC3D,QAAQ,EAAE,CAAC,GAAG,SAAS,EACvB,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe,GACzB,IAAI,CAWN"}
@@ -4,9 +4,11 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.isValidPublicKey = isValidPublicKey;
7
+ exports.isMockModeKey = isMockModeKey;
7
8
  exports.isValidOrderId = isValidOrderId;
8
9
  exports.isValidUrl = isValidUrl;
9
10
  exports.isValidPaymentPayload = isValidPaymentPayload;
11
+ exports.missingPaymentFields = missingPaymentFields;
10
12
  exports.safeCallback = safeCallback;
11
13
  /**
12
14
  * Validates that a public key has the correct format
@@ -21,6 +23,26 @@ function isValidPublicKey(publicKey) {
21
23
  return (publicKey.length > 5 &&
22
24
  (publicKey.startsWith("live_") || publicKey.startsWith("mock_") || publicKey.startsWith("test_")));
23
25
  }
26
+ /**
27
+ * Prefixes that run the checkout against mocks instead of taking a live payment.
28
+ *
29
+ * `isValidPublicKey` has always accepted all three prefixes while every mock-mode check tested only
30
+ * `test_`, so a `mock_` key passed validation and then had `mode=live` built into its checkout URL -
31
+ * a mock key taking a real payment. Declared once here so the two cannot drift again.
32
+ */
33
+ const MOCK_MODE_PREFIXES = ["test_", "mock_"];
34
+ /**
35
+ * Whether this public key should run in mock mode.
36
+ *
37
+ * Case-insensitive, matching what the call sites already did.
38
+ */
39
+ function isMockModeKey(publicKey) {
40
+ const normalized = publicKey === null || publicKey === void 0 ? void 0 : publicKey.toLowerCase();
41
+ if (!normalized) {
42
+ return false;
43
+ }
44
+ return MOCK_MODE_PREFIXES.some((prefix) => normalized.startsWith(prefix));
45
+ }
24
46
  /**
25
47
  * Validates that an order ID has the correct format
26
48
  * @param orderId - The order ID to validate
@@ -44,23 +66,57 @@ function isValidUrl(url) {
44
66
  }
45
67
  return url.startsWith("http://") || url.startsWith("https://");
46
68
  }
69
+ /**
70
+ * The fields a payment payload may be missing and still be usable
71
+ *
72
+ * `paymentId` and `signature` are resolved by the checkout backend, and a page can report a
73
+ * terminal outcome before that has happened - open banking completes on the bank's side first.
74
+ * Requiring them meant the SDK dropped a real payment outcome on the floor rather than reporting an
75
+ * incomplete one, so the merchant learned nothing at all.
76
+ */
77
+ const OPTIONAL_PAYMENT_FIELDS = ["paymentId", "signature"];
47
78
  /**
48
79
  * Validates the payment payload structure
80
+ *
81
+ * `orderId` is the only field the SDK can route on and the only one required. The other two are
82
+ * reported when present and named in telemetry when absent - see missingPaymentFields.
83
+ *
84
+ * Relaxed in v5. Before this, a payload missing either optional field failed here and the whole
85
+ * outcome was dropped behind a devMode-only log: no callback, no telemetry, no trace in a release
86
+ * build. `strict: true` in tsconfig is what makes the looser shape safe for merchants - an optional
87
+ * field cannot reach anything expecting a `string` without a narrowing check, so this lands as a
88
+ * compile error rather than a runtime surprise.
89
+ *
90
+ * An empty `orderId` is accepted, and must stay accepted. Open banking and pay-via-bank post
91
+ * `payment.success` with empty fields when the payment completes on the bank's side before the
92
+ * checkout backend has resolved ids - so rejecting `""` here would drop a payment that actually
93
+ * succeeded, which is the exact failure the widening above exists to remove. v4 carried the same
94
+ * rule in a comment; a length check briefly reintroduced the bug in v5 before review caught it.
95
+ * Type is the only thing checked: a non-string `orderId` is malformed, an empty one is early.
96
+ *
49
97
  * @param payload - The payload to validate
50
- * @returns true if valid, false otherwise
98
+ * @returns true if the payload carries a usable orderId
51
99
  */
52
100
  function isValidPaymentPayload(payload) {
53
101
  if (!payload || typeof payload !== "object") {
54
102
  return false;
55
103
  }
56
104
  const p = payload;
57
- /**
58
- * Accept payloads where orderId, paymentId, and signature are strings.
59
- * Empty strings are allowed because some flows (e.g. open banking / pay via bank)
60
- * send payment.success with empty fields when the payment completes on the
61
- * bank's side before the checkout backend has resolved ids.
62
- */
63
- return typeof p.orderId === "string" && typeof p.paymentId === "string" && typeof p.signature === "string";
105
+ if (typeof p.orderId !== "string") {
106
+ return false;
107
+ }
108
+ // Present-but-wrong-type is a malformed payload, unlike absent, which is merely incomplete
109
+ return OPTIONAL_PAYMENT_FIELDS.every((field) => p[field] === undefined || typeof p[field] === "string");
110
+ }
111
+ /**
112
+ * Which optional fields a valid payload did not carry.
113
+ *
114
+ * Empty for a complete payload. Treats the empty string as missing: the page sends `""` rather than
115
+ * omitting the key when the backend has not resolved an id, and the two mean the same thing to
116
+ * anyone reading the event.
117
+ */
118
+ function missingPaymentFields(payload) {
119
+ return OPTIONAL_PAYMENT_FIELDS.filter((field) => !payload[field]);
64
120
  }
65
121
  /**
66
122
  * Safely executes a callback with error handling
@@ -0,0 +1,27 @@
1
+ /** Shared WebView configuration for every GlomoPay checkout WebView */
2
+ import { type WebViewProps } from "react-native-webview";
3
+ import { type WebViewPermissionRequest } from "./use-permission-request";
4
+ /**
5
+ * react-native-webview's props plus the Android-only permission handler, which is
6
+ * missing from its type definitions. Declaring it here removes the @ts-expect-error
7
+ * each call site used to carry.
8
+ */
9
+ export type CapabilityWebViewProps = Partial<WebViewProps> & {
10
+ onPermissionRequest?: (request: WebViewPermissionRequest) => void;
11
+ };
12
+ /** The options for buildCapabilityProps */
13
+ export interface CapabilityPropsOptions {
14
+ devMode?: boolean;
15
+ /** From usePermissionRequest. Omit only for WebViews that render our own content. */
16
+ onPermissionRequest?: (request: WebViewPermissionRequest) => void;
17
+ }
18
+ /**
19
+ * Configuration shared by every WebView the SDK renders, regardless of flow.
20
+ *
21
+ * Only configuration belongs here - things that describe how a WebView behaves. Per-WebView
22
+ * concerns (source, refs, message handlers, injected scripts, styles) stay at the call site,
23
+ * and defensive patches against a bank's markup stay in the flow injection script, since the
24
+ * WebViews that render our own pages have no third-party markup to defend against.
25
+ */
26
+ export declare function buildCapabilityProps({ devMode, onPermissionRequest, }: CapabilityPropsOptions): CapabilityWebViewProps;
27
+ //# sourceMappingURL=capability-props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-props.d.ts","sourceRoot":"","sources":["../../src/webview/capability-props.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzE;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG;IACzD,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACrE,CAAC;AAEF,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,qFAAqF;IACrF,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACrE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,OAAe,EACf,mBAAmB,GACtB,EAAE,sBAAsB,GAAG,sBAAsB,CAmDjD"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /** Shared WebView configuration for every GlomoPay checkout WebView */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.buildCapabilityProps = buildCapabilityProps;
5
+ /**
6
+ * Configuration shared by every WebView the SDK renders, regardless of flow.
7
+ *
8
+ * Only configuration belongs here - things that describe how a WebView behaves. Per-WebView
9
+ * concerns (source, refs, message handlers, injected scripts, styles) stay at the call site,
10
+ * and defensive patches against a bank's markup stay in the flow injection script, since the
11
+ * WebViews that render our own pages have no third-party markup to defend against.
12
+ */
13
+ function buildCapabilityProps({ devMode = false, onPermissionRequest, }) {
14
+ return {
15
+ webviewDebuggingEnabled: __DEV__ && devMode,
16
+ javaScriptEnabled: true,
17
+ domStorageEnabled: true,
18
+ javaScriptCanOpenWindowsAutomatically: true,
19
+ setSupportMultipleWindows: true,
20
+ incognito: true,
21
+ cacheEnabled: false,
22
+ cacheMode: "LOAD_NO_CACHE",
23
+ pullToRefreshEnabled: false,
24
+ limitsNavigationsToAppBoundDomains: false,
25
+ menuItems: [],
26
+ showsHorizontalScrollIndicator: false,
27
+ showsVerticalScrollIndicator: false,
28
+ mixedContentMode: "always",
29
+ thirdPartyCookiesEnabled: true,
30
+ sharedCookiesEnabled: true,
31
+ allowsInlineMediaPlayback: true,
32
+ mediaPlaybackRequiresUserAction: false,
33
+ /**
34
+ * Camera for bank authentication. iOS raises its own prompt; Android routes through
35
+ * onPermissionRequest below.
36
+ */
37
+ mediaCapturePermissionGrantType: "prompt",
38
+ onPermissionRequest,
39
+ /**
40
+ * File access props.
41
+ *
42
+ * These only apply to JavaScript running inside a file:// document, and no file://
43
+ * document can load here - neither factory sets originWhitelist, so
44
+ * react-native-webview's default of ['http://*', 'https://*'] applies and a file://
45
+ * navigation is never taken. Every WebView loads remote https under incognito. So
46
+ * these are inert, and kept for uniformity across flows rather than because anything
47
+ * needs them.
48
+ *
49
+ * Note allowFileAccess is not inert in the same way: rnw defaults it to false on
50
+ * Android, so we are the ones turning it on. Deliberate, and safe only because of the
51
+ * originWhitelist default above.
52
+ *
53
+ * If originWhitelist is ever set to admit file://, all three become live and this
54
+ * needs revisiting.
55
+ */
56
+ allowFileAccess: true,
57
+ allowUniversalAccessFromFileURLs: true,
58
+ allowFileAccessFromFileURLs: true,
59
+ androidLayerType: "hardware",
60
+ };
61
+ }
@@ -0,0 +1,33 @@
1
+ /** Shared WebView permission handling for every GlomoPay checkout flow */
2
+ /** Android-only WebView permission request, not yet in react-native-webview type definitions */
3
+ export interface WebViewPermissionRequest {
4
+ nativeEvent: {
5
+ resources: string[];
6
+ };
7
+ grant: (resources: string[]) => void;
8
+ deny: () => void;
9
+ }
10
+ /** The options for the usePermissionRequest hook */
11
+ export interface UsePermissionRequestOptions {
12
+ /** Log prefix of the calling flow, e.g. "[Glomo-RN-SDK LRS]" */
13
+ logPrefix: string;
14
+ /**
15
+ * Called when the user refuses camera access, or when the request errors out.
16
+ * The caller owns what happens next - closing the checkout, moving its status,
17
+ * and notifying the merchant are all flow-level concerns and stay outside this hook.
18
+ */
19
+ onDenied: () => void;
20
+ devMode?: boolean;
21
+ }
22
+ /**
23
+ * Handler for WebView permission requests (e.g. camera access for bank authentication).
24
+ * On Android, prompts the user for camera permission via PermissionsAndroid.
25
+ * On iOS, grants camera access automatically (iOS handles its own permission prompt).
26
+ * Non-camera permissions are denied by default.
27
+ *
28
+ * Lives here rather than inside a flow hook so every flow gets the same behaviour.
29
+ * It previously existed only on the standard checkout, which left LRS with no camera
30
+ * support at all on either platform.
31
+ */
32
+ export declare function usePermissionRequest({ logPrefix, onDenied, devMode }: UsePermissionRequestOptions): (request: WebViewPermissionRequest) => Promise<void>;
33
+ //# sourceMappingURL=use-permission-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-permission-request.d.ts","sourceRoot":"","sources":["../../src/webview/use-permission-request.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAK1E,gGAAgG;AAChG,MAAM,WAAW,wBAAwB;IACrC,WAAW,EAAE;QAAE,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACrC,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,2BAA2B;IACxC,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;IAErB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAe,EAAE,EAAE,2BAA2B,aAElF,wBAAwB,mBA0E/C"}
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /** Shared WebView permission handling for every GlomoPay checkout flow */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.usePermissionRequest = usePermissionRequest;
5
+ const react_1 = require("react");
6
+ const react_native_1 = require("react-native");
7
+ /**
8
+ * Handler for WebView permission requests (e.g. camera access for bank authentication).
9
+ * On Android, prompts the user for camera permission via PermissionsAndroid.
10
+ * On iOS, grants camera access automatically (iOS handles its own permission prompt).
11
+ * Non-camera permissions are denied by default.
12
+ *
13
+ * Lives here rather than inside a flow hook so every flow gets the same behaviour.
14
+ * It previously existed only on the standard checkout, which left LRS with no camera
15
+ * support at all on either platform.
16
+ */
17
+ function usePermissionRequest({ logPrefix, onDenied, devMode = false }) {
18
+ return (0, react_1.useCallback)(async (request) => {
19
+ try {
20
+ const { resources } = request.nativeEvent;
21
+ // Checking if the requested permission is camera-related
22
+ const isCameraPermissionRequest = resources.some((resource) => resource === "camera" || resource.toLowerCase().includes("camera"));
23
+ if (!isCameraPermissionRequest) {
24
+ if (devMode) {
25
+ console.log(`${logPrefix} Non-camera permission requested, denying:`, resources);
26
+ }
27
+ request.deny();
28
+ return;
29
+ }
30
+ if (devMode) {
31
+ console.log(`${logPrefix} Camera permission requested by webpage`);
32
+ }
33
+ if (react_native_1.Platform.OS === "android") {
34
+ try {
35
+ const granted = await react_native_1.PermissionsAndroid.request(react_native_1.PermissionsAndroid.PERMISSIONS.CAMERA, {
36
+ title: "Camera Permission",
37
+ message: "This app needs camera access for identity verification during checkout",
38
+ buttonNeutral: "Ask Me Later",
39
+ buttonNegative: "Cancel",
40
+ buttonPositive: "OK",
41
+ });
42
+ if (granted === react_native_1.PermissionsAndroid.RESULTS.GRANTED) {
43
+ if (devMode) {
44
+ console.log(`${logPrefix} Camera permission granted`);
45
+ }
46
+ request.grant(resources);
47
+ }
48
+ else {
49
+ if (devMode) {
50
+ console.log(`${logPrefix} Camera permission denied by user`);
51
+ }
52
+ onDenied();
53
+ request.deny();
54
+ }
55
+ }
56
+ catch (err) {
57
+ if (devMode) {
58
+ console.error(`${logPrefix} Error requesting camera permission:`, err);
59
+ }
60
+ onDenied();
61
+ request.deny();
62
+ }
63
+ }
64
+ else if (react_native_1.Platform.OS === "ios") {
65
+ /**
66
+ * Granting to the WebView, not to the user - WKWebView raises its own prompt
67
+ * from here and never reports the answer back, so the SDK cannot know whether
68
+ * the user allowed it. onDenied is unreachable on this path.
69
+ */
70
+ if (devMode) {
71
+ console.log(`${logPrefix} Granting camera permission request on iOS`);
72
+ }
73
+ request.grant(resources);
74
+ }
75
+ else {
76
+ if (devMode) {
77
+ console.warn(`${logPrefix} Unsupported platform for camera permissions`);
78
+ }
79
+ request.deny();
80
+ }
81
+ }
82
+ catch (error) {
83
+ if (devMode) {
84
+ console.error(`${logPrefix} Error in handlePermissionRequest:`, error);
85
+ }
86
+ request.deny();
87
+ }
88
+ }, [logPrefix, onDenied, devMode]);
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glomopay/react-native-sdk",
3
- "version": "4.1.0",
3
+ "version": "5.0.0",
4
4
  "description": "The React Native SDK for GlomoPay",
5
5
  "license": "Apache-2.0",
6
6
  "main": "lib/index.js",
@@ -55,26 +55,34 @@
55
55
  "axios": "1.13.2"
56
56
  },
57
57
  "peerDependenciesMeta": {
58
- "jail-monkey": {
58
+ "react-native-blob-util": {
59
+ "optional": true
60
+ },
61
+ "@react-native-documents/picker": {
59
62
  "optional": true
60
63
  }
61
64
  },
62
65
  "peerDependencies": {
66
+ "@react-native-documents/picker": ">=10.1.3",
63
67
  "jail-monkey": "^2.6.0",
64
68
  "react": ">=17.0.0",
65
69
  "react-native": ">=0.68.0",
70
+ "react-native-blob-util": "^0.25.0",
66
71
  "react-native-webview": "^13.0.0"
67
72
  },
68
73
  "devDependencies": {
74
+ "@react-native-documents/picker": "^10.1.7",
69
75
  "@types/react": "^18.0.0",
70
76
  "@types/react-native": "^0.73.0",
71
77
  "@typescript-eslint/eslint-plugin": "^8.60.1",
72
78
  "eslint": "^9.39.4",
73
79
  "eslint-plugin-react-hooks": "^7.1.1",
80
+ "jail-monkey": "^2.8.5",
74
81
  "jest": "^29.6.3",
75
82
  "prettier": "^3.0.0",
76
83
  "react": "18.2.0",
77
84
  "react-native": "0.73.5",
85
+ "react-native-blob-util": "^0.25.0",
78
86
  "typescript": "^5.0.0",
79
87
  "typescript-eslint": "^8.60.1"
80
88
  },
@@ -0,0 +1,66 @@
1
+ /** The `file.input` bridge message - the user tapped a file input on a bank's page */
2
+
3
+ import { type WebViewMessageHandler } from "./message-router";
4
+ import { normalizeAcceptTypes } from "../utils/file-metadata";
5
+ import { type FlowType, trackFileUploadRequested } from "../utils/analytics";
6
+
7
+ /** The context an upload attempt needs in order to report itself */
8
+ export interface CreateFileInputHandlerOptions {
9
+ flowType: FlowType;
10
+ orderId?: string;
11
+ publicKey: string;
12
+ checkoutUrl: string;
13
+ mockMode: boolean;
14
+ devMode: boolean;
15
+ }
16
+
17
+ /**
18
+ * Builds the handler for `file.input`.
19
+ *
20
+ * Telemetry only, and there is nothing else it could be: the chooser is opened and resolved
21
+ * natively by react-native-webview, so this message is the attempt and no outcome follows it.
22
+ * Nothing merchant-facing fires here - an SDK that reported "upload started" with no way to report
23
+ * "upload finished" would be worse than silent.
24
+ *
25
+ * Reports the shape of the input, never its identity. `accept` is normalised so it joins with
26
+ * anything else reported for the same click.
27
+ */
28
+ export function createFileInputHandler({
29
+ flowType,
30
+ orderId,
31
+ publicKey,
32
+ checkoutUrl,
33
+ mockMode,
34
+ devMode,
35
+ }: CreateFileInputHandlerOptions): WebViewMessageHandler {
36
+ return (message) => {
37
+ const accept = typeof message.accept === "string" ? message.accept : undefined;
38
+ /**
39
+ * Read as a boolean, not derived from the attribute's value. `capture` is an enumerated
40
+ * attribute and its commonest spelling on a bank's KYC form is valueless - `<input
41
+ * type="file" capture>` - for which getAttribute returns "". Measuring string length there
42
+ * reported hasCapture: false on exactly the camera-backed inputs this exists to count.
43
+ */
44
+ const hasCapture = message.hasCapture === true;
45
+
46
+ /**
47
+ * Logged as well as tracked. Nothing merchant-facing fires for an upload attempt - the
48
+ * chooser is opened and resolved natively - so without this there is no observable signal
49
+ * that the bridge received it, and the path cannot be tested.
50
+ */
51
+ if (devMode) {
52
+ console.log(`[Glomo-RN-SDK] file.input requested: hasCapture=${hasCapture}`);
53
+ }
54
+
55
+ trackFileUploadRequested(
56
+ normalizeAcceptTypes(accept),
57
+ hasCapture,
58
+ orderId,
59
+ publicKey,
60
+ devMode,
61
+ mockMode,
62
+ checkoutUrl,
63
+ flowType
64
+ );
65
+ };
66
+ }
@@ -0,0 +1,141 @@
1
+ /** The `file.save` bridge message, wired once and registered on every WebView that can receive it */
2
+
3
+ import { type WebViewMessage, type WebViewMessageHandler } from "./message-router";
4
+ import {
5
+ FILE_SAVE_OUTCOMES,
6
+ type FileSaveResult,
7
+ isFileSaveFailure,
8
+ saveDocumentToDevice,
9
+ toFileSaveEventData,
10
+ toFileSaveSdkError,
11
+ } from "../services/file-saver";
12
+ import { EXTENSION_EXTRACTION_FAILED, SIZE_UNAVAILABLE } from "../utils/file-metadata";
13
+ import { type FlowType, type SdkError, trackFileSaveCompleted, trackFileSaveRequested } from "../utils/analytics";
14
+ import { safeCallback } from "../utils/validation";
15
+
16
+ /** The context a save needs in order to report itself. Supplied by whichever flow registered it. */
17
+ export interface CreateFileSaveHandlerOptions {
18
+ flowType: FlowType;
19
+ orderId?: string;
20
+ publicKey: string;
21
+ checkoutUrl: string;
22
+ mockMode: boolean;
23
+ devMode: boolean;
24
+
25
+ /**
26
+ * The merchant's error callback.
27
+ *
28
+ * Reached directly, and deliberately not through whatever a flow uses to end a checkout: a
29
+ * document failing to save must not touch checkout status or end the customer's payment session.
30
+ */
31
+ onSdkError: (errors: Array<SdkError>) => void;
32
+ }
33
+
34
+ /**
35
+ * Correlates the request/completion pair when the page supplies no id.
36
+ *
37
+ * Module scope rather than per handler, because the same SDK session registers this on the main and
38
+ * flow WebViews and a per-handler counter would have both minting `sdk-1`. Saves are not serialised
39
+ * either - the agreements screen has three download controls, so two taps in quick succession
40
+ * interleave and would otherwise be indistinguishable in telemetry.
41
+ */
42
+ let fallbackSaveId = 0;
43
+
44
+ /** Prefers the page's requestId so one id spans both sides' telemetry. */
45
+ function resolveSaveId(message: WebViewMessage): string {
46
+ const supplied = message.requestId;
47
+ if (typeof supplied === "string" && supplied.length > 0) {
48
+ return supplied;
49
+ }
50
+ return `sdk-${++fallbackSaveId}`;
51
+ }
52
+
53
+ /**
54
+ * Builds the handler for `file.save`.
55
+ *
56
+ * Fire-and-forget by contract - the page gets no acknowledgement and must not wait for one. Once
57
+ * past the guard below, every outcome lands on `File Save Completed`, and a failure additionally
58
+ * reaches the host's onSdkError without ending the checkout.
59
+ *
60
+ * The handler itself is synchronous because the router's handlers are; the save runs detached and
61
+ * reports through telemetry rather than a return value, which is the same shape the contract
62
+ * describes on the page side.
63
+ */
64
+ export function createFileSaveHandler({
65
+ flowType,
66
+ orderId,
67
+ publicKey,
68
+ checkoutUrl,
69
+ mockMode,
70
+ devMode,
71
+ onSdkError,
72
+ }: CreateFileSaveHandlerOptions): WebViewMessageHandler {
73
+ return (message) => {
74
+ const saveId = resolveSaveId(message);
75
+
76
+ const report = (result: FileSaveResult) => {
77
+ if (devMode) {
78
+ console.log(`[Glomo-RN-SDK] file.save ${saveId} completed: ${result.outcome}`);
79
+ }
80
+ trackFileSaveCompleted(
81
+ saveId,
82
+ toFileSaveEventData(result),
83
+ orderId,
84
+ publicKey,
85
+ devMode,
86
+ mockMode,
87
+ checkoutUrl,
88
+ flowType
89
+ );
90
+
91
+ if (isFileSaveFailure(result)) {
92
+ safeCallback(onSdkError, [[toFileSaveSdkError(result)]], "onSdkError", devMode);
93
+ }
94
+ };
95
+
96
+ if (devMode) {
97
+ console.log(`[Glomo-RN-SDK] file.save ${saveId} requested`);
98
+ }
99
+ trackFileSaveRequested(saveId, orderId, publicKey, devMode, mockMode, checkoutUrl, flowType);
100
+
101
+ const url = message.url;
102
+ if (typeof url !== "string" || url.length === 0) {
103
+ /**
104
+ * Reported rather than dropped. The Flutter SDK returns here without emitting anything
105
+ * and relies on its error tracker to surface it; RN has none yet, so silence would make
106
+ * a malformed message invisible. Completing the pair keeps the "every request has an
107
+ * outcome" invariant that makes a missing completion mean the handler died.
108
+ */
109
+ report({
110
+ outcome: FILE_SAVE_OUTCOMES.REJECTED,
111
+ fileExtension: EXTENSION_EXTRACTION_FAILED,
112
+ fileSize: SIZE_UNAVAILABLE,
113
+ failureType: "missing_url",
114
+ });
115
+ return;
116
+ }
117
+
118
+ void saveDocumentToDevice({
119
+ url,
120
+ filename: typeof message.filename === "string" ? message.filename : undefined,
121
+ devMode,
122
+ })
123
+ .then(report)
124
+ .catch((error) => {
125
+ /**
126
+ * saveDocumentToDevice is documented never to throw, so this is the case where that
127
+ * promise is broken. It still has to close the pair - and only the failure type
128
+ * leaves here, never the message that produced it.
129
+ */
130
+ if (devMode) {
131
+ console.error("[Glomo-RN-SDK] file.save handler failed:", error);
132
+ }
133
+ report({
134
+ outcome: FILE_SAVE_OUTCOMES.SAVE_FAILED,
135
+ fileExtension: EXTENSION_EXTRACTION_FAILED,
136
+ fileSize: SIZE_UNAVAILABLE,
137
+ failureType: "handler_threw",
138
+ });
139
+ });
140
+ };
141
+ }