@sparkvault/sdk-mobile 6.0.2 → 7.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 (148) hide show
  1. package/README.md +5 -5
  2. package/dist/account.d.ts +4 -3
  3. package/dist/account.js +16 -11
  4. package/dist/account.js.map +1 -1
  5. package/dist/app-sdk/email-address.d.ts +37 -0
  6. package/dist/app-sdk/email-address.js +91 -0
  7. package/dist/app-sdk/email-address.js.map +1 -0
  8. package/dist/app-sdk/encoding.d.ts +64 -0
  9. package/dist/app-sdk/encoding.js +245 -0
  10. package/dist/app-sdk/encoding.js.map +1 -0
  11. package/dist/app-sdk/entropy-contract.d.ts +33 -0
  12. package/dist/app-sdk/entropy-contract.js +51 -0
  13. package/dist/app-sdk/entropy-contract.js.map +1 -0
  14. package/dist/app-sdk/filename.d.ts +93 -0
  15. package/dist/app-sdk/filename.js +161 -0
  16. package/dist/app-sdk/filename.js.map +1 -0
  17. package/dist/app-sdk/forge-url.d.ts +107 -0
  18. package/dist/app-sdk/forge-url.js +222 -0
  19. package/dist/app-sdk/forge-url.js.map +1 -0
  20. package/dist/app-sdk/ids.d.ts +48 -0
  21. package/dist/app-sdk/ids.js +65 -0
  22. package/dist/app-sdk/ids.js.map +1 -0
  23. package/dist/app-sdk/ingot-access-log.d.ts +29 -0
  24. package/dist/app-sdk/ingot-access-log.js +49 -0
  25. package/dist/app-sdk/ingot-access-log.js.map +1 -0
  26. package/dist/app-sdk/phone.d.ts +37 -0
  27. package/dist/app-sdk/phone.js +134 -0
  28. package/dist/app-sdk/phone.js.map +1 -0
  29. package/dist/app-sdk/platform-domains.d.ts +41 -0
  30. package/dist/app-sdk/platform-domains.js +82 -0
  31. package/dist/app-sdk/platform-domains.js.map +1 -0
  32. package/dist/app-sdk/request-path.d.ts +49 -0
  33. package/dist/app-sdk/request-path.js +81 -0
  34. package/dist/app-sdk/request-path.js.map +1 -0
  35. package/dist/app-sdk/transient.d.ts +23 -0
  36. package/dist/app-sdk/transient.js +29 -0
  37. package/dist/app-sdk/transient.js.map +1 -0
  38. package/dist/app-sdk/vat-refusal.d.ts +66 -0
  39. package/dist/app-sdk/vat-refusal.js +69 -0
  40. package/dist/app-sdk/vat-refusal.js.map +1 -0
  41. package/dist/billing.d.ts +9 -17
  42. package/dist/billing.js +14 -17
  43. package/dist/billing.js.map +1 -1
  44. package/dist/config.d.ts +7 -1
  45. package/dist/config.js +10 -17
  46. package/dist/config.js.map +1 -1
  47. package/dist/encoding.d.ts +30 -4
  48. package/dist/encoding.js +89 -83
  49. package/dist/encoding.js.map +1 -1
  50. package/dist/entropy.d.ts +25 -4
  51. package/dist/entropy.js +62 -15
  52. package/dist/entropy.js.map +1 -1
  53. package/dist/entropy.test-d.d.ts +40 -0
  54. package/dist/entropy.test-d.js +12 -0
  55. package/dist/entropy.test-d.js.map +1 -0
  56. package/dist/errors.d.ts +67 -17
  57. package/dist/errors.js +70 -27
  58. package/dist/errors.js.map +1 -1
  59. package/dist/folders.js +8 -7
  60. package/dist/folders.js.map +1 -1
  61. package/dist/http.d.ts +57 -15
  62. package/dist/http.js +125 -138
  63. package/dist/http.js.map +1 -1
  64. package/dist/identity-dialog.js +4 -30
  65. package/dist/identity-dialog.js.map +1 -1
  66. package/dist/identity-input.d.ts +39 -0
  67. package/dist/identity-input.js +59 -0
  68. package/dist/identity-input.js.map +1 -0
  69. package/dist/index.d.ts +6 -5
  70. package/dist/index.js +4 -3
  71. package/dist/index.js.map +1 -1
  72. package/dist/ingots.d.ts +51 -18
  73. package/dist/ingots.js +132 -122
  74. package/dist/ingots.js.map +1 -1
  75. package/dist/mutex.d.ts +1 -2
  76. package/dist/mutex.js +0 -6
  77. package/dist/mutex.js.map +1 -1
  78. package/dist/notify.d.ts +7 -2
  79. package/dist/notify.js +12 -15
  80. package/dist/notify.js.map +1 -1
  81. package/dist/notify.test-d.d.ts +3 -2
  82. package/dist/notify.test-d.js +1 -1
  83. package/dist/products/identity.d.ts +5 -0
  84. package/dist/products/identity.js +40 -35
  85. package/dist/products/identity.js.map +1 -1
  86. package/dist/request-path.d.ts +32 -0
  87. package/dist/request-path.js +35 -0
  88. package/dist/request-path.js.map +1 -0
  89. package/dist/sparks.js +16 -15
  90. package/dist/sparks.js.map +1 -1
  91. package/dist/tus.d.ts +19 -2
  92. package/dist/tus.js +86 -35
  93. package/dist/tus.js.map +1 -1
  94. package/dist/types.d.ts +167 -15
  95. package/dist/users.d.ts +12 -5
  96. package/dist/users.js +27 -19
  97. package/dist/users.js.map +1 -1
  98. package/dist/validation.d.ts +20 -5
  99. package/dist/validation.js +29 -27
  100. package/dist/validation.js.map +1 -1
  101. package/dist/vaults.d.ts +34 -83
  102. package/dist/vaults.js +64 -44
  103. package/dist/vaults.js.map +1 -1
  104. package/dist/vaults.test-d.d.ts +22 -0
  105. package/dist/vaults.test-d.js +14 -0
  106. package/dist/vaults.test-d.js.map +1 -0
  107. package/package.json +4 -2
  108. package/src/account.ts +16 -11
  109. package/src/app-sdk/email-address.js +94 -0
  110. package/src/app-sdk/encoding.js +250 -0
  111. package/src/app-sdk/entropy-contract.js +56 -0
  112. package/src/app-sdk/filename.js +174 -0
  113. package/src/app-sdk/forge-url.js +224 -0
  114. package/src/app-sdk/ids.js +71 -0
  115. package/src/app-sdk/ingot-access-log.js +51 -0
  116. package/src/app-sdk/phone.js +139 -0
  117. package/src/app-sdk/platform-domains.js +99 -0
  118. package/src/app-sdk/request-path.js +81 -0
  119. package/src/app-sdk/transient.js +30 -0
  120. package/src/app-sdk/vat-refusal.js +74 -0
  121. package/src/billing.ts +16 -25
  122. package/src/config.ts +16 -23
  123. package/src/encoding.ts +91 -103
  124. package/src/entropy.test-d.ts +47 -0
  125. package/src/entropy.ts +86 -20
  126. package/src/errors.ts +93 -33
  127. package/src/folders.ts +8 -7
  128. package/src/http.ts +145 -150
  129. package/src/identity-dialog.tsx +4 -37
  130. package/src/identity-input.ts +70 -0
  131. package/src/index.ts +11 -3
  132. package/src/ingots.ts +173 -159
  133. package/src/mutex.ts +1 -8
  134. package/src/notify.test-d.ts +3 -1
  135. package/src/notify.ts +19 -17
  136. package/src/products/identity.ts +45 -39
  137. package/src/request-path.ts +45 -0
  138. package/src/sparks.ts +16 -13
  139. package/src/tus.ts +90 -37
  140. package/src/types.ts +165 -15
  141. package/src/users.ts +27 -17
  142. package/src/validation.ts +30 -24
  143. package/src/vaults.test-d.ts +29 -0
  144. package/src/vaults.ts +87 -153
  145. package/dist/platform-domains.generated.d.ts +0 -2
  146. package/dist/platform-domains.generated.js +0 -16
  147. package/dist/platform-domains.generated.js.map +0 -1
  148. package/src/platform-domains.generated.ts +0 -17
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Whether a value is an id of the given kind.
3
+ *
4
+ * @param {string} kind - A key of RESOURCE_ID_PREFIXES
5
+ * @param {unknown} value
6
+ * @returns {boolean}
7
+ * @throws {TypeError} for a kind the grammar does not name: a misspelled kind
8
+ * would otherwise refuse every id without saying why
9
+ */
10
+ export function isResourceIdOf(kind: string, value: unknown): boolean;
11
+ /**
12
+ * Whether a value is an id of any kind.
13
+ *
14
+ * @param {unknown} value
15
+ * @returns {boolean}
16
+ */
17
+ export function isResourceId(value: unknown): boolean;
18
+ /**
19
+ * SparkVault resource ids: the one grammar every surface reads an id by before
20
+ * the id names a request path or a stored file.
21
+ *
22
+ * Core mints an id as its kind's prefix, an underscore and a body: the hex of a
23
+ * UUIDv7 (apps/api lib/utils.js buildPrefixedIdFromBytes), or a hex digest for
24
+ * an invitation and an inbox notification. The grammar admits the kind's
25
+ * prefix, '_' and a body of the path-safe alphabet [A-Za-z0-9_-], 128
26
+ * characters in all, so an id that passes is exactly one request-path segment
27
+ * and one file-name component: it can never name a parent path, carry a
28
+ * separator or a dot-segment, or read as an id of another kind.
29
+ *
30
+ * - `isResourceIdOf(kind, value)`: an id of that kind.
31
+ * - `isResourceId(value)`: an id of any kind, for a check that takes several.
32
+ *
33
+ * Browser-safe and import-free: sdk-js bundles it, sdk-mobile mirrors it, and
34
+ * the apps, the console and the mobile app import it.
35
+ */
36
+ /** The prefix Core mints each resource kind's id with. */
37
+ export const RESOURCE_ID_PREFIXES: Readonly<{
38
+ account: "acc";
39
+ folder: "fld";
40
+ ingot: "ing";
41
+ invite: "inv";
42
+ notification: "ntf";
43
+ spark: "spk";
44
+ user: "usr";
45
+ vault: "vlt";
46
+ }>;
47
+ /** The longest id, prefix included. */
48
+ export const RESOURCE_ID_MAX_LENGTH: 128;
@@ -0,0 +1,65 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/ids.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * SparkVault resource ids: the one grammar every surface reads an id by before
6
+ * the id names a request path or a stored file.
7
+ *
8
+ * Core mints an id as its kind's prefix, an underscore and a body: the hex of a
9
+ * UUIDv7 (apps/api lib/utils.js buildPrefixedIdFromBytes), or a hex digest for
10
+ * an invitation and an inbox notification. The grammar admits the kind's
11
+ * prefix, '_' and a body of the path-safe alphabet [A-Za-z0-9_-], 128
12
+ * characters in all, so an id that passes is exactly one request-path segment
13
+ * and one file-name component: it can never name a parent path, carry a
14
+ * separator or a dot-segment, or read as an id of another kind.
15
+ *
16
+ * - `isResourceIdOf(kind, value)`: an id of that kind.
17
+ * - `isResourceId(value)`: an id of any kind, for a check that takes several.
18
+ *
19
+ * Browser-safe and import-free: sdk-js bundles it, sdk-mobile mirrors it, and
20
+ * the apps, the console and the mobile app import it.
21
+ */
22
+ /** The prefix Core mints each resource kind's id with. */
23
+ export const RESOURCE_ID_PREFIXES = Object.freeze({
24
+ account: 'acc',
25
+ folder: 'fld',
26
+ ingot: 'ing',
27
+ invite: 'inv',
28
+ notification: 'ntf',
29
+ spark: 'spk',
30
+ user: 'usr',
31
+ vault: 'vlt'
32
+ });
33
+ /** The longest id, prefix included. */
34
+ export const RESOURCE_ID_MAX_LENGTH = 128;
35
+ const ID_BODY = /^[A-Za-z0-9_-]+$/;
36
+ /**
37
+ * Whether a value is an id of the given kind.
38
+ *
39
+ * @param {string} kind - A key of RESOURCE_ID_PREFIXES
40
+ * @param {unknown} value
41
+ * @returns {boolean}
42
+ * @throws {TypeError} for a kind the grammar does not name: a misspelled kind
43
+ * would otherwise refuse every id without saying why
44
+ */
45
+ export function isResourceIdOf(kind, value) {
46
+ if (!Object.hasOwn(RESOURCE_ID_PREFIXES, kind)) {
47
+ throw new TypeError(`No resource kind '${kind}'`);
48
+ }
49
+ const prefix = `${RESOURCE_ID_PREFIXES[kind]}_`;
50
+ return typeof value === 'string'
51
+ && value.length <= RESOURCE_ID_MAX_LENGTH
52
+ && value.startsWith(prefix)
53
+ && ID_BODY.test(value.slice(prefix.length));
54
+ }
55
+ const KINDS = Object.freeze(Object.keys(RESOURCE_ID_PREFIXES));
56
+ /**
57
+ * Whether a value is an id of any kind.
58
+ *
59
+ * @param {unknown} value
60
+ * @returns {boolean}
61
+ */
62
+ export function isResourceId(value) {
63
+ return KINDS.some((kind) => isResourceIdOf(kind, value));
64
+ }
65
+ //# sourceMappingURL=ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.js","sourceRoot":"","sources":["../../src/app-sdk/ids.js"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,+CAA+C;AAC/C,8DAA8D;AAE9D;;;;;;;;;;;;;;;;;GAiBG;AAEH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,KAAK;IACb,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,YAAY,EAAE,KAAK;IACnB,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,KAAK;IACX,KAAK,EAAE,KAAK;CACb,CAAC,CAAC;AAEH,uCAAuC;AACvC,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C,MAAM,OAAO,GAAG,kBAAkB,CAAC;AAEnC;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAI,EAAE,KAAK;IACxC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,SAAS,CAAC,qBAAqB,IAAI,GAAG,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ;WAC3B,KAAK,CAAC,MAAM,IAAI,sBAAsB;WACtC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;WACxB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAK;IAChC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * One audit entry as an access. The actor reads as the server resolved it (a
3
+ * member's email, an API key's name), else as the identity a SparkLink visitor
4
+ * gave (kept in the entry's metadata), else as the raw actor id.
5
+ *
6
+ * @param {{ timestamp: number, event_type: string, actor_id?: string | null, actor_display?: string,
7
+ * ip_address?: string | null, user_agent?: string | null, metadata?: { identity?: string | null } | null }} entry
8
+ * @returns {{ timestamp: number, action: 'download', identity: string, ip_address: string,
9
+ * user_agent: string } | null} null for an event that is not an access
10
+ */
11
+ export function readIngotAccess(entry: {
12
+ timestamp: number;
13
+ event_type: string;
14
+ actor_id?: string | null;
15
+ actor_display?: string;
16
+ ip_address?: string | null;
17
+ user_agent?: string | null;
18
+ metadata?: {
19
+ identity?: string | null;
20
+ } | null;
21
+ }): {
22
+ timestamp: number;
23
+ action: "download";
24
+ identity: string;
25
+ ip_address: string;
26
+ user_agent: string;
27
+ } | null;
28
+ /** The event types an access-log request asks the server for. */
29
+ export const INGOT_ACCESS_EVENT_TYPES: readonly string[];
@@ -0,0 +1,49 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/ingot-access-log.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * Ingot access logs: which of an ingot's audit events are accesses, and how one
6
+ * audit entry reads as an access. Both SDKs read
7
+ * GET /v1/vaults/{vault_id}/ingots/{ingot_id}/audit-logs through it: sdk-js
8
+ * bundles this module, and sdk-mobile (which publishes standalone) carries a
9
+ * build-time copy pinned byte-for-byte by its drift test.
10
+ *
11
+ * An entry arrives in the server's public audit shape (AuditLogRepository
12
+ * toPublicFormat, annotated by the audit-log controller's actor resolution):
13
+ * `timestamp`, `event_type`, `actor_id`, `actor_type`, `actor_display`,
14
+ * `ip_address`, `user_agent`, `metadata` (null while encrypted), `encrypted`,
15
+ * `vault_id` and `ingot_id`.
16
+ */
17
+ /**
18
+ * The ingot audit events that are accesses, and what each one is: Core records
19
+ * an access to a file as the download that delivered it, a SparkLink open of
20
+ * the file included.
21
+ */
22
+ const ACCESS_ACTIONS = new Map([
23
+ ['ingot_downloaded', 'download'],
24
+ ]);
25
+ /** The event types an access-log request asks the server for. */
26
+ export const INGOT_ACCESS_EVENT_TYPES = Object.freeze([...ACCESS_ACTIONS.keys()]);
27
+ /**
28
+ * One audit entry as an access. The actor reads as the server resolved it (a
29
+ * member's email, an API key's name), else as the identity a SparkLink visitor
30
+ * gave (kept in the entry's metadata), else as the raw actor id.
31
+ *
32
+ * @param {{ timestamp: number, event_type: string, actor_id?: string | null, actor_display?: string,
33
+ * ip_address?: string | null, user_agent?: string | null, metadata?: { identity?: string | null } | null }} entry
34
+ * @returns {{ timestamp: number, action: 'download', identity: string, ip_address: string,
35
+ * user_agent: string } | null} null for an event that is not an access
36
+ */
37
+ export function readIngotAccess(entry) {
38
+ const action = ACCESS_ACTIONS.get(entry.event_type);
39
+ if (action === undefined)
40
+ return null;
41
+ return {
42
+ timestamp: entry.timestamp,
43
+ action,
44
+ identity: entry.actor_display || entry.metadata?.identity || entry.actor_id || 'Unknown',
45
+ ip_address: entry.ip_address ?? '',
46
+ user_agent: entry.user_agent ?? '',
47
+ };
48
+ }
49
+ //# sourceMappingURL=ingot-access-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingot-access-log.js","sourceRoot":"","sources":["../../src/app-sdk/ingot-access-log.js"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,4DAA4D;AAC5D,8DAA8D;AAE9D;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,CAAC,kBAAkB,EAAE,UAAU,CAAC;CACjC,CAAC,CAAC;AAEH,iEAAiE;AACjE,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAElF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,KAAK;IACnC,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACpD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM;QACN,QAAQ,EAAE,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,QAAQ,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,IAAI,SAAS;QACxF,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;QAClC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;KACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Read a typed phone number as E.164.
3
+ *
4
+ * @param {unknown} raw
5
+ * @returns {string|null} E.164 with its leading '+', or null when the input
6
+ * names no dialable number
7
+ */
8
+ export function parsePhone(raw: unknown): string | null;
9
+ /**
10
+ * Whether a value is already a canonical E.164 phone number — exactly the
11
+ * values parsePhone returns unchanged.
12
+ *
13
+ * @param {unknown} value
14
+ * @returns {boolean}
15
+ */
16
+ export function isE164Phone(value: unknown): boolean;
17
+ /**
18
+ * Whether an identifier typed where either is accepted is an email address or
19
+ * a phone number.
20
+ *
21
+ * @param {unknown} value
22
+ * @returns {'email' | 'phone'}
23
+ */
24
+ export function inferIdentityType(value: unknown): "email" | "phone";
25
+ /**
26
+ * A phone number as a phone field shows it while it is typed: a NANP number as
27
+ * (XXX) XXX-XXXX; a declared country code, or a digit string that is not a NANP
28
+ * number, keeps every digit as given, so formatting never turns one destination
29
+ * into another. Text holding anything but phone characters (a letter, a
30
+ * dial-control character, a digit of another script) is left as typed, so it
31
+ * reaches parsePhone and is refused there instead of being dropped from the
32
+ * field.
33
+ *
34
+ * @param {string} value
35
+ * @returns {string}
36
+ */
37
+ export function formatPhoneDisplay(value: string): string;
@@ -0,0 +1,134 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/phone.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * Phone numbers a person types: how one reads as E.164, and whether a typed
6
+ * identifier is a phone number at all. Pure, with no imports, so a browser or
7
+ * an SDK bundle can carry it: Core reads and checks every phone it is given
8
+ * with it, the console imports it, sdk-js bundles it, and sdk-mobile carries a
9
+ * build-time copy pinned byte-for-byte by its drift test.
10
+ *
11
+ * A '+' ahead of the digits DECLARES the country code, so every digit is kept
12
+ * exactly as given: ten digits after a '+' are a foreign number (+47 Norway,
13
+ * +45 Denmark, +65 Singapore, +354 Iceland …), and reading them as a NANP number
14
+ * would hand an OTP or a bearer link to whoever holds the matching North
15
+ * American line. Only input WITHOUT a '+' may take the NANP shorthand: exactly
16
+ * ten digits, or eleven starting with the '1'. Any other bare digit string
17
+ * declares no country and is refused rather than guessed.
18
+ *
19
+ * A typed number holds ASCII digits, at most one '+' ahead of all of them, and
20
+ * the separators people type: whitespace, parentheses, dots and dashes of any
21
+ * script, and the invisible formatting marks a contacts app wraps a number in.
22
+ * The separators and marks are dropped. Anything else is refused, never
23
+ * dropped: stripping it would fold part of the text into the number and deliver
24
+ * to someone else's line — a letter or a dial-control character (',' and ';'
25
+ * pause, '#' and '*' are keypad tones) carries an extension ("x2", ",89"), a
26
+ * digit of another script ('٧', '7') is a digit the ASCII read would silently
27
+ * lose, and a '+' after a digit declares nothing.
28
+ *
29
+ * An identifier typed where an email address or a phone number is accepted is
30
+ * a phone number only when it holds phone characters only, led by a digit, '+'
31
+ * or '(': an email whose local part starts with a digit ("2024user@gmail.com",
32
+ * "12345678@qq.com") stays an email.
33
+ */
34
+ /** A '+' before any digit: the caller declared the country code. */
35
+ const DECLARED_COUNTRY_CODE = /^\D*\+/;
36
+ /** E.164 digits once a country code is declared: 8–15, never a leading 0. */
37
+ const E164_DIGITS = /^[1-9]\d{7,14}$/;
38
+ /** A canonical E.164 value. */
39
+ const E164 = /^\+[1-9]\d{7,14}$/;
40
+ /** Invisible formatting marks (bidi embeddings, zero-width characters), in any number. */
41
+ const FORMAT_MARKS = /\p{Cf}/gu;
42
+ /**
43
+ * Typed phone text once its formatting marks are dropped: ASCII digits and
44
+ * separators, with at most one '+', ahead of every digit.
45
+ */
46
+ const PHONE_TEXT = /^[\s().\p{Pd}]*\+?[\d\s().\p{Pd}]*$/u;
47
+ /** How phone text starts where an identifier may be either: a digit, '+' or '('. */
48
+ const PHONE_LEAD = /^[+(\d]/;
49
+ /**
50
+ * @param {string} value
51
+ * @returns {string} The value without its formatting marks
52
+ */
53
+ function withoutFormatMarks(value) {
54
+ return value.replace(FORMAT_MARKS, '');
55
+ }
56
+ /**
57
+ * Read a typed phone number as E.164.
58
+ *
59
+ * @param {unknown} raw
60
+ * @returns {string|null} E.164 with its leading '+', or null when the input
61
+ * names no dialable number
62
+ */
63
+ export function parsePhone(raw) {
64
+ if (typeof raw !== 'string')
65
+ return null;
66
+ const text = withoutFormatMarks(raw);
67
+ if (!PHONE_TEXT.test(text))
68
+ return null;
69
+ const digits = text.replace(/\D/g, '');
70
+ if (DECLARED_COUNTRY_CODE.test(text)) {
71
+ return E164_DIGITS.test(digits) ? `+${digits}` : null;
72
+ }
73
+ if (digits.length === 10)
74
+ return `+1${digits}`;
75
+ if (digits.length === 11 && digits.startsWith('1'))
76
+ return `+${digits}`;
77
+ return null;
78
+ }
79
+ /**
80
+ * Whether a value is already a canonical E.164 phone number — exactly the
81
+ * values parsePhone returns unchanged.
82
+ *
83
+ * @param {unknown} value
84
+ * @returns {boolean}
85
+ */
86
+ export function isE164Phone(value) {
87
+ return typeof value === 'string' && E164.test(value);
88
+ }
89
+ /**
90
+ * Whether an identifier typed where either is accepted is an email address or
91
+ * a phone number.
92
+ *
93
+ * @param {unknown} value
94
+ * @returns {'email' | 'phone'}
95
+ */
96
+ export function inferIdentityType(value) {
97
+ if (typeof value !== 'string')
98
+ return 'email';
99
+ const text = withoutFormatMarks(value).trim();
100
+ return PHONE_LEAD.test(text) && PHONE_TEXT.test(text) ? 'phone' : 'email';
101
+ }
102
+ /**
103
+ * A phone number as a phone field shows it while it is typed: a NANP number as
104
+ * (XXX) XXX-XXXX; a declared country code, or a digit string that is not a NANP
105
+ * number, keeps every digit as given, so formatting never turns one destination
106
+ * into another. Text holding anything but phone characters (a letter, a
107
+ * dial-control character, a digit of another script) is left as typed, so it
108
+ * reaches parsePhone and is refused there instead of being dropped from the
109
+ * field.
110
+ *
111
+ * @param {string} value
112
+ * @returns {string}
113
+ */
114
+ export function formatPhoneDisplay(value) {
115
+ const text = withoutFormatMarks(value);
116
+ if (!PHONE_TEXT.test(text))
117
+ return value;
118
+ const digits = text.replace(/\D/g, '');
119
+ if (DECLARED_COUNTRY_CODE.test(text) && !(digits.startsWith('1') && digits.length === 11)) {
120
+ return `+${digits}`;
121
+ }
122
+ if (digits.length > 11 || (digits.length === 11 && !digits.startsWith('1'))) {
123
+ return digits;
124
+ }
125
+ const national = digits.length === 11 ? digits.slice(1) : digits;
126
+ if (national.length === 0)
127
+ return '';
128
+ if (national.length <= 3)
129
+ return `(${national}`;
130
+ if (national.length <= 6)
131
+ return `(${national.slice(0, 3)}) ${national.slice(3)}`;
132
+ return `(${national.slice(0, 3)}) ${national.slice(3, 6)}-${national.slice(6, 10)}`;
133
+ }
134
+ //# sourceMappingURL=phone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phone.js","sourceRoot":"","sources":["../../src/app-sdk/phone.js"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export function isSparkVaultRootOrSubdomain(hostname: any): boolean;
2
+ export function isSparkVaultPlatformSvHost(hostname: any): boolean;
3
+ export function isSparkVaultOwnedHostname(hostname: any): boolean;
4
+ export function isSparkVaultReservedDomain(hostname: any): boolean;
5
+ export function sparkVaultPlatformSvOrigins(): string[];
6
+ export function sparkVaultWebSocketOrigins(): string[];
7
+ /**
8
+ * SparkVault-owned domain registry.
9
+ *
10
+ * Keep platform domain decisions centralized: CSP, redirect safety, custom
11
+ * domain reservation, and Identity postMessage trust all depend on the same
12
+ * ownership boundary.
13
+ */
14
+ export const SPARKVAULT_ROOT_DOMAIN: "sparkvault.com";
15
+ /** The person-facing Identity portal host (auth.sv shell + hosted login). */
16
+ export const SPARKVAULT_AUTH_HOST: "auth.sv";
17
+ /**
18
+ * The Identity IdP origin's host: it serves the OIDC ceremonies (/authorize,
19
+ * /token), the SDK sign-in page and the Identity API, issues every Identity
20
+ * token (`iss` is https://{host}/{account_id}), and holds the hub session
21
+ * cookie. Core verifies the tokens it issues and the Identity product mints
22
+ * them, so both name it from here. Never the auth.sv portal host.
23
+ */
24
+ export const SPARKVAULT_IDENTITY_HOST: "auth.sparkvault.com";
25
+ /** The SparkLinks host: shared Sparks, single-use links and the Messaging recipient pages. */
26
+ export const SPARKVAULT_LINKS_HOST: "x.sv";
27
+ /** The durable file-share and Secure Upload Portal host. */
28
+ export const SPARKVAULT_FILES_HOST: "files.sv";
29
+ export const SPARKVAULT_PLATFORM_SV_HOSTS: readonly string[];
30
+ /** The SparkVault console. */
31
+ export const SPARKVAULT_APP_HOST: "app.sparkvault.com";
32
+ /** Forge, the streaming encrypt/decrypt service every ingot transfer goes through. */
33
+ export const SPARKVAULT_FORGE_HOST: "forge.sparkvault.com";
34
+ /**
35
+ * The platform's own hosts as patterns over a URL's hostname: the root domain
36
+ * and its subdomains, and each .sv host exactly (isSparkVaultOwnedHostname).
37
+ * Both SDKs trust a backend-issued transfer URL on these hosts by default;
38
+ * their `allowedDownloadHostPatterns` option replaces the list for another
39
+ * deployment.
40
+ */
41
+ export const SPARKVAULT_OWNED_HOST_PATTERNS: readonly RegExp[];
@@ -0,0 +1,82 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/platform-domains.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * SparkVault-owned domain registry.
6
+ *
7
+ * Keep platform domain decisions centralized: CSP, redirect safety, custom
8
+ * domain reservation, and Identity postMessage trust all depend on the same
9
+ * ownership boundary.
10
+ */
11
+ export const SPARKVAULT_ROOT_DOMAIN = 'sparkvault.com';
12
+ /** The person-facing Identity portal host (auth.sv shell + hosted login). */
13
+ export const SPARKVAULT_AUTH_HOST = 'auth.sv';
14
+ /**
15
+ * The Identity IdP origin's host: it serves the OIDC ceremonies (/authorize,
16
+ * /token), the SDK sign-in page and the Identity API, issues every Identity
17
+ * token (`iss` is https://{host}/{account_id}), and holds the hub session
18
+ * cookie. Core verifies the tokens it issues and the Identity product mints
19
+ * them, so both name it from here. Never the auth.sv portal host.
20
+ */
21
+ export const SPARKVAULT_IDENTITY_HOST = 'auth.sparkvault.com';
22
+ /** The SparkLinks host: shared Sparks, single-use links and the Messaging recipient pages. */
23
+ export const SPARKVAULT_LINKS_HOST = 'x.sv';
24
+ /** The durable file-share and Secure Upload Portal host. */
25
+ export const SPARKVAULT_FILES_HOST = 'files.sv';
26
+ export const SPARKVAULT_PLATFORM_SV_HOSTS = Object.freeze([
27
+ SPARKVAULT_AUTH_HOST,
28
+ SPARKVAULT_LINKS_HOST,
29
+ SPARKVAULT_FILES_HOST,
30
+ 'file.sv',
31
+ 'send.sv',
32
+ 'spark.sv',
33
+ 'by.sv',
34
+ 'at.sv',
35
+ 'db.sv'
36
+ ]);
37
+ /** The SparkVault console. */
38
+ export const SPARKVAULT_APP_HOST = 'app.sparkvault.com';
39
+ /** Forge, the streaming encrypt/decrypt service every ingot transfer goes through. */
40
+ export const SPARKVAULT_FORGE_HOST = 'forge.sparkvault.com';
41
+ const SPARKVAULT_WEBSOCKET_HOSTS = Object.freeze([
42
+ 'ws.sparkvault.com'
43
+ ]);
44
+ function escapeRegExp(value) {
45
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
46
+ }
47
+ /**
48
+ * The platform's own hosts as patterns over a URL's hostname: the root domain
49
+ * and its subdomains, and each .sv host exactly (isSparkVaultOwnedHostname).
50
+ * Both SDKs trust a backend-issued transfer URL on these hosts by default;
51
+ * their `allowedDownloadHostPatterns` option replaces the list for another
52
+ * deployment.
53
+ */
54
+ export const SPARKVAULT_OWNED_HOST_PATTERNS = Object.freeze([
55
+ new RegExp(`(^|\\.)${escapeRegExp(SPARKVAULT_ROOT_DOMAIN)}$`, 'i'),
56
+ new RegExp(`^(${SPARKVAULT_PLATFORM_SV_HOSTS.map(escapeRegExp).join('|')})$`, 'i')
57
+ ]);
58
+ function normalizeHostname(hostname) {
59
+ if (typeof hostname !== 'string')
60
+ return '';
61
+ return hostname.trim().toLowerCase().replace(/\.$/, '');
62
+ }
63
+ export function isSparkVaultRootOrSubdomain(hostname) {
64
+ const normalized = normalizeHostname(hostname);
65
+ return normalized === SPARKVAULT_ROOT_DOMAIN || normalized.endsWith(`.${SPARKVAULT_ROOT_DOMAIN}`);
66
+ }
67
+ export function isSparkVaultPlatformSvHost(hostname) {
68
+ return SPARKVAULT_PLATFORM_SV_HOSTS.includes(normalizeHostname(hostname));
69
+ }
70
+ export function isSparkVaultOwnedHostname(hostname) {
71
+ return isSparkVaultRootOrSubdomain(hostname) || isSparkVaultPlatformSvHost(hostname);
72
+ }
73
+ export function isSparkVaultReservedDomain(hostname) {
74
+ return isSparkVaultOwnedHostname(hostname);
75
+ }
76
+ export function sparkVaultPlatformSvOrigins() {
77
+ return SPARKVAULT_PLATFORM_SV_HOSTS.map((host) => `https://${host}`);
78
+ }
79
+ export function sparkVaultWebSocketOrigins() {
80
+ return SPARKVAULT_WEBSOCKET_HOSTS.map((host) => `wss://${host}`);
81
+ }
82
+ //# sourceMappingURL=platform-domains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-domains.js","sourceRoot":"","sources":["../../src/app-sdk/platform-domains.js"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,4DAA4D;AAC5D,8DAA8D;AAE9D;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,gBAAgB,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAE9D,8FAA8F;AAC9F,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C,4DAA4D;AAC5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC;AAEhD,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC,MAAM,CAAC;IACxD,oBAAoB;IACpB,qBAAqB;IACrB,qBAAqB;IACrB,SAAS;IACT,SAAS;IACT,UAAU;IACV,OAAO;IACP,OAAO;IACP,OAAO;CACR,CAAC,CAAC;AAEH,8BAA8B;AAC9B,MAAM,CAAC,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAExD,sFAAsF;AACtF,MAAM,CAAC,MAAM,qBAAqB,GAAG,sBAAsB,CAAC;AAE5D,MAAM,0BAA0B,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/C,mBAAmB;CACpB,CAAC,CAAC;AAEH,SAAS,YAAY,CAAC,KAAK;IACzB,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1D,IAAI,MAAM,CAAC,UAAU,YAAY,CAAC,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC;IAClE,IAAI,MAAM,CAAC,KAAK,4BAA4B,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC;CACnF,CAAC,CAAC;AAEH,SAAS,iBAAiB,CAAC,QAAQ;IACjC,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAC5C,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,QAAQ;IAClD,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC/C,OAAO,UAAU,KAAK,sBAAsB,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,sBAAsB,EAAE,CAAC,CAAC;AACpG,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,QAAQ;IACjD,OAAO,4BAA4B,CAAC,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,QAAQ;IAChD,OAAO,2BAA2B,CAAC,QAAQ,CAAC,IAAI,0BAA0B,CAAC,QAAQ,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,QAAQ;IACjD,OAAO,yBAAyB,CAAC,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,4BAA4B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,OAAO,0BAA0B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Request paths: the one rule every SDK applies before a caller-supplied value
3
+ * becomes part of a SparkVault request URL.
4
+ *
5
+ * A request carries the caller's credentials (a Bearer token, an API key, a
6
+ * Vault Access Token), so a value that smuggles '/' or a dot-segment into the
7
+ * path would re-target that credentialed request at another route. The rules:
8
+ *
9
+ * - `encodePathSegment`: every value interpolated into a path is encoded as
10
+ * exactly one segment. Percent-encoding keeps '/', '?' and '#' inside the
11
+ * segment, and the three values no encoding can keep one segment ('', '.'
12
+ * and '..', which URL resolution drops or collapses) are refused.
13
+ * - `buildRequestPath`: the template loop an SDK's tagged path builder runs,
14
+ * so every interpolated value passes `encodePathSegment` by construction.
15
+ * - `queryString`: the one query encoding, which leaves unset values out.
16
+ *
17
+ * A server-issued id is checked for its shape first, by the one resource-id
18
+ * grammar (ids.js).
19
+ *
20
+ * Browser-safe and dependency-free: sdk-js bundles it, and sdk-mobile (which
21
+ * publishes standalone) carries a build-time copy pinned byte-for-byte by its
22
+ * drift test. Each SDK wraps it in a typed front door that throws its own
23
+ * validation error.
24
+ */
25
+ /**
26
+ * Encode a value as exactly one request-path segment.
27
+ * @param {unknown} value
28
+ * @returns {string | null} the encoded segment, or null when the value cannot
29
+ * be one segment: not a string, empty, '.', '..', or not well-formed UTF-16
30
+ * (a lone surrogate has no percent-encoding).
31
+ */
32
+ export function encodePathSegment(value: unknown): string | null;
33
+ /**
34
+ * Join a tagged template's literals with its values, each value encoded as
35
+ * exactly one path segment.
36
+ * @param {readonly string[]} literals
37
+ * @param {readonly unknown[]} values
38
+ * @returns {string | null} the path, or null when any value cannot be one
39
+ * segment (see `encodePathSegment`).
40
+ */
41
+ export function buildRequestPath(literals: readonly string[], values: readonly unknown[]): string | null;
42
+ /**
43
+ * Encode query parameters, leaving out every unset value (`undefined`, `null`
44
+ * or `''`). Other values are sent as their string form, so `0` and `false`
45
+ * are sent.
46
+ * @param {Readonly<Record<string, string | number | boolean | null | undefined>> | undefined} params
47
+ * @returns {string} `?a=1&b=2`, or '' when no value is set.
48
+ */
49
+ export function queryString(params: Readonly<Record<string, string | number | boolean | null | undefined>> | undefined): string;
@@ -0,0 +1,81 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/request-path.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * Request paths: the one rule every SDK applies before a caller-supplied value
6
+ * becomes part of a SparkVault request URL.
7
+ *
8
+ * A request carries the caller's credentials (a Bearer token, an API key, a
9
+ * Vault Access Token), so a value that smuggles '/' or a dot-segment into the
10
+ * path would re-target that credentialed request at another route. The rules:
11
+ *
12
+ * - `encodePathSegment`: every value interpolated into a path is encoded as
13
+ * exactly one segment. Percent-encoding keeps '/', '?' and '#' inside the
14
+ * segment, and the three values no encoding can keep one segment ('', '.'
15
+ * and '..', which URL resolution drops or collapses) are refused.
16
+ * - `buildRequestPath`: the template loop an SDK's tagged path builder runs,
17
+ * so every interpolated value passes `encodePathSegment` by construction.
18
+ * - `queryString`: the one query encoding, which leaves unset values out.
19
+ *
20
+ * A server-issued id is checked for its shape first, by the one resource-id
21
+ * grammar (ids.js).
22
+ *
23
+ * Browser-safe and dependency-free: sdk-js bundles it, and sdk-mobile (which
24
+ * publishes standalone) carries a build-time copy pinned byte-for-byte by its
25
+ * drift test. Each SDK wraps it in a typed front door that throws its own
26
+ * validation error.
27
+ */
28
+ /**
29
+ * Encode a value as exactly one request-path segment.
30
+ * @param {unknown} value
31
+ * @returns {string | null} the encoded segment, or null when the value cannot
32
+ * be one segment: not a string, empty, '.', '..', or not well-formed UTF-16
33
+ * (a lone surrogate has no percent-encoding).
34
+ */
35
+ export function encodePathSegment(value) {
36
+ if (typeof value !== 'string' || value === '' || value === '.' || value === '..') {
37
+ return null;
38
+ }
39
+ try {
40
+ return encodeURIComponent(value);
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ /**
47
+ * Join a tagged template's literals with its values, each value encoded as
48
+ * exactly one path segment.
49
+ * @param {readonly string[]} literals
50
+ * @param {readonly unknown[]} values
51
+ * @returns {string | null} the path, or null when any value cannot be one
52
+ * segment (see `encodePathSegment`).
53
+ */
54
+ export function buildRequestPath(literals, values) {
55
+ let path = literals[0];
56
+ for (let index = 0; index < values.length; index += 1) {
57
+ const segment = encodePathSegment(values[index]);
58
+ if (segment === null)
59
+ return null;
60
+ path += segment + literals[index + 1];
61
+ }
62
+ return path;
63
+ }
64
+ /**
65
+ * Encode query parameters, leaving out every unset value (`undefined`, `null`
66
+ * or `''`). Other values are sent as their string form, so `0` and `false`
67
+ * are sent.
68
+ * @param {Readonly<Record<string, string | number | boolean | null | undefined>> | undefined} params
69
+ * @returns {string} `?a=1&b=2`, or '' when no value is set.
70
+ */
71
+ export function queryString(params) {
72
+ const search = new URLSearchParams();
73
+ for (const [key, value] of Object.entries(params ?? {})) {
74
+ if (value === undefined || value === null || value === '')
75
+ continue;
76
+ search.append(key, String(value));
77
+ }
78
+ const query = search.toString();
79
+ return query ? `?${query}` : '';
80
+ }
81
+ //# sourceMappingURL=request-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-path.js","sourceRoot":"","sources":["../../src/app-sdk/request-path.js"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,wDAAwD;AACxD,8DAA8D;AAE9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAK;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAQ,EAAE,MAAM;IAC/C,IAAI,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACvB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAClC,IAAI,IAAI,OAAO,GAAG,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,MAAM;IAChC,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;YAAE,SAAS;QACpE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAChC,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAClC,CAAC"}