@useupup/core 3.3.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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +8 -0
  3. package/README.md +75 -0
  4. package/dist/browser-SXR426OQ.cjs +10 -0
  5. package/dist/browser-WD6T5ABJ.js +1 -0
  6. package/dist/chunk-2H5ICKFT.cjs +122 -0
  7. package/dist/chunk-6HF3I2JS.cjs +39 -0
  8. package/dist/chunk-6S5DQDQH.js +212 -0
  9. package/dist/chunk-BMQC27OE.cjs +92 -0
  10. package/dist/chunk-BSGQGINR.cjs +219 -0
  11. package/dist/chunk-CPKZ2CXD.cjs +179 -0
  12. package/dist/chunk-DTKDIMKT.cjs +573 -0
  13. package/dist/chunk-DYE5XJ6Z.cjs +2134 -0
  14. package/dist/chunk-E3ZCNOXG.cjs +74 -0
  15. package/dist/chunk-FEQY2WYY.cjs +2377 -0
  16. package/dist/chunk-FMURBCZ7.js +120 -0
  17. package/dist/chunk-FYQQRULL.js +37 -0
  18. package/dist/chunk-GE4LIVXP.js +133 -0
  19. package/dist/chunk-HFR5HZJZ.js +56 -0
  20. package/dist/chunk-I7ULPCA2.js +72 -0
  21. package/dist/chunk-MHYXDLZR.js +569 -0
  22. package/dist/chunk-N2SLHVAW.cjs +61 -0
  23. package/dist/chunk-OOPDEOTM.js +169 -0
  24. package/dist/chunk-PMBR3OUW.js +2359 -0
  25. package/dist/chunk-QK7HZIVY.cjs +144 -0
  26. package/dist/chunk-T2AQHY7V.cjs +59 -0
  27. package/dist/chunk-TRCP5FUR.js +90 -0
  28. package/dist/chunk-U7KL5M2H.cjs +374 -0
  29. package/dist/chunk-UXNPM54W.js +2108 -0
  30. package/dist/chunk-VV6QCQLL.js +365 -0
  31. package/dist/chunk-ZMS2HXLB.js +56 -0
  32. package/dist/context-shapes-123dd3b6.d.cts +207 -0
  33. package/dist/context-shapes-27b24c8f.d.ts +207 -0
  34. package/dist/contracts-pipeline-cf177270.d.cts +78 -0
  35. package/dist/contracts-pipeline-cf177270.d.ts +78 -0
  36. package/dist/contracts-strategies-378a16b7.d.cts +168 -0
  37. package/dist/contracts-strategies-378a16b7.d.ts +168 -0
  38. package/dist/contracts.cjs +181 -0
  39. package/dist/contracts.d.cts +11 -0
  40. package/dist/contracts.d.ts +11 -0
  41. package/dist/contracts.js +4 -0
  42. package/dist/core-141335ac.d.ts +651 -0
  43. package/dist/core-bea2ceca.d.cts +651 -0
  44. package/dist/create-worker-provider-AA5SRII6.js +58 -0
  45. package/dist/create-worker-provider-KJNUSSTW.cjs +60 -0
  46. package/dist/eligibility-ONLNJ6FB.cjs +10 -0
  47. package/dist/eligibility-ULFC3E4G.js +8 -0
  48. package/dist/error-code-map-9c44a425.d.ts +62 -0
  49. package/dist/error-code-map-dd5becfa.d.cts +62 -0
  50. package/dist/i18n/index.cjs +90 -0
  51. package/dist/i18n/index.d.cts +3 -0
  52. package/dist/i18n/index.d.ts +3 -0
  53. package/dist/i18n/index.js +1 -0
  54. package/dist/index.cjs +1770 -0
  55. package/dist/index.d.cts +280 -0
  56. package/dist/index.d.ts +280 -0
  57. package/dist/index.js +1589 -0
  58. package/dist/internal.cjs +2407 -0
  59. package/dist/internal.d.cts +595 -0
  60. package/dist/internal.d.ts +595 -0
  61. package/dist/internal.js +2275 -0
  62. package/dist/pipeline/index.cjs +10 -0
  63. package/dist/pipeline/index.d.cts +11 -0
  64. package/dist/pipeline/index.d.ts +11 -0
  65. package/dist/pipeline/index.js +1 -0
  66. package/dist/pipeline-worker.cjs +199 -0
  67. package/dist/pipeline-worker.d.cts +2 -0
  68. package/dist/pipeline-worker.d.ts +2 -0
  69. package/dist/pipeline-worker.js +197 -0
  70. package/dist/resolve-locale-81feef21.d.ts +20 -0
  71. package/dist/resolve-locale-9cb63600.d.cts +20 -0
  72. package/dist/resolve-theme-01758105.d.ts +53 -0
  73. package/dist/resolve-theme-66cf8d98.d.cts +53 -0
  74. package/dist/server-mode-drive-controller-b5f8ee12.d.cts +180 -0
  75. package/dist/server-mode-drive-controller-b5f8ee12.d.ts +180 -0
  76. package/dist/steps/compress.cjs +66 -0
  77. package/dist/steps/compress.d.cts +10 -0
  78. package/dist/steps/compress.d.ts +10 -0
  79. package/dist/steps/compress.js +64 -0
  80. package/dist/steps/exif.cjs +44 -0
  81. package/dist/steps/exif.d.cts +5 -0
  82. package/dist/steps/exif.d.ts +5 -0
  83. package/dist/steps/exif.js +42 -0
  84. package/dist/steps/hash.cjs +44 -0
  85. package/dist/steps/hash.d.cts +5 -0
  86. package/dist/steps/hash.d.ts +5 -0
  87. package/dist/steps/hash.js +42 -0
  88. package/dist/steps/heic.cjs +59 -0
  89. package/dist/steps/heic.d.cts +5 -0
  90. package/dist/steps/heic.d.ts +5 -0
  91. package/dist/steps/heic.js +57 -0
  92. package/dist/steps/thumbnail.cjs +54 -0
  93. package/dist/steps/thumbnail.d.cts +10 -0
  94. package/dist/steps/thumbnail.d.ts +10 -0
  95. package/dist/steps/thumbnail.js +52 -0
  96. package/dist/strategies/index.cjs +82 -0
  97. package/dist/strategies/index.d.cts +187 -0
  98. package/dist/strategies/index.d.ts +187 -0
  99. package/dist/strategies/index.js +60 -0
  100. package/dist/strategies/tus-upload.cjs +14 -0
  101. package/dist/strategies/tus-upload.d.cts +15 -0
  102. package/dist/strategies/tus-upload.d.ts +15 -0
  103. package/dist/strategies/tus-upload.js +5 -0
  104. package/dist/theme/index.cjs +38 -0
  105. package/dist/theme/index.d.cts +3 -0
  106. package/dist/theme/index.d.ts +3 -0
  107. package/dist/theme/index.js +1 -0
  108. package/dist/theme-store-2e56a674.d.cts +39 -0
  109. package/dist/theme-store-f13e0315.d.ts +39 -0
  110. package/dist/token-endpoint-5fdb4a35.d.ts +20 -0
  111. package/dist/token-endpoint-a7d15ab6.d.cts +20 -0
  112. package/dist/ui-translations-c5d00392.d.cts +455 -0
  113. package/dist/ui-translations-c5d00392.d.ts +455 -0
  114. package/dist/uploader-props-72242002.d.ts +340 -0
  115. package/dist/uploader-props-7f956e60.d.cts +340 -0
  116. package/dist/vars-d3421f6c.d.cts +240 -0
  117. package/dist/vars-d3421f6c.d.ts +240 -0
  118. package/package.json +182 -0
@@ -0,0 +1,573 @@
1
+ 'use strict';
2
+
3
+ var chunkCPKZ2CXD_cjs = require('./chunk-CPKZ2CXD.cjs');
4
+ var chunkQK7HZIVY_cjs = require('./chunk-QK7HZIVY.cjs');
5
+
6
+ // src/strategies/server-credentials.ts
7
+ function operationForPath(path) {
8
+ if (path.startsWith("/presign")) return "presign";
9
+ if (path.startsWith("/multipart/init")) return "multipart-init";
10
+ if (path.startsWith("/multipart/sign-part")) return "multipart-sign-part";
11
+ if (path.startsWith("/multipart/complete")) return "multipart-complete";
12
+ if (path.startsWith("/multipart/abort")) return "multipart-abort";
13
+ if (path.startsWith("/multipart/resume")) return "multipart-resume";
14
+ return "upload";
15
+ }
16
+ var ServerCredentials = class {
17
+ constructor(options) {
18
+ this.serverUrl = options.serverUrl.replace(/\/$/, "");
19
+ this.headers = {
20
+ "Content-Type": "application/json",
21
+ ...options.headers
22
+ };
23
+ }
24
+ async post(path, body) {
25
+ const response = await fetch(`${this.serverUrl}${path}`, {
26
+ method: "POST",
27
+ headers: this.headers,
28
+ body: JSON.stringify(body)
29
+ });
30
+ if (!response.ok) {
31
+ const responseBody = await response.text().catch(() => "");
32
+ const err = chunkQK7HZIVY_cjs.uploadErrorFromResponse({
33
+ status: response.status,
34
+ statusText: response.statusText,
35
+ body: responseBody,
36
+ kind: "storage",
37
+ operation: operationForPath(path)
38
+ });
39
+ throw err;
40
+ }
41
+ return response.json();
42
+ }
43
+ async getPresignedUrl(file) {
44
+ return this.post("/presign", {
45
+ name: file.name,
46
+ size: file.size,
47
+ type: file.type,
48
+ metadata: file.metadata ?? {}
49
+ });
50
+ }
51
+ async initMultipartUpload(file) {
52
+ return this.post("/multipart/init", {
53
+ name: file.name,
54
+ size: file.size,
55
+ type: file.type,
56
+ metadata: file.metadata ?? {}
57
+ });
58
+ }
59
+ async signPart(params) {
60
+ return this.post(
61
+ "/multipart/sign-part",
62
+ params
63
+ );
64
+ }
65
+ async completeMultipartUpload(params) {
66
+ return this.post(
67
+ "/multipart/complete",
68
+ params
69
+ );
70
+ }
71
+ async abortMultipartUpload(params) {
72
+ await this.post("/multipart/abort", params);
73
+ }
74
+ /** Re-attach to an upload the client left in flight (page reload, pause,
75
+ * retry) or whose token aged past its 1h TTL. The presented token is the
76
+ * ONLY input — key/uploadId are never sent by the client — and the
77
+ * response carries a freshly-signed replacement plus the parts storage
78
+ * already holds, each with its byte size. */
79
+ async resumeMultipartUpload(params) {
80
+ return this.post("/multipart/resume", params);
81
+ }
82
+ };
83
+
84
+ // src/strategies/multipart-upload.ts
85
+ var DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
86
+ var DEFAULT_MAX_CONCURRENT = 3;
87
+ var DEFAULT_RETRY_DELAYS = [0, 1e3, 3e3, 5e3];
88
+ var DEFAULT_PART_TIMEOUT_MS = 18e4;
89
+ var MATERIALIZE_PART_MAX_BYTES = 16 * 1024 * 1024;
90
+ var MAX_PARTS = 1e4;
91
+ function isAborted(signal) {
92
+ return signal.aborted;
93
+ }
94
+ function isClientError(error) {
95
+ const status = error instanceof chunkQK7HZIVY_cjs.UpupError ? error.status : void 0;
96
+ return status !== void 0 && status >= 400 && status < 500;
97
+ }
98
+ function isExpiredTokenError(error) {
99
+ return error instanceof chunkQK7HZIVY_cjs.UpupError && error.status === 403 && error.code === "expired";
100
+ }
101
+ function isUnresumableFailure(error) {
102
+ return error instanceof chunkQK7HZIVY_cjs.UpupStorageError && (error.operation === "multipart-init" || error.operation === "multipart-complete") && isClientError(error);
103
+ }
104
+ function partStallError(partNumber, timeoutMs) {
105
+ return new chunkQK7HZIVY_cjs.UpupError(
106
+ `Part ${partNumber} stalled: no response within ${timeoutMs}ms`,
107
+ "TIMEOUT" /* TIMEOUT */,
108
+ true
109
+ );
110
+ }
111
+ function isRetryablePartFailure(error) {
112
+ if (error instanceof chunkQK7HZIVY_cjs.UpupError) {
113
+ return error.retryable || error.status === 429 || error.status !== void 0 && error.status >= 500;
114
+ }
115
+ if (error instanceof DOMException && error.name === "AbortError") {
116
+ return false;
117
+ }
118
+ return error instanceof TypeError;
119
+ }
120
+ function abortableDelay(ms, signal) {
121
+ return new Promise((resolve) => {
122
+ const wake = () => {
123
+ clearTimeout(timer);
124
+ signal.removeEventListener("abort", wake);
125
+ resolve();
126
+ };
127
+ const timer = setTimeout(wake, ms);
128
+ signal.addEventListener("abort", wake, { once: true });
129
+ });
130
+ }
131
+ function readContentHash(file) {
132
+ const metadata = file.metadata;
133
+ return typeof metadata?.originalContentHash === "string" ? metadata.originalContentHash : void 0;
134
+ }
135
+ function abortPersistedMultipartSessions(files, credentials) {
136
+ for (const file of files) {
137
+ if (!(file instanceof File)) continue;
138
+ try {
139
+ const fingerprint = chunkCPKZ2CXD_cjs.fileFingerprint(file);
140
+ const session = chunkCPKZ2CXD_cjs.loadSession(fingerprint);
141
+ if (!session) continue;
142
+ chunkCPKZ2CXD_cjs.removeSession(fingerprint);
143
+ void credentials.abortMultipartUpload?.({ token: session.token }).catch(() => {
144
+ });
145
+ } catch {
146
+ }
147
+ }
148
+ }
149
+ var MultipartUpload = class {
150
+ constructor(options) {
151
+ const { credentials } = options;
152
+ if (!credentials.initMultipartUpload || !credentials.signPart || !credentials.completeMultipartUpload) {
153
+ throw new chunkQK7HZIVY_cjs.UpupConfigError(
154
+ "CredentialStrategy must implement multipart methods (initMultipartUpload, signPart, completeMultipartUpload)"
155
+ );
156
+ }
157
+ this.credentials = credentials;
158
+ this.initMultipartUpload = credentials.initMultipartUpload.bind(credentials);
159
+ this.signPart = credentials.signPart.bind(credentials);
160
+ this.completeMultipartUpload = credentials.completeMultipartUpload.bind(credentials);
161
+ this.resumeMultipartUpload = credentials.resumeMultipartUpload?.bind(credentials);
162
+ this.chunkSizeBytes = options.chunkSizeBytes ?? DEFAULT_CHUNK_SIZE;
163
+ const requestedConcurrency = Math.floor(
164
+ options.maxConcurrentParts ?? DEFAULT_MAX_CONCURRENT
165
+ );
166
+ this.maxConcurrentParts = Number.isFinite(requestedConcurrency) ? Math.max(1, requestedConcurrency) : DEFAULT_MAX_CONCURRENT;
167
+ this.persist = options.persist ?? false;
168
+ this.sessionScope = options.sessionScope;
169
+ this.retryDelays = options.retryDelays ?? DEFAULT_RETRY_DELAYS;
170
+ this.partTimeoutMs = options.partTimeoutMs ?? DEFAULT_PART_TIMEOUT_MS;
171
+ }
172
+ /**
173
+ * Each stored part must be byte-for-byte where this file's own chunking
174
+ * would put it: exactly `partSize`, except the file's TRUE final part
175
+ * (`partNumber === totalParts`), which must be exactly the remaining tail.
176
+ *
177
+ * The "highest part number we happen to hold" is NOT the exemption — the
178
+ * server may hold a sparse prefix (say parts 1, 2, 5 of 10), and letting
179
+ * part 5 be short would splice a short block into the MIDDLE of the object.
180
+ * S3 only enforces the ≥5 MiB floor on non-final parts, so it would accept
181
+ * the complete call and hand back a silently corrupt object. A part with no
182
+ * reported `size` is unverifiable and therefore equally unusable.
183
+ */
184
+ partsMatchSession(parts, partSize, fileSize) {
185
+ if (partSize <= 0) return false;
186
+ const totalParts = Math.ceil(fileSize / partSize);
187
+ const finalPartSize = fileSize - (totalParts - 1) * partSize;
188
+ return parts.every((part) => {
189
+ if (typeof part.size !== "number") return false;
190
+ if (part.partNumber < 1 || part.partNumber > totalParts) {
191
+ return false;
192
+ }
193
+ return part.partNumber === totalParts ? part.size === finalPartSize : part.size === partSize;
194
+ });
195
+ }
196
+ /**
197
+ * Try to re-attach to the upload a previous session left in flight.
198
+ * Returns `null` for every "just start fresh" outcome — a resume failure
199
+ * must never fail an upload that a fresh init could still serve.
200
+ */
201
+ async tryResume(fingerprint, fileSize, contentHash) {
202
+ const resume = this.resumeMultipartUpload;
203
+ if (!resume) return null;
204
+ const session = chunkCPKZ2CXD_cjs.loadSession(fingerprint);
205
+ if (!session) return null;
206
+ if (session.scope !== this.sessionScope) {
207
+ chunkCPKZ2CXD_cjs.removeSession(fingerprint);
208
+ return null;
209
+ }
210
+ if (contentHash !== void 0 && session.contentHash !== void 0 && session.contentHash !== contentHash) {
211
+ chunkCPKZ2CXD_cjs.removeSession(fingerprint);
212
+ return null;
213
+ }
214
+ let response;
215
+ try {
216
+ response = await resume({ token: session.token });
217
+ } catch (error) {
218
+ if (isClientError(error)) chunkCPKZ2CXD_cjs.removeSession(fingerprint);
219
+ return null;
220
+ }
221
+ const parts = [...response.parts].sort(
222
+ (a, b) => a.partNumber - b.partNumber
223
+ );
224
+ if (!this.partsMatchSession(parts, session.partSize, fileSize)) {
225
+ chunkCPKZ2CXD_cjs.removeSession(fingerprint);
226
+ return null;
227
+ }
228
+ const uploadedBytes = parts.reduce(
229
+ (sum, part) => sum + (part.size ?? 0),
230
+ 0
231
+ );
232
+ chunkCPKZ2CXD_cjs.updateSessionToken(fingerprint, response.token);
233
+ chunkCPKZ2CXD_cjs.updateSessionProgress(fingerprint, uploadedBytes);
234
+ return {
235
+ token: response.token,
236
+ partSize: session.partSize,
237
+ // Drop `size`: the complete call ignores it, and the wire payload
238
+ // stays byte-identical to a non-resumed upload's.
239
+ parts: parts.map(({ partNumber, eTag }) => ({ partNumber, eTag })),
240
+ uploadedBytes
241
+ };
242
+ }
243
+ /**
244
+ * Failure teardown. THE behavior change of cross-reload resume: with
245
+ * `persist` on, an error or abort deliberately leaves the server-side
246
+ * upload alive so the next attempt can resume into it — pause(), a dropped
247
+ * connection, and UploadManager's own retries all re-enter `upload()` and
248
+ * pick the session back up. Without persist this is exactly the historical
249
+ * best-effort abort. Either way, a 4xx from init/complete kills the
250
+ * session: the server has already torn the upload down.
251
+ */
252
+ async handleUploadFailure(error, token, fingerprint) {
253
+ if (fingerprint && isUnresumableFailure(error)) {
254
+ chunkCPKZ2CXD_cjs.removeSession(fingerprint);
255
+ }
256
+ if (this.persist && this.resumeMultipartUpload) return;
257
+ await this.credentials.abortMultipartUpload?.({ token }).catch(() => {
258
+ });
259
+ }
260
+ /**
261
+ * PUT one signed part over XMLHttpRequest, under an INACTIVITY watchdog.
262
+ *
263
+ * The watchdog resets on every `upload` progress event and fires only after
264
+ * `partTimeoutMs` with no forward motion at all. That distinction is the
265
+ * point: `fetch()` for a PUT resolves only once the whole body is sent, so a
266
+ * flat ceiling on total time aborts a large part on a slow-but-healthy link
267
+ * (a 5 MiB part under ~233 kbit/s crosses the 180s default) even though it
268
+ * is uploading fine. An inactivity timer kills the connection the watchdog
269
+ * was built for — one that has genuinely stopped moving bytes — without
270
+ * penalizing the slow one. Mirrors the XHR PUT in direct-upload.ts.
271
+ *
272
+ * Resolves with the part's ETag. Rejects with: the retryable stall on
273
+ * inactivity (`partStallError`) — covering both the slice READ and the PUT;
274
+ * a retryable `UPLOAD_FAILED` when the slice read itself rejects;
275
+ * `UpupNetworkError('Upload aborted')` when
276
+ * the caller's signal aborts; a retryable `TypeError` on a network-level
277
+ * failure (the shape `fetch` produced, which `isRetryablePartFailure`
278
+ * already classifies); or an `uploadErrorFromResponse` on a non-2xx — its
279
+ * body read synchronously from `xhr.responseText`, so the whole request
280
+ * including the error body stays inside the one abort/inactivity window (the
281
+ * old `await response.text()` ran AFTER the watchdog was cleared and could
282
+ * hang a half-dead connection forever on the body read).
283
+ */
284
+ async putPart(signed, chunk, partNumber, signal) {
285
+ if (isAborted(signal)) {
286
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
287
+ }
288
+ let body = chunk;
289
+ if (chunk.size <= MATERIALIZE_PART_MAX_BYTES) {
290
+ let readTimer;
291
+ let onReadAbort;
292
+ body = await Promise.race([
293
+ chunk.arrayBuffer().catch((cause) => {
294
+ const readErr = new chunkQK7HZIVY_cjs.UpupError(
295
+ `Part ${partNumber} source read failed: ${cause instanceof Error ? cause.message : String(cause)}`,
296
+ "UPLOAD_FAILED" /* UPLOAD_FAILED */,
297
+ true
298
+ );
299
+ throw readErr;
300
+ }),
301
+ new Promise((_, reject) => {
302
+ readTimer = setTimeout(() => {
303
+ reject(partStallError(partNumber, this.partTimeoutMs));
304
+ }, this.partTimeoutMs);
305
+ }),
306
+ new Promise((_, reject) => {
307
+ onReadAbort = () => {
308
+ const abortErr = new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
309
+ reject(abortErr);
310
+ };
311
+ signal.addEventListener("abort", onReadAbort, {
312
+ once: true
313
+ });
314
+ })
315
+ ]).finally(() => {
316
+ clearTimeout(readTimer);
317
+ if (onReadAbort) {
318
+ signal.removeEventListener("abort", onReadAbort);
319
+ }
320
+ });
321
+ }
322
+ if (isAborted(signal)) {
323
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
324
+ }
325
+ return new Promise((resolve, reject) => {
326
+ const xhr = new XMLHttpRequest();
327
+ let stalled = false;
328
+ let watchdog;
329
+ const armWatchdog = () => {
330
+ clearTimeout(watchdog);
331
+ watchdog = setTimeout(() => {
332
+ stalled = true;
333
+ xhr.abort();
334
+ }, this.partTimeoutMs);
335
+ };
336
+ const forwardAbort = () => {
337
+ xhr.abort();
338
+ };
339
+ const cleanup = () => {
340
+ clearTimeout(watchdog);
341
+ signal.removeEventListener("abort", forwardAbort);
342
+ };
343
+ xhr.upload.addEventListener("progress", () => {
344
+ armWatchdog();
345
+ });
346
+ xhr.addEventListener("load", () => {
347
+ cleanup();
348
+ if (xhr.status >= 200 && xhr.status < 300) {
349
+ const eTag = xhr.getResponseHeader("ETag") ?? `"part-${partNumber}"`;
350
+ resolve(eTag);
351
+ return;
352
+ }
353
+ const err = chunkQK7HZIVY_cjs.uploadErrorFromResponse({
354
+ status: xhr.status,
355
+ statusText: xhr.statusText,
356
+ body: xhr.responseText,
357
+ kind: "storage",
358
+ operation: "multipart-sign-part"
359
+ });
360
+ reject(err);
361
+ });
362
+ xhr.addEventListener("error", () => {
363
+ cleanup();
364
+ const err = new TypeError("Network error during part upload");
365
+ reject(err);
366
+ });
367
+ xhr.addEventListener("abort", () => {
368
+ cleanup();
369
+ if (stalled && !isAborted(signal)) {
370
+ const stallErr = partStallError(
371
+ partNumber,
372
+ this.partTimeoutMs
373
+ );
374
+ reject(stallErr);
375
+ return;
376
+ }
377
+ const abortErr = new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
378
+ reject(abortErr);
379
+ });
380
+ signal.addEventListener("abort", forwardAbort, { once: true });
381
+ xhr.open("PUT", signed.uploadUrl);
382
+ for (const [key, value] of Object.entries(
383
+ signed.uploadHeaders ?? {}
384
+ )) {
385
+ xhr.setRequestHeader(key, value);
386
+ }
387
+ armWatchdog();
388
+ xhr.send(body);
389
+ });
390
+ }
391
+ async upload(file, _credentials, options) {
392
+ const fileSize = file.size;
393
+ const fileName = file instanceof File ? file.name : "blob";
394
+ const fileType = file.type || "application/octet-stream";
395
+ const fingerprint = this.persist && file instanceof File ? chunkCPKZ2CXD_cjs.fileFingerprint(file) : null;
396
+ const contentHash = readContentHash(file);
397
+ const resumed = fingerprint ? await this.tryResume(fingerprint, fileSize, contentHash) : null;
398
+ let currentToken;
399
+ let partSize;
400
+ const completedParts = [];
401
+ let totalUploaded = 0;
402
+ if (resumed) {
403
+ currentToken = resumed.token;
404
+ partSize = resumed.partSize;
405
+ completedParts.push(...resumed.parts);
406
+ totalUploaded = resumed.uploadedBytes;
407
+ options.onProgress(totalUploaded, fileSize);
408
+ } else {
409
+ let init;
410
+ try {
411
+ init = await this.initMultipartUpload({
412
+ name: fileName,
413
+ size: fileSize,
414
+ type: fileType
415
+ });
416
+ } catch (error) {
417
+ if (fingerprint && isClientError(error)) {
418
+ chunkCPKZ2CXD_cjs.removeSession(fingerprint);
419
+ }
420
+ throw error;
421
+ }
422
+ if (!init.token) {
423
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
424
+ "Multipart init did not return an upload token (server too old or misconfigured)"
425
+ );
426
+ }
427
+ currentToken = init.token;
428
+ partSize = init.partSize || Math.max(this.chunkSizeBytes, Math.ceil(fileSize / MAX_PARTS));
429
+ if (fingerprint) {
430
+ chunkCPKZ2CXD_cjs.saveSession(fingerprint, {
431
+ token: currentToken,
432
+ key: init.key,
433
+ partSize,
434
+ updatedAt: Date.now(),
435
+ uploadedBytes: 0,
436
+ ...this.sessionScope !== void 0 ? { scope: this.sessionScope } : {},
437
+ ...contentHash !== void 0 ? { contentHash } : {}
438
+ });
439
+ }
440
+ }
441
+ const totalParts = Math.ceil(fileSize / partSize);
442
+ let refreshInFlight = null;
443
+ const refreshToken = async (staleToken) => {
444
+ const resume = this.resumeMultipartUpload;
445
+ if (!resume) return null;
446
+ if (currentToken !== staleToken) return currentToken;
447
+ const pending = refreshInFlight ?? resume({ token: staleToken }).then((response) => {
448
+ currentToken = response.token;
449
+ if (fingerprint) {
450
+ chunkCPKZ2CXD_cjs.updateSessionToken(fingerprint, response.token);
451
+ }
452
+ return response.token;
453
+ }).catch(() => null).finally(() => {
454
+ refreshInFlight = null;
455
+ });
456
+ refreshInFlight = pending;
457
+ return pending;
458
+ };
459
+ const withTokenRefresh = async (call) => {
460
+ const attemptToken = currentToken;
461
+ try {
462
+ return await call(attemptToken);
463
+ } catch (error) {
464
+ if (!isExpiredTokenError(error)) throw error;
465
+ const freshToken = await refreshToken(attemptToken);
466
+ if (!freshToken) throw error;
467
+ return call(freshToken);
468
+ }
469
+ };
470
+ try {
471
+ const alreadyUploaded = new Set(
472
+ completedParts.map((part) => part.partNumber)
473
+ );
474
+ const partQueue = Array.from(
475
+ { length: totalParts },
476
+ (_, i) => i + 1
477
+ ).filter((partNumber) => !alreadyUploaded.has(partNumber));
478
+ const activeParts = [];
479
+ const attemptPart = async (partNumber) => {
480
+ const start = (partNumber - 1) * partSize;
481
+ const end = Math.min(start + partSize, fileSize);
482
+ const chunk = file.slice(start, end);
483
+ let signTimer;
484
+ const signed = await Promise.race([
485
+ withTokenRefresh(
486
+ (token) => this.signPart({
487
+ token,
488
+ partNumber
489
+ })
490
+ ),
491
+ new Promise((_, reject) => {
492
+ signTimer = setTimeout(() => {
493
+ reject(
494
+ partStallError(partNumber, this.partTimeoutMs)
495
+ );
496
+ }, this.partTimeoutMs);
497
+ })
498
+ ]).finally(() => {
499
+ clearTimeout(signTimer);
500
+ });
501
+ if (isAborted(options.signal)) {
502
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
503
+ }
504
+ const eTag = await this.putPart(
505
+ signed,
506
+ chunk,
507
+ partNumber,
508
+ options.signal
509
+ );
510
+ completedParts.push({ partNumber, eTag });
511
+ totalUploaded += end - start;
512
+ options.onProgress(totalUploaded, fileSize);
513
+ if (fingerprint) {
514
+ chunkCPKZ2CXD_cjs.updateSessionProgress(fingerprint, totalUploaded);
515
+ }
516
+ };
517
+ const uploadPart = async (partNumber) => {
518
+ for (let attempt = 0; ; attempt++) {
519
+ if (isAborted(options.signal)) {
520
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
521
+ }
522
+ try {
523
+ await attemptPart(partNumber);
524
+ return;
525
+ } catch (error) {
526
+ if (isAborted(options.signal)) throw error;
527
+ const delay = this.retryDelays[attempt];
528
+ if (delay === void 0 || !isRetryablePartFailure(error)) {
529
+ throw error;
530
+ }
531
+ await abortableDelay(delay, options.signal);
532
+ }
533
+ }
534
+ };
535
+ for (const partNumber of partQueue) {
536
+ if (options.signal.aborted) break;
537
+ const partPromise = uploadPart(partNumber).then(() => {
538
+ const idx = activeParts.indexOf(partPromise);
539
+ if (idx !== -1) void activeParts.splice(idx, 1);
540
+ });
541
+ activeParts.push(partPromise);
542
+ if (activeParts.length >= this.maxConcurrentParts) {
543
+ await Promise.race(activeParts);
544
+ }
545
+ }
546
+ await Promise.all(activeParts);
547
+ if (isAborted(options.signal)) {
548
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
549
+ }
550
+ completedParts.sort((a, b) => a.partNumber - b.partNumber);
551
+ const result = await withTokenRefresh(
552
+ (token) => this.completeMultipartUpload({
553
+ token,
554
+ parts: completedParts
555
+ })
556
+ );
557
+ if (fingerprint) chunkCPKZ2CXD_cjs.removeSession(fingerprint);
558
+ return {
559
+ key: result.key,
560
+ publicUrl: result.publicUrl,
561
+ downloadUrl: result.downloadUrl,
562
+ etag: result.etag
563
+ };
564
+ } catch (error) {
565
+ await this.handleUploadFailure(error, currentToken, fingerprint);
566
+ throw error;
567
+ }
568
+ }
569
+ };
570
+
571
+ exports.MultipartUpload = MultipartUpload;
572
+ exports.ServerCredentials = ServerCredentials;
573
+ exports.abortPersistedMultipartSessions = abortPersistedMultipartSessions;