@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,23 @@
1
+ /**
2
+ * Whether sending the SAME request again can succeed, read from its HTTP
3
+ * status: the one retry rule every client and every provider adapter applies.
4
+ *
5
+ * A retry rule restated beside every client drifts, and the copy that drifted
6
+ * decides whether a deliverable message is retried or abandoned, or whether a
7
+ * download is tried again. Each caller keeps its provider's own extras on top of
8
+ * this rule (a success body, a dead-token reason, a vendor error name).
9
+ *
10
+ * Browser-safe and import-free: sdk-js bundles it and sdk-mobile mirrors it.
11
+ */
12
+ /**
13
+ * Is this HTTP status one a retry of the same request can turn into a success?
14
+ *
15
+ * RFC 9110: 408 means the server never received the whole request, 429 that it
16
+ * throttled it, and a 5xx that it failed; every other status is the server's
17
+ * verdict on this exact request. A caller that got no response at all (a network
18
+ * error, a timeout) has no status to pass and decides that case itself.
19
+ *
20
+ * @param {number} status
21
+ * @returns {boolean}
22
+ */
23
+ export function isTransientHttpStatus(status: number): boolean;
@@ -0,0 +1,29 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/transient.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * Whether sending the SAME request again can succeed, read from its HTTP
6
+ * status: the one retry rule every client and every provider adapter applies.
7
+ *
8
+ * A retry rule restated beside every client drifts, and the copy that drifted
9
+ * decides whether a deliverable message is retried or abandoned, or whether a
10
+ * download is tried again. Each caller keeps its provider's own extras on top of
11
+ * this rule (a success body, a dead-token reason, a vendor error name).
12
+ *
13
+ * Browser-safe and import-free: sdk-js bundles it and sdk-mobile mirrors it.
14
+ */
15
+ /**
16
+ * Is this HTTP status one a retry of the same request can turn into a success?
17
+ *
18
+ * RFC 9110: 408 means the server never received the whole request, 429 that it
19
+ * throttled it, and a 5xx that it failed; every other status is the server's
20
+ * verdict on this exact request. A caller that got no response at all (a network
21
+ * error, a timeout) has no status to pass and decides that case itself.
22
+ *
23
+ * @param {number} status
24
+ * @returns {boolean}
25
+ */
26
+ export function isTransientHttpStatus(status) {
27
+ return status === 408 || status === 429 || status >= 500;
28
+ }
29
+ //# sourceMappingURL=transient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transient.js","sourceRoot":"","sources":["../../src/app-sdk/transient.js"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,qDAAqD;AACrD,8DAA8D;AAE9D;;;;;;;;;;GAUG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAM;IAC1C,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The VAT refusal a Core error answer names, or null when it is not one.
3
+ *
4
+ * @param {{ statusCode?: number, details?: { reason?: string } }} error - An
5
+ * error as parsed from a Core response (status plus the body's `details`)
6
+ * @returns {string|null} One of VAT_REFUSAL_REASONS, or null
7
+ */
8
+ export function vatRefusalReason(error: {
9
+ statusCode?: number;
10
+ details?: {
11
+ reason?: string;
12
+ };
13
+ }): string | null;
14
+ /**
15
+ * Whether a refusal says the token's vault session is over: an invalid or an
16
+ * expired token, which only a new unseal replaces, so a client forgets it. A
17
+ * read-only refusal leaves the session standing (the token still reads), and
18
+ * so does a refusal that names no VAT reason.
19
+ *
20
+ * @param {string|null} reason - What vatRefusalReason returned
21
+ * @returns {boolean}
22
+ */
23
+ export function endsVatSession(reason: string | null): boolean;
24
+ /**
25
+ * True when Core refused the request's login, not a Vault Access Token it
26
+ * carried: a 401 bearing Core's session code (AUTHENTICATION_ERROR, the
27
+ * mapped error's `upstreamCode`) that names no VAT refusal. Its token was
28
+ * refused, or its account is unavailable (details.reason ACCOUNT_UNAVAILABLE,
29
+ * ./refusal-reasons.js). A 401 for a credential in the request body (an
30
+ * unseal key) carries Core's credential code (UNAUTHORIZED) and is not one.
31
+ *
32
+ * @param {{ statusCode?: number, upstreamCode?: string|null, details?: { reason?: string } }} error -
33
+ * An error as parsed from a Core response (parseApiError)
34
+ * @returns {boolean}
35
+ */
36
+ export function isRefusedLogin(error: {
37
+ statusCode?: number;
38
+ upstreamCode?: string | null;
39
+ details?: {
40
+ reason?: string;
41
+ };
42
+ }): boolean;
43
+ /**
44
+ * Why Core refused a Vault Access Token (VAT): the `details.reason` every VAT
45
+ * refusal carries, and the one reading of it clients share.
46
+ *
47
+ * 401 VAT_INVALID no token, or a malformed, unknown or swept one, or one a
48
+ * since-revoked DVAK minted — unseal again
49
+ * 403 VAT_EXPIRED the token outlived its TTL — unseal again, retry as-is
50
+ * 403 VAT_READ_ONLY a View Only token on a write — no unseal fixes it
51
+ *
52
+ * A 403 with no reason is a token for another vault or account, which no
53
+ * unseal of this vault fixes; a 401 with no reason is the login itself, never
54
+ * the VAT. Browser-safe: no imports.
55
+ */
56
+ export const VAT_REFUSAL_REASONS: Readonly<{
57
+ INVALID: "VAT_INVALID";
58
+ EXPIRED: "VAT_EXPIRED";
59
+ READ_ONLY: "VAT_READ_ONLY";
60
+ }>;
61
+ /** The status each refusal is answered with. */
62
+ export const VAT_REFUSAL_STATUS: Readonly<{
63
+ VAT_INVALID: 401;
64
+ VAT_EXPIRED: 403;
65
+ VAT_READ_ONLY: 403;
66
+ }>;
@@ -0,0 +1,69 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/vat-refusal.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * Why Core refused a Vault Access Token (VAT): the `details.reason` every VAT
6
+ * refusal carries, and the one reading of it clients share.
7
+ *
8
+ * 401 VAT_INVALID no token, or a malformed, unknown or swept one, or one a
9
+ * since-revoked DVAK minted — unseal again
10
+ * 403 VAT_EXPIRED the token outlived its TTL — unseal again, retry as-is
11
+ * 403 VAT_READ_ONLY a View Only token on a write — no unseal fixes it
12
+ *
13
+ * A 403 with no reason is a token for another vault or account, which no
14
+ * unseal of this vault fixes; a 401 with no reason is the login itself, never
15
+ * the VAT. Browser-safe: no imports.
16
+ */
17
+ export const VAT_REFUSAL_REASONS = Object.freeze({
18
+ INVALID: 'VAT_INVALID',
19
+ EXPIRED: 'VAT_EXPIRED',
20
+ READ_ONLY: 'VAT_READ_ONLY'
21
+ });
22
+ /** The status each refusal is answered with. */
23
+ export const VAT_REFUSAL_STATUS = Object.freeze({
24
+ [VAT_REFUSAL_REASONS.INVALID]: 401,
25
+ [VAT_REFUSAL_REASONS.EXPIRED]: 403,
26
+ [VAT_REFUSAL_REASONS.READ_ONLY]: 403
27
+ });
28
+ /**
29
+ * The VAT refusal a Core error answer names, or null when it is not one.
30
+ *
31
+ * @param {{ statusCode?: number, details?: { reason?: string } }} error - An
32
+ * error as parsed from a Core response (status plus the body's `details`)
33
+ * @returns {string|null} One of VAT_REFUSAL_REASONS, or null
34
+ */
35
+ export function vatRefusalReason(error) {
36
+ const reason = error?.details?.reason;
37
+ return typeof reason === 'string' && Object.hasOwn(VAT_REFUSAL_STATUS, reason) &&
38
+ VAT_REFUSAL_STATUS[reason] === error?.statusCode
39
+ ? reason
40
+ : null;
41
+ }
42
+ /**
43
+ * Whether a refusal says the token's vault session is over: an invalid or an
44
+ * expired token, which only a new unseal replaces, so a client forgets it. A
45
+ * read-only refusal leaves the session standing (the token still reads), and
46
+ * so does a refusal that names no VAT reason.
47
+ *
48
+ * @param {string|null} reason - What vatRefusalReason returned
49
+ * @returns {boolean}
50
+ */
51
+ export function endsVatSession(reason) {
52
+ return reason === VAT_REFUSAL_REASONS.INVALID || reason === VAT_REFUSAL_REASONS.EXPIRED;
53
+ }
54
+ /**
55
+ * True when Core refused the request's login, not a Vault Access Token it
56
+ * carried: a 401 bearing Core's session code (AUTHENTICATION_ERROR, the
57
+ * mapped error's `upstreamCode`) that names no VAT refusal. Its token was
58
+ * refused, or its account is unavailable (details.reason ACCOUNT_UNAVAILABLE,
59
+ * ./refusal-reasons.js). A 401 for a credential in the request body (an
60
+ * unseal key) carries Core's credential code (UNAUTHORIZED) and is not one.
61
+ *
62
+ * @param {{ statusCode?: number, upstreamCode?: string|null, details?: { reason?: string } }} error -
63
+ * An error as parsed from a Core response (parseApiError)
64
+ * @returns {boolean}
65
+ */
66
+ export function isRefusedLogin(error) {
67
+ return error?.statusCode === 401 && error?.upstreamCode === 'AUTHENTICATION_ERROR' && vatRefusalReason(error) === null;
68
+ }
69
+ //# sourceMappingURL=vat-refusal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vat-refusal.js","sourceRoot":"","sources":["../../src/app-sdk/vat-refusal.js"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,uDAAuD;AACvD,8DAA8D;AAE9D;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/C,OAAO,EAAE,aAAa;IACtB,OAAO,EAAE,aAAa;IACtB,SAAS,EAAE,eAAe;CAC3B,CAAC,CAAC;AAEH,gDAAgD;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,GAAG;IAClC,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,GAAG;IAClC,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,GAAG;CACrC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAK;IACpC,MAAM,MAAM,GAAG,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC;IACtC,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC;QAC5E,kBAAkB,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,UAAU;QAChD,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,MAAM;IACnC,OAAO,MAAM,KAAK,mBAAmB,CAAC,OAAO,IAAI,MAAM,KAAK,mBAAmB,CAAC,OAAO,CAAC;AAC1F,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,KAAK;IAClC,OAAO,KAAK,EAAE,UAAU,KAAK,GAAG,IAAI,KAAK,EAAE,YAAY,KAAK,sBAAsB,IAAI,gBAAgB,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;AACzH,CAAC"}
package/dist/billing.d.ts CHANGED
@@ -1,27 +1,19 @@
1
1
  import type { MobileHttpClient } from './http.js';
2
- import type { IapPlatform, SparkVaultSubscription } from './types.js';
3
- /** Short-lived Stripe Customer Portal URL for managing the subscription on web. */
4
- export interface PortalSession {
5
- portal_url: string;
6
- }
2
+ import type { IapPlatform, IapValidation, SparkVaultSubscription } from './types.js';
7
3
  export declare class MobileBillingClient {
8
4
  private readonly http;
9
5
  constructor(http: MobileHttpClient);
10
6
  /**
11
- * Current subscription: plan, licensed vs. used seats, capacity blocks, live
12
- * pool usage, and Identity attempts. Drives the subscription / usage view.
7
+ * Current subscription: billing lifecycle, licensed vs. used seats, capacity
8
+ * blocks, live pool usage, and Identity attempts. Drives the subscription /
9
+ * usage view.
13
10
  */
14
11
  getSubscription(): Promise<SparkVaultSubscription>;
15
12
  /**
16
- * Open the Stripe Customer Portal to manage the subscription on web (seat /
17
- * block changes that fixed native SKUs cannot carry route here). The caller
18
- * loads the returned URL in the WebView.
19
- */
20
- createPortalSession(): Promise<PortalSession>;
21
- /**
22
- * Validate a native in-app purchase receipt server-side and apply the
23
- * resulting entitlements. Returns the refreshed subscription so the client
24
- * can update its plan / usage view immediately after purchase.
13
+ * Validate a native in-app purchase server-side and apply the entitlements
14
+ * it buys. `receipt` is the App Store signed transaction (JWS) or the Play
15
+ * purchase token. Answers whether Core provisioned the purchase; read the
16
+ * subscription again with getSubscription() to show the change.
25
17
  */
26
- iapValidate(platform: IapPlatform, productId: string, receipt: string): Promise<SparkVaultSubscription>;
18
+ iapValidate(platform: IapPlatform, productId: string, receipt: string): Promise<IapValidation>;
27
19
  }
package/dist/billing.js CHANGED
@@ -1,34 +1,31 @@
1
+ import { apiPath } from './request-path.js';
1
2
  export class MobileBillingClient {
2
3
  constructor(http) {
3
4
  this.http = http;
4
5
  }
5
6
  /**
6
- * Current subscription: plan, licensed vs. used seats, capacity blocks, live
7
- * pool usage, and Identity attempts. Drives the subscription / usage view.
7
+ * Current subscription: billing lifecycle, licensed vs. used seats, capacity
8
+ * blocks, live pool usage, and Identity attempts. Drives the subscription /
9
+ * usage view.
8
10
  */
9
11
  async getSubscription() {
10
- const response = await this.http.get('/billing/subscription');
12
+ const response = await this.http.get(apiPath `/billing/subscription`);
11
13
  return response.data;
12
14
  }
13
15
  /**
14
- * Open the Stripe Customer Portal to manage the subscription on web (seat /
15
- * block changes that fixed native SKUs cannot carry route here). The caller
16
- * loads the returned URL in the WebView.
17
- */
18
- async createPortalSession() {
19
- const response = await this.http.post('/billing/portal');
20
- return response.data;
21
- }
22
- /**
23
- * Validate a native in-app purchase receipt server-side and apply the
24
- * resulting entitlements. Returns the refreshed subscription so the client
25
- * can update its plan / usage view immediately after purchase.
16
+ * Validate a native in-app purchase server-side and apply the entitlements
17
+ * it buys. `receipt` is the App Store signed transaction (JWS) or the Play
18
+ * purchase token. Answers whether Core provisioned the purchase; read the
19
+ * subscription again with getSubscription() to show the change.
26
20
  */
27
21
  async iapValidate(platform, productId, receipt) {
28
- const response = await this.http.post('/billing/iap/validate', {
22
+ // Core reads an App Store purchase's signed transaction as `receipt` and a
23
+ // Play purchase's token as `purchase_token`.
24
+ const credential = platform === 'android' ? { purchase_token: receipt } : { receipt };
25
+ const response = await this.http.post(apiPath `/billing/iap/validate`, {
29
26
  platform,
30
27
  product_id: productId,
31
- receipt,
28
+ ...credential,
32
29
  });
33
30
  return response.data;
34
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,mBAAmB;IAG9B,YAAY,IAAsB;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe;QACnB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAyB,uBAAuB,CAAC,CAAC;QACtF,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,mBAAmB;QACvB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAgB,iBAAiB,CAAC,CAAC;QACxE,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CACf,QAAqB,EACrB,SAAiB,EACjB,OAAe;QAEf,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAyB,uBAAuB,EAAE;YACrF,QAAQ;YACR,UAAU,EAAE,SAAS;YACrB,OAAO;SACR,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;CACF"}
1
+ {"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE5C,MAAM,OAAO,mBAAmB;IAG9B,YAAY,IAAsB;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe;QACnB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAyB,OAAO,CAAA,uBAAuB,CAAC,CAAC;QAC7F,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CACf,QAAqB,EACrB,SAAiB,EACjB,OAAe;QAEf,2EAA2E;QAC3E,6CAA6C;QAC7C,MAAM,UAAU,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QACtF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAgB,OAAO,CAAA,uBAAuB,EAAE;YACnF,QAAQ;YACR,UAAU,EAAE,SAAS;YACrB,GAAG,UAAU;SACd,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;CACF"}
package/dist/config.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { FetchLike, MobileBackgroundTransfer, MobileBackgroundUploader, MobileFileDownloader, MobileFileReader, SparkVaultLogger, TokenStorageAdapter } from './types.js';
2
+ import { type ApiPath } from './request-path.js';
2
3
  export interface SparkVaultMobileConfig {
3
4
  tokenStorage: TokenStorageAdapter;
4
5
  apiBaseUrl?: string;
@@ -31,6 +32,11 @@ export interface SparkVaultMobileConfig {
31
32
  */
32
33
  backgroundUploader?: MobileBackgroundUploader;
33
34
  logger?: Partial<SparkVaultLogger>;
35
+ /**
36
+ * Host allowlist for the backend-issued Forge transfer URLs the SDK sends an
37
+ * ingot's ISTK to (downloads and uploads alike). Defaults to the SparkVault
38
+ * platform's own hosts; override only when pointing at another deployment.
39
+ */
34
40
  allowedDownloadHostPatterns?: RegExp[];
35
41
  }
36
42
  export interface ResolvedMobileConfig {
@@ -53,4 +59,4 @@ export interface ResolvedMobileConfig {
53
59
  allowedDownloadHostPatterns: RegExp[];
54
60
  }
55
61
  export declare function resolveMobileConfig(config: SparkVaultMobileConfig): ResolvedMobileConfig;
56
- export declare function identityUrl(config: ResolvedMobileConfig, path: string): string;
62
+ export declare function identityUrl(config: ResolvedMobileConfig, path: ApiPath): string;
package/dist/config.js CHANGED
@@ -1,18 +1,9 @@
1
1
  import { SparkVaultValidationError } from './errors.js';
2
- import { SPARKVAULT_PLATFORM_SV_HOSTS, SPARKVAULT_ROOT_DOMAIN, } from './platform-domains.generated.js';
2
+ import { isResourceIdOf } from './app-sdk/ids.js';
3
+ import { SPARKVAULT_IDENTITY_HOST, SPARKVAULT_OWNED_HOST_PATTERNS } from './app-sdk/platform-domains.js';
4
+ import { apiPath } from './request-path.js';
3
5
  const DEFAULT_API_BASE_URL = 'https://api.sparkvault.com/v1';
4
- const DEFAULT_IDENTITY_BASE_URL = 'https://auth.sparkvault.com';
5
- function escapeRegExp(value) {
6
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
- }
8
- // Hosts that are allowed to serve backend-issued ingot download URLs, derived
9
- // from the canonical platform domain registry baked in at build time.
10
- const DEFAULT_ALLOWED_DOWNLOAD_HOST_PATTERNS = [
11
- new RegExp(`(^|\\.)${escapeRegExp(SPARKVAULT_ROOT_DOMAIN)}$`, 'i'),
12
- new RegExp(`(^|\\.)(${SPARKVAULT_PLATFORM_SV_HOSTS.map(escapeRegExp).join('|')})$`, 'i'),
13
- /\.amazonaws\.com$/i,
14
- /\.cloudfront\.net$/i,
15
- ];
6
+ const DEFAULT_IDENTITY_BASE_URL = `https://${SPARKVAULT_IDENTITY_HOST}`;
16
7
  const noopLogger = {
17
8
  debug: () => undefined,
18
9
  info: () => undefined,
@@ -60,8 +51,10 @@ export function resolveMobileConfig(config) {
60
51
  received: identityAccountId,
61
52
  });
62
53
  }
63
- if (!identityAccountId.startsWith('acc_')) {
64
- throw new SparkVaultValidationError('identityAccountId must start with "acc_"', {
54
+ // The account id names Identity request paths: the platform's one id
55
+ // grammar keeps it a single path segment.
56
+ if (!isResourceIdOf('account', identityAccountId)) {
57
+ throw new SparkVaultValidationError('identityAccountId must be a SparkVault account id (acc_...)', {
65
58
  field: 'identityAccountId',
66
59
  received: identityAccountId,
67
60
  });
@@ -83,10 +76,10 @@ export function resolveMobileConfig(config) {
83
76
  backgroundTransfer: config.backgroundTransfer,
84
77
  backgroundUploader: config.backgroundUploader,
85
78
  logger: { ...noopLogger, ...config.logger },
86
- allowedDownloadHostPatterns: config.allowedDownloadHostPatterns ?? DEFAULT_ALLOWED_DOWNLOAD_HOST_PATTERNS,
79
+ allowedDownloadHostPatterns: config.allowedDownloadHostPatterns ?? [...SPARKVAULT_OWNED_HOST_PATTERNS],
87
80
  };
88
81
  }
89
82
  export function identityUrl(config, path) {
90
- return `${config.identityBaseUrl}/${config.identityAccountId}${path}`;
83
+ return `${config.identityBaseUrl}${apiPath `/${config.identityAccountId}`}${path}`;
91
84
  }
92
85
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,33 @@
1
+ /**
2
+ * Base64 and UTF-8 for the mobile SDK.
3
+ *
4
+ * The base64 codec is the platform's one copy, mirrored from
5
+ * `@sparkvault/app-sdk/encoding` into `./app-sdk/encoding.js`; the decoders here
6
+ * raise this SDK's validation error where that codec refuses a text. UTF-8 is
7
+ * converted here rather than with TextEncoder/TextDecoder, which the JavaScript
8
+ * engines of the React Native versions this SDK supports need not provide.
9
+ * Decoding UTF-8 is fatal: bytes that are not UTF-8 are refused, never read as
10
+ * invented characters.
11
+ */
12
+ import { bytesToBase64, bytesToBase64url } from './app-sdk/encoding.js';
13
+ export { bytesToBase64, bytesToBase64url };
14
+ /**
15
+ * Decode standard base64.
16
+ * @throws SparkVaultValidationError when the text is not base64
17
+ */
18
+ export declare function base64ToBytes(value: string): Uint8Array;
19
+ /**
20
+ * Decode base64url.
21
+ * @throws SparkVaultValidationError when the text is not base64url
22
+ */
23
+ export declare function base64UrlToBytes(value: string): Uint8Array;
1
24
  export declare function utf8ToBytes(value: string): Uint8Array;
2
- export declare function base64EncodeUtf8(value: string): string;
25
+ /**
26
+ * Decode UTF-8 by the WHATWG decoder's rules in fatal mode: a byte that cannot
27
+ * start a sequence, a sequence cut short, an overlong form, a surrogate or a
28
+ * code point past U+10FFFF refuses the whole input.
29
+ * @throws SparkVaultValidationError when the bytes are not UTF-8
30
+ */
3
31
  export declare function bytesToUtf8(bytes: Uint8Array): string;
4
- export declare function base64UrlToBytes(value: string): Uint8Array;
32
+ export declare function base64EncodeUtf8(value: string): string;
5
33
  export declare function base64UrlToUtf8(value: string): string;
6
- export declare function bytesToBase64(bytes: Uint8Array): string;
7
- export declare function base64ToBytes(value: string): Uint8Array;
package/dist/encoding.js CHANGED
@@ -1,5 +1,39 @@
1
- const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
2
- const BASE64_LOOKUP = new Map(Array.from(BASE64_ALPHABET).map((char, index) => [char, index]));
1
+ /**
2
+ * Base64 and UTF-8 for the mobile SDK.
3
+ *
4
+ * The base64 codec is the platform's one copy, mirrored from
5
+ * `@sparkvault/app-sdk/encoding` into `./app-sdk/encoding.js`; the decoders here
6
+ * raise this SDK's validation error where that codec refuses a text. UTF-8 is
7
+ * converted here rather than with TextEncoder/TextDecoder, which the JavaScript
8
+ * engines of the React Native versions this SDK supports need not provide.
9
+ * Decoding UTF-8 is fatal: bytes that are not UTF-8 are refused, never read as
10
+ * invented characters.
11
+ */
12
+ import { base64ToBytes as decodeBase64, base64urlToBytes as decodeBase64url, bytesToBase64, bytesToBase64url, } from './app-sdk/encoding.js';
13
+ import { SparkVaultValidationError } from './errors.js';
14
+ export { bytesToBase64, bytesToBase64url };
15
+ /**
16
+ * Decode standard base64.
17
+ * @throws SparkVaultValidationError when the text is not base64
18
+ */
19
+ export function base64ToBytes(value) {
20
+ const bytes = decodeBase64(value);
21
+ if (bytes === null) {
22
+ throw new SparkVaultValidationError('Invalid base64 input');
23
+ }
24
+ return bytes;
25
+ }
26
+ /**
27
+ * Decode base64url.
28
+ * @throws SparkVaultValidationError when the text is not base64url
29
+ */
30
+ export function base64UrlToBytes(value) {
31
+ const bytes = decodeBase64url(value);
32
+ if (bytes === null) {
33
+ throw new SparkVaultValidationError('Invalid base64url input');
34
+ }
35
+ return bytes;
36
+ }
3
37
  export function utf8ToBytes(value) {
4
38
  const bytes = [];
5
39
  for (let i = 0; i < value.length; i++) {
@@ -26,98 +60,70 @@ export function utf8ToBytes(value) {
26
60
  }
27
61
  return new Uint8Array(bytes);
28
62
  }
29
- export function base64EncodeUtf8(value) {
30
- return bytesToBase64(utf8ToBytes(value));
31
- }
63
+ /**
64
+ * Decode UTF-8 by the WHATWG decoder's rules in fatal mode: a byte that cannot
65
+ * start a sequence, a sequence cut short, an overlong form, a surrogate or a
66
+ * code point past U+10FFFF refuses the whole input.
67
+ * @throws SparkVaultValidationError when the bytes are not UTF-8
68
+ */
32
69
  export function bytesToUtf8(bytes) {
33
70
  let output = '';
34
- let i = 0;
35
- while (i < bytes.length) {
36
- const byte1 = bytes[i++];
37
- if (byte1 < 0x80) {
38
- output += String.fromCharCode(byte1);
71
+ let index = 0;
72
+ while (index < bytes.length) {
73
+ const lead = bytes[index++];
74
+ if (lead < 0x80) {
75
+ output += String.fromCharCode(lead);
39
76
  continue;
40
77
  }
41
- if (byte1 >= 0xc0 && byte1 < 0xe0) {
42
- const byte2 = bytes[i++];
43
- output += String.fromCharCode(((byte1 & 0x1f) << 6) | (byte2 & 0x3f));
44
- continue;
78
+ // The first continuation byte's range is what excludes overlong forms,
79
+ // surrogates and code points past U+10FFFF.
80
+ let continuations;
81
+ let codePoint;
82
+ let lower = 0x80;
83
+ let upper = 0xbf;
84
+ if (lead >= 0xc2 && lead <= 0xdf) {
85
+ continuations = 1;
86
+ codePoint = lead & 0x1f;
45
87
  }
46
- if (byte1 >= 0xe0 && byte1 < 0xf0) {
47
- const byte2 = bytes[i++];
48
- const byte3 = bytes[i++];
49
- output += String.fromCharCode(((byte1 & 0x0f) << 12) |
50
- ((byte2 & 0x3f) << 6) |
51
- (byte3 & 0x3f));
52
- continue;
88
+ else if (lead >= 0xe0 && lead <= 0xef) {
89
+ continuations = 2;
90
+ codePoint = lead & 0x0f;
91
+ if (lead === 0xe0)
92
+ lower = 0xa0;
93
+ if (lead === 0xed)
94
+ upper = 0x9f;
53
95
  }
54
- const byte2 = bytes[i++];
55
- const byte3 = bytes[i++];
56
- const byte4 = bytes[i++];
57
- const codePoint = (((byte1 & 0x07) << 18) |
58
- ((byte2 & 0x3f) << 12) |
59
- ((byte3 & 0x3f) << 6) |
60
- (byte4 & 0x3f)) - 0x10000;
61
- output += String.fromCharCode(0xd800 + (codePoint >> 10), 0xdc00 + (codePoint & 0x3ff));
96
+ else if (lead >= 0xf0 && lead <= 0xf4) {
97
+ continuations = 3;
98
+ codePoint = lead & 0x07;
99
+ if (lead === 0xf0)
100
+ lower = 0x90;
101
+ if (lead === 0xf4)
102
+ upper = 0x8f;
103
+ }
104
+ else {
105
+ throw new SparkVaultValidationError('Invalid UTF-8 input');
106
+ }
107
+ for (let seen = 0; seen < continuations; seen++) {
108
+ if (index >= bytes.length) {
109
+ throw new SparkVaultValidationError('Invalid UTF-8 input');
110
+ }
111
+ const next = bytes[index++];
112
+ if (next < lower || next > upper) {
113
+ throw new SparkVaultValidationError('Invalid UTF-8 input');
114
+ }
115
+ lower = 0x80;
116
+ upper = 0xbf;
117
+ codePoint = (codePoint << 6) | (next & 0x3f);
118
+ }
119
+ output += String.fromCodePoint(codePoint);
62
120
  }
63
121
  return output;
64
122
  }
65
- export function base64UrlToBytes(value) {
66
- return base64ToBytes(value);
123
+ export function base64EncodeUtf8(value) {
124
+ return bytesToBase64(utf8ToBytes(value));
67
125
  }
68
126
  export function base64UrlToUtf8(value) {
69
127
  return bytesToUtf8(base64UrlToBytes(value));
70
128
  }
71
- export function bytesToBase64(bytes) {
72
- let output = '';
73
- for (let i = 0; i < bytes.length; i += 3) {
74
- const byte1 = bytes[i];
75
- const byte2 = bytes[i + 1] ?? 0;
76
- const byte3 = bytes[i + 2] ?? 0;
77
- const triplet = (byte1 << 16) | (byte2 << 8) | byte3;
78
- output += BASE64_ALPHABET[(triplet >> 18) & 0x3f];
79
- output += BASE64_ALPHABET[(triplet >> 12) & 0x3f];
80
- output += i + 1 < bytes.length ? BASE64_ALPHABET[(triplet >> 6) & 0x3f] : '=';
81
- output += i + 2 < bytes.length ? BASE64_ALPHABET[triplet & 0x3f] : '=';
82
- }
83
- return output;
84
- }
85
- export function base64ToBytes(value) {
86
- const sanitized = value.replace(/\s/g, '').replace(/-/g, '+').replace(/_/g, '/');
87
- if (sanitized.length % 4 === 1) {
88
- throw new Error('Invalid base64 input');
89
- }
90
- const padded = sanitized.padEnd(Math.ceil(sanitized.length / 4) * 4, '=');
91
- // Preallocate the exact output size: 3 bytes per quartet, minus one byte per
92
- // '=' (a '=' in quartet positions 1-2 throws below, so counting every '=' is
93
- // safe). Multi-megabyte upload chunks decode through here, so per-byte array
94
- // growth is not acceptable.
95
- let paddingCount = 0;
96
- for (let i = 0; i < padded.length; i++) {
97
- if (padded[i] === '=')
98
- paddingCount++;
99
- }
100
- const bytes = new Uint8Array((padded.length / 4) * 3 - paddingCount);
101
- let outIndex = 0;
102
- for (let i = 0; i < padded.length; i += 4) {
103
- const c1 = padded[i];
104
- const c2 = padded[i + 1];
105
- const c3 = padded[i + 2];
106
- const c4 = padded[i + 3];
107
- const v1 = BASE64_LOOKUP.get(c1);
108
- const v2 = BASE64_LOOKUP.get(c2);
109
- const v3 = c3 === '=' ? 0 : BASE64_LOOKUP.get(c3);
110
- const v4 = c4 === '=' ? 0 : BASE64_LOOKUP.get(c4);
111
- if (v1 === undefined || v2 === undefined || v3 === undefined || v4 === undefined) {
112
- throw new Error('Invalid base64 input');
113
- }
114
- const triplet = (v1 << 18) | (v2 << 12) | (v3 << 6) | v4;
115
- bytes[outIndex++] = (triplet >> 16) & 0xff;
116
- if (c3 !== '=')
117
- bytes[outIndex++] = (triplet >> 8) & 0xff;
118
- if (c4 !== '=')
119
- bytes[outIndex++] = triplet & 0xff;
120
- }
121
- return bytes;
122
- }
123
129
  //# sourceMappingURL=encoding.js.map