@sparkvault/sdk-mobile 6.0.2 → 7.0.1

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 (156) 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/transfer-retry.d.ts +298 -0
  36. package/dist/app-sdk/transfer-retry.js +535 -0
  37. package/dist/app-sdk/transfer-retry.js.map +1 -0
  38. package/dist/app-sdk/transient.d.ts +23 -0
  39. package/dist/app-sdk/transient.js +29 -0
  40. package/dist/app-sdk/transient.js.map +1 -0
  41. package/dist/app-sdk/upload-publication.d.ts +81 -0
  42. package/dist/app-sdk/upload-publication.js +93 -0
  43. package/dist/app-sdk/upload-publication.js.map +1 -0
  44. package/dist/app-sdk/vat-refusal.d.ts +66 -0
  45. package/dist/app-sdk/vat-refusal.js +69 -0
  46. package/dist/app-sdk/vat-refusal.js.map +1 -0
  47. package/dist/billing.d.ts +9 -17
  48. package/dist/billing.js +14 -17
  49. package/dist/billing.js.map +1 -1
  50. package/dist/config.d.ts +33 -1
  51. package/dist/config.js +23 -17
  52. package/dist/config.js.map +1 -1
  53. package/dist/encoding.d.ts +30 -4
  54. package/dist/encoding.js +89 -83
  55. package/dist/encoding.js.map +1 -1
  56. package/dist/entropy.d.ts +25 -4
  57. package/dist/entropy.js +62 -15
  58. package/dist/entropy.js.map +1 -1
  59. package/dist/entropy.test-d.d.ts +40 -0
  60. package/dist/entropy.test-d.js +12 -0
  61. package/dist/entropy.test-d.js.map +1 -0
  62. package/dist/errors.d.ts +105 -21
  63. package/dist/errors.js +74 -28
  64. package/dist/errors.js.map +1 -1
  65. package/dist/folders.js +8 -7
  66. package/dist/folders.js.map +1 -1
  67. package/dist/http.d.ts +57 -15
  68. package/dist/http.js +125 -138
  69. package/dist/http.js.map +1 -1
  70. package/dist/identity-dialog.js +4 -30
  71. package/dist/identity-dialog.js.map +1 -1
  72. package/dist/identity-input.d.ts +39 -0
  73. package/dist/identity-input.js +59 -0
  74. package/dist/identity-input.js.map +1 -0
  75. package/dist/index.d.ts +8 -7
  76. package/dist/index.js +5 -4
  77. package/dist/index.js.map +1 -1
  78. package/dist/ingots.d.ts +77 -32
  79. package/dist/ingots.js +152 -136
  80. package/dist/ingots.js.map +1 -1
  81. package/dist/mutex.d.ts +1 -2
  82. package/dist/mutex.js +0 -6
  83. package/dist/mutex.js.map +1 -1
  84. package/dist/notify.d.ts +7 -2
  85. package/dist/notify.js +12 -15
  86. package/dist/notify.js.map +1 -1
  87. package/dist/notify.test-d.d.ts +3 -2
  88. package/dist/notify.test-d.js +1 -1
  89. package/dist/products/identity.d.ts +5 -0
  90. package/dist/products/identity.js +40 -35
  91. package/dist/products/identity.js.map +1 -1
  92. package/dist/request-path.d.ts +32 -0
  93. package/dist/request-path.js +35 -0
  94. package/dist/request-path.js.map +1 -0
  95. package/dist/sparks.js +16 -15
  96. package/dist/sparks.js.map +1 -1
  97. package/dist/tus.d.ts +198 -16
  98. package/dist/tus.js +816 -327
  99. package/dist/tus.js.map +1 -1
  100. package/dist/types.d.ts +183 -23
  101. package/dist/users.d.ts +12 -5
  102. package/dist/users.js +27 -19
  103. package/dist/users.js.map +1 -1
  104. package/dist/validation.d.ts +20 -5
  105. package/dist/validation.js +29 -27
  106. package/dist/validation.js.map +1 -1
  107. package/dist/vaults.d.ts +34 -83
  108. package/dist/vaults.js +64 -44
  109. package/dist/vaults.js.map +1 -1
  110. package/dist/vaults.test-d.d.ts +22 -0
  111. package/dist/vaults.test-d.js +14 -0
  112. package/dist/vaults.test-d.js.map +1 -0
  113. package/package.json +4 -2
  114. package/src/account.ts +16 -11
  115. package/src/app-sdk/email-address.js +94 -0
  116. package/src/app-sdk/encoding.js +250 -0
  117. package/src/app-sdk/entropy-contract.js +56 -0
  118. package/src/app-sdk/filename.js +174 -0
  119. package/src/app-sdk/forge-url.js +224 -0
  120. package/src/app-sdk/ids.js +71 -0
  121. package/src/app-sdk/ingot-access-log.js +51 -0
  122. package/src/app-sdk/phone.js +139 -0
  123. package/src/app-sdk/platform-domains.js +99 -0
  124. package/src/app-sdk/request-path.js +81 -0
  125. package/src/app-sdk/transfer-retry.js +563 -0
  126. package/src/app-sdk/transient.js +30 -0
  127. package/src/app-sdk/upload-publication.js +102 -0
  128. package/src/app-sdk/vat-refusal.js +74 -0
  129. package/src/billing.ts +16 -25
  130. package/src/config.ts +56 -23
  131. package/src/encoding.ts +91 -103
  132. package/src/entropy.test-d.ts +47 -0
  133. package/src/entropy.ts +86 -20
  134. package/src/errors.ts +135 -38
  135. package/src/folders.ts +8 -7
  136. package/src/http.ts +145 -150
  137. package/src/identity-dialog.tsx +4 -37
  138. package/src/identity-input.ts +70 -0
  139. package/src/index.ts +13 -3
  140. package/src/ingots.ts +225 -180
  141. package/src/mutex.ts +1 -8
  142. package/src/notify.test-d.ts +3 -1
  143. package/src/notify.ts +19 -17
  144. package/src/products/identity.ts +45 -39
  145. package/src/request-path.ts +45 -0
  146. package/src/sparks.ts +16 -13
  147. package/src/tus.ts +1054 -408
  148. package/src/types.ts +181 -23
  149. package/src/users.ts +27 -17
  150. package/src/validation.ts +30 -24
  151. package/src/vaults.test-d.ts +29 -0
  152. package/src/vaults.ts +87 -153
  153. package/dist/platform-domains.generated.d.ts +0 -2
  154. package/dist/platform-domains.generated.js +0 -16
  155. package/dist/platform-domains.generated.js.map +0 -1
  156. package/src/platform-domains.generated.ts +0 -17
@@ -0,0 +1,535 @@
1
+ // GENERATED FILE — do not edit by hand.
2
+ // Source of truth: packages/app-sdk/src/transfer-retry.js
3
+ // Regenerate with: pnpm --filter @sparkvault/sdk-mobile build
4
+ /**
5
+ * The transfer retry policy: what every first-party client does with the
6
+ * answer to a Forge transfer request (a tus chunk PATCH, an offset HEAD, the
7
+ * receipt PATCH at the final offset), in one place, so the Node uploader
8
+ * (forge-tus), the browser SDK (sdk-js bundles this module) and the mobile SDK
9
+ * (sdk-mobile carries a generated copy) read every answer the same way.
10
+ *
11
+ * It encodes what Google Drive, Google Cloud Storage, OneDrive, Dropbox and
12
+ * AWS document for resumable transfers:
13
+ * - only a request that got no answer, 408, 425, 429 or a 5xx is retried;
14
+ * every other 4xx is the server's verdict on the request;
15
+ * - `Retry-After` is a floor, never a ceiling;
16
+ * - retries back off exponentially, capped, with jitter;
17
+ * - before a chunk is sent again the server is asked for the offset it
18
+ * committed, because a request whose answer was lost may have landed;
19
+ * - a session that is gone (404, 410) is never resumed: only a new upload
20
+ * from byte 0 can succeed;
21
+ * - a transfer's retries are bounded in time and in failures in a row, and
22
+ * both bounds start over whenever the server confirms progress.
23
+ *
24
+ * Forge's own answers beyond the general rule:
25
+ * - the 409, in two kinds: one carrying `Upload-Offset` names where the
26
+ * session stands (continue from there), and one without it means another
27
+ * request holds the session's write lease, a chunk or the finalization
28
+ * (retry after its `Retry-After`);
29
+ * - a terminal answer (a 404, 410 or 412) names the code Core ended the
30
+ * upload with: its error envelope in `error.details.coreCode` for the
31
+ * refusals it words itself, and every one of them in `X-Upload-Refusal`,
32
+ * which is all a HEAD's answer, having no body, can carry; the body's code
33
+ * wins when both name one. The code decides the verdict: a full storage
34
+ * pool or a later upload that replaced this one is a refusal no new upload
35
+ * of the same file remedies, and any other code (the file the upload was
36
+ * for gone, a retired contract, a replacement base that moved on, a code
37
+ * this policy does not know) ends the session, which a new upload can
38
+ * start over;
39
+ * - a 503 carrying `Retry-After` to the receipt (the PATCH at the final
40
+ * offset) is a deferral: Forge holds every byte and is still publishing,
41
+ * and finishes without the client, so it is asked again, each time after a
42
+ * longer backoff, for as long as the idle limit allows, and never counts as
43
+ * a failure. Forge's deferral always names when to ask again; a 503 that
44
+ * does not is an ordinary failure.
45
+ *
46
+ * Isomorphic: no Node imports, no host APIs, no randomness. The jitter only
47
+ * has to spread retries, so it hashes the transfer and the attempt
48
+ * (docs/CONVENTIONS.md → Randomness).
49
+ */
50
+ import { isTransientHttpStatus } from './transient.js';
51
+ import { UPLOAD_FINAL_REFUSAL_CODES, UPLOAD_REFUSAL_HEADER } from './upload-publication.js';
52
+ /** What a client does after a transfer request, read from its answer. */
53
+ export const TRANSFER_VERDICT = Object.freeze({
54
+ /** 2xx: the request did what it asked. */
55
+ OK: 'ok',
56
+ /**
57
+ * No answer (a reset, a timeout), 408, 425, 429, a 5xx, or a 409 without
58
+ * `Upload-Offset` (another request holds the session's lease): back off,
59
+ * never sooner than `Retry-After`, then ask the server's offset before
60
+ * sending a chunk again.
61
+ */
62
+ RETRY: 'retry',
63
+ /** 409 with `Upload-Offset`: the session stands elsewhere; continue from that offset at once. */
64
+ RESYNC: 'resync',
65
+ /**
66
+ * 503 with `Retry-After` to the receipt: every byte is received and the
67
+ * publication is still running. Ask again after a backoff that grows with
68
+ * each deferral, never sooner than `Retry-After`; only the idle limit ends
69
+ * the asking, never the failures in a row.
70
+ */
71
+ DEFERRED: 'deferred',
72
+ /**
73
+ * 404 or 410 naming no code, or a 404, 410 or 412 naming a code of Core's
74
+ * that is not a final refusal (`coreCode` carries it): the session no
75
+ * longer exists; only a new upload from byte 0 can succeed.
76
+ */
77
+ GONE: 'gone',
78
+ /**
79
+ * Any other status, or a 404, 410 or 412 naming a final refusal of Core's
80
+ * (`coreCode`: UPLOAD_POOL_FULL, UPLOAD_SUPERSEDED): the server's verdict on
81
+ * this request, which repeating it, or a new upload of the same file,
82
+ * earns again.
83
+ */
84
+ REFUSED: 'refused'
85
+ });
86
+ /** The failures a transfer recovers from by trying again: the verdicts a budget's `failed` records. */
87
+ const RETRYABLE_VERDICTS = new Set([TRANSFER_VERDICT.RETRY, TRANSFER_VERDICT.RESYNC]);
88
+ /** RFC 8470: the server refused a request sent in TLS early data; the same request succeeds once the handshake completes. */
89
+ const TOO_EARLY = 425;
90
+ /** Forge's answer to a receipt whose publication it deferred. */
91
+ const SERVICE_UNAVAILABLE = 503;
92
+ /**
93
+ * The statuses a terminal answer naming Core's code arrives with: Forge's 410
94
+ * (UPLOAD_GONE, or Core's own relayed), a relayed 404, and Core's own 412.
95
+ */
96
+ const TERMINAL_STATUSES = new Set([404, 410, 412]);
97
+ /** Core's error codes are upper-case words joined by underscores; anything else names no code. */
98
+ const CORE_CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
99
+ const UPLOAD_OFFSET_HEADER = 'Upload-Offset';
100
+ const RETRY_AFTER_HEADER = 'Retry-After';
101
+ /** The tus `expiration` extension's header: when the session lapses, as an HTTP-date. */
102
+ export const UPLOAD_EXPIRES_HEADER = 'Upload-Expires';
103
+ /**
104
+ * How long a client waits for the answer to an offset HEAD or a receipt
105
+ * PATCH, whatever its other requests get: on a received upload, Forge answers
106
+ * either only after its own call to Core (up to 30 s) has published the
107
+ * upload or deferred it, so each gets twice that.
108
+ */
109
+ export const HEAD_AND_RECEIPT_TIMEOUT_MS = 60000;
110
+ /** How a retry's delay spreads past its backoff: up to this much more. */
111
+ const JITTER_SPAN_MS = 1000;
112
+ /** How long a transfer may sit without progress or a sign of life by default. */
113
+ const DEFAULT_IDLE_LIMIT_MS = 10 * 60 * 1000;
114
+ /** How many failures in a row, without progress between them, end a transfer by default. */
115
+ const DEFAULT_MAX_CONSECUTIVE_FAILURES = 8;
116
+ /**
117
+ * @typedef {'ok'|'retry'|'resync'|'deferred'|'gone'|'refused'} TransferVerdict
118
+ */
119
+ /**
120
+ * The verdicts a transfer recovers from by trying again that count as
121
+ * failures: the ones a budget's `failed` records.
122
+ * @typedef {'retry'|'resync'} RetryableTransferVerdict
123
+ */
124
+ /**
125
+ * @typedef {Object} TransferOutcome
126
+ * @property {TransferVerdict} verdict - What the client does next
127
+ * @property {number|null} retryAfterMs - The answer's `Retry-After`, in ms from
128
+ * the answer (an HTTP-date already past reads as 0); null when absent or unreadable
129
+ * @property {number|null} offset - The answer's `Upload-Offset`; null when absent or unreadable
130
+ * @property {string|null} coreCode - The code Core ended the upload with, as a
131
+ * terminal answer named it (a 404, 410 or 412: its body's details or, those
132
+ * naming none, its `X-Upload-Refusal`): REFUSED for a final refusal
133
+ * (`UPLOAD_POOL_FULL`, `UPLOAD_SUPERSEDED`), GONE for any other code
134
+ * (`NOT_FOUND`, `UPLOAD_PROTOCOL_RETIRED`, a 412's, one this policy does not
135
+ * know); null when the answer names none
136
+ */
137
+ /**
138
+ * Read the answer to a transfer request the way the policy does.
139
+ *
140
+ * @param {Object} response
141
+ * @param {number} response.status - The HTTP status; 0 when the request got no answer
142
+ * @param {((name: string) => string|null)|null} [response.headers] - Reads a
143
+ * response header by name: a fetch `Headers`' `get` or an XHR's
144
+ * `getResponseHeader`, bound to its owner (`(name) => xhr.getResponseHeader(name)`)
145
+ * @param {unknown} [response.details] - The `details` of the error envelope the
146
+ * answer's body carries (`{ error: { code, message, details } }`), as the
147
+ * runtime's one reader of a failed answer parsed it from the whole body;
148
+ * where Forge names the refusal Core decided (`coreCode`), ahead of the
149
+ * answer's `X-Upload-Refusal`. Anything that is not an object names none.
150
+ * @param {boolean} [response.receipt] - The request was the receipt: the PATCH
151
+ * at the final offset, whose 503 carrying `Retry-After` is a deferral
152
+ * @param {number} [response.now] - When the answer arrived (epoch ms), which an
153
+ * HTTP-date `Retry-After` counts from
154
+ * @returns {TransferOutcome}
155
+ * @throws {TypeError} When the status is not a whole number, headers is not a
156
+ * function, or receipt is not a boolean
157
+ */
158
+ export function classifyTransferResponse({ status, headers = null, details = null, receipt = false, now = Date.now() }) {
159
+ if (!Number.isSafeInteger(status) || status < 0) {
160
+ throw new TypeError(`A transfer answer's status is a whole number (0 for no answer), not ${status}`);
161
+ }
162
+ if (headers !== null && typeof headers !== 'function') {
163
+ throw new TypeError('A transfer answer\'s headers are read through a function from a header name to its value');
164
+ }
165
+ if (typeof receipt !== 'boolean') {
166
+ throw new TypeError(`Whether a transfer request was the receipt is a boolean, not ${receipt}`);
167
+ }
168
+ if (status === 0) {
169
+ return { verdict: TRANSFER_VERDICT.RETRY, retryAfterMs: null, offset: null, coreCode: null };
170
+ }
171
+ const header = (name) => (headers === null ? null : headers(name));
172
+ const offset = readCount(header(UPLOAD_OFFSET_HEADER));
173
+ const coreCode = TERMINAL_STATUSES.has(status)
174
+ ? coreCodeIn(details) ?? coreCodeOf(header(UPLOAD_REFUSAL_HEADER))
175
+ : null;
176
+ const retryAfterMs = readRetryAfterMs(header(RETRY_AFTER_HEADER), now);
177
+ return {
178
+ verdict: verdictFor(status, { offset, coreCode, deferral: receipt && retryAfterMs !== null }),
179
+ retryAfterMs,
180
+ offset,
181
+ coreCode
182
+ };
183
+ }
184
+ /**
185
+ * The verdict on a status that came with an answer: `offset` its readable
186
+ * `Upload-Offset`, `coreCode` the code of Core's a terminal answer names,
187
+ * `deferral` whether it answered the receipt with a readable `Retry-After`.
188
+ */
189
+ function verdictFor(status, { offset, coreCode, deferral }) {
190
+ if (status >= 200 && status < 300)
191
+ return TRANSFER_VERDICT.OK;
192
+ if (status === 409)
193
+ return offset === null ? TRANSFER_VERDICT.RETRY : TRANSFER_VERDICT.RESYNC;
194
+ if (coreCode !== null)
195
+ return UPLOAD_FINAL_REFUSAL_CODES.includes(coreCode) ? TRANSFER_VERDICT.REFUSED : TRANSFER_VERDICT.GONE;
196
+ if (status === 404 || status === 410)
197
+ return TRANSFER_VERDICT.GONE;
198
+ if (deferral && status === SERVICE_UNAVAILABLE)
199
+ return TRANSFER_VERDICT.DEFERRED;
200
+ if (status === TOO_EARLY || isTransientHttpStatus(status))
201
+ return TRANSFER_VERDICT.RETRY;
202
+ return TRANSFER_VERDICT.REFUSED;
203
+ }
204
+ /** The code an error envelope's details name (`coreCode`), or null. */
205
+ function coreCodeIn(details) {
206
+ return details && typeof details === 'object' ? coreCodeOf(details.coreCode) : null;
207
+ }
208
+ /** A value that is one of Core's codes (surrounding whitespace allowed), or null. */
209
+ function coreCodeOf(value) {
210
+ if (typeof value !== 'string')
211
+ return null;
212
+ const code = value.trim();
213
+ return CORE_CODE.test(code) ? code : null;
214
+ }
215
+ /** A header's non-negative whole number (surrounding whitespace allowed), or null. */
216
+ function readCount(value) {
217
+ if (typeof value !== 'string')
218
+ return null;
219
+ const text = value.trim();
220
+ if (!/^\d+$/.test(text))
221
+ return null;
222
+ const count = Number(text);
223
+ return Number.isSafeInteger(count) ? count : null;
224
+ }
225
+ /** RFC 9110 §10.2.3: `Retry-After` is delay-seconds or an HTTP-date. */
226
+ function readRetryAfterMs(value, now) {
227
+ const seconds = readCount(value);
228
+ if (seconds !== null) {
229
+ const ms = seconds * 1000;
230
+ return Number.isSafeInteger(ms) ? ms : null;
231
+ }
232
+ const at = typeof value === 'string' ? parseHttpDate(value.trim(), now) : null;
233
+ return at === null ? null : Math.max(0, at - now);
234
+ }
235
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
236
+ const DAY_NAME = '(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)';
237
+ const LONG_DAY_NAME = '(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)';
238
+ const MONTH = `(${MONTHS.join('|')})`;
239
+ const TIME_OF_DAY = '(\\d{2}):(\\d{2}):(\\d{2})';
240
+ /** IMF-fixdate, what every current server sends: `Sun, 06 Nov 1994 08:49:37 GMT`. */
241
+ const IMF_FIXDATE = new RegExp(`^${DAY_NAME}, (\\d{2}) ${MONTH} (\\d{4}) ${TIME_OF_DAY} GMT$`);
242
+ /** The obsolete RFC 850 form a recipient still accepts: `Sunday, 06-Nov-94 08:49:37 GMT`. */
243
+ const RFC850_DATE = new RegExp(`^${LONG_DAY_NAME}, (\\d{2})-${MONTH}-(\\d{2}) ${TIME_OF_DAY} GMT$`);
244
+ /** The obsolete asctime form a recipient still accepts: `Sun Nov 6 08:49:37 1994`. */
245
+ const ASCTIME_DATE = new RegExp(`^${DAY_NAME} ${MONTH} ( \\d|\\d{2}) ${TIME_OF_DAY} (\\d{4})$`);
246
+ /**
247
+ * An HTTP-date (RFC 9110 §5.6.7, all three forms a recipient must accept) as
248
+ * epoch ms, or null when it is not one or names no real moment.
249
+ *
250
+ * @param {string} text
251
+ * @param {number} now - epoch ms, which places an RFC 850 two-digit year
252
+ * @returns {number|null}
253
+ */
254
+ function parseHttpDate(text, now) {
255
+ let match = IMF_FIXDATE.exec(text);
256
+ if (match) {
257
+ const [, day, month, year, hours, minutes, seconds] = match;
258
+ return utcMs(Number(year), month, Number(day), hours, minutes, seconds);
259
+ }
260
+ match = RFC850_DATE.exec(text);
261
+ if (match) {
262
+ const [, day, month, shortYear, hours, minutes, seconds] = match;
263
+ return utcMs(rfc850Year(Number(shortYear), now), month, Number(day), hours, minutes, seconds);
264
+ }
265
+ match = ASCTIME_DATE.exec(text);
266
+ if (match) {
267
+ const [, month, day, hours, minutes, seconds, year] = match;
268
+ return utcMs(Number(year), month, Number(day.trim()), hours, minutes, seconds);
269
+ }
270
+ return null;
271
+ }
272
+ /** RFC 9110: a two-digit year more than 50 years ahead is the latest past year with those digits. */
273
+ function rfc850Year(shortYear, now) {
274
+ const currentYear = new Date(now).getUTCFullYear();
275
+ const year = currentYear - (currentYear % 100) + shortYear;
276
+ return year > currentYear + 50 ? year - 100 : year;
277
+ }
278
+ /** The UTC instant of a calendar date and time of day, or null for a date or time that does not exist. */
279
+ function utcMs(year, monthName, day, hours, minutes, seconds) {
280
+ const monthIndex = MONTHS.indexOf(monthName);
281
+ const [h, m, s] = [Number(hours), Number(minutes), Number(seconds)];
282
+ // A leap second (60) is a real time of day; the instant rolls into the next minute.
283
+ if (h > 23 || m > 59 || s > 60)
284
+ return null;
285
+ const date = new Date(0);
286
+ date.setUTCFullYear(year, monthIndex, day);
287
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== monthIndex || date.getUTCDate() !== day) {
288
+ return null;
289
+ }
290
+ return date.getTime() + ((h * 60 + m) * 60 + s) * 1000;
291
+ }
292
+ /**
293
+ * When a session lapses, read from its `Upload-Expires` (an HTTP-date).
294
+ *
295
+ * @param {unknown} headerValue - The header as received (null when absent)
296
+ * @param {number} [now] - epoch ms, which places an obsolete two-digit year
297
+ * @returns {number|null} epoch ms, or null when absent or not an HTTP-date
298
+ */
299
+ export function parseUploadExpires(headerValue, now = Date.now()) {
300
+ return typeof headerValue === 'string' ? parseHttpDate(headerValue.trim(), now) : null;
301
+ }
302
+ const FNV_OFFSET_BASIS = 0x811c9dc5;
303
+ const FNV_PRIME = 0x01000193;
304
+ /**
305
+ * 32-bit FNV-1a over the text's UTF-8 bytes: the jitter's hash. A retry's
306
+ * delay only has to spread, so it hashes what identifies the retry and draws
307
+ * nothing (docs/CONVENTIONS.md → Randomness).
308
+ *
309
+ * @param {string} text
310
+ * @returns {number} An unsigned 32-bit integer
311
+ * @throws {TypeError} When the text is not a string
312
+ */
313
+ export function fnv1a32(text) {
314
+ if (typeof text !== 'string') {
315
+ throw new TypeError(`fnv1a32 hashes a string, not ${typeof text}`);
316
+ }
317
+ let hash = FNV_OFFSET_BASIS;
318
+ const mix = (byte) => {
319
+ hash = Math.imul(hash ^ byte, FNV_PRIME) >>> 0;
320
+ };
321
+ for (const character of text) {
322
+ let point = character.codePointAt(0);
323
+ // A lone surrogate has no UTF-8 form; it encodes as U+FFFD, as TextEncoder does.
324
+ if (point >= 0xd800 && point <= 0xdfff)
325
+ point = 0xfffd;
326
+ if (point < 0x80) {
327
+ mix(point);
328
+ }
329
+ else if (point < 0x800) {
330
+ mix(0xc0 | (point >> 6));
331
+ mix(0x80 | (point & 0x3f));
332
+ }
333
+ else if (point < 0x10000) {
334
+ mix(0xe0 | (point >> 12));
335
+ mix(0x80 | ((point >> 6) & 0x3f));
336
+ mix(0x80 | (point & 0x3f));
337
+ }
338
+ else {
339
+ mix(0xf0 | (point >> 18));
340
+ mix(0x80 | ((point >> 12) & 0x3f));
341
+ mix(0x80 | ((point >> 6) & 0x3f));
342
+ mix(0x80 | (point & 0x3f));
343
+ }
344
+ }
345
+ return hash;
346
+ }
347
+ /** Transfers this runtime has keyed by `transferJitterKey`: with the start time, what tells them apart. */
348
+ let transfersKeyed = 0;
349
+ /**
350
+ * What a transfer's retries spread by when no session URL keys them yet (a
351
+ * session create) or ever (a download, whose link carries its token): the
352
+ * scope (the create endpoint, or the kind of transfer), when the transfer
353
+ * started, and this runtime's count of keyed transfers, which tell
354
+ * concurrent transfers apart. A transfer's token is a credential and never
355
+ * feeds anything observable such as a delay, so no part of the key derives
356
+ * from it (docs/CONVENTIONS.md → Randomness).
357
+ *
358
+ * @param {string} scope - The endpoint or the kind of transfer
359
+ * @returns {string} The key, for every retry of this one transfer
360
+ * @throws {TypeError} For a scope that is not a non-empty string
361
+ */
362
+ export function transferJitterKey(scope) {
363
+ if (typeof scope !== 'string' || scope.length === 0) {
364
+ throw new TypeError('A transfer jitter key needs the scope its transfer runs in');
365
+ }
366
+ transfersKeyed += 1;
367
+ return `${scope}#${Date.now()}-${transfersKeyed}`;
368
+ }
369
+ /**
370
+ * How long to wait before retry `attempt` of a transfer: `baseMs` doubling per
371
+ * attempt, capped at `capMs`, plus a jitter in [0, 1000) ms hashed from the
372
+ * transfer and the attempt, and never less than the server's `Retry-After`.
373
+ * The same transfer and attempt always wait the same; different transfers
374
+ * failing together spread out.
375
+ *
376
+ * @param {Object} options
377
+ * @param {number} options.attempt - Which retry this is, from 1
378
+ * @param {number|null} [options.retryAfterMs] - The answer's `Retry-After`: a floor
379
+ * @param {number} [options.baseMs] - The first retry's backoff
380
+ * @param {number} [options.capMs] - The longest backoff, before jitter
381
+ * @param {string} options.jitterKey - What identifies the transfer (its session
382
+ * URL, or a `transferJitterKey`)
383
+ * @returns {number} ms
384
+ * @throws {TypeError} For an attempt that is not a whole number from 1, a
385
+ * missing jitter key, or a delay bound that is not a positive number of ms
386
+ */
387
+ export function retryDelayMs({ attempt, retryAfterMs = null, baseMs = 1000, capMs = 32000, jitterKey }) {
388
+ if (!Number.isSafeInteger(attempt) || attempt < 1) {
389
+ throw new TypeError(`A retry's attempt is a whole number from 1, not ${attempt}`);
390
+ }
391
+ if (typeof jitterKey !== 'string' || jitterKey.length === 0) {
392
+ throw new TypeError('A retry delay needs the key its transfer spreads by');
393
+ }
394
+ if (!isDuration(baseMs) || baseMs === 0 || !isDuration(capMs) || capMs < baseMs) {
395
+ throw new TypeError(`A retry backs off from a positive base (${baseMs} ms) up to a cap no smaller (${capMs} ms)`);
396
+ }
397
+ if (retryAfterMs !== null && !isDuration(retryAfterMs)) {
398
+ throw new TypeError(`A Retry-After floor is a non-negative number of ms, not ${retryAfterMs}`);
399
+ }
400
+ const backoff = Math.min(capMs, baseMs * 2 ** (attempt - 1));
401
+ const jitter = Math.floor((fnv1a32(`${jitterKey}|${attempt}`) / 2 ** 32) * JITTER_SPAN_MS);
402
+ return Math.max(backoff + jitter, retryAfterMs ?? 0);
403
+ }
404
+ function isDuration(ms) {
405
+ return Number.isFinite(ms) && ms >= 0;
406
+ }
407
+ /**
408
+ * @typedef {Object} TransferBudget
409
+ * @property {() => void} progressed - The server confirmed progress (bytes
410
+ * landed, or the offset it names moved forward): the failures in a row and
411
+ * the idle clock start over
412
+ * @property {() => void} alive - A sign of life the server has not confirmed
413
+ * (bytes are leaving, or the device is back online): the idle clock starts
414
+ * over and the failures in a row stand, so attempts that die before the
415
+ * server confirms anything still end the transfer
416
+ * @property {(verdict: RetryableTransferVerdict, retryAfterMs?: number|null) => boolean} failed -
417
+ * Records a retryable answer (RETRY or RESYNC) and says whether another
418
+ * attempt is allowed: the failures in a row stay under the max, and the
419
+ * attempt `retryAfterMs` allows would start inside the idle limit, so a
420
+ * transfer never sleeps on a Retry-After longer than its budget. The limit
421
+ * bounds when a retry is decided; a retry decided inside it still waits its
422
+ * own backoff, so a transfer ends within one backoff of the limit.
423
+ * @property {(retryAfterMs?: number|null) => boolean} deferred - Records a
424
+ * DEFERRED answer and says whether to ask again: only the idle limit bounds
425
+ * it, as `failed` applies it, because a deferral is the server at work,
426
+ * never a failure, so the failures in a row stand
427
+ * @property {number} consecutiveFailures - Retryable failures since the last progress
428
+ * @property {number} attempt - Failures and deferrals since the last progress:
429
+ * the attempt the next wait's delay is computed for, so each deferral backs
430
+ * the next ask off as a failure does, sparing a Core that is struggling to
431
+ * publish
432
+ * @property {number} msSinceProgress - ms since the last progress or sign of life
433
+ */
434
+ /**
435
+ * The retry budget of one transfer: retries are bounded by failures in a row
436
+ * and by time without progress, deferrals by time alone, and both bounds
437
+ * start over whenever the server confirms progress, so a slow transfer that
438
+ * keeps landing bytes is never cut off while a dead one ends in bounded time.
439
+ *
440
+ * @param {Object} [options]
441
+ * @param {number} [options.idleLimitMs] - How long the transfer may go without
442
+ * progress or a sign of life
443
+ * @param {number} [options.maxConsecutiveFailures] - Failures in a row that end the transfer
444
+ * @param {() => number} [options.now] - The clock, epoch ms
445
+ * @returns {TransferBudget}
446
+ * @throws {TypeError} For a limit that is not a positive number, or a clock that is not a function
447
+ */
448
+ export function createTransferBudget({ idleLimitMs = DEFAULT_IDLE_LIMIT_MS, maxConsecutiveFailures = DEFAULT_MAX_CONSECUTIVE_FAILURES, now = () => Date.now() } = {}) {
449
+ if (!isDuration(idleLimitMs) || idleLimitMs === 0) {
450
+ throw new TypeError(`A transfer's idle limit is a positive number of ms, not ${idleLimitMs}`);
451
+ }
452
+ if (!Number.isSafeInteger(maxConsecutiveFailures) || maxConsecutiveFailures < 1) {
453
+ throw new TypeError(`A transfer's failure limit is a whole number from 1, not ${maxConsecutiveFailures}`);
454
+ }
455
+ if (typeof now !== 'function') {
456
+ throw new TypeError('A transfer budget reads time from a clock function');
457
+ }
458
+ let consecutiveFailures = 0;
459
+ let deferrals = 0;
460
+ let lastSignOfLifeAt = now();
461
+ const msIdle = () => now() - lastSignOfLifeAt;
462
+ const startsInsideIdleLimit = (retryAfterMs) => {
463
+ if (retryAfterMs !== null && !isDuration(retryAfterMs)) {
464
+ throw new TypeError(`A Retry-After floor is a non-negative number of ms, not ${retryAfterMs}`);
465
+ }
466
+ return msIdle() + (retryAfterMs ?? 0) < idleLimitMs;
467
+ };
468
+ return Object.freeze({
469
+ progressed() {
470
+ consecutiveFailures = 0;
471
+ deferrals = 0;
472
+ lastSignOfLifeAt = now();
473
+ },
474
+ alive() {
475
+ lastSignOfLifeAt = now();
476
+ },
477
+ failed(verdict, retryAfterMs = null) {
478
+ if (!RETRYABLE_VERDICTS.has(verdict)) {
479
+ throw new TypeError(`A transfer budget records retryable answers only, not '${verdict}'`);
480
+ }
481
+ const allowed = startsInsideIdleLimit(retryAfterMs);
482
+ consecutiveFailures += 1;
483
+ return allowed && consecutiveFailures < maxConsecutiveFailures;
484
+ },
485
+ deferred(retryAfterMs = null) {
486
+ const allowed = startsInsideIdleLimit(retryAfterMs);
487
+ deferrals += 1;
488
+ return allowed;
489
+ },
490
+ get consecutiveFailures() {
491
+ return consecutiveFailures;
492
+ },
493
+ get attempt() {
494
+ return consecutiveFailures + deferrals;
495
+ },
496
+ get msSinceProgress() {
497
+ return msIdle();
498
+ }
499
+ });
500
+ }
501
+ /** Why an offset the server names cannot be resumed from. */
502
+ export const RESUME_OFFSET_PROBLEM = Object.freeze({
503
+ /** Not a non-negative whole number of bytes. */
504
+ INVALID: 'invalid',
505
+ /** Past the upload's length: resuming from it would report a partial upload complete. */
506
+ BEYOND_TOTAL: 'beyond_total',
507
+ /** Inside a chunk: a session commits whole chunks only, so the answer is corrupt. */
508
+ MISALIGNED: 'misaligned'
509
+ });
510
+ /**
511
+ * Why a session offset the server named (a HEAD's, or a 409's `Upload-Offset`)
512
+ * cannot be resumed from, or null when it can. Forge commits whole chunks, so
513
+ * a session stands at a multiple of its chunk size or at its total.
514
+ *
515
+ * @param {number|null} offset
516
+ * @param {Object} geometry
517
+ * @param {number} geometry.totalSize - The upload's length (a non-negative whole number)
518
+ * @param {number} geometry.chunkSize - The session's chunk size (a positive whole number)
519
+ * @returns {'invalid'|'beyond_total'|'misaligned'|null}
520
+ * @throws {TypeError} When the length is not a non-negative whole number or
521
+ * the chunk size not a positive one
522
+ */
523
+ export function resumeOffsetProblem(offset, { totalSize, chunkSize }) {
524
+ if (!Number.isSafeInteger(totalSize) || totalSize < 0 || !Number.isSafeInteger(chunkSize) || chunkSize < 1) {
525
+ throw new TypeError(`An upload's geometry is a whole length and a positive chunk size, not ${totalSize} and ${chunkSize}`);
526
+ }
527
+ if (!Number.isSafeInteger(offset) || offset < 0)
528
+ return RESUME_OFFSET_PROBLEM.INVALID;
529
+ if (offset > totalSize)
530
+ return RESUME_OFFSET_PROBLEM.BEYOND_TOTAL;
531
+ if (offset !== totalSize && offset % chunkSize !== 0)
532
+ return RESUME_OFFSET_PROBLEM.MISALIGNED;
533
+ return null;
534
+ }
535
+ //# sourceMappingURL=transfer-retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer-retry.js","sourceRoot":"","sources":["../../src/app-sdk/transfer-retry.js"],"names":[],"mappings":"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"}
@@ -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"}