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