@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.
- package/CHANGELOG.md +29 -0
- package/MIGRATION.md +112 -0
- package/README.md +173 -36
- package/lib/bridge/file-input-handler.d.ts +25 -0
- package/lib/bridge/file-input-handler.d.ts.map +1 -0
- package/lib/bridge/file-input-handler.js +38 -0
- package/lib/bridge/file-save-handler.d.ts +32 -0
- package/lib/bridge/file-save-handler.d.ts.map +1 -0
- package/lib/bridge/file-save-handler.js +92 -0
- package/lib/bridge/message-router.d.ts +44 -0
- package/lib/bridge/message-router.d.ts.map +1 -0
- package/lib/bridge/message-router.js +48 -0
- package/lib/bridge/payment-payload.d.ts +18 -0
- package/lib/bridge/payment-payload.d.ts.map +1 -0
- package/lib/bridge/payment-payload.js +31 -0
- package/lib/capabilities/file-save.d.ts +40 -0
- package/lib/capabilities/file-save.d.ts.map +1 -0
- package/lib/capabilities/file-save.js +101 -0
- package/lib/capabilities/index.d.ts +22 -0
- package/lib/capabilities/index.d.ts.map +1 -0
- package/lib/capabilities/index.js +34 -0
- package/lib/capabilities/use-capability-reporting.d.ts +24 -0
- package/lib/capabilities/use-capability-reporting.d.ts.map +1 -0
- package/lib/capabilities/use-capability-reporting.js +44 -0
- package/lib/config/base.d.ts +12 -0
- package/lib/config/base.d.ts.map +1 -1
- package/lib/config/base.js +13 -1
- package/lib/glomo-checkout.d.ts +6 -1
- package/lib/glomo-checkout.d.ts.map +1 -1
- package/lib/glomo-checkout.js +11 -5
- package/lib/glomo-lrs-checkout.d.ts.map +1 -1
- package/lib/glomo-lrs-checkout.js +33 -10
- package/lib/glomo-standard-checkout.d.ts +3 -1
- package/lib/glomo-standard-checkout.d.ts.map +1 -1
- package/lib/glomo-standard-checkout.js +21 -13
- package/lib/glomo-subscriptions-checkout.d.ts.map +1 -1
- package/lib/glomo-subscriptions-checkout.js +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -3
- package/lib/injections/index.d.ts +24 -0
- package/lib/injections/index.d.ts.map +1 -1
- package/lib/injections/index.js +41 -0
- package/lib/injections/webview-flow.injection.d.ts.map +1 -1
- package/lib/injections/webview-flow.injection.js +76 -0
- package/lib/injections/webview-main.injection.d.ts.map +1 -1
- package/lib/injections/webview-main.injection.js +31 -0
- package/lib/injections/webview-standard.injection.d.ts.map +1 -1
- package/lib/injections/webview-standard.injection.js +31 -0
- package/lib/services/file-saver.d.ts +109 -0
- package/lib/services/file-saver.d.ts.map +1 -0
- package/lib/services/file-saver.js +416 -0
- package/lib/types/checkout.d.ts +58 -6
- package/lib/types/checkout.d.ts.map +1 -1
- package/lib/types/checkout.js +24 -2
- package/lib/types/standard-checkout.d.ts +6 -6
- package/lib/types/standard-checkout.d.ts.map +1 -1
- package/lib/types/subscriptions-checkout.d.ts +3 -3
- package/lib/types/subscriptions-checkout.d.ts.map +1 -1
- package/lib/use-glomo-checkout.js +2 -2
- package/lib/use-lrs-checkout.d.ts +9 -5
- package/lib/use-lrs-checkout.d.ts.map +1 -1
- package/lib/use-lrs-checkout.js +317 -171
- package/lib/use-standard-checkout.d.ts +17 -13
- package/lib/use-standard-checkout.d.ts.map +1 -1
- package/lib/use-standard-checkout.js +281 -207
- package/lib/utils/analytics.d.ts +177 -51
- package/lib/utils/analytics.d.ts.map +1 -1
- package/lib/utils/analytics.js +299 -81
- package/lib/utils/device-compliance.d.ts +57 -12
- package/lib/utils/device-compliance.d.ts.map +1 -1
- package/lib/utils/device-compliance.js +101 -37
- package/lib/utils/event-projections.d.ts +56 -0
- package/lib/utils/event-projections.d.ts.map +1 -0
- package/lib/utils/event-projections.js +42 -0
- package/lib/utils/file-metadata.d.ts +52 -0
- package/lib/utils/file-metadata.d.ts.map +1 -0
- package/lib/utils/file-metadata.js +134 -0
- package/lib/utils/use-sdk-session.d.ts +12 -0
- package/lib/utils/use-sdk-session.d.ts.map +1 -0
- package/lib/utils/use-sdk-session.js +37 -0
- package/lib/utils/validation.d.ts +37 -3
- package/lib/utils/validation.d.ts.map +1 -1
- package/lib/utils/validation.js +64 -8
- package/lib/webview/capability-props.d.ts +27 -0
- package/lib/webview/capability-props.d.ts.map +1 -0
- package/lib/webview/capability-props.js +61 -0
- package/lib/webview/use-permission-request.d.ts +33 -0
- package/lib/webview/use-permission-request.d.ts.map +1 -0
- package/lib/webview/use-permission-request.js +89 -0
- package/package.json +10 -2
- package/src/bridge/file-input-handler.ts +66 -0
- package/src/bridge/file-save-handler.ts +141 -0
- package/src/bridge/message-router.ts +92 -0
- package/src/bridge/payment-payload.ts +31 -0
- package/src/capabilities/file-save.ts +124 -0
- package/src/capabilities/index.ts +34 -0
- package/src/capabilities/use-capability-reporting.ts +55 -0
- package/src/config/base.ts +13 -0
- package/src/glomo-checkout.tsx +11 -7
- package/src/glomo-lrs-checkout.tsx +41 -26
- package/src/glomo-standard-checkout.tsx +31 -34
- package/src/glomo-subscriptions-checkout.tsx +1 -0
- package/src/index.ts +4 -2
- package/src/injections/index.ts +43 -0
- package/src/injections/webview-flow.injection.ts +76 -0
- package/src/injections/webview-main.injection.ts +31 -0
- package/src/injections/webview-standard.injection.ts +31 -0
- package/src/services/file-saver.ts +497 -0
- package/src/types/checkout.ts +62 -6
- package/src/types/standard-checkout.ts +6 -6
- package/src/types/subscriptions-checkout.ts +7 -3
- package/src/use-glomo-checkout.tsx +2 -2
- package/src/use-lrs-checkout.tsx +656 -365
- package/src/use-standard-checkout.tsx +728 -466
- package/src/utils/analytics.ts +422 -117
- package/src/utils/device-compliance.ts +148 -38
- package/src/utils/event-projections.ts +74 -0
- package/src/utils/file-metadata.ts +140 -0
- package/src/utils/use-sdk-session.ts +41 -0
- package/src/utils/validation.ts +70 -10
- package/src/webview/capability-props.ts +86 -0
- package/src/webview/use-permission-request.ts +114 -0
- 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
|
|
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
|
|
30
|
-
signature
|
|
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;
|
|
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"}
|
package/lib/utils/validation.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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": "
|
|
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
|
-
"
|
|
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
|
+
}
|