@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,2377 @@
1
+ 'use strict';
2
+
3
+ var chunkQK7HZIVY_cjs = require('./chunk-QK7HZIVY.cjs');
4
+
5
+ // src/events.ts
6
+ var EventEmitter = class {
7
+ constructor() {
8
+ this.handlers = /* @__PURE__ */ new Map();
9
+ }
10
+ on(event, handler) {
11
+ let handlers = this.handlers.get(event);
12
+ if (!handlers) {
13
+ handlers = /* @__PURE__ */ new Set();
14
+ this.handlers.set(event, handlers);
15
+ }
16
+ handlers.add(handler);
17
+ return () => {
18
+ this.off(event, handler);
19
+ };
20
+ }
21
+ off(event, handler) {
22
+ this.handlers.get(event)?.delete(handler);
23
+ }
24
+ emit(event, payload) {
25
+ const handlers = this.handlers.get(event);
26
+ if (handlers) {
27
+ for (const handler of handlers) {
28
+ try {
29
+ handler(payload);
30
+ } catch (err) {
31
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
32
+ console.error(
33
+ `[upup] listener for "${event}" threw:`,
34
+ err
35
+ );
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ removeAllListeners(event) {
42
+ if (event) {
43
+ this.handlers.delete(event);
44
+ } else {
45
+ this.handlers.clear();
46
+ }
47
+ }
48
+ };
49
+
50
+ // src/plugin.ts
51
+ var PluginManager = class {
52
+ constructor() {
53
+ this.plugins = /* @__PURE__ */ new Map();
54
+ this.extensions = /* @__PURE__ */ new Map();
55
+ }
56
+ register(plugin) {
57
+ if (this.plugins.has(plugin.name)) {
58
+ throw new chunkQK7HZIVY_cjs.UpupConfigError(
59
+ `Plugin "${plugin.name}" is already registered`,
60
+ "PLUGIN_ALREADY_REGISTERED"
61
+ );
62
+ }
63
+ this.plugins.set(plugin.name, plugin);
64
+ }
65
+ registerExtension(name, methods) {
66
+ if (this.extensions.has(name)) {
67
+ throw new chunkQK7HZIVY_cjs.UpupConfigError(
68
+ `Extension "${name}" is already registered`,
69
+ "EXTENSION_ALREADY_REGISTERED"
70
+ );
71
+ }
72
+ this.extensions.set(name, methods);
73
+ }
74
+ getPlugin(name) {
75
+ return this.plugins.get(name);
76
+ }
77
+ getExtension(name) {
78
+ return this.extensions.get(name);
79
+ }
80
+ getExtensions() {
81
+ return Object.fromEntries(this.extensions);
82
+ }
83
+ destroy() {
84
+ this.plugins.clear();
85
+ this.extensions.clear();
86
+ }
87
+ };
88
+
89
+ // src/validate-file-restrictions.ts
90
+ function fileSizeInBytes(size) {
91
+ const units = {
92
+ B: 1,
93
+ KB: 1024,
94
+ MB: 1024 ** 2,
95
+ GB: 1024 ** 3
96
+ };
97
+ return size.size * (units[size.unit] ?? 1);
98
+ }
99
+ function matchesAccept(file, accept) {
100
+ const types = accept.split(",").map((t) => t.trim()).filter(Boolean);
101
+ if (types.length === 0 || types.some((type) => type === "*" || type === "*/*")) {
102
+ return true;
103
+ }
104
+ return types.some((type) => {
105
+ if (type.endsWith("/*")) {
106
+ return file.type.startsWith(type.replace("/*", "/"));
107
+ }
108
+ if (type.startsWith(".")) {
109
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
110
+ }
111
+ return file.type === type;
112
+ });
113
+ }
114
+ function validateFileRestrictions(file, options) {
115
+ const errors = [];
116
+ if (options.allowedFileTypes && !matchesAccept(file, options.allowedFileTypes)) {
117
+ errors.push({
118
+ code: "TYPE_MISMATCH" /* TYPE_MISMATCH */,
119
+ message: `File type "${file.type}" is not accepted`
120
+ });
121
+ }
122
+ if (options.maxFileSize) {
123
+ const maxBytes = fileSizeInBytes(options.maxFileSize);
124
+ if (file.size > maxBytes) {
125
+ errors.push({
126
+ code: "FILE_TOO_LARGE" /* FILE_TOO_LARGE */,
127
+ message: `File "${file.name}" exceeds maximum size`
128
+ });
129
+ }
130
+ }
131
+ if (options.minFileSize) {
132
+ const minBytes = fileSizeInBytes(options.minFileSize);
133
+ if (file.size < minBytes) {
134
+ errors.push({
135
+ code: "FILE_TOO_SMALL" /* FILE_TOO_SMALL */,
136
+ message: `File "${file.name}" is below minimum size`
137
+ });
138
+ }
139
+ }
140
+ return errors;
141
+ }
142
+
143
+ // src/file-manager.ts
144
+ var fileIdCounter = 0;
145
+ function generateFileId() {
146
+ return `upup-${Date.now()}-${++fileIdCounter}`;
147
+ }
148
+ async function computeContentHash(file) {
149
+ const buffer = await file.arrayBuffer();
150
+ const bytes = new Uint8Array(buffer);
151
+ const subtleCrypto = typeof crypto !== "undefined" ? crypto.subtle : void 0;
152
+ if (subtleCrypto) {
153
+ const hashBuffer = await subtleCrypto.digest("SHA-256", buffer);
154
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
155
+ }
156
+ let hash = 2166136261;
157
+ for (const byte of bytes) {
158
+ hash ^= byte;
159
+ hash = Math.imul(hash, 16777619);
160
+ }
161
+ return `fnv1a-${(hash >>> 0).toString(16).padStart(8, "0")}`;
162
+ }
163
+ function storedContentHash(file) {
164
+ const metadata = file.metadata;
165
+ return (typeof metadata?.originalContentHash === "string" ? metadata.originalContentHash : void 0) ?? (typeof metadata?.checksum === "string" ? metadata.checksum : void 0) ?? // eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
166
+ file.checksumSHA256 ?? // eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
167
+ file.fileHash;
168
+ }
169
+ function applyContentHash(file, hash) {
170
+ const fileRecord = file;
171
+ fileRecord.fileHash = hash;
172
+ file.metadata = {
173
+ ...file.metadata,
174
+ originalContentHash: hash
175
+ };
176
+ return file;
177
+ }
178
+ function contentHashPatch(file, hash) {
179
+ const patch = {
180
+ fileHash: hash,
181
+ metadata: { ...file.metadata, originalContentHash: hash }
182
+ };
183
+ return patch;
184
+ }
185
+ function nativeToUploadFile(file, source = "local" /* LOCAL */) {
186
+ const url = typeof URL !== "undefined" && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : void 0;
187
+ const extendedFile = file;
188
+ const relativePath = typeof extendedFile.relativePath === "string" && extendedFile.relativePath.trim() !== "" ? extendedFile.relativePath : typeof extendedFile.webkitRelativePath === "string" && extendedFile.webkitRelativePath.trim() !== "" ? extendedFile.webkitRelativePath : void 0;
189
+ const existingMetadata = extendedFile.metadata && typeof extendedFile.metadata === "object" && !Array.isArray(extendedFile.metadata) ? extendedFile.metadata : {};
190
+ const uploadFile = Object.assign(file, {
191
+ id: generateFileId(),
192
+ source,
193
+ status: "IDLE" /* IDLE */,
194
+ metadata: {
195
+ ...existingMetadata,
196
+ ...relativePath ? { relativePath } : {}
197
+ },
198
+ ...url !== void 0 ? { url } : {}
199
+ });
200
+ if (relativePath) {
201
+ try {
202
+ Object.defineProperty(uploadFile, "relativePath", {
203
+ value: relativePath,
204
+ configurable: true,
205
+ enumerable: false,
206
+ writable: true
207
+ });
208
+ } catch {
209
+ try {
210
+ uploadFile.relativePath = relativePath;
211
+ } catch {
212
+ }
213
+ }
214
+ }
215
+ return uploadFile;
216
+ }
217
+ function revokeObjectUrl(file) {
218
+ if (file?.url && file.url.startsWith("blob:") && typeof URL !== "undefined" && typeof URL.revokeObjectURL === "function") {
219
+ URL.revokeObjectURL(file.url);
220
+ }
221
+ }
222
+ var FileManager = class {
223
+ constructor(options) {
224
+ this.files = /* @__PURE__ */ new Map();
225
+ this.options = options;
226
+ }
227
+ updateOptions(options) {
228
+ this.options = {
229
+ ...this.options,
230
+ ...options
231
+ };
232
+ }
233
+ getFiles() {
234
+ return new Map(this.files);
235
+ }
236
+ /**
237
+ * Immutable file-status transition: produce a NEW UploadFile carrying the patch,
238
+ * store and return it. FileManager is the sole writer of a file's status/key/metadata
239
+ * (F-144), and the new reference lets the orchestrator's ref-diff projection track the
240
+ * transition (F-145).
241
+ *
242
+ * Constraint: object-spread (`{ ...prev }`) would strip File's blob data and methods —
243
+ * they live in internal slots / on the prototype, not as own-enumerable props — leaving
244
+ * a plain object that breaks `xhr.send(file)` in the direct-upload strategy. So we
245
+ * re-wrap the same backing bytes into a fresh File (a view, not a copy) and copy the
246
+ * UploadFile fields onto it — mirroring `cloneUploadFile` (steps/image-utils.ts). Do
247
+ * NOT "simplify" this back to a spread.
248
+ */
249
+ updateFile(id, patch) {
250
+ const prev = this.files.get(id);
251
+ if (!prev) return void 0;
252
+ const next = new File([prev], prev.name, {
253
+ type: prev.type,
254
+ lastModified: prev.lastModified
255
+ });
256
+ Object.assign(next, prev, { relativePath: prev.relativePath }, patch);
257
+ const updated = next;
258
+ this.files.set(id, updated);
259
+ return updated;
260
+ }
261
+ validateFile(file) {
262
+ const [violation] = validateFileRestrictions(file, this.options);
263
+ if (violation) {
264
+ throw new chunkQK7HZIVY_cjs.UpupValidationError(
265
+ violation.message,
266
+ violation.code,
267
+ file
268
+ );
269
+ }
270
+ }
271
+ async collectAcceptedFiles(nativeFiles) {
272
+ const accepted = [];
273
+ for (const nativeFile of nativeFiles) {
274
+ let candidate = nativeFile;
275
+ if (this.options.onBeforeFileAdded) {
276
+ const result = await this.options.onBeforeFileAdded(nativeFile);
277
+ if (result === false) continue;
278
+ if (result instanceof File) {
279
+ candidate = result;
280
+ }
281
+ }
282
+ this.validateFile(candidate);
283
+ accepted.push(candidate);
284
+ }
285
+ return accepted;
286
+ }
287
+ async deduplicateByContent(nativeFiles, existingFiles) {
288
+ if (!this.options.contentDeduplication) {
289
+ return nativeFiles.map((file) => ({ file }));
290
+ }
291
+ const seen = /* @__PURE__ */ new Set();
292
+ for (const existing of existingFiles) {
293
+ const nativeExisting = existing;
294
+ const hash = storedContentHash(existing) ?? (typeof nativeExisting.arrayBuffer === "function" ? await computeContentHash(nativeExisting) : void 0);
295
+ if (hash) {
296
+ if (storedContentHash(existing) !== hash) {
297
+ this.updateFile(
298
+ existing.id,
299
+ contentHashPatch(existing, hash)
300
+ );
301
+ }
302
+ seen.add(hash);
303
+ }
304
+ }
305
+ const accepted = [];
306
+ for (const file of nativeFiles) {
307
+ const hash = await computeContentHash(file);
308
+ if (seen.has(hash)) continue;
309
+ seen.add(hash);
310
+ accepted.push({ file, hash });
311
+ }
312
+ return accepted;
313
+ }
314
+ assertBatchFits(files, existingFiles, fallbackFile) {
315
+ if (files.length === 0) return;
316
+ const violatingFile = files[0] ?? fallbackFile;
317
+ if (!violatingFile) {
318
+ throw new chunkQK7HZIVY_cjs.UpupError(
319
+ "assertBatchFits: no file available to attach to the violation error",
320
+ "BAD_REQUEST" /* BAD_REQUEST */
321
+ );
322
+ }
323
+ const existing = [...existingFiles];
324
+ if (this.options.limit) {
325
+ const remainingSlots = this.options.limit - existing.length;
326
+ if (remainingSlots <= 0 || files.length > remainingSlots) {
327
+ throw new chunkQK7HZIVY_cjs.UpupValidationError(
328
+ `Adding ${files.length} files would exceed the limit of ${this.options.limit}`,
329
+ "LIMIT_EXCEEDED" /* LIMIT_EXCEEDED */,
330
+ violatingFile
331
+ );
332
+ }
333
+ }
334
+ if (this.options.maxTotalFileSize) {
335
+ const maxTotal = fileSizeInBytes(this.options.maxTotalFileSize);
336
+ const currentTotal = existing.reduce((sum, f) => sum + f.size, 0);
337
+ const newTotal = files.reduce((sum, f) => sum + f.size, 0);
338
+ if (currentTotal + newTotal > maxTotal) {
339
+ throw new chunkQK7HZIVY_cjs.UpupValidationError(
340
+ "Total file size exceeds maximum",
341
+ "TOTAL_SIZE_EXCEEDED" /* TOTAL_SIZE_EXCEEDED */,
342
+ violatingFile
343
+ );
344
+ }
345
+ }
346
+ }
347
+ async addFiles(nativeFiles) {
348
+ const acceptedNativeFiles = await this.collectAcceptedFiles(nativeFiles);
349
+ const dedupedFiles = await this.deduplicateByContent(
350
+ acceptedNativeFiles,
351
+ this.files.values()
352
+ );
353
+ this.assertBatchFits(
354
+ dedupedFiles.map((item) => item.file),
355
+ this.files.values(),
356
+ nativeFiles[0]
357
+ );
358
+ const accepted = dedupedFiles.map(({ file, hash }) => {
359
+ const uploadFile = nativeToUploadFile(file);
360
+ return hash ? applyContentHash(uploadFile, hash) : uploadFile;
361
+ });
362
+ for (const file of accepted) {
363
+ this.files.set(file.id, file);
364
+ }
365
+ return accepted;
366
+ }
367
+ removeFile(id) {
368
+ const file = this.files.get(id);
369
+ if (file) {
370
+ revokeObjectUrl(file);
371
+ this.files.delete(id);
372
+ }
373
+ return file;
374
+ }
375
+ replaceFile(id, file) {
376
+ const current = this.files.get(id);
377
+ if (!current) {
378
+ throw new chunkQK7HZIVY_cjs.UpupError(
379
+ `replaceFile: unknown file ID "${id}"`,
380
+ "BAD_REQUEST" /* BAD_REQUEST */
381
+ );
382
+ }
383
+ const next = "metadata" in file && "source" in file && "status" in file ? Object.assign(file, { id }) : Object.assign(nativeToUploadFile(file, current.source), {
384
+ id
385
+ });
386
+ if (current.url !== next.url) {
387
+ revokeObjectUrl(current);
388
+ }
389
+ this.files.set(id, next);
390
+ return next;
391
+ }
392
+ removeAll() {
393
+ for (const file of this.files.values()) {
394
+ revokeObjectUrl(file);
395
+ }
396
+ this.files.clear();
397
+ }
398
+ /** Bulk-apply pipeline-produced files (already UploadFile with status/key set). */
399
+ applyProcessed(files) {
400
+ for (const file of files) {
401
+ this.files.set(file.id, file);
402
+ }
403
+ }
404
+ /** Re-instate a prior snapshot: clear and repopulate from entries. */
405
+ restore(entries) {
406
+ this.files.clear();
407
+ for (const [id, file] of entries) {
408
+ this.files.set(id, file);
409
+ }
410
+ }
411
+ async setFiles(nativeFiles) {
412
+ const acceptedNativeFiles = await this.collectAcceptedFiles(nativeFiles);
413
+ const dedupedFiles = await this.deduplicateByContent(
414
+ acceptedNativeFiles,
415
+ []
416
+ );
417
+ this.assertBatchFits(
418
+ dedupedFiles.map((item) => item.file),
419
+ [],
420
+ nativeFiles[0]
421
+ );
422
+ const accepted = dedupedFiles.map(({ file, hash }) => {
423
+ const uploadFile = nativeToUploadFile(file);
424
+ return hash ? applyContentHash(uploadFile, hash) : uploadFile;
425
+ });
426
+ for (const file of this.files.values()) {
427
+ revokeObjectUrl(file);
428
+ }
429
+ this.files.clear();
430
+ for (const file of accepted) {
431
+ this.files.set(file.id, file);
432
+ }
433
+ return accepted;
434
+ }
435
+ reorderFiles(fileIds) {
436
+ if (fileIds.length !== this.files.size) {
437
+ throw new chunkQK7HZIVY_cjs.UpupError(
438
+ `reorderFiles: expected ${this.files.size} IDs but received ${fileIds.length}`,
439
+ "BAD_REQUEST" /* BAD_REQUEST */
440
+ );
441
+ }
442
+ const newMap = /* @__PURE__ */ new Map();
443
+ for (const id of fileIds) {
444
+ const file = this.files.get(id);
445
+ if (!file) {
446
+ throw new chunkQK7HZIVY_cjs.UpupError(
447
+ `reorderFiles: unknown file ID "${id}"`,
448
+ "BAD_REQUEST" /* BAD_REQUEST */
449
+ );
450
+ }
451
+ newMap.set(id, file);
452
+ }
453
+ this.files = newMap;
454
+ }
455
+ };
456
+
457
+ // src/upload-manager.ts
458
+ var UpupUploadBatchError = class extends Error {
459
+ constructor(message, errors) {
460
+ super(message);
461
+ this.name = "UpupUploadBatchError";
462
+ this.errors = errors;
463
+ }
464
+ };
465
+ var UploadManager = class {
466
+ constructor(options) {
467
+ this.abortController = new AbortController();
468
+ this.options = options;
469
+ }
470
+ async uploadAll(files) {
471
+ const { maxConcurrentUploads } = this.options;
472
+ const results = [];
473
+ const errors = [];
474
+ const queue = [...files];
475
+ let active = 0;
476
+ await new Promise((resolve) => {
477
+ const tryNext = () => {
478
+ if (queue.length === 0 && active === 0) {
479
+ resolve();
480
+ return;
481
+ }
482
+ while (active < maxConcurrentUploads && queue.length > 0) {
483
+ const file = queue.shift();
484
+ if (!file) break;
485
+ active++;
486
+ this.uploadFile(file).then((result) => {
487
+ results.push(result);
488
+ this.options.onFileComplete(file, result);
489
+ }).catch((err) => {
490
+ const error = err instanceof Error ? err : new Error(String(err));
491
+ if (this.options.onFileError) {
492
+ this.options.onFileError(file, error);
493
+ }
494
+ errors.push({ file, error });
495
+ }).finally(() => {
496
+ active--;
497
+ tryNext();
498
+ });
499
+ }
500
+ };
501
+ tryNext();
502
+ if (files.length === 0) {
503
+ resolve();
504
+ }
505
+ });
506
+ if (errors.length > 0) {
507
+ throw new UpupUploadBatchError(
508
+ `${errors.length} file upload(s) failed`,
509
+ errors
510
+ );
511
+ }
512
+ return results;
513
+ }
514
+ // A method (rather than inlining `this.abortController.signal.aborted`,
515
+ // or a getter — TS's control-flow narrowing treats a no-setter accessor
516
+ // like a readonly field) so each call is re-evaluated fresh:
517
+ // `abortController` can be replaced concurrently by `pause()`/`abort()`
518
+ // while an attempt is in flight, across the `await`s below.
519
+ checkAborted() {
520
+ return this.abortController.signal.aborted;
521
+ }
522
+ async uploadFile(file) {
523
+ const {
524
+ credentials,
525
+ uploadStrategy,
526
+ maxRetries = 3,
527
+ isSuccessfulCall
528
+ } = this.options;
529
+ const maxAttempts = maxRetries + 1;
530
+ let lastError = null;
531
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
532
+ if (this.checkAborted()) {
533
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted");
534
+ }
535
+ try {
536
+ this.options.onFileStart?.(file);
537
+ const plan = this.options.resolveUploadStrategy?.(file) ?? {
538
+ uploadStrategy,
539
+ presign: true
540
+ };
541
+ const currentMetadata = file.metadata;
542
+ const metadata = {
543
+ ...currentMetadata,
544
+ ...file.relativePath && currentMetadata.relativePath == null ? { relativePath: file.relativePath } : {}
545
+ };
546
+ const fileMetadata = {
547
+ name: file.name,
548
+ size: file.size,
549
+ type: file.type,
550
+ metadata
551
+ };
552
+ const presigned = plan.presign ? await credentials.getPresignedUrl(fileMetadata) : {
553
+ key: file.id,
554
+ uploadUrl: "",
555
+ expiresIn: 0
556
+ };
557
+ const result = await plan.uploadStrategy.upload(
558
+ file,
559
+ presigned,
560
+ {
561
+ onProgress: (loaded, total) => {
562
+ this.options.onProgress(file.id, loaded, total);
563
+ },
564
+ signal: this.abortController.signal
565
+ }
566
+ );
567
+ if (isSuccessfulCall) {
568
+ const syntheticResponse = {
569
+ status: 200,
570
+ headers: {},
571
+ body: result
572
+ };
573
+ const ok = await isSuccessfulCall(syntheticResponse);
574
+ if (!ok) {
575
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
576
+ "isSuccessfulCall returned false",
577
+ 0
578
+ );
579
+ }
580
+ }
581
+ return result;
582
+ } catch (err) {
583
+ if (this.checkAborted()) {
584
+ throw err;
585
+ }
586
+ const isNetwork = err instanceof chunkQK7HZIVY_cjs.UpupNetworkError;
587
+ if (isNetwork && err.status !== void 0 && err.status >= 400 && err.status < 500) {
588
+ throw err;
589
+ }
590
+ if (isNetwork && err.status === 0) {
591
+ throw err;
592
+ }
593
+ lastError = err instanceof Error ? err : new Error(String(err));
594
+ if (attempt < maxAttempts - 1) {
595
+ await new Promise(
596
+ (r) => setTimeout(r, 100 * Math.pow(2, attempt))
597
+ );
598
+ }
599
+ }
600
+ }
601
+ const finalError = lastError ?? new chunkQK7HZIVY_cjs.UpupNetworkError("Upload failed after retries");
602
+ throw finalError;
603
+ }
604
+ abort() {
605
+ this.abortController.abort();
606
+ }
607
+ pause() {
608
+ this.abortController.abort();
609
+ this.abortController = new AbortController();
610
+ }
611
+ };
612
+
613
+ // src/utils/status-helpers.ts
614
+ function isUploadActive(status) {
615
+ return status === "UPLOADING" /* UPLOADING */ || status === "PROCESSING" /* PROCESSING */;
616
+ }
617
+ function isUploadIdle(status) {
618
+ return status === "IDLE" /* IDLE */ || status === "READY" /* READY */;
619
+ }
620
+ function isFileRemovalLocked(progress, fileStatus) {
621
+ return !!progress && fileStatus !== "PAUSED" /* PAUSED */;
622
+ }
623
+
624
+ // src/crash-recovery.ts
625
+ var STORAGE_KEY = "upup-crash-recovery";
626
+ var CrashRecoveryManager = class {
627
+ constructor(storage) {
628
+ this.storage = storage;
629
+ }
630
+ async save(snapshot) {
631
+ await this.storage.set(STORAGE_KEY, snapshot);
632
+ }
633
+ async restore() {
634
+ const data = await this.storage.get(STORAGE_KEY);
635
+ return data ?? null;
636
+ }
637
+ async clear() {
638
+ await this.storage.delete(STORAGE_KEY);
639
+ }
640
+ };
641
+ var IndexedDBStorage = class {
642
+ constructor(dbName = "upup-crash-recovery") {
643
+ this.storeName = "upup-store";
644
+ /** ONE cached connection for the storage's lifetime — never closed after
645
+ * an operation. Firefox ties Blob/File handles read from IndexedDB to
646
+ * the connection they were read over: with the old open-per-operation /
647
+ * close-on-complete lifecycle, the File revived by the crash-recovery
648
+ * restore lost its backing store shortly after the restore's transaction
649
+ * closed, and mid-resume slice reads rejected with AbortError ("The
650
+ * operation was aborted") — a Firefox-only reload-resume stall reproduced
651
+ * against both MinIO and Backblaze B2 (Chromium materializes IndexedDB
652
+ * blobs independently of the connection and never failed). The cache
653
+ * still yields: `versionchange` (a deleteDatabase/upgrade elsewhere)
654
+ * closes and drops it so the other context is never blocked, and a
655
+ * browser-initiated `close` drops it so the next operation reopens. */
656
+ this.dbPromise = null;
657
+ this.dbName = dbName;
658
+ }
659
+ getDB() {
660
+ if (this.dbPromise) return this.dbPromise;
661
+ const promise = new Promise((resolve, reject) => {
662
+ const request = indexedDB.open(this.dbName, 1);
663
+ request.onupgradeneeded = () => {
664
+ request.result.createObjectStore(this.storeName);
665
+ };
666
+ request.onsuccess = () => {
667
+ const db = request.result;
668
+ db.onversionchange = () => {
669
+ db.close();
670
+ if (this.dbPromise === promise) {
671
+ this.dbPromise = null;
672
+ }
673
+ };
674
+ db.onclose = () => {
675
+ if (this.dbPromise === promise) {
676
+ this.dbPromise = null;
677
+ }
678
+ };
679
+ resolve(db);
680
+ };
681
+ request.onerror = () => {
682
+ if (this.dbPromise === promise) {
683
+ this.dbPromise = null;
684
+ }
685
+ reject(request.error ?? new Error("IndexedDB open failed"));
686
+ };
687
+ });
688
+ this.dbPromise = promise;
689
+ return promise;
690
+ }
691
+ /** Close and release the cached connection. Safe at any time — the next
692
+ * operation reopens. `UpupCore.destroy()` calls this so an unmounted
693
+ * uploader doesn't keep the database held open (pending transactions
694
+ * still drain first per the IndexedDB spec, so a queued snapshot write
695
+ * is never cut off mid-transaction). */
696
+ close() {
697
+ const promise = this.dbPromise;
698
+ if (!promise) return;
699
+ this.dbPromise = null;
700
+ void promise.then(
701
+ (db) => {
702
+ db.close();
703
+ },
704
+ () => {
705
+ }
706
+ );
707
+ }
708
+ async get(key) {
709
+ try {
710
+ const db = await this.getDB();
711
+ return await new Promise((resolve, reject) => {
712
+ const tx = db.transaction(this.storeName, "readonly");
713
+ const store = tx.objectStore(this.storeName);
714
+ const request = store.get(key);
715
+ request.onsuccess = () => {
716
+ resolve(request.result);
717
+ };
718
+ request.onerror = () => {
719
+ reject(request.error ?? new Error("IndexedDB get failed"));
720
+ };
721
+ });
722
+ } catch {
723
+ return void 0;
724
+ }
725
+ }
726
+ async set(key, value) {
727
+ try {
728
+ const db = await this.getDB();
729
+ await new Promise((resolve, reject) => {
730
+ const tx = db.transaction(this.storeName, "readwrite");
731
+ const store = tx.objectStore(this.storeName);
732
+ const request = store.put(value, key);
733
+ request.onsuccess = () => {
734
+ resolve();
735
+ };
736
+ request.onerror = () => {
737
+ reject(request.error ?? new Error("IndexedDB put failed"));
738
+ };
739
+ });
740
+ } catch {
741
+ }
742
+ }
743
+ async delete(key) {
744
+ try {
745
+ const db = await this.getDB();
746
+ await new Promise((resolve, reject) => {
747
+ const tx = db.transaction(this.storeName, "readwrite");
748
+ const store = tx.objectStore(this.storeName);
749
+ const request = store.delete(key);
750
+ request.onsuccess = () => {
751
+ resolve();
752
+ };
753
+ request.onerror = () => {
754
+ reject(
755
+ request.error ?? new Error("IndexedDB delete failed")
756
+ );
757
+ };
758
+ });
759
+ } catch {
760
+ }
761
+ }
762
+ };
763
+
764
+ // src/drives/pkce.ts
765
+ function generateCodeVerifier(length = 128) {
766
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
767
+ const values = crypto.getRandomValues(new Uint8Array(length));
768
+ return Array.from(values, (b) => charset[b % charset.length]).join("");
769
+ }
770
+ async function generateCodeChallenge(verifier) {
771
+ const data = new TextEncoder().encode(verifier);
772
+ const digest = await crypto.subtle.digest("SHA-256", data);
773
+ return btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
774
+ }
775
+
776
+ // src/drives/session-storage.ts
777
+ function storageGet(key) {
778
+ if (typeof window === "undefined") return null;
779
+ try {
780
+ return sessionStorage.getItem(key);
781
+ } catch {
782
+ return null;
783
+ }
784
+ }
785
+ function storageSet(key, value) {
786
+ if (typeof window === "undefined") return;
787
+ try {
788
+ sessionStorage.setItem(key, value);
789
+ } catch {
790
+ }
791
+ }
792
+ function storageDel(key) {
793
+ if (typeof window === "undefined") return;
794
+ try {
795
+ sessionStorage.removeItem(key);
796
+ } catch {
797
+ }
798
+ }
799
+
800
+ // src/drives/popup-oauth-plugin.ts
801
+ var PopupOAuthPlugin = class {
802
+ constructor() {
803
+ this.emitter = null;
804
+ this.config = { clientId: "" };
805
+ this.accessToken = null;
806
+ this.refreshTokenValue = null;
807
+ this.tokenExpiry = 0;
808
+ this.state = "idle";
809
+ this.codeVerifier = null;
810
+ // Popup polling references
811
+ this.popupWindow = null;
812
+ this.pollTimer = null;
813
+ }
814
+ get id() {
815
+ return this.spec.id;
816
+ }
817
+ get name() {
818
+ return this.spec.id;
819
+ }
820
+ // ── Plugin lifecycle ──
821
+ configure(config) {
822
+ this.config = config;
823
+ return this;
824
+ }
825
+ getConfig() {
826
+ return this.config;
827
+ }
828
+ init(emitter) {
829
+ this.emitter = emitter;
830
+ }
831
+ destroy() {
832
+ this.cleanupPopup();
833
+ this.emitter = null;
834
+ }
835
+ // ── State management ──
836
+ getState() {
837
+ return this.state;
838
+ }
839
+ setState(newState) {
840
+ this.state = newState;
841
+ this.emitter?.emit(`${this.spec.eventPrefix}:state-change`, {
842
+ state: newState
843
+ });
844
+ }
845
+ // ── Auth: build the OAuth URL with PKCE ──
846
+ async getAuthUrl() {
847
+ const clientId = this.config.clientId;
848
+ if (!clientId) {
849
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
850
+ `${this.spec.displayName} client_id is not configured`,
851
+ this.spec.displayName
852
+ );
853
+ }
854
+ const redirectUri = this.getRedirectUri();
855
+ this.codeVerifier = generateCodeVerifier();
856
+ const challenge = await generateCodeChallenge(this.codeVerifier);
857
+ const params = new URLSearchParams({
858
+ client_id: clientId,
859
+ response_type: "code",
860
+ redirect_uri: redirectUri,
861
+ code_challenge: challenge,
862
+ code_challenge_method: "S256",
863
+ ...this.spec.authParams ?? {}
864
+ });
865
+ if (this.spec.scopes) {
866
+ params.set("scope", this.spec.scopes);
867
+ }
868
+ return `${this.spec.authUrl}?${params.toString()}`;
869
+ }
870
+ // ── Auth: exchange authorization code for tokens ──
871
+ async authenticate(code) {
872
+ const clientId = this.config.clientId;
873
+ if (!clientId) {
874
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
875
+ `${this.spec.displayName} client_id is not configured`,
876
+ this.spec.displayName
877
+ );
878
+ }
879
+ if (!this.codeVerifier) {
880
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
881
+ "No PKCE code verifier \u2014 call getAuthUrl() first",
882
+ this.spec.displayName
883
+ );
884
+ }
885
+ this.setState("authenticating");
886
+ try {
887
+ const body = new URLSearchParams({
888
+ code,
889
+ client_id: clientId,
890
+ code_verifier: this.codeVerifier,
891
+ grant_type: "authorization_code",
892
+ redirect_uri: this.getRedirectUri()
893
+ });
894
+ if (this.spec.scopes) {
895
+ body.set("scope", this.spec.scopes);
896
+ }
897
+ const res = await fetch(this.spec.tokenUrl, {
898
+ method: "POST",
899
+ headers: {
900
+ "Content-Type": "application/x-www-form-urlencoded"
901
+ },
902
+ body
903
+ });
904
+ if (!res.ok) {
905
+ const text = await res.text();
906
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
907
+ `Token exchange failed: ${text}`,
908
+ this.spec.displayName
909
+ );
910
+ }
911
+ const data = await res.json();
912
+ this.setTokens(
913
+ data.access_token,
914
+ data.refresh_token ?? null,
915
+ data.expires_in
916
+ );
917
+ let user;
918
+ try {
919
+ user = await this.fetchUserProfile();
920
+ } catch {
921
+ }
922
+ this.setState("authenticated");
923
+ this.emitter?.emit(`${this.spec.eventPrefix}:authenticated`, {
924
+ user
925
+ });
926
+ } catch (err) {
927
+ this.setState("idle");
928
+ this.emitter?.emit(`${this.spec.eventPrefix}:error`, {
929
+ error: err instanceof Error ? err : new Error(String(err)),
930
+ action: "authenticate"
931
+ });
932
+ throw err;
933
+ }
934
+ }
935
+ // ── Auth: popup-based flow (open popup, poll for redirect code) ──
936
+ async authenticateViaPopup() {
937
+ if (typeof window === "undefined") {
938
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
939
+ "authenticateViaPopup requires a browser environment",
940
+ this.spec.displayName
941
+ );
942
+ }
943
+ const url = await this.getAuthUrl();
944
+ this.cleanupPopup();
945
+ const left = (typeof window !== "undefined" ? window.screenX : 0) + 60;
946
+ const top = (typeof window !== "undefined" ? window.screenY : 0) + 60;
947
+ this.popupWindow = window.open(
948
+ url,
949
+ this.spec.popupName,
950
+ `width=800,height=600,left=${left},top=${top},toolbar=0,scrollbars=1,status=1,resizable=1`
951
+ );
952
+ if (!this.popupWindow) {
953
+ this.emitter?.emit(`${this.spec.eventPrefix}:error`, {
954
+ error: new Error("Popup was blocked by the browser"),
955
+ action: "authenticateViaPopup"
956
+ });
957
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
958
+ "Popup was blocked by the browser",
959
+ this.spec.displayName
960
+ );
961
+ }
962
+ this.setState("authenticating");
963
+ return new Promise((resolve, reject) => {
964
+ const redirectUri = this.getRedirectUri();
965
+ this.pollTimer = setInterval(() => {
966
+ void (async () => {
967
+ try {
968
+ if (!this.popupWindow || this.popupWindow.closed) {
969
+ this.cleanupPopup();
970
+ this.setState("idle");
971
+ resolve();
972
+ return;
973
+ }
974
+ let href;
975
+ try {
976
+ href = this.popupWindow.location.href;
977
+ } catch {
978
+ return;
979
+ }
980
+ if (!href.startsWith(redirectUri) || !href.includes("code=")) {
981
+ return;
982
+ }
983
+ this.cleanupPollTimer();
984
+ const code = new URL(href).searchParams.get("code");
985
+ this.popupWindow.close();
986
+ this.popupWindow = null;
987
+ if (!code) {
988
+ const err = new Error(
989
+ "No authorization code found in redirect URL"
990
+ );
991
+ this.setState("idle");
992
+ this.emitter?.emit(
993
+ `${this.spec.eventPrefix}:error`,
994
+ {
995
+ error: err,
996
+ action: "authenticateViaPopup"
997
+ }
998
+ );
999
+ reject(err);
1000
+ return;
1001
+ }
1002
+ await this.authenticate(code);
1003
+ resolve();
1004
+ } catch (err) {
1005
+ const message = err instanceof Error ? err.message : String(err);
1006
+ if (message.includes("cross-origin") || message.includes("Cross-Origin") || message.includes("Permission denied") || message.includes("Failed to read")) {
1007
+ return;
1008
+ }
1009
+ this.cleanupPopup();
1010
+ this.setState("idle");
1011
+ this.emitter?.emit(`${this.spec.eventPrefix}:error`, {
1012
+ error: err instanceof Error ? err : new Error(String(err)),
1013
+ action: "authenticateViaPopup"
1014
+ });
1015
+ reject(err instanceof Error ? err : new Error(message));
1016
+ }
1017
+ })();
1018
+ }, 500);
1019
+ });
1020
+ }
1021
+ // ── Auth: refresh access token ──
1022
+ async refreshAccessToken() {
1023
+ const clientId = this.config.clientId;
1024
+ if (!clientId || !this.refreshTokenValue) return null;
1025
+ try {
1026
+ const body = new URLSearchParams({
1027
+ grant_type: "refresh_token",
1028
+ refresh_token: this.refreshTokenValue,
1029
+ client_id: clientId
1030
+ });
1031
+ if (this.spec.scopes) {
1032
+ body.set("scope", this.spec.scopes);
1033
+ }
1034
+ const res = await fetch(this.spec.tokenUrl, {
1035
+ method: "POST",
1036
+ headers: {
1037
+ "Content-Type": "application/x-www-form-urlencoded"
1038
+ },
1039
+ body
1040
+ });
1041
+ if (!res.ok) {
1042
+ const text = await res.text();
1043
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
1044
+ `Token refresh failed: ${text}`,
1045
+ this.spec.displayName
1046
+ );
1047
+ }
1048
+ const data = await res.json();
1049
+ this.setTokens(
1050
+ data.access_token,
1051
+ data.refresh_token ?? this.refreshTokenValue,
1052
+ data.expires_in
1053
+ );
1054
+ return data.access_token;
1055
+ } catch (err) {
1056
+ this.emitter?.emit(`${this.spec.eventPrefix}:session-expired`, {});
1057
+ this.emitter?.emit(`${this.spec.eventPrefix}:error`, {
1058
+ error: err instanceof Error ? err : new Error(String(err)),
1059
+ action: "refreshAccessToken"
1060
+ });
1061
+ this.clearTokens();
1062
+ this.setState("session-expired");
1063
+ return null;
1064
+ }
1065
+ }
1066
+ // ── Auth: sign out ──
1067
+ signOut() {
1068
+ this.clearTokens();
1069
+ this.cleanupPopup();
1070
+ this.setState("idle");
1071
+ this.emitter?.emit(`${this.spec.eventPrefix}:signed-out`, {});
1072
+ }
1073
+ // ── Auth: restore session from sessionStorage ──
1074
+ restoreSession() {
1075
+ const token = storageGet(this.spec.storageKeys.access);
1076
+ const refresh = storageGet(this.spec.storageKeys.refresh);
1077
+ const expiry = storageGet(this.spec.storageKeys.expiry);
1078
+ if (!token) return false;
1079
+ this.accessToken = token;
1080
+ this.refreshTokenValue = refresh;
1081
+ this.tokenExpiry = expiry ? parseInt(expiry, 10) : 0;
1082
+ this.setState("authenticated");
1083
+ return true;
1084
+ }
1085
+ // ── Auth helpers ──
1086
+ isAuthenticated() {
1087
+ return this.state === "authenticated" && this.accessToken !== null;
1088
+ }
1089
+ getAccessToken() {
1090
+ return this.accessToken;
1091
+ }
1092
+ // Guarded public entry point the controller drives (F-123): swallow-to-null
1093
+ // instead of throwing, so a failed profile fetch never strands the restore
1094
+ // flow in an unhandled rejection. Requires isAuthenticated() — unlike
1095
+ // authenticate()'s internal call, which fetches before the flag flips.
1096
+ async getUserInfo() {
1097
+ if (!this.isAuthenticated()) return null;
1098
+ try {
1099
+ return await this.fetchUserProfile();
1100
+ } catch {
1101
+ return null;
1102
+ }
1103
+ }
1104
+ // ── Private: authenticated API request with proactive + reactive refresh ──
1105
+ async apiRequest(url, options, isRetry = false) {
1106
+ await this.ensureValidToken();
1107
+ const headers = new Headers(options.headers ?? {});
1108
+ headers.set("Authorization", `Bearer ${this.accessToken}`);
1109
+ const res = await fetch(url, { ...options, headers });
1110
+ if (res.ok) return res;
1111
+ const errorText = await res.text();
1112
+ if (res.status === 401 && !isRetry) {
1113
+ if (this.refreshTokenValue) {
1114
+ const newToken = await this.refreshAccessToken();
1115
+ if (newToken) {
1116
+ return this.apiRequest(url, options, true);
1117
+ }
1118
+ }
1119
+ this.emitter?.emit(`${this.spec.eventPrefix}:session-expired`, {});
1120
+ this.clearTokens();
1121
+ this.setState("session-expired");
1122
+ }
1123
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
1124
+ `${this.spec.displayName} API error (${res.status}): ${errorText}`,
1125
+ res.status
1126
+ );
1127
+ }
1128
+ // ── Private: token management ──
1129
+ setTokens(access, refresh, expiresIn) {
1130
+ this.accessToken = access;
1131
+ this.refreshTokenValue = refresh;
1132
+ this.tokenExpiry = expiresIn ? Date.now() + expiresIn * 1e3 : 0;
1133
+ storageSet(this.spec.storageKeys.access, access);
1134
+ if (refresh) storageSet(this.spec.storageKeys.refresh, refresh);
1135
+ if (this.tokenExpiry) {
1136
+ storageSet(this.spec.storageKeys.expiry, String(this.tokenExpiry));
1137
+ }
1138
+ }
1139
+ clearTokens() {
1140
+ this.accessToken = null;
1141
+ this.refreshTokenValue = null;
1142
+ this.tokenExpiry = 0;
1143
+ storageDel(this.spec.storageKeys.access);
1144
+ storageDel(this.spec.storageKeys.refresh);
1145
+ storageDel(this.spec.storageKeys.expiry);
1146
+ }
1147
+ async ensureValidToken() {
1148
+ if (!this.accessToken) {
1149
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
1150
+ "Not authenticated \u2014 no access token",
1151
+ this.spec.displayName
1152
+ );
1153
+ }
1154
+ if (this.tokenExpiry > 0 && Date.now() > this.tokenExpiry - 6e4 && this.refreshTokenValue) {
1155
+ await this.refreshAccessToken();
1156
+ }
1157
+ }
1158
+ // ── Private: redirect URI ──
1159
+ getRedirectUri() {
1160
+ if (this.config.redirectUri) {
1161
+ return this.config.redirectUri;
1162
+ }
1163
+ if (typeof window !== "undefined") {
1164
+ return `${window.location.origin}${this.spec.redirectPath}`;
1165
+ }
1166
+ return "";
1167
+ }
1168
+ // ── Private: popup cleanup ──
1169
+ cleanupPopup() {
1170
+ this.cleanupPollTimer();
1171
+ if (this.popupWindow && !this.popupWindow.closed) {
1172
+ this.popupWindow.close();
1173
+ }
1174
+ this.popupWindow = null;
1175
+ }
1176
+ cleanupPollTimer() {
1177
+ if (this.pollTimer) {
1178
+ clearInterval(this.pollTimer);
1179
+ this.pollTimer = null;
1180
+ }
1181
+ }
1182
+ };
1183
+
1184
+ // src/drives/mime.ts
1185
+ function guessMimeType(name) {
1186
+ if (!name) return "application/octet-stream";
1187
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
1188
+ const map = {
1189
+ jpg: "image/jpeg",
1190
+ jpeg: "image/jpeg",
1191
+ png: "image/png",
1192
+ gif: "image/gif",
1193
+ webp: "image/webp",
1194
+ svg: "image/svg+xml",
1195
+ pdf: "application/pdf",
1196
+ doc: "application/msword",
1197
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1198
+ xls: "application/vnd.ms-excel",
1199
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1200
+ ppt: "application/vnd.ms-powerpoint",
1201
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1202
+ txt: "text/plain",
1203
+ csv: "text/csv",
1204
+ json: "application/json",
1205
+ xml: "application/xml",
1206
+ zip: "application/zip",
1207
+ mp3: "audio/mpeg",
1208
+ mp4: "video/mp4",
1209
+ mov: "video/quicktime",
1210
+ avi: "video/x-msvideo",
1211
+ html: "text/html",
1212
+ css: "text/css",
1213
+ js: "application/javascript",
1214
+ ts: "application/typescript"
1215
+ };
1216
+ return map[ext] ?? "application/octet-stream";
1217
+ }
1218
+
1219
+ // src/drives/dropbox-plugin.ts
1220
+ var AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
1221
+ var TOKEN_URL = "https://api.dropbox.com/oauth2/token";
1222
+ var LIST_FOLDER_URL = "https://api.dropbox.com/2/files/list_folder";
1223
+ var LIST_FOLDER_CONTINUE_URL = "https://api.dropbox.com/2/files/list_folder/continue";
1224
+ var SEARCH_URL = "https://api.dropbox.com/2/files/search_v2";
1225
+ var USER_INFO_URL = "https://api.dropbox.com/2/users/get_current_account";
1226
+ var TEMP_LINK_URL = "https://api.dropbox.com/2/files/get_temporary_link";
1227
+ var OAUTH_SCOPES = "files.metadata.read files.content.read files.content.write account_info.read";
1228
+ function mapEntry(entry) {
1229
+ const tag = entry[".tag"];
1230
+ const isFolder = tag === "folder";
1231
+ return {
1232
+ id: entry.id ?? "",
1233
+ name: entry.name ?? "",
1234
+ path: entry.path_display ?? "",
1235
+ size: isFolder ? 0 : entry.size ?? 0,
1236
+ mimeType: isFolder ? "folder" : guessMimeType(entry.name),
1237
+ isFolder,
1238
+ thumbnail: void 0,
1239
+ modifiedAt: entry.server_modified ?? void 0
1240
+ };
1241
+ }
1242
+ var DropboxPlugin = class extends PopupOAuthPlugin {
1243
+ constructor() {
1244
+ super(...arguments);
1245
+ this.spec = {
1246
+ id: "dropbox",
1247
+ displayName: "Dropbox",
1248
+ eventPrefix: "dropbox",
1249
+ popupName: "UpupDropboxAuth",
1250
+ authUrl: AUTH_URL,
1251
+ tokenUrl: TOKEN_URL,
1252
+ redirectPath: "/dp_redirect",
1253
+ storageKeys: {
1254
+ access: "upup_dropbox_access_token",
1255
+ refresh: "upup_dropbox_refresh_token",
1256
+ expiry: "upup_dropbox_token_expiry"
1257
+ },
1258
+ scopes: OAUTH_SCOPES,
1259
+ authParams: { token_access_type: "offline" }
1260
+ };
1261
+ }
1262
+ configure(config) {
1263
+ return super.configure(config);
1264
+ }
1265
+ getConfig() {
1266
+ return this.config;
1267
+ }
1268
+ // ── File operations: list folder ──
1269
+ async loadFiles(path = "") {
1270
+ this.setState("browsing");
1271
+ try {
1272
+ const res = await this.apiRequest(LIST_FOLDER_URL, {
1273
+ method: "POST",
1274
+ headers: { "Content-Type": "application/json" },
1275
+ body: JSON.stringify({
1276
+ path,
1277
+ recursive: false,
1278
+ include_media_info: true,
1279
+ include_deleted: false,
1280
+ include_has_explicit_shared_members: false
1281
+ })
1282
+ });
1283
+ const data = await res.json();
1284
+ const files = (data.entries ?? []).map(mapEntry);
1285
+ this.setState("authenticated");
1286
+ this.emitter?.emit("dropbox:files-loaded", {
1287
+ files,
1288
+ path,
1289
+ hasMore: !!data.has_more,
1290
+ cursor: data.cursor
1291
+ });
1292
+ return {
1293
+ files,
1294
+ hasMore: !!data.has_more,
1295
+ ...data.cursor !== void 0 ? { cursor: data.cursor } : {}
1296
+ };
1297
+ } catch (err) {
1298
+ this.setState("authenticated");
1299
+ this.emitter?.emit("dropbox:error", {
1300
+ error: err instanceof Error ? err : new Error(String(err)),
1301
+ action: "loadFiles"
1302
+ });
1303
+ throw err;
1304
+ }
1305
+ }
1306
+ // ── File operations: continue listing (pagination) ──
1307
+ async loadMoreFiles(cursor) {
1308
+ try {
1309
+ const res = await this.apiRequest(LIST_FOLDER_CONTINUE_URL, {
1310
+ method: "POST",
1311
+ headers: { "Content-Type": "application/json" },
1312
+ body: JSON.stringify({ cursor })
1313
+ });
1314
+ const data = await res.json();
1315
+ const files = (data.entries ?? []).map(mapEntry);
1316
+ return {
1317
+ files,
1318
+ hasMore: !!data.has_more,
1319
+ ...data.cursor !== void 0 ? { cursor: data.cursor } : {}
1320
+ };
1321
+ } catch (err) {
1322
+ this.emitter?.emit("dropbox:error", {
1323
+ error: err instanceof Error ? err : new Error(String(err)),
1324
+ action: "loadMoreFiles"
1325
+ });
1326
+ throw err;
1327
+ }
1328
+ }
1329
+ // ── File operations: load all files in folder recursively ──
1330
+ async loadAllFilesInFolder(folderPath) {
1331
+ const allFiles = [];
1332
+ try {
1333
+ const initialRes = await this.apiRequest(LIST_FOLDER_URL, {
1334
+ method: "POST",
1335
+ headers: { "Content-Type": "application/json" },
1336
+ body: JSON.stringify({
1337
+ path: folderPath,
1338
+ recursive: true,
1339
+ include_media_info: true
1340
+ })
1341
+ });
1342
+ let data = await initialRes.json();
1343
+ const entries = data.entries ?? [];
1344
+ while (data.has_more) {
1345
+ const contRes = await this.apiRequest(
1346
+ LIST_FOLDER_CONTINUE_URL,
1347
+ {
1348
+ method: "POST",
1349
+ headers: { "Content-Type": "application/json" },
1350
+ body: JSON.stringify({ cursor: data.cursor })
1351
+ }
1352
+ );
1353
+ data = await contRes.json();
1354
+ entries.push(...data.entries ?? []);
1355
+ }
1356
+ for (const entry of entries) {
1357
+ const file = mapEntry(entry);
1358
+ if (!file.isFolder) allFiles.push(file);
1359
+ }
1360
+ return allFiles;
1361
+ } catch (err) {
1362
+ this.emitter?.emit("dropbox:error", {
1363
+ error: err instanceof Error ? err : new Error(String(err)),
1364
+ action: "loadAllFilesInFolder"
1365
+ });
1366
+ throw err;
1367
+ }
1368
+ }
1369
+ // ── File operations: download files ──
1370
+ async downloadFiles(driveFiles) {
1371
+ const results = [];
1372
+ for (const driveFile of driveFiles) {
1373
+ if (driveFile.isFolder) continue;
1374
+ try {
1375
+ const file = await this.downloadSingleFile(driveFile);
1376
+ if (file) {
1377
+ results.push(file);
1378
+ }
1379
+ } catch (err) {
1380
+ this.emitter?.emit("dropbox:error", {
1381
+ error: err instanceof Error ? err : new Error(String(err)),
1382
+ action: "downloadFiles"
1383
+ });
1384
+ }
1385
+ }
1386
+ return results;
1387
+ }
1388
+ // ── File operations: search ──
1389
+ async searchFiles(query, path) {
1390
+ try {
1391
+ const body = {
1392
+ query,
1393
+ options: {
1394
+ max_results: 100,
1395
+ file_status: { ".tag": "active" }
1396
+ }
1397
+ };
1398
+ if (path) {
1399
+ body.options = {
1400
+ ...body.options,
1401
+ path
1402
+ };
1403
+ }
1404
+ const res = await this.apiRequest(SEARCH_URL, {
1405
+ method: "POST",
1406
+ headers: { "Content-Type": "application/json" },
1407
+ body: JSON.stringify(body)
1408
+ });
1409
+ const data = await res.json();
1410
+ const matches = data.matches ?? [];
1411
+ return matches.map((m) => mapEntry(m.metadata.metadata));
1412
+ } catch (err) {
1413
+ this.emitter?.emit("dropbox:error", {
1414
+ error: err instanceof Error ? err : new Error(String(err)),
1415
+ action: "searchFiles"
1416
+ });
1417
+ throw err;
1418
+ }
1419
+ }
1420
+ // ── Provider hooks required by the base ──
1421
+ mapEntry(entry) {
1422
+ return mapEntry(entry);
1423
+ }
1424
+ // ── Private: download a single file via temporary link ──
1425
+ async downloadSingleFile(driveFile) {
1426
+ const linkRes = await this.apiRequest(TEMP_LINK_URL, {
1427
+ method: "POST",
1428
+ headers: { "Content-Type": "application/json" },
1429
+ body: JSON.stringify({ path: driveFile.path })
1430
+ });
1431
+ const linkData = await linkRes.json();
1432
+ const downloadLink = linkData.link;
1433
+ if (!downloadLink) {
1434
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
1435
+ `No download link returned for ${driveFile.name}`
1436
+ );
1437
+ }
1438
+ const downloadRes = await fetch(downloadLink, { method: "GET" });
1439
+ if (!downloadRes.ok) {
1440
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
1441
+ `Download failed (${downloadRes.status}) for ${driveFile.name}`,
1442
+ downloadRes.status
1443
+ );
1444
+ }
1445
+ const blob = await downloadRes.blob();
1446
+ return new File([blob], driveFile.name, {
1447
+ type: blob.type || driveFile.mimeType || "application/octet-stream"
1448
+ });
1449
+ }
1450
+ // ── Private: fetch user profile ──
1451
+ async fetchUserProfile() {
1452
+ const res = await this.apiRequest(USER_INFO_URL, {
1453
+ method: "POST",
1454
+ headers: { "Content-Type": "application/json" },
1455
+ body: null
1456
+ });
1457
+ const data = await res.json();
1458
+ return {
1459
+ name: data.name?.display_name ?? "",
1460
+ email: data.email ?? ""
1461
+ };
1462
+ }
1463
+ };
1464
+
1465
+ // src/drives/google-drive-plugin.ts
1466
+ var SK_ACCESS = "upup_gdrive_access_token";
1467
+ var SK_EXPIRY = "upup_gdrive_token_expiry";
1468
+ var FILES_URL = "https://www.googleapis.com/drive/v3/files";
1469
+ var USER_INFO_URL2 = "https://www.googleapis.com/oauth2/v3/userinfo";
1470
+ var WORKSPACE_EXPORT_MAP = {
1471
+ "application/vnd.google-apps.document": {
1472
+ exportMime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1473
+ ext: "docx",
1474
+ docType: "document"
1475
+ },
1476
+ "application/vnd.google-apps.spreadsheet": {
1477
+ exportMime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1478
+ ext: "xlsx",
1479
+ docType: "spreadsheets"
1480
+ },
1481
+ "application/vnd.google-apps.presentation": {
1482
+ exportMime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1483
+ ext: "pptx",
1484
+ docType: "presentation"
1485
+ },
1486
+ "application/vnd.google-apps.drawing": {
1487
+ exportMime: "image/png",
1488
+ ext: "png",
1489
+ docType: "drawings"
1490
+ }
1491
+ };
1492
+ var FORMAT_MAP = {
1493
+ docx: "docx",
1494
+ xlsx: "xlsx",
1495
+ pptx: "pptx",
1496
+ png: "png"
1497
+ };
1498
+ function isWorkspaceFile(mimeType) {
1499
+ return mimeType in WORKSPACE_EXPORT_MAP;
1500
+ }
1501
+ function getExportUrl(fileId, mimeType) {
1502
+ const mapping = WORKSPACE_EXPORT_MAP[mimeType];
1503
+ if (!mapping) return null;
1504
+ const format = FORMAT_MAP[mapping.ext];
1505
+ return `https://docs.google.com/${mapping.docType}/d/${fileId}/export?format=${format}`;
1506
+ }
1507
+ function mapGoogleEntry(entry) {
1508
+ const mimeType = entry.mimeType ?? "";
1509
+ const isFolder = mimeType === "application/vnd.google-apps.folder";
1510
+ return {
1511
+ id: entry.id ?? "",
1512
+ name: entry.name ?? "",
1513
+ path: "",
1514
+ // Google Drive doesn't return a path, uses parent IDs
1515
+ size: isFolder ? 0 : typeof entry.size === "number" ? entry.size : typeof entry.size === "string" ? parseInt(entry.size, 10) || 0 : 0,
1516
+ mimeType: isFolder ? "folder" : mimeType,
1517
+ isFolder,
1518
+ thumbnail: entry.thumbnailLink ?? void 0,
1519
+ modifiedAt: void 0
1520
+ };
1521
+ }
1522
+ var GoogleDrivePlugin = class {
1523
+ constructor() {
1524
+ this.id = "google-drive";
1525
+ this.name = "google-drive";
1526
+ this.emitter = null;
1527
+ this.config = {
1528
+ apiKey: "",
1529
+ appId: "",
1530
+ clientId: ""
1531
+ };
1532
+ this.accessToken = null;
1533
+ this.tokenExpiry = 0;
1534
+ this.state = "idle";
1535
+ }
1536
+ // ── Plugin lifecycle ──
1537
+ configure(config) {
1538
+ this.config = config;
1539
+ return this;
1540
+ }
1541
+ getConfig() {
1542
+ return this.config;
1543
+ }
1544
+ init(emitter) {
1545
+ this.emitter = emitter;
1546
+ }
1547
+ destroy() {
1548
+ this.emitter = null;
1549
+ }
1550
+ // ── State management ──
1551
+ getState() {
1552
+ return this.state;
1553
+ }
1554
+ setState(newState) {
1555
+ this.state = newState;
1556
+ this.emitter?.emit("google-drive:state-change", { state: newState });
1557
+ }
1558
+ // ── Auth: accept token from GIS popup (called by React thin hook) ──
1559
+ setAccessToken(token, expiresIn) {
1560
+ const expiry = expiresIn ? Date.now() + expiresIn * 1e3 : 0;
1561
+ this.accessToken = token;
1562
+ this.tokenExpiry = expiry;
1563
+ storageSet(SK_ACCESS, token);
1564
+ if (expiry) storageSet(SK_EXPIRY, String(expiry));
1565
+ this.setState("authenticated");
1566
+ }
1567
+ // ── Auth: authenticate with token + fetch user info ──
1568
+ async authenticate(token, expiresIn) {
1569
+ this.setState("authenticating");
1570
+ try {
1571
+ this.setAccessToken(token, expiresIn);
1572
+ let user;
1573
+ try {
1574
+ user = await this.getUserInfo();
1575
+ } catch {
1576
+ }
1577
+ this.setState("authenticated");
1578
+ this.emitter?.emit("google-drive:authenticated", { user });
1579
+ } catch (err) {
1580
+ this.setState("idle");
1581
+ this.emitter?.emit("google-drive:error", {
1582
+ error: err instanceof Error ? err : new Error(String(err)),
1583
+ action: "authenticate"
1584
+ });
1585
+ throw err;
1586
+ }
1587
+ }
1588
+ // ── Auth: sign out ──
1589
+ signOut() {
1590
+ this.clearTokens();
1591
+ this.setState("idle");
1592
+ this.emitter?.emit("google-drive:signed-out", {});
1593
+ }
1594
+ // ── Auth: restore session from sessionStorage ──
1595
+ restoreSession() {
1596
+ const token = storageGet(SK_ACCESS);
1597
+ const expiry = storageGet(SK_EXPIRY);
1598
+ if (!token) return false;
1599
+ this.accessToken = token;
1600
+ this.tokenExpiry = expiry ? parseInt(expiry, 10) : 0;
1601
+ if (this.tokenExpiry > 0 && Date.now() > this.tokenExpiry) {
1602
+ this.clearTokens();
1603
+ this.emitter?.emit("google-drive:session-expired", {});
1604
+ this.setState("session-expired");
1605
+ return false;
1606
+ }
1607
+ this.setState("authenticated");
1608
+ return true;
1609
+ }
1610
+ // ── Auth helpers ──
1611
+ isAuthenticated() {
1612
+ return this.state === "authenticated" && this.accessToken !== null;
1613
+ }
1614
+ getAccessToken() {
1615
+ return this.accessToken;
1616
+ }
1617
+ // ── User info ──
1618
+ async getUserInfo() {
1619
+ const res = await this.apiRequest(USER_INFO_URL2, { method: "GET" });
1620
+ const data = await res.json();
1621
+ return {
1622
+ name: data.name ?? "",
1623
+ email: data.email ?? "",
1624
+ ...data.picture !== void 0 ? { picture: data.picture } : {}
1625
+ };
1626
+ }
1627
+ // ── File operations: list files ──
1628
+ async loadFiles(folderId) {
1629
+ this.setState("browsing");
1630
+ try {
1631
+ const parentId = folderId || "root";
1632
+ const q = `'${parentId}' in parents and trashed = false`;
1633
+ const params = new URLSearchParams({
1634
+ q,
1635
+ fields: "nextPageToken,files(fileExtension,id,mimeType,name,parents,size,thumbnailLink)",
1636
+ key: this.config.apiKey,
1637
+ pageSize: "1000"
1638
+ });
1639
+ const res = await this.apiRequest(
1640
+ `${FILES_URL}?${params.toString()}`,
1641
+ { method: "GET" }
1642
+ );
1643
+ const data = await res.json();
1644
+ const files = (data.files ?? []).map(mapGoogleEntry);
1645
+ const hasMore = !!data.nextPageToken;
1646
+ const cursor = hasMore ? JSON.stringify({
1647
+ folderId: parentId,
1648
+ pageToken: data.nextPageToken
1649
+ }) : void 0;
1650
+ this.setState("authenticated");
1651
+ this.emitter?.emit("google-drive:files-loaded", {
1652
+ files,
1653
+ folderId: parentId,
1654
+ hasMore,
1655
+ cursor
1656
+ });
1657
+ return {
1658
+ files,
1659
+ folderId: parentId,
1660
+ hasMore,
1661
+ ...cursor !== void 0 ? { cursor } : {}
1662
+ };
1663
+ } catch (err) {
1664
+ this.setState("authenticated");
1665
+ this.emitter?.emit("google-drive:error", {
1666
+ error: err instanceof Error ? err : new Error(String(err)),
1667
+ action: "loadFiles"
1668
+ });
1669
+ throw err;
1670
+ }
1671
+ }
1672
+ // ── File operations: continue listing (pagination) ──
1673
+ async loadMoreFiles(cursor) {
1674
+ try {
1675
+ const { folderId, pageToken } = JSON.parse(cursor);
1676
+ const q = `'${folderId}' in parents and trashed = false`;
1677
+ const params = new URLSearchParams({
1678
+ q,
1679
+ fields: "nextPageToken,files(fileExtension,id,mimeType,name,parents,size,thumbnailLink)",
1680
+ key: this.config.apiKey,
1681
+ pageSize: "1000",
1682
+ pageToken
1683
+ });
1684
+ const res = await this.apiRequest(
1685
+ `${FILES_URL}?${params.toString()}`,
1686
+ { method: "GET" }
1687
+ );
1688
+ const data = await res.json();
1689
+ const files = (data.files ?? []).map(mapGoogleEntry);
1690
+ const hasMore = !!data.nextPageToken;
1691
+ const nextCursor = hasMore ? JSON.stringify({ folderId, pageToken: data.nextPageToken }) : void 0;
1692
+ return {
1693
+ files,
1694
+ hasMore,
1695
+ ...nextCursor !== void 0 ? { cursor: nextCursor } : {}
1696
+ };
1697
+ } catch (err) {
1698
+ this.emitter?.emit("google-drive:error", {
1699
+ error: err instanceof Error ? err : new Error(String(err)),
1700
+ action: "loadMoreFiles"
1701
+ });
1702
+ throw err;
1703
+ }
1704
+ }
1705
+ // ── File operations: download multiple files ──
1706
+ async downloadFiles(driveFiles) {
1707
+ const results = [];
1708
+ for (const driveFile of driveFiles) {
1709
+ if (driveFile.isFolder) continue;
1710
+ try {
1711
+ const file = await this.downloadFile(driveFile);
1712
+ if (file) {
1713
+ results.push(file);
1714
+ }
1715
+ } catch (err) {
1716
+ this.emitter?.emit("google-drive:error", {
1717
+ error: err instanceof Error ? err : new Error(String(err)),
1718
+ action: "downloadFiles"
1719
+ });
1720
+ }
1721
+ }
1722
+ return results;
1723
+ }
1724
+ // ── File operations: download single file ──
1725
+ async downloadFile(driveFile) {
1726
+ if (isWorkspaceFile(driveFile.mimeType)) {
1727
+ return this.downloadWorkspaceFile(driveFile);
1728
+ }
1729
+ return this.downloadRegularFile(driveFile);
1730
+ }
1731
+ // ── Private: download regular (non-Workspace) file ──
1732
+ async downloadRegularFile(driveFile) {
1733
+ const params = new URLSearchParams({
1734
+ key: this.config.apiKey,
1735
+ alt: "media"
1736
+ });
1737
+ const res = await this.apiRequest(
1738
+ `${FILES_URL}/${driveFile.id}?${params.toString()}`,
1739
+ { method: "GET" }
1740
+ );
1741
+ const blob = await res.blob();
1742
+ return new File([blob], driveFile.name, {
1743
+ type: blob.type || driveFile.mimeType || "application/octet-stream"
1744
+ });
1745
+ }
1746
+ // ── Private: download Google Workspace file via export ──
1747
+ async downloadWorkspaceFile(driveFile) {
1748
+ const exportUrl = getExportUrl(driveFile.id, driveFile.mimeType);
1749
+ if (!exportUrl) return null;
1750
+ const mapping = WORKSPACE_EXPORT_MAP[driveFile.mimeType];
1751
+ if (!mapping) return null;
1752
+ const res = await this.apiRequest(exportUrl, { method: "GET" });
1753
+ const blob = await res.blob();
1754
+ const baseName = driveFile.name.replace(/\.[^.]+$/, "");
1755
+ const fileName = `${baseName}.${mapping.ext}`;
1756
+ return new File([blob], fileName, {
1757
+ type: mapping.exportMime
1758
+ });
1759
+ }
1760
+ // ── Private: authenticated API request ──
1761
+ async apiRequest(url, options) {
1762
+ this.ensureValidToken();
1763
+ const headers = new Headers(options.headers ?? {});
1764
+ headers.set("Authorization", `Bearer ${this.accessToken}`);
1765
+ const res = await fetch(url, { ...options, headers });
1766
+ if (res.ok) return res;
1767
+ const errorText = await res.text();
1768
+ if (res.status === 401) {
1769
+ this.emitter?.emit("google-drive:session-expired", {});
1770
+ this.clearTokens();
1771
+ this.setState("session-expired");
1772
+ }
1773
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
1774
+ `Google Drive API error (${res.status}): ${errorText}`,
1775
+ res.status
1776
+ );
1777
+ }
1778
+ // ── Private: token management ──
1779
+ clearTokens() {
1780
+ this.accessToken = null;
1781
+ this.tokenExpiry = 0;
1782
+ storageDel(SK_ACCESS);
1783
+ storageDel(SK_EXPIRY);
1784
+ }
1785
+ ensureValidToken() {
1786
+ if (!this.accessToken) {
1787
+ throw new chunkQK7HZIVY_cjs.UpupAuthError(
1788
+ "Not authenticated \u2014 no access token",
1789
+ "Google Drive"
1790
+ );
1791
+ }
1792
+ if (this.tokenExpiry > 0 && Date.now() > this.tokenExpiry) {
1793
+ this.clearTokens();
1794
+ this.emitter?.emit("google-drive:session-expired", {});
1795
+ this.setState("session-expired");
1796
+ throw new chunkQK7HZIVY_cjs.UpupAuthError("Access token has expired", "Google Drive");
1797
+ }
1798
+ }
1799
+ };
1800
+
1801
+ // src/drives/box-plugin.ts
1802
+ var AUTH_URL2 = "https://account.box.com/api/oauth2/authorize";
1803
+ var TOKEN_URL2 = "https://api.box.com/oauth2/token";
1804
+ var USER_URL = "https://api.box.com/2.0/users/me";
1805
+ var FOLDERS_URL = "https://api.box.com/2.0/folders";
1806
+ var FILES_URL2 = "https://api.box.com/2.0/files";
1807
+ var SEARCH_URL2 = "https://api.box.com/2.0/search";
1808
+ function mapEntry2(entry) {
1809
+ const type = entry.type;
1810
+ const isFolder = type === "folder";
1811
+ return {
1812
+ id: entry.id ?? "",
1813
+ name: entry.name ?? "",
1814
+ path: entry.id ?? "",
1815
+ // Box uses IDs for navigation, not paths
1816
+ size: isFolder ? 0 : entry.size ?? 0,
1817
+ mimeType: isFolder ? "folder" : guessMimeType(entry.name),
1818
+ isFolder,
1819
+ thumbnail: void 0,
1820
+ modifiedAt: entry.modified_at ?? void 0
1821
+ };
1822
+ }
1823
+ var BoxPlugin = class extends PopupOAuthPlugin {
1824
+ constructor() {
1825
+ super(...arguments);
1826
+ this.spec = {
1827
+ id: "box",
1828
+ displayName: "Box",
1829
+ eventPrefix: "box",
1830
+ popupName: "UpupBoxAuth",
1831
+ authUrl: AUTH_URL2,
1832
+ tokenUrl: TOKEN_URL2,
1833
+ redirectPath: "/box_redirect",
1834
+ storageKeys: {
1835
+ access: "upup_box_access_token",
1836
+ refresh: "upup_box_refresh_token",
1837
+ expiry: "upup_box_token_expiry"
1838
+ }
1839
+ // Box scopes are configured on the Box app, not sent on the authorize URL.
1840
+ };
1841
+ }
1842
+ configure(config) {
1843
+ return super.configure(config);
1844
+ }
1845
+ getConfig() {
1846
+ return this.config;
1847
+ }
1848
+ // ── File operations: list folder ──
1849
+ async loadFiles(folderId = "0", offset = "0") {
1850
+ this.setState("browsing");
1851
+ try {
1852
+ const params = new URLSearchParams({
1853
+ fields: "id,name,type,size,modified_at",
1854
+ limit: "1000",
1855
+ offset
1856
+ });
1857
+ const res = await this.apiRequest(
1858
+ `${FOLDERS_URL}/${folderId}/items?${params.toString()}`,
1859
+ { method: "GET" }
1860
+ );
1861
+ const data = await res.json();
1862
+ const files = (data.entries ?? []).map(mapEntry2);
1863
+ const off = Number(data.offset ?? offset);
1864
+ const loaded = off + files.length;
1865
+ const hasMore = loaded < (data.total_count ?? loaded);
1866
+ const cursor = hasMore ? `${folderId}:${loaded}` : void 0;
1867
+ this.setState("authenticated");
1868
+ this.emitter?.emit("box:files-loaded", {
1869
+ files,
1870
+ folderId,
1871
+ hasMore,
1872
+ cursor
1873
+ });
1874
+ return {
1875
+ files,
1876
+ folderId,
1877
+ hasMore,
1878
+ ...cursor !== void 0 ? { cursor } : {}
1879
+ };
1880
+ } catch (err) {
1881
+ this.setState("authenticated");
1882
+ this.emitter?.emit("box:error", {
1883
+ error: err instanceof Error ? err : new Error(String(err)),
1884
+ action: "loadFiles"
1885
+ });
1886
+ throw err;
1887
+ }
1888
+ }
1889
+ // ── File operations: continue listing (pagination) ──
1890
+ async loadMoreFiles(cursor) {
1891
+ const [folderId, offsetStr] = cursor.split(":");
1892
+ try {
1893
+ const params = new URLSearchParams({
1894
+ fields: "id,name,type,size,modified_at",
1895
+ limit: "1000",
1896
+ offset: offsetStr ?? "0"
1897
+ });
1898
+ const res = await this.apiRequest(
1899
+ `${FOLDERS_URL}/${folderId}/items?${params.toString()}`,
1900
+ { method: "GET" }
1901
+ );
1902
+ const data = await res.json();
1903
+ const files = (data.entries ?? []).map(mapEntry2);
1904
+ const off = Number(data.offset ?? offsetStr ?? 0);
1905
+ const loaded = off + files.length;
1906
+ const hasMore = loaded < (data.total_count ?? loaded);
1907
+ const nextCursor = hasMore ? `${folderId}:${loaded}` : void 0;
1908
+ return {
1909
+ files,
1910
+ hasMore,
1911
+ ...nextCursor !== void 0 ? { cursor: nextCursor } : {}
1912
+ };
1913
+ } catch (err) {
1914
+ this.emitter?.emit("box:error", {
1915
+ error: err instanceof Error ? err : new Error(String(err)),
1916
+ action: "loadMoreFiles"
1917
+ });
1918
+ throw err;
1919
+ }
1920
+ }
1921
+ // ── File operations: download files ──
1922
+ async downloadFiles(driveFiles) {
1923
+ const results = [];
1924
+ for (const driveFile of driveFiles) {
1925
+ if (driveFile.isFolder) continue;
1926
+ try {
1927
+ const file = await this.downloadSingleFile(driveFile);
1928
+ if (file) {
1929
+ results.push(file);
1930
+ }
1931
+ } catch (err) {
1932
+ this.emitter?.emit("box:error", {
1933
+ error: err instanceof Error ? err : new Error(String(err)),
1934
+ action: "downloadFiles"
1935
+ });
1936
+ }
1937
+ }
1938
+ return results;
1939
+ }
1940
+ // ── File operations: load all files in folder recursively ──
1941
+ async loadAllFilesInFolder(folderId) {
1942
+ const allFiles = [];
1943
+ try {
1944
+ const { files } = await this.loadFiles(folderId);
1945
+ for (const file of files) {
1946
+ if (file.isFolder) {
1947
+ const nested = await this.loadAllFilesInFolder(file.id);
1948
+ allFiles.push(...nested);
1949
+ } else {
1950
+ allFiles.push(file);
1951
+ }
1952
+ }
1953
+ return allFiles;
1954
+ } catch (err) {
1955
+ this.emitter?.emit("box:error", {
1956
+ error: err instanceof Error ? err : new Error(String(err)),
1957
+ action: "loadAllFilesInFolder"
1958
+ });
1959
+ throw err;
1960
+ }
1961
+ }
1962
+ // ── File operations: search ──
1963
+ async searchFiles(query) {
1964
+ try {
1965
+ const params = new URLSearchParams({
1966
+ query,
1967
+ fields: "id,name,type,size,modified_at",
1968
+ limit: "100"
1969
+ });
1970
+ const res = await this.apiRequest(
1971
+ `${SEARCH_URL2}?${params.toString()}`,
1972
+ { method: "GET" }
1973
+ );
1974
+ const data = await res.json();
1975
+ const entries = data.entries ?? [];
1976
+ return entries.map(mapEntry2);
1977
+ } catch (err) {
1978
+ this.emitter?.emit("box:error", {
1979
+ error: err instanceof Error ? err : new Error(String(err)),
1980
+ action: "searchFiles"
1981
+ });
1982
+ throw err;
1983
+ }
1984
+ }
1985
+ // ── Provider hooks required by the base ──
1986
+ mapEntry(entry) {
1987
+ return mapEntry2(entry);
1988
+ }
1989
+ // ── Private: download a single file ──
1990
+ async downloadSingleFile(driveFile) {
1991
+ const res = await this.apiRequest(
1992
+ `${FILES_URL2}/${driveFile.id}/content`,
1993
+ { method: "GET" }
1994
+ );
1995
+ const blob = await res.blob();
1996
+ return new File([blob], driveFile.name, {
1997
+ type: blob.type || driveFile.mimeType || "application/octet-stream"
1998
+ });
1999
+ }
2000
+ // ── Private: fetch user profile ──
2001
+ async fetchUserProfile() {
2002
+ const res = await this.apiRequest(USER_URL, { method: "GET" });
2003
+ const data = await res.json();
2004
+ return {
2005
+ name: data.name ?? "",
2006
+ email: data.login ?? ""
2007
+ };
2008
+ }
2009
+ };
2010
+
2011
+ // src/drives/one-drive-plugin.ts
2012
+ var AUTH_URL3 = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
2013
+ var TOKEN_URL3 = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
2014
+ var GRAPH_BASE = "https://graph.microsoft.com/v1.0";
2015
+ var OAUTH_SCOPES2 = "user.read files.readwrite.all offline_access";
2016
+ var FILE_SELECT = "id,name,folder,file,size,@microsoft.graph.downloadUrl";
2017
+ var FILE_EXPAND = "thumbnails";
2018
+ function mapGraphItem(item) {
2019
+ const isFolder = !!item.folder;
2020
+ const file = item.file;
2021
+ const mimeType = isFolder ? "folder" : file?.mimeType ?? guessMimeType(item.name);
2022
+ const thumbnails = item.thumbnails;
2023
+ let thumbnail;
2024
+ if (thumbnails && thumbnails.length > 0) {
2025
+ const thumbSet = thumbnails[0];
2026
+ const medium = thumbSet?.medium;
2027
+ thumbnail = medium?.url ?? void 0;
2028
+ }
2029
+ return {
2030
+ id: item.id ?? "",
2031
+ name: item.name ?? "",
2032
+ path: item.id ?? "",
2033
+ // OneDrive uses id-based navigation
2034
+ size: isFolder ? 0 : item.size ?? 0,
2035
+ mimeType,
2036
+ isFolder,
2037
+ thumbnail,
2038
+ modifiedAt: item.lastModifiedDateTime ?? void 0
2039
+ };
2040
+ }
2041
+ var OneDrivePlugin = class extends PopupOAuthPlugin {
2042
+ constructor() {
2043
+ super(...arguments);
2044
+ this.spec = {
2045
+ id: "one-drive",
2046
+ displayName: "OneDrive",
2047
+ eventPrefix: "one-drive",
2048
+ popupName: "UpupOneDriveAuth",
2049
+ authUrl: AUTH_URL3,
2050
+ tokenUrl: TOKEN_URL3,
2051
+ redirectPath: "/od_redirect",
2052
+ storageKeys: {
2053
+ access: "upup_one-drive_access_token",
2054
+ refresh: "upup_one-drive_refresh_token",
2055
+ expiry: "upup_one-drive_token_expiry"
2056
+ },
2057
+ scopes: OAUTH_SCOPES2,
2058
+ authParams: { response_mode: "query" }
2059
+ };
2060
+ }
2061
+ configure(config) {
2062
+ return super.configure(config);
2063
+ }
2064
+ getConfig() {
2065
+ return this.config;
2066
+ }
2067
+ // ── File operations: list files ──
2068
+ async loadFiles(folderId) {
2069
+ this.setState("browsing");
2070
+ try {
2071
+ const path = folderId ? `/me/drive/items/${folderId}/children` : "/me/drive/root/children";
2072
+ const params = new URLSearchParams({
2073
+ $select: FILE_SELECT,
2074
+ $expand: FILE_EXPAND,
2075
+ $top: "200"
2076
+ });
2077
+ const data = await this.graphRequest(`${path}?${params.toString()}`);
2078
+ const items = Array.isArray(data.value) ? data.value : [];
2079
+ const files = items.map(mapGraphItem);
2080
+ const nextLink = data["@odata.nextLink"];
2081
+ const hasMore = !!nextLink;
2082
+ this.setState("authenticated");
2083
+ this.emitter?.emit("one-drive:files-loaded", {
2084
+ files,
2085
+ folderId: folderId ?? "root",
2086
+ hasMore,
2087
+ cursor: nextLink
2088
+ });
2089
+ return {
2090
+ files,
2091
+ folderId: folderId ?? "root",
2092
+ hasMore,
2093
+ ...nextLink !== void 0 ? { cursor: nextLink } : {}
2094
+ };
2095
+ } catch (err) {
2096
+ this.setState("authenticated");
2097
+ this.emitter?.emit("one-drive:error", {
2098
+ error: err instanceof Error ? err : new Error(String(err)),
2099
+ action: "loadFiles"
2100
+ });
2101
+ throw err;
2102
+ }
2103
+ }
2104
+ // ── File operations: continue listing (pagination) ──
2105
+ async loadMoreFiles(cursor) {
2106
+ try {
2107
+ const data = await this.graphRequest(cursor);
2108
+ const items = Array.isArray(data.value) ? data.value : [];
2109
+ const files = items.map(mapGraphItem);
2110
+ const nextLink = data["@odata.nextLink"];
2111
+ return {
2112
+ files,
2113
+ hasMore: !!nextLink,
2114
+ ...nextLink !== void 0 ? { cursor: nextLink } : {}
2115
+ };
2116
+ } catch (err) {
2117
+ this.emitter?.emit("one-drive:error", {
2118
+ error: err instanceof Error ? err : new Error(String(err)),
2119
+ action: "loadMoreFiles"
2120
+ });
2121
+ throw err;
2122
+ }
2123
+ }
2124
+ // ── File operations: download files ──
2125
+ async downloadFiles(driveFiles) {
2126
+ const results = [];
2127
+ for (const driveFile of driveFiles) {
2128
+ if (driveFile.isFolder) continue;
2129
+ try {
2130
+ const file = await this.downloadSingleFile(driveFile);
2131
+ if (file) {
2132
+ results.push(file);
2133
+ }
2134
+ } catch (err) {
2135
+ this.emitter?.emit("one-drive:error", {
2136
+ error: err instanceof Error ? err : new Error(String(err)),
2137
+ action: "downloadFiles"
2138
+ });
2139
+ }
2140
+ }
2141
+ return results;
2142
+ }
2143
+ // ── File operations: load all files in folder recursively ──
2144
+ async loadAllFilesInFolder(folderId) {
2145
+ const allFiles = [];
2146
+ try {
2147
+ const { files } = await this.loadFiles(folderId);
2148
+ for (const file of files) {
2149
+ if (file.isFolder) {
2150
+ const nested = await this.loadAllFilesInFolder(file.id);
2151
+ allFiles.push(...nested);
2152
+ } else {
2153
+ allFiles.push(file);
2154
+ }
2155
+ }
2156
+ return allFiles;
2157
+ } catch (err) {
2158
+ this.emitter?.emit("one-drive:error", {
2159
+ error: err instanceof Error ? err : new Error(String(err)),
2160
+ action: "loadAllFilesInFolder"
2161
+ });
2162
+ throw err;
2163
+ }
2164
+ }
2165
+ // ── Provider hooks required by the base ──
2166
+ mapEntry(entry) {
2167
+ return mapGraphItem(entry);
2168
+ }
2169
+ // ── Private: fetch user profile (unguarded — called once tokens are set
2170
+ // but before isAuthenticated() flips, and again via the public getUserInfo) ──
2171
+ async fetchUserProfile() {
2172
+ const data = await this.graphRequest("/me");
2173
+ const displayName = data.displayName;
2174
+ const mail = data.mail;
2175
+ const userPrincipalName = data.userPrincipalName;
2176
+ return {
2177
+ name: typeof displayName === "string" ? displayName : "",
2178
+ email: typeof mail === "string" ? mail : typeof userPrincipalName === "string" ? userPrincipalName : ""
2179
+ };
2180
+ }
2181
+ // ── Private: Microsoft Graph API request (delegates auth/refresh/retry to
2182
+ // the base apiRequest; returns parsed JSON, tolerating absolute URLs) ──
2183
+ async graphRequest(path, options = {}) {
2184
+ const url = path.startsWith("http") ? path : `${GRAPH_BASE}${path}`;
2185
+ const res = await this.apiRequest(url, options);
2186
+ return await res.json();
2187
+ }
2188
+ // ── Private: download a single file ──
2189
+ async downloadSingleFile(driveFile) {
2190
+ const itemData = await this.graphRequest(
2191
+ `/me/drive/items/${driveFile.id}?select=@microsoft.graph.downloadUrl`
2192
+ );
2193
+ const downloadUrl = itemData["@microsoft.graph.downloadUrl"] ?? itemData["@content.downloadUrl"];
2194
+ if (!downloadUrl) {
2195
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
2196
+ `No download URL available for ${driveFile.name}`
2197
+ );
2198
+ }
2199
+ const downloadRes = await fetch(downloadUrl, { method: "GET" });
2200
+ if (!downloadRes.ok) {
2201
+ throw new chunkQK7HZIVY_cjs.UpupNetworkError(
2202
+ `Download failed (${downloadRes.status}) for ${driveFile.name}`,
2203
+ downloadRes.status
2204
+ );
2205
+ }
2206
+ const blob = await downloadRes.blob();
2207
+ return new File([blob], driveFile.name, {
2208
+ type: blob.type || driveFile.mimeType || "application/octet-stream"
2209
+ });
2210
+ }
2211
+ };
2212
+
2213
+ // src/utils/accept-presets.ts
2214
+ var ACCEPT_PRESETS = {
2215
+ images: {
2216
+ label: "Images",
2217
+ accept: "image/*"
2218
+ },
2219
+ video: {
2220
+ label: "Video",
2221
+ accept: "video/*"
2222
+ },
2223
+ audio: {
2224
+ label: "Audio",
2225
+ accept: "audio/*"
2226
+ },
2227
+ documents: {
2228
+ label: "Documents",
2229
+ accept: "application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.oasis.opendocument.text,text/plain,text/rtf"
2230
+ },
2231
+ spreadsheets: {
2232
+ label: "Spreadsheets",
2233
+ accept: "application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv,application/vnd.oasis.opendocument.spreadsheet,.tsv"
2234
+ },
2235
+ presentations: {
2236
+ label: "Presentations",
2237
+ accept: "application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.oasis.opendocument.presentation,.key"
2238
+ },
2239
+ archives: {
2240
+ label: "Archives",
2241
+ accept: "application/zip,application/x-rar-compressed,application/x-7z-compressed,application/x-tar,application/gzip,.bz2"
2242
+ },
2243
+ code: {
2244
+ label: "Code",
2245
+ accept: "text/javascript,application/json,text/html,text/css,text/xml,text/yaml,.ts,.tsx,.jsx,.py,.rb,.go,.rs,.java,.cpp,.c,.h,.sh,.sql"
2246
+ },
2247
+ fonts: {
2248
+ label: "Fonts",
2249
+ accept: "font/ttf,font/otf,font/woff,font/woff2,.ttf,.otf,.woff,.woff2"
2250
+ },
2251
+ "3d": {
2252
+ label: "3D",
2253
+ accept: ".obj,.stl,.gltf,.glb,.fbx,.usdz"
2254
+ },
2255
+ design: {
2256
+ label: "Design",
2257
+ accept: "image/svg+xml,.psd,.ai,.sketch,.fig,.xd"
2258
+ },
2259
+ ebooks: {
2260
+ label: "Ebooks",
2261
+ accept: "application/epub+zip,application/pdf,.mobi,.azw"
2262
+ },
2263
+ photography: {
2264
+ label: "Photography",
2265
+ accept: "image/*,.cr2,.cr3,.nef,.arw,.dng,.orf,.rw2,.raf,.raw"
2266
+ },
2267
+ animation: {
2268
+ label: "Animation",
2269
+ accept: "image/gif,image/apng,image/webp,.lottie,.rive"
2270
+ },
2271
+ ar: {
2272
+ label: "AR",
2273
+ accept: ".usdz,.reality,.glb,.gltf"
2274
+ },
2275
+ vector: {
2276
+ label: "Vector",
2277
+ accept: "image/svg+xml,.eps,.ai"
2278
+ },
2279
+ cad: {
2280
+ label: "CAD",
2281
+ accept: ".dwg,.dxf,.step,.stp,.iges,.igs"
2282
+ },
2283
+ gis: {
2284
+ label: "GIS",
2285
+ accept: "application/geo+json,.kml,.kmz,.shp,.gpx,.geojson"
2286
+ },
2287
+ data: {
2288
+ label: "Data",
2289
+ accept: "application/json,text/xml,text/csv,text/yaml,.toml,.parquet,.jsonl,.ndjson"
2290
+ },
2291
+ markup: {
2292
+ label: "Markup",
2293
+ accept: "text/markdown,.md,.rst,.tex,.adoc,.latex"
2294
+ },
2295
+ subtitles: {
2296
+ label: "Subtitles",
2297
+ accept: ".srt,.vtt,.ass,.sub,.ssa"
2298
+ },
2299
+ email: {
2300
+ label: "Email",
2301
+ accept: ".eml,.msg,.mbox"
2302
+ },
2303
+ calendar: {
2304
+ label: "Calendar",
2305
+ accept: "text/calendar,.ics,.ical"
2306
+ },
2307
+ contacts: {
2308
+ label: "Contacts",
2309
+ accept: "text/vcard,.vcf"
2310
+ },
2311
+ disk: {
2312
+ label: "Disk",
2313
+ accept: ".iso,.img,.dmg,.vhd,.vhdx"
2314
+ },
2315
+ ml: {
2316
+ label: "ML",
2317
+ accept: ".onnx,.h5,.pb,.pt,.safetensors,.pkl"
2318
+ },
2319
+ database: {
2320
+ label: "Database",
2321
+ accept: ".sql,.sqlite,.db,.mdb,.accdb"
2322
+ },
2323
+ certificates: {
2324
+ label: "Certificates",
2325
+ accept: ".pem,.crt,.cer,.p12,.pfx,.key"
2326
+ },
2327
+ firmware: {
2328
+ label: "Firmware",
2329
+ accept: ".bin,.hex,.elf,.srec"
2330
+ },
2331
+ executable: {
2332
+ label: "Executable",
2333
+ accept: ".exe,.msi,.dmg,.apk,.deb,.rpm,.appimage"
2334
+ },
2335
+ config: {
2336
+ label: "Config",
2337
+ accept: ".ini,.cfg,.conf,.env,.toml,.properties,.yaml,.yml"
2338
+ },
2339
+ financial: {
2340
+ label: "Financial",
2341
+ accept: ".ofx,.qfx,.qbo,.qif"
2342
+ },
2343
+ scientific: {
2344
+ label: "Scientific",
2345
+ accept: ".fits,.hdf5,.nc,.mat,.netcdf"
2346
+ },
2347
+ medical: {
2348
+ label: "Medical",
2349
+ accept: "application/dicom,.dcm,.nii,.nii.gz"
2350
+ }
2351
+ };
2352
+ function resolveAccept(input) {
2353
+ const raw = Array.isArray(input) ? input.join(",") : input;
2354
+ return raw.split(",").map((token) => {
2355
+ const trimmed = token.trim();
2356
+ const key = trimmed.toLowerCase();
2357
+ return ACCEPT_PRESETS[key]?.accept ?? trimmed;
2358
+ }).join(",");
2359
+ }
2360
+
2361
+ exports.ACCEPT_PRESETS = ACCEPT_PRESETS;
2362
+ exports.BoxPlugin = BoxPlugin;
2363
+ exports.CrashRecoveryManager = CrashRecoveryManager;
2364
+ exports.DropboxPlugin = DropboxPlugin;
2365
+ exports.EventEmitter = EventEmitter;
2366
+ exports.FileManager = FileManager;
2367
+ exports.GoogleDrivePlugin = GoogleDrivePlugin;
2368
+ exports.IndexedDBStorage = IndexedDBStorage;
2369
+ exports.OneDrivePlugin = OneDrivePlugin;
2370
+ exports.PluginManager = PluginManager;
2371
+ exports.PopupOAuthPlugin = PopupOAuthPlugin;
2372
+ exports.UploadManager = UploadManager;
2373
+ exports.isFileRemovalLocked = isFileRemovalLocked;
2374
+ exports.isUploadActive = isUploadActive;
2375
+ exports.isUploadIdle = isUploadIdle;
2376
+ exports.resolveAccept = resolveAccept;
2377
+ exports.validateFileRestrictions = validateFileRestrictions;