@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
@@ -1,24 +1,69 @@
1
1
  /**
2
- * Device compliance utilities for the GlomoPay RN SDK
2
+ * Device compliance for the GlomoPay RN SDK
3
3
  *
4
- * These utilities check device security compliance using jail-monkey (if available).
5
- * If jail-monkey is not installed, compliance checks are skipped.
4
+ * Whether a checkout may run on this device, from `jail-monkey`'s root/jailbreak detection.
6
5
  *
7
- * Merchants are STRONGLY encouraged to install peerDependencies for better security compliance.
6
+ * The gate exists to protect a **compromised customer device**: malware on a real user's rooted
7
+ * phone reading cached KYC documents. That framing is what decides the two judgement calls below -
8
+ * which root signals to trust, and when not to gate at all.
8
9
  */
10
+ /** The package name, so a missing dependency can be named in a warning and in telemetry */
11
+ export declare const COMPLIANCE_PACKAGE = "jail-monkey";
9
12
  /**
10
- * Checks if the device is rooted or jailbroken
11
- * Returns null if jail-monkey is not available (compliance check skipped)
12
- * Returns true if device is compromised, false if device is safe
13
+ * rootBeer checks deliberately excluded from our verdict.
13
14
  *
14
- * @param devMode - Whether to log debug information
15
- * @returns true if device is compromised, false if safe, null if check unavailable
15
+ * Both fire on a stock AOSP emulator image - `ro.build.tags=test-keys`, and `ro.debuggable=1` /
16
+ * `ro.secure=0` - so an Android emulator reports as rooted through `isJailBroken()`, which is a
17
+ * hardcoded OR over every check and is not configurable. Merchants integration-test with live keys
18
+ * on emulators; blocking that turns integration into a support ticket, and an emulator is not the
19
+ * threat this gate exists for - there is no unwitting victim whose documents are being read.
20
+ *
21
+ * What this costs is custom-ROM detection on physical devices, the weakest signal in the set. Every
22
+ * strong indicator is kept: a genuinely rooted handset trips su, Magisk or a root-manager app
23
+ * essentially always.
24
+ */
25
+ export declare const IGNORED_ROOT_SIGNALS: string[];
26
+ /** How the verdict below was reached. Rides telemetry, because the three are not equally strong. */
27
+ export type ComplianceSource =
28
+ /** Per-check rootBeer breakdown, with IGNORED_ROOT_SIGNALS excluded. Android only. */
29
+ "decomposed"
30
+ /** jail-monkey's own `isJailBroken()`. iOS, and Android builds that expose no breakdown. */
31
+ | "aggregate"
32
+ /** jail-monkey could not be loaded at all. */
33
+ | "unavailable";
34
+ /** What the probe concluded */
35
+ export interface DeviceComplianceResult {
36
+ /** True if compromised, false if clean, **null if it could not be determined** - never "clean" */
37
+ compromised: boolean | null;
38
+ source: ComplianceSource;
39
+ /** Names of the checks that tripped. Never the device identifiers behind them. */
40
+ trippedSignals: string[];
41
+ }
42
+ /**
43
+ * Whether this device is compromised.
44
+ *
45
+ * Prefers the per-check breakdown over `isJailBroken()`, because the aggregate is a hardcoded OR
46
+ * that includes the two emulator-tripping checks. Falls back to the aggregate whenever the
47
+ * breakdown is absent - iOS never reports one, and a future jail-monkey that stops exporting it
48
+ * degrades to today's behaviour rather than to "clean".
49
+ */
50
+ export declare function resolveDeviceCompliance(devMode?: boolean): DeviceComplianceResult;
51
+ /**
52
+ * Whether a checkout must be refused on this device.
53
+ *
54
+ * **Fails closed on a live key**: unknown blocks, because on RN unknown overwhelmingly means the
55
+ * merchant did not install `jail-monkey` rather than that a platform probe failed. That is why the
56
+ * package stopped being an optional peer dependency in v5 - a dependency cannot be optional and
57
+ * load-bearing at once, and an install-time warning beats a silent runtime outage.
58
+ *
59
+ * **Never gates mock or test traffic.** Measured, never blocked - matching the Flutter SDK's
60
+ * `enforced` model, and so QA on a rooted handset or an emulator image stays possible.
16
61
  */
17
- export declare function checkDeviceCompliance(devMode?: boolean): boolean | null;
62
+ export declare function blocksCheckout(result: DeviceComplianceResult, publicKey?: string): boolean;
18
63
  /**
19
- * Checks if compliance checking is possible in the current environment
64
+ * Whether the device compliance check could run at all
20
65
  *
21
- * @returns true if peerDependencies for compliance checking are installed and available
66
+ * @returns true if jail-monkey is installed and its native module is linked
22
67
  */
23
68
  export declare function isComplianceCheckAvailable(): boolean;
24
69
  //# sourceMappingURL=device-compliance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"device-compliance.d.ts","sourceRoot":"","sources":["../../src/utils/device-compliance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,OAAe,GAAG,OAAO,GAAG,IAAI,CA+B9E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD"}
1
+ {"version":3,"file":"device-compliance.d.ts","sourceRoot":"","sources":["../../src/utils/device-compliance.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,UAA+C,CAAC;AAEjF,oGAAoG;AACpG,MAAM,MAAM,gBAAgB;AACxB,sFAAsF;AACpF,YAAY;AAEd,4FAA4F;GAC1F,WAAW;AAEb,8CAA8C;GAC5C,aAAa,CAAC;AAEpB,+BAA+B;AAC/B,MAAM,WAAW,sBAAsB;IACnC,kGAAkG;IAClG,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;IAE5B,MAAM,EAAE,gBAAgB,CAAC;IAEzB,kFAAkF;IAClF,cAAc,EAAE,MAAM,EAAE,CAAC;CAC5B;AA4CD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,OAAe,GAAG,sBAAsB,CAuDxF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,sBAAsB,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAK1F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD"}
@@ -1,71 +1,135 @@
1
1
  "use strict";
2
2
  /**
3
- * Device compliance utilities for the GlomoPay RN SDK
3
+ * Device compliance for the GlomoPay RN SDK
4
4
  *
5
- * These utilities check device security compliance using jail-monkey (if available).
6
- * If jail-monkey is not installed, compliance checks are skipped.
5
+ * Whether a checkout may run on this device, from `jail-monkey`'s root/jailbreak detection.
7
6
  *
8
- * Merchants are STRONGLY encouraged to install peerDependencies for better security compliance.
7
+ * The gate exists to protect a **compromised customer device**: malware on a real user's rooted
8
+ * phone reading cached KYC documents. That framing is what decides the two judgement calls below -
9
+ * which root signals to trust, and when not to gate at all.
9
10
  */
10
11
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.checkDeviceCompliance = checkDeviceCompliance;
12
+ exports.IGNORED_ROOT_SIGNALS = exports.COMPLIANCE_PACKAGE = void 0;
13
+ exports.resolveDeviceCompliance = resolveDeviceCompliance;
14
+ exports.blocksCheckout = blocksCheckout;
12
15
  exports.isComplianceCheckAvailable = isComplianceCheckAvailable;
16
+ const validation_1 = require("./validation");
17
+ /** The package name, so a missing dependency can be named in a warning and in telemetry */
18
+ exports.COMPLIANCE_PACKAGE = "jail-monkey";
13
19
  /**
14
- * Safely checks if jail-monkey is available
15
- * Returns null if the library is not installed
20
+ * rootBeer checks deliberately excluded from our verdict.
21
+ *
22
+ * Both fire on a stock AOSP emulator image - `ro.build.tags=test-keys`, and `ro.debuggable=1` /
23
+ * `ro.secure=0` - so an Android emulator reports as rooted through `isJailBroken()`, which is a
24
+ * hardcoded OR over every check and is not configurable. Merchants integration-test with live keys
25
+ * on emulators; blocking that turns integration into a support ticket, and an emulator is not the
26
+ * threat this gate exists for - there is no unwitting victim whose documents are being read.
27
+ *
28
+ * What this costs is custom-ROM detection on physical devices, the weakest signal in the set. Every
29
+ * strong indicator is kept: a genuinely rooted handset trips su, Magisk or a root-manager app
30
+ * essentially always.
16
31
  */
17
- function getJailMonkey() {
32
+ exports.IGNORED_ROOT_SIGNALS = ["detectTestKeys", "checkForDangerousProps"];
33
+ /**
34
+ * Loads jail-monkey, or null.
35
+ *
36
+ * Literal `require` directly inside the `try`, resolved once at module init. Both are what Metro
37
+ * requires of an optional dependency, and the second is the one that is easy to get wrong: a lazy
38
+ * first `require` runs outside Metro's `inGuard`, where a failed load is reported through
39
+ * `ErrorUtils.reportFatalError` and raises a full-screen LogBox in dev builds even though the
40
+ * `catch` here degrades correctly. See the full note in capabilities/file-save.ts.
41
+ *
42
+ * Also catches the installed-but-not-rebuilt case: jail-monkey reads
43
+ * `NativeModules.JailMonkey.rootedDetectionMethods` at module scope, so an unlinked native module
44
+ * throws on import rather than degrading.
45
+ */
46
+ const jailMonkeyModule = (() => {
47
+ var _a;
18
48
  try {
19
- const JailMonkey = require("jail-monkey");
20
- if (JailMonkey && typeof JailMonkey.isJailBroken === "function") {
21
- return JailMonkey;
22
- }
23
- return null;
49
+ const loaded = require("jail-monkey");
50
+ const module = ((_a = loaded === null || loaded === void 0 ? void 0 : loaded.default) !== null && _a !== void 0 ? _a : loaded);
51
+ return module && typeof module.isJailBroken === "function" ? module : null;
24
52
  }
25
- catch (_a) {
26
- // jail-monkey is not installed
53
+ catch (_b) {
27
54
  return null;
28
55
  }
56
+ })();
57
+ function loadJailMonkey() {
58
+ return jailMonkeyModule;
29
59
  }
30
60
  /**
31
- * Checks if the device is rooted or jailbroken
32
- * Returns null if jail-monkey is not available (compliance check skipped)
33
- * Returns true if device is compromised, false if device is safe
61
+ * Whether this device is compromised.
34
62
  *
35
- * @param devMode - Whether to log debug information
36
- * @returns true if device is compromised, false if safe, null if check unavailable
63
+ * Prefers the per-check breakdown over `isJailBroken()`, because the aggregate is a hardcoded OR
64
+ * that includes the two emulator-tripping checks. Falls back to the aggregate whenever the
65
+ * breakdown is absent - iOS never reports one, and a future jail-monkey that stops exporting it
66
+ * degrades to today's behaviour rather than to "clean".
37
67
  */
38
- function checkDeviceCompliance(devMode = false) {
39
- const JailMonkey = getJailMonkey();
40
- if (!JailMonkey) {
68
+ function resolveDeviceCompliance(devMode = false) {
69
+ const jailMonkey = loadJailMonkey();
70
+ if (!jailMonkey) {
41
71
  if (devMode) {
42
- console.log("[Glomo-RN-SDK] jail-monkey not available. Device compliance check skipped. " +
43
- "Install jail-monkey for enhanced security compliance.");
72
+ console.log(`[Glomo-RN-SDK] ${exports.COMPLIANCE_PACKAGE} not available. Device compliance could not be determined.`);
44
73
  }
45
- // Check unavailable - allowing checkout to proceed
46
- return null;
74
+ return { compromised: null, source: "unavailable", trippedSignals: [] };
47
75
  }
48
76
  try {
49
- const isCompromised = JailMonkey.isJailBroken();
77
+ const breakdown = jailMonkey.androidRootedDetectionMethods;
78
+ // Read as an open map: the checks are iterated, and a jail-monkey that adds one should have
79
+ // it counted rather than ignored for being absent from a hardcoded list
80
+ const rootBeer = breakdown === null || breakdown === void 0 ? void 0 : breakdown.rootBeer;
81
+ if (rootBeer && typeof rootBeer === "object") {
82
+ const trippedSignals = Object.keys(rootBeer).filter((signal) => rootBeer[signal] === true && !exports.IGNORED_ROOT_SIGNALS.includes(signal));
83
+ // jail-monkey's own non-rootBeer signal sits beside the map rather than inside it
84
+ if ((breakdown === null || breakdown === void 0 ? void 0 : breakdown.jailMonkey) === true) {
85
+ trippedSignals.push("jailMonkey");
86
+ }
87
+ if (devMode) {
88
+ console.log(`[Glomo-RN-SDK] Device compliance (decomposed): ${trippedSignals.length > 0 ? `FAILED via ${trippedSignals.join(", ")}` : "PASSED"}`);
89
+ }
90
+ return { compromised: trippedSignals.length > 0, source: "decomposed", trippedSignals };
91
+ }
92
+ const compromised = jailMonkey.isJailBroken();
50
93
  if (devMode) {
51
- console.log(`[Glomo-RN-SDK] Device compliance check: ${isCompromised ? "FAILED (device compromised)" : "PASSED"}`);
94
+ console.log(`[Glomo-RN-SDK] Device compliance (aggregate): ${compromised ? "FAILED" : "PASSED"}`);
52
95
  }
53
- return isCompromised;
96
+ return {
97
+ compromised,
98
+ source: "aggregate",
99
+ trippedSignals: compromised ? ["isJailBroken"] : [],
100
+ };
54
101
  }
55
102
  catch (error) {
56
103
  if (devMode) {
57
- const errorMessage = error instanceof Error ? error.message : String(error);
58
- console.error("[Glomo-RN-SDK] Error checking device compliance:", errorMessage);
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ console.error("[Glomo-RN-SDK] Error checking device compliance:", message);
59
106
  }
60
- // On error, allowing checkout to proceed (fail open strategy)
61
- return null;
107
+ // Unknown, not clean. The caller decides what to do with that, and on a live key it blocks.
108
+ return { compromised: null, source: "unavailable", trippedSignals: [] };
109
+ }
110
+ }
111
+ /**
112
+ * Whether a checkout must be refused on this device.
113
+ *
114
+ * **Fails closed on a live key**: unknown blocks, because on RN unknown overwhelmingly means the
115
+ * merchant did not install `jail-monkey` rather than that a platform probe failed. That is why the
116
+ * package stopped being an optional peer dependency in v5 - a dependency cannot be optional and
117
+ * load-bearing at once, and an install-time warning beats a silent runtime outage.
118
+ *
119
+ * **Never gates mock or test traffic.** Measured, never blocked - matching the Flutter SDK's
120
+ * `enforced` model, and so QA on a rooted handset or an emulator image stays possible.
121
+ */
122
+ function blocksCheckout(result, publicKey) {
123
+ if ((0, validation_1.isMockModeKey)(publicKey)) {
124
+ return false;
62
125
  }
126
+ return result.compromised !== false;
63
127
  }
64
128
  /**
65
- * Checks if compliance checking is possible in the current environment
129
+ * Whether the device compliance check could run at all
66
130
  *
67
- * @returns true if peerDependencies for compliance checking are installed and available
131
+ * @returns true if jail-monkey is installed and its native module is linked
68
132
  */
69
133
  function isComplianceCheckAvailable() {
70
- return getJailMonkey() !== null;
134
+ return loadJailMonkey() !== null;
71
135
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Runtime allowlists for what a callback payload may contribute to telemetry
3
+ *
4
+ * **Every tracker that reports a payload takes one of these, never the payload itself.**
5
+ *
6
+ * The rule exists because TypeScript types are erased. A `Pick<>`, a cast or a projection
7
+ * *interface* removes nothing from the object at runtime - the value still carries every field, and
8
+ * `client.post("/track", payload)` serialises all of them. So "pass the payload, constrain it with a
9
+ * type" compiles cleanly, reviews cleanly, and ships `signature` to Segment on every payment. A
10
+ * signature is a bearer credential.
11
+ *
12
+ * Constructing the outbound object field by field is what actually enforces the allowlist, and
13
+ * keeping it here rather than at each call site is what keeps the callback and the event assembled
14
+ * from one source, so a field added to a payload cannot reach an event unless someone adds it here
15
+ * deliberately.
16
+ */
17
+ import { type GlomoCheckoutPayload, type GlomoTerminationPayload } from "../types/checkout";
18
+ /** What a payment payload may say about itself */
19
+ export interface PaymentEventData {
20
+ orderId: string;
21
+ paymentId?: string;
22
+ }
23
+ /**
24
+ * Projection of a success or failure payload.
25
+ *
26
+ * `signature` is excluded and must stay excluded - it is the field this whole module exists for.
27
+ */
28
+ export declare function toPaymentEventData(payload: GlomoCheckoutPayload): PaymentEventData;
29
+ /** What a termination payload may say about itself */
30
+ export interface TerminationEventData {
31
+ source: string;
32
+ }
33
+ /** Projection of a termination payload. Everything it carries today is safe; that is not a licence. */
34
+ export declare function toTerminationEventData(payload: GlomoTerminationPayload): TerminationEventData;
35
+ /** What an asynchronous journey payload may say about itself */
36
+ export interface JourneyEventData {
37
+ journeyType: string;
38
+ journeyStatus?: string;
39
+ /**
40
+ * Whether the page sent bank details, not what they were.
41
+ *
42
+ * `senderAccountNumber` is a customer bank account number and `transactionReference` identifies
43
+ * a specific transfer; neither belongs in analytics. Presence is what triage actually needs - it
44
+ * separates "the page reported a transfer without details" from "the SDK dropped them".
45
+ */
46
+ hasSenderAccountNumber: boolean;
47
+ hasTransactionReference: boolean;
48
+ }
49
+ /** Projection of an asynchronous journey payload */
50
+ export declare function toJourneyEventData(payload: {
51
+ journeyType: string;
52
+ status?: string;
53
+ senderAccountNumber?: string;
54
+ transactionReference?: string;
55
+ }): JourneyEventData;
56
+ //# sourceMappingURL=event-projections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-projections.d.ts","sourceRoot":"","sources":["../../src/utils/event-projections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5F,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CAElF;AAED,sDAAsD;AACtD,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,uGAAuG;AACvG,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,oBAAoB,CAE7F;AAED,gEAAgE;AAChE,MAAM,WAAW,gBAAgB;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;OAMG;IACH,sBAAsB,EAAE,OAAO,CAAC;IAChC,uBAAuB,EAAE,OAAO,CAAC;CACpC;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC,GAAG,gBAAgB,CAOnB"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /**
3
+ * Runtime allowlists for what a callback payload may contribute to telemetry
4
+ *
5
+ * **Every tracker that reports a payload takes one of these, never the payload itself.**
6
+ *
7
+ * The rule exists because TypeScript types are erased. A `Pick<>`, a cast or a projection
8
+ * *interface* removes nothing from the object at runtime - the value still carries every field, and
9
+ * `client.post("/track", payload)` serialises all of them. So "pass the payload, constrain it with a
10
+ * type" compiles cleanly, reviews cleanly, and ships `signature` to Segment on every payment. A
11
+ * signature is a bearer credential.
12
+ *
13
+ * Constructing the outbound object field by field is what actually enforces the allowlist, and
14
+ * keeping it here rather than at each call site is what keeps the callback and the event assembled
15
+ * from one source, so a field added to a payload cannot reach an event unless someone adds it here
16
+ * deliberately.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.toPaymentEventData = toPaymentEventData;
20
+ exports.toTerminationEventData = toTerminationEventData;
21
+ exports.toJourneyEventData = toJourneyEventData;
22
+ /**
23
+ * Projection of a success or failure payload.
24
+ *
25
+ * `signature` is excluded and must stay excluded - it is the field this whole module exists for.
26
+ */
27
+ function toPaymentEventData(payload) {
28
+ return { orderId: payload.orderId, paymentId: payload.paymentId };
29
+ }
30
+ /** Projection of a termination payload. Everything it carries today is safe; that is not a licence. */
31
+ function toTerminationEventData(payload) {
32
+ return { source: payload.source };
33
+ }
34
+ /** Projection of an asynchronous journey payload */
35
+ function toJourneyEventData(payload) {
36
+ return {
37
+ journeyType: payload.journeyType,
38
+ journeyStatus: payload.status,
39
+ hasSenderAccountNumber: Boolean(payload.senderAccountNumber),
40
+ hasTransactionReference: Boolean(payload.transactionReference),
41
+ };
42
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Shapes what a saved or picked file may say about itself in telemetry
3
+ *
4
+ * Extension and byte count only - never the file name, which is the user's data
5
+ * (`passport_A1234567.pdf`) where these two are not.
6
+ *
7
+ * Values are sentinels rather than nulls so an event always carries the same shape and a missing
8
+ * value is something to filter on. Ported from the Flutter SDK's `FileMetadata` so the two report
9
+ * the same vocabulary on the same events.
10
+ */
11
+ /**
12
+ * Length could not be read. Negative because `0` is legitimate: a zero-byte file is a real failure
13
+ * that would otherwise look like an unexplained bank rejection.
14
+ */
15
+ export declare const SIZE_UNAVAILABLE = -1;
16
+ /** The file had no extension. User behaviour, not an error. */
17
+ export declare const EXTENSION_NONE = "FILE_WITH_NO_EXTENSION";
18
+ /**
19
+ * The tail was rejected: too long, or not purely alphanumeric. In the field this is ordinary user
20
+ * naming (`scan.jpeg (1)`), not an SDK fault. Do not alert on it.
21
+ */
22
+ export declare const EXTENSION_EXTRACTION_FAILED = "EXTRACTION_FAILED";
23
+ /**
24
+ * A well-formed extension that is not one we know. Reports that something novel was handled without
25
+ * carrying the value, so a new format is still visible as an event.
26
+ */
27
+ export declare const EXTENSION_UNKNOWN = "UNKNOWN_EXTENSION";
28
+ /**
29
+ * Lowercased extension of a path, or a sentinel.
30
+ *
31
+ * Not `split(".").pop()`, which returns the whole filename when there is no dot - shipping exactly
32
+ * what this exists to keep out.
33
+ */
34
+ export declare function extensionOf(path?: string | null): string;
35
+ /**
36
+ * The MIME type to declare for a file, from the extension extensionOf resolved.
37
+ *
38
+ * Falls back to the generic type for every sentinel: an unrecognised extension is exactly the case
39
+ * where a guess would be wrong, and Android will still let the user save with a generic type.
40
+ */
41
+ export declare function mimeTypeFor(fileExtension: string): string;
42
+ /**
43
+ * Normalises a raw HTML `accept` attribute for telemetry.
44
+ *
45
+ * The injected JS reads the attribute verbatim (`".pdf, .jpeg"`) where a native layer may hand over
46
+ * an already-parsed list (`".pdf,.jpeg"`), so without this the request event and anything reported
47
+ * later for the same click will not join on it.
48
+ *
49
+ * Never used to decide what may be picked - the bank owns that.
50
+ */
51
+ export declare function normalizeAcceptTypes(acceptTypes?: string | null): string | undefined;
52
+ //# sourceMappingURL=file-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/file-metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;GAGG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AA2CrD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CA4BxD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAWpF"}
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * Shapes what a saved or picked file may say about itself in telemetry
4
+ *
5
+ * Extension and byte count only - never the file name, which is the user's data
6
+ * (`passport_A1234567.pdf`) where these two are not.
7
+ *
8
+ * Values are sentinels rather than nulls so an event always carries the same shape and a missing
9
+ * value is something to filter on. Ported from the Flutter SDK's `FileMetadata` so the two report
10
+ * the same vocabulary on the same events.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.EXTENSION_UNKNOWN = exports.EXTENSION_EXTRACTION_FAILED = exports.EXTENSION_NONE = exports.SIZE_UNAVAILABLE = void 0;
14
+ exports.extensionOf = extensionOf;
15
+ exports.mimeTypeFor = mimeTypeFor;
16
+ exports.normalizeAcceptTypes = normalizeAcceptTypes;
17
+ /**
18
+ * Length could not be read. Negative because `0` is legitimate: a zero-byte file is a real failure
19
+ * that would otherwise look like an unexplained bank rejection.
20
+ */
21
+ exports.SIZE_UNAVAILABLE = -1;
22
+ /** The file had no extension. User behaviour, not an error. */
23
+ exports.EXTENSION_NONE = "FILE_WITH_NO_EXTENSION";
24
+ /**
25
+ * The tail was rejected: too long, or not purely alphanumeric. In the field this is ordinary user
26
+ * naming (`scan.jpeg (1)`), not an SDK fault. Do not alert on it.
27
+ */
28
+ exports.EXTENSION_EXTRACTION_FAILED = "EXTRACTION_FAILED";
29
+ /**
30
+ * A well-formed extension that is not one we know. Reports that something novel was handled without
31
+ * carrying the value, so a new format is still visible as an event.
32
+ */
33
+ exports.EXTENSION_UNKNOWN = "UNKNOWN_EXTENSION";
34
+ /** First of two gates. Rejects a long tail early; KNOWN_EXTENSIONS is what actually bounds this. */
35
+ const MAX_EXTENSION_LENGTH = 5;
36
+ /**
37
+ * Extensions reported verbatim, and the MIME type each one maps to.
38
+ *
39
+ * Anything outside this map becomes a sentinel, so no part of a filename can reach telemetry by
40
+ * being short enough to look plausible. The MIME side exists because Android's save dialog is an
41
+ * `ACTION_CREATE_DOCUMENT` intent that wants a type: deriving it here rather than forwarding the
42
+ * `mimeType` the page sends keeps untrusted page input out of an Intent, and is the same call the
43
+ * Flutter SDK makes when it declines to forward that field to `file_picker`.
44
+ */
45
+ const KNOWN_EXTENSIONS = {
46
+ pdf: "application/pdf",
47
+ epdf: "application/pdf",
48
+ jpg: "image/jpeg",
49
+ jpeg: "image/jpeg",
50
+ png: "image/png",
51
+ gif: "image/gif",
52
+ webp: "image/webp",
53
+ bmp: "image/bmp",
54
+ tiff: "image/tiff",
55
+ tif: "image/tiff",
56
+ heic: "image/heic",
57
+ heif: "image/heif",
58
+ jfif: "image/jpeg",
59
+ pjpeg: "image/jpeg",
60
+ doc: "application/msword",
61
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
62
+ xls: "application/vnd.ms-excel",
63
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
64
+ csv: "text/csv",
65
+ txt: "text/plain",
66
+ rtf: "application/rtf",
67
+ odt: "application/vnd.oasis.opendocument.text",
68
+ zip: "application/zip",
69
+ };
70
+ /** Hoisted - extensionOf runs on every save. */
71
+ const ALPHANUMERIC = /^[a-z0-9]+$/;
72
+ /**
73
+ * Lowercased extension of a path, or a sentinel.
74
+ *
75
+ * Not `split(".").pop()`, which returns the whole filename when there is no dot - shipping exactly
76
+ * what this exists to keep out.
77
+ */
78
+ function extensionOf(path) {
79
+ var _a;
80
+ if (!path) {
81
+ return exports.EXTENSION_EXTRACTION_FAILED;
82
+ }
83
+ // A staging directory can carry dots, so the basename is isolated before looking for one
84
+ const name = (_a = path.split("/").pop()) !== null && _a !== void 0 ? _a : "";
85
+ if (name.length === 0) {
86
+ return exports.EXTENSION_EXTRACTION_FAILED;
87
+ }
88
+ const dot = name.lastIndexOf(".");
89
+ // `dot === 0` is a dotfile (`.bank_statement`), not an extension
90
+ if (dot <= 0 || dot === name.length - 1) {
91
+ return exports.EXTENSION_NONE;
92
+ }
93
+ /**
94
+ * Rejected, not stripped. Stripping separators first let a 15-character filename tail collapse
95
+ * into a plausible 8-character extension (`aadhaar.9876-5432` -> `98765432`), which is filename
96
+ * content on a KYC upload. A real extension has no separators to remove.
97
+ */
98
+ const raw = name.slice(dot + 1).toLowerCase();
99
+ if (raw.length === 0 || raw.length > MAX_EXTENSION_LENGTH || !ALPHANUMERIC.test(raw)) {
100
+ return exports.EXTENSION_EXTRACTION_FAILED;
101
+ }
102
+ return raw in KNOWN_EXTENSIONS ? raw : exports.EXTENSION_UNKNOWN;
103
+ }
104
+ /**
105
+ * The MIME type to declare for a file, from the extension extensionOf resolved.
106
+ *
107
+ * Falls back to the generic type for every sentinel: an unrecognised extension is exactly the case
108
+ * where a guess would be wrong, and Android will still let the user save with a generic type.
109
+ */
110
+ function mimeTypeFor(fileExtension) {
111
+ var _a;
112
+ return (_a = KNOWN_EXTENSIONS[fileExtension]) !== null && _a !== void 0 ? _a : "application/octet-stream";
113
+ }
114
+ /**
115
+ * Normalises a raw HTML `accept` attribute for telemetry.
116
+ *
117
+ * The injected JS reads the attribute verbatim (`".pdf, .jpeg"`) where a native layer may hand over
118
+ * an already-parsed list (`".pdf,.jpeg"`), so without this the request event and anything reported
119
+ * later for the same click will not join on it.
120
+ *
121
+ * Never used to decide what may be picked - the bank owns that.
122
+ */
123
+ function normalizeAcceptTypes(acceptTypes) {
124
+ if (!acceptTypes || acceptTypes.trim().length === 0) {
125
+ return undefined;
126
+ }
127
+ const parts = acceptTypes
128
+ .toLowerCase()
129
+ .split(",")
130
+ .map((part) => part.trim())
131
+ .filter((part) => part.length > 0)
132
+ .sort();
133
+ return parts.length === 0 ? undefined : parts.join(",");
134
+ }
@@ -0,0 +1,12 @@
1
+ /** Binds one checkout attempt's telemetry context for as long as the component is mounted */
2
+ /**
3
+ * Mints a session id and binds a checkout-status getter, both for the mounted lifetime.
4
+ *
5
+ * Deliberately a hook rather than something `start()` calls, so a merchant who retries produces two
6
+ * `start_attempted` events inside one session instead of two unrelated sessions.
7
+ *
8
+ * The status getter is held in a ref and re-read on every event, so a status that changes mid-session
9
+ * reaches later events without anything re-binding.
10
+ */
11
+ export declare function useSdkSession(readStatus: () => string): void;
12
+ //# sourceMappingURL=use-sdk-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sdk-session.d.ts","sourceRoot":"","sources":["../../src/utils/use-sdk-session.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAS7F;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,MAAM,GAAG,IAAI,CAsB5D"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /** Binds one checkout attempt's telemetry context for as long as the component is mounted */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.useSdkSession = useSdkSession;
5
+ const react_1 = require("react");
6
+ const analytics_1 = require("./analytics");
7
+ /** Distinguishes two mounts in the same process without reaching for a uuid dependency */
8
+ let mountCounter = 0;
9
+ /**
10
+ * Mints a session id and binds a checkout-status getter, both for the mounted lifetime.
11
+ *
12
+ * Deliberately a hook rather than something `start()` calls, so a merchant who retries produces two
13
+ * `start_attempted` events inside one session instead of two unrelated sessions.
14
+ *
15
+ * The status getter is held in a ref and re-read on every event, so a status that changes mid-session
16
+ * reaches later events without anything re-binding.
17
+ */
18
+ function useSdkSession(readStatus) {
19
+ const readStatusRef = (0, react_1.useRef)(readStatus);
20
+ readStatusRef.current = readStatus;
21
+ (0, react_1.useEffect)(() => {
22
+ const sessionId = `sess-${Date.now().toString(36)}-${(++mountCounter).toString(36)}-${Math.random()
23
+ .toString(36)
24
+ .slice(2, 8)}`;
25
+ (0, analytics_1.setSdkSession)(sessionId);
26
+ (0, analytics_1.bindCheckoutStatus)(() => readStatusRef.current());
27
+ return () => {
28
+ /**
29
+ * Cleared rather than left dangling, so an event from an unmounted checkout carries
30
+ * neither - but only if this mount's session is still the active one. A merchant with
31
+ * two checkouts alive would otherwise have the one unmounting silence the one still
32
+ * taking a payment.
33
+ */
34
+ (0, analytics_1.releaseSdkSession)(sessionId);
35
+ };
36
+ }, []);
37
+ }