firebase-storage-kit 1.2.1 → 1.4.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.
- package/README.md +16 -56
- package/dist/chunk-PBO3CMOD.js +766 -0
- package/dist/chunk-PBO3CMOD.js.map +1 -0
- package/dist/firebase-storage-manager-DT6lxmUD.d.ts +260 -0
- package/dist/index.d.ts +8 -262
- package/dist/index.js +1 -682
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +29 -0
- package/dist/react/index.js +115 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +29 -16
package/dist/index.js
CHANGED
|
@@ -1,684 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// src/core/emitter.ts
|
|
4
|
-
var Emitter = class {
|
|
5
|
-
listeners = {};
|
|
6
|
-
/**
|
|
7
|
-
* @param event The event to listen for.
|
|
8
|
-
* @param listener The function to call when the event is emitted.
|
|
9
|
-
* @returns A function to unsubscribe from the event.
|
|
10
|
-
*/
|
|
11
|
-
on(event, listener) {
|
|
12
|
-
if (!this.listeners[event]) {
|
|
13
|
-
this.listeners[event] = /* @__PURE__ */ new Set();
|
|
14
|
-
}
|
|
15
|
-
this.listeners[event].add(listener);
|
|
16
|
-
return () => {
|
|
17
|
-
this.listeners[event]?.delete(listener);
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* @param event The event to emit.
|
|
22
|
-
* @param payload The payload to emit.
|
|
23
|
-
*/
|
|
24
|
-
emit(event, payload) {
|
|
25
|
-
const listeners = this.listeners[event];
|
|
26
|
-
if (!listeners) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
for (const listener of listeners) {
|
|
30
|
-
listener(payload);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// src/core/batch-handle.ts
|
|
36
|
-
var BatchHandle = class extends Emitter {
|
|
37
|
-
id;
|
|
38
|
-
uploads;
|
|
39
|
-
concurrency;
|
|
40
|
-
continueOnError;
|
|
41
|
-
startNext;
|
|
42
|
-
onChange;
|
|
43
|
-
running = 0;
|
|
44
|
-
nextIdx = 0;
|
|
45
|
-
/** Indices that were started via {@link fillSlots} and still hold a concurrency slot. */
|
|
46
|
-
activeSlots = /* @__PURE__ */ new Set();
|
|
47
|
-
settledCount = 0;
|
|
48
|
-
succeededCount = 0;
|
|
49
|
-
failedCount = 0;
|
|
50
|
-
failedFast = false;
|
|
51
|
-
canceled = false;
|
|
52
|
-
paused = false;
|
|
53
|
-
terminalEmitted = false;
|
|
54
|
-
// Stable view of UploadItems; the items themselves mutate in place, but the
|
|
55
|
-
// array reference never changes, so we allocate it once instead of rebuilding
|
|
56
|
-
// it on every snapshot.
|
|
57
|
-
uploadsView;
|
|
58
|
-
// Running aggregates updated incrementally on each child event so snapshot()
|
|
59
|
-
// is O(1) instead of O(n) per progress tick.
|
|
60
|
-
aggregateBytesTransferred = 0;
|
|
61
|
-
aggregateTotalBytes = 0;
|
|
62
|
-
lastBytes;
|
|
63
|
-
lastTotal;
|
|
64
|
-
constructor(init) {
|
|
65
|
-
super();
|
|
66
|
-
this.id = init.id;
|
|
67
|
-
this.uploads = init.uploads;
|
|
68
|
-
this.concurrency = Math.max(1, init.options.concurrency ?? 3);
|
|
69
|
-
this.continueOnError = init.options.continueOnError ?? true;
|
|
70
|
-
this.startNext = init.startNext;
|
|
71
|
-
this.onChange = init.onChange;
|
|
72
|
-
this.uploadsView = init.uploads.map((h) => h.upload);
|
|
73
|
-
this.lastBytes = new Array(init.uploads.length).fill(0);
|
|
74
|
-
this.lastTotal = new Array(init.uploads.length).fill(0);
|
|
75
|
-
for (let i = 0; i < this.uploadsView.length; i++) {
|
|
76
|
-
const u = this.uploadsView[i];
|
|
77
|
-
this.lastBytes[i] = u.bytesTransferred;
|
|
78
|
-
this.lastTotal[i] = u.totalBytes;
|
|
79
|
-
this.aggregateBytesTransferred += u.bytesTransferred;
|
|
80
|
-
this.aggregateTotalBytes += u.totalBytes;
|
|
81
|
-
}
|
|
82
|
-
this.uploads.forEach((child, idx) => {
|
|
83
|
-
child.on("progress", (upload) => this.handleChildProgress(idx, upload));
|
|
84
|
-
child.on("retry", (retryEvent) => this.handleChildRetry(idx, retryEvent));
|
|
85
|
-
child.on(
|
|
86
|
-
"success",
|
|
87
|
-
(upload) => this.handleChildSettled(idx, upload, "success")
|
|
88
|
-
);
|
|
89
|
-
child.on(
|
|
90
|
-
"error",
|
|
91
|
-
(upload) => this.handleChildSettled(idx, upload, "error")
|
|
92
|
-
);
|
|
93
|
-
child.on(
|
|
94
|
-
"canceled",
|
|
95
|
-
(upload) => this.handleChildSettled(idx, upload, "canceled")
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
/** @internal */
|
|
100
|
-
_start() {
|
|
101
|
-
this.fillSlots();
|
|
102
|
-
}
|
|
103
|
-
cancel() {
|
|
104
|
-
if (this.canceled || this.terminalEmitted) return;
|
|
105
|
-
this.canceled = true;
|
|
106
|
-
for (const h of this.uploads) {
|
|
107
|
-
const s = h.upload.status;
|
|
108
|
-
if (s === "queued" || s === "uploading" || s === "retrying" || s === "paused") {
|
|
109
|
-
h.cancel();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
this.onChange();
|
|
113
|
-
}
|
|
114
|
-
pause() {
|
|
115
|
-
if (this.paused || this.canceled || this.terminalEmitted) return;
|
|
116
|
-
this.paused = true;
|
|
117
|
-
for (const h of this.uploads) {
|
|
118
|
-
if (h.upload.status === "uploading" || h.upload.status === "retrying") {
|
|
119
|
-
h.pause();
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
this.onChange();
|
|
123
|
-
}
|
|
124
|
-
resume() {
|
|
125
|
-
if (!this.paused || this.canceled || this.terminalEmitted) return;
|
|
126
|
-
this.paused = false;
|
|
127
|
-
for (const h of this.uploads) {
|
|
128
|
-
if (h.upload.status === "paused") h.resume();
|
|
129
|
-
}
|
|
130
|
-
this.fillSlots();
|
|
131
|
-
this.onChange();
|
|
132
|
-
}
|
|
133
|
-
snapshot() {
|
|
134
|
-
const totalProgress = this.aggregateTotalBytes > 0 ? this.aggregateBytesTransferred / this.aggregateTotalBytes * 100 : 0;
|
|
135
|
-
return {
|
|
136
|
-
id: this.id,
|
|
137
|
-
uploads: this.uploadsView,
|
|
138
|
-
totalProgress,
|
|
139
|
-
completedCount: this.succeededCount,
|
|
140
|
-
failedCount: this.failedCount
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
updateAggregate(idx, u) {
|
|
144
|
-
const prevBytes = this.lastBytes[idx] ?? 0;
|
|
145
|
-
const prevTotal = this.lastTotal[idx] ?? 0;
|
|
146
|
-
this.aggregateBytesTransferred += u.bytesTransferred - prevBytes;
|
|
147
|
-
this.aggregateTotalBytes += u.totalBytes - prevTotal;
|
|
148
|
-
this.lastBytes[idx] = u.bytesTransferred;
|
|
149
|
-
this.lastTotal[idx] = u.totalBytes;
|
|
150
|
-
}
|
|
151
|
-
fillSlots() {
|
|
152
|
-
while (!this.canceled && !this.paused && !this.failedFast && this.running < this.concurrency && this.nextIdx < this.uploads.length) {
|
|
153
|
-
const idx = this.nextIdx++;
|
|
154
|
-
const handle = this.uploads[idx];
|
|
155
|
-
if (!handle || handle.upload.status !== "queued") continue;
|
|
156
|
-
this.activeSlots.add(idx);
|
|
157
|
-
this.running++;
|
|
158
|
-
this.startNext(handle);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
handleChildProgress(idx, upload) {
|
|
162
|
-
this.updateAggregate(idx, upload);
|
|
163
|
-
const snap = this.snapshot();
|
|
164
|
-
this.emit("progress", snap);
|
|
165
|
-
this.emit("change", snap);
|
|
166
|
-
}
|
|
167
|
-
handleChildRetry(idx, event) {
|
|
168
|
-
this.updateAggregate(idx, event.upload);
|
|
169
|
-
this.emit("uploadRetry", event);
|
|
170
|
-
const snap = this.snapshot();
|
|
171
|
-
this.emit("change", snap);
|
|
172
|
-
}
|
|
173
|
-
handleChildSettled(idx, upload, kind) {
|
|
174
|
-
this.updateAggregate(idx, upload);
|
|
175
|
-
if (this.activeSlots.delete(idx)) {
|
|
176
|
-
this.running = Math.max(0, this.running - 1);
|
|
177
|
-
}
|
|
178
|
-
this.settledCount++;
|
|
179
|
-
if (kind === "success") {
|
|
180
|
-
this.succeededCount++;
|
|
181
|
-
this.emit("uploadSuccess", upload);
|
|
182
|
-
} else if (kind === "error") {
|
|
183
|
-
this.failedCount++;
|
|
184
|
-
this.emit("uploadError", upload);
|
|
185
|
-
}
|
|
186
|
-
const snap = this.snapshot();
|
|
187
|
-
this.emit("change", snap);
|
|
188
|
-
if (kind === "error" && !this.continueOnError && !this.failedFast && !this.canceled) {
|
|
189
|
-
this.failedFast = true;
|
|
190
|
-
for (const h of this.uploads) {
|
|
191
|
-
if (h.upload.id === upload.id) continue;
|
|
192
|
-
const s = h.upload.status;
|
|
193
|
-
if (s === "queued" || s === "uploading" || s === "retrying" || s === "paused") {
|
|
194
|
-
h.cancel();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
this.terminalEmitted = true;
|
|
198
|
-
this.emit("error", snap);
|
|
199
|
-
this.onChange();
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (this.failedFast || this.canceled) {
|
|
203
|
-
this.onChange();
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
this.fillSlots();
|
|
207
|
-
if (this.settledCount >= this.uploads.length && !this.terminalEmitted) {
|
|
208
|
-
this.terminalEmitted = true;
|
|
209
|
-
this.emit("success", snap);
|
|
210
|
-
}
|
|
211
|
-
this.onChange();
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// src/core/retry.ts
|
|
216
|
-
var DEFAULT_RETRY_OPTIONS = {
|
|
217
|
-
maxRetries: 3,
|
|
218
|
-
initialDelayMs: 1e3,
|
|
219
|
-
maxDelayMs: 3e4,
|
|
220
|
-
jitter: true
|
|
221
|
-
};
|
|
222
|
-
var NON_RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
223
|
-
"storage/canceled",
|
|
224
|
-
"storage/unauthorized",
|
|
225
|
-
"storage/unauthenticated",
|
|
226
|
-
"storage/quota-exceeded",
|
|
227
|
-
"storage/invalid-argument",
|
|
228
|
-
"storage/invalid-argument-count",
|
|
229
|
-
"storage/invalid-url",
|
|
230
|
-
"storage/invalid-default-bucket",
|
|
231
|
-
"storage/no-default-bucket",
|
|
232
|
-
"storage/bucket-not-found",
|
|
233
|
-
"storage/project-not-found",
|
|
234
|
-
"storage/invalid-root-operation",
|
|
235
|
-
"storage/invalid-event-name",
|
|
236
|
-
"storage/invalid-format",
|
|
237
|
-
"storage/no-download-url",
|
|
238
|
-
"storage/unauthorized-app",
|
|
239
|
-
"storage/unsupported-environment"
|
|
240
|
-
]);
|
|
241
|
-
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
242
|
-
"storage/retry-limit-exceeded",
|
|
243
|
-
"storage/unknown",
|
|
244
|
-
"storage/invalid-checksum",
|
|
245
|
-
"storage/cannot-slice-blob",
|
|
246
|
-
"storage/server-file-wrong-size"
|
|
247
|
-
]);
|
|
248
|
-
function resolveRetryOptions(retry) {
|
|
249
|
-
if (retry === false) return null;
|
|
250
|
-
return {
|
|
251
|
-
...DEFAULT_RETRY_OPTIONS,
|
|
252
|
-
...retry
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
function getStorageErrorCode(error) {
|
|
256
|
-
return error.code;
|
|
257
|
-
}
|
|
258
|
-
function isRetryableStorageError(error, options) {
|
|
259
|
-
if (!options) return false;
|
|
260
|
-
if (options.isRetryable) {
|
|
261
|
-
return options.isRetryable(error);
|
|
262
|
-
}
|
|
263
|
-
const code = getStorageErrorCode(error);
|
|
264
|
-
if (code) {
|
|
265
|
-
if (NON_RETRYABLE_CODES.has(code)) return false;
|
|
266
|
-
if (RETRYABLE_CODES.has(code)) return true;
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
function computeRetryDelay(failedAttempt, options) {
|
|
272
|
-
const base = Math.min(
|
|
273
|
-
options.maxDelayMs,
|
|
274
|
-
options.initialDelayMs * 2 ** (failedAttempt - 1)
|
|
275
|
-
);
|
|
276
|
-
if (!options.jitter) return base;
|
|
277
|
-
return Math.round(base * (0.5 + Math.random() * 0.5));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// src/core/upload-handle.ts
|
|
281
|
-
var UploadHandle = class extends Emitter {
|
|
282
|
-
upload;
|
|
283
|
-
task = null;
|
|
284
|
-
onChange;
|
|
285
|
-
terminated = false;
|
|
286
|
-
controlHooks = null;
|
|
287
|
-
pausedDuringRetry = false;
|
|
288
|
-
constructor(upload, onChange) {
|
|
289
|
-
super();
|
|
290
|
-
this.upload = upload;
|
|
291
|
-
this.onChange = onChange;
|
|
292
|
-
}
|
|
293
|
-
/** @internal */
|
|
294
|
-
_attachTask(task) {
|
|
295
|
-
this.task = task;
|
|
296
|
-
}
|
|
297
|
-
/** @internal */
|
|
298
|
-
_registerControlHooks(hooks) {
|
|
299
|
-
this.controlHooks = hooks;
|
|
300
|
-
}
|
|
301
|
-
/** @internal */
|
|
302
|
-
_clearControlHooks() {
|
|
303
|
-
this.controlHooks = null;
|
|
304
|
-
}
|
|
305
|
-
/** @internal */
|
|
306
|
-
_setStatus(status) {
|
|
307
|
-
if (this.upload.status === status) return;
|
|
308
|
-
this.upload.status = status;
|
|
309
|
-
this.emit("statusChange", this.upload);
|
|
310
|
-
}
|
|
311
|
-
/** @internal */
|
|
312
|
-
_reportProgress(bytesTransferred, totalBytes) {
|
|
313
|
-
if (this.terminated) return;
|
|
314
|
-
this.upload.bytesTransferred = bytesTransferred;
|
|
315
|
-
this.upload.totalBytes = totalBytes;
|
|
316
|
-
this.upload.progress = totalBytes > 0 ? bytesTransferred / totalBytes * 100 : 0;
|
|
317
|
-
if (this.upload.status !== "uploading") {
|
|
318
|
-
this._setStatus("uploading");
|
|
319
|
-
}
|
|
320
|
-
this.emit("progress", this.upload);
|
|
321
|
-
this.onChange();
|
|
322
|
-
}
|
|
323
|
-
/** @internal */
|
|
324
|
-
_prepareRetryAttempt(attempt) {
|
|
325
|
-
if (this.terminated) return;
|
|
326
|
-
this.upload.retryAttempt = attempt;
|
|
327
|
-
this.upload.bytesTransferred = 0;
|
|
328
|
-
this.upload.progress = 0;
|
|
329
|
-
this.upload.error = void 0;
|
|
330
|
-
this.pausedDuringRetry = false;
|
|
331
|
-
this._setStatus("uploading");
|
|
332
|
-
this.onChange();
|
|
333
|
-
}
|
|
334
|
-
/** @internal */
|
|
335
|
-
_enterRetryBackoff(details) {
|
|
336
|
-
if (this.terminated) return;
|
|
337
|
-
this.upload.retryAttempt = details.attempt;
|
|
338
|
-
this.upload.error = details.error;
|
|
339
|
-
this.task = null;
|
|
340
|
-
this._setStatus("retrying");
|
|
341
|
-
this.emit("retry", {
|
|
342
|
-
upload: this.upload,
|
|
343
|
-
error: details.error,
|
|
344
|
-
attempt: details.attempt,
|
|
345
|
-
maxAttempts: details.maxAttempts,
|
|
346
|
-
delayMs: details.delayMs
|
|
347
|
-
});
|
|
348
|
-
this.onChange();
|
|
349
|
-
}
|
|
350
|
-
/** @internal */
|
|
351
|
-
_reportSuccess(downloadURL) {
|
|
352
|
-
if (this.terminated) return;
|
|
353
|
-
this.terminated = true;
|
|
354
|
-
this.upload.downloadURL = downloadURL;
|
|
355
|
-
this.upload.progress = 100;
|
|
356
|
-
this._clearControlHooks();
|
|
357
|
-
this._setStatus("success");
|
|
358
|
-
this.emit("success", this.upload);
|
|
359
|
-
this.onChange();
|
|
360
|
-
}
|
|
361
|
-
/** @internal */
|
|
362
|
-
_reportError(error) {
|
|
363
|
-
if (this.terminated) return;
|
|
364
|
-
this.terminated = true;
|
|
365
|
-
this.upload.error = error;
|
|
366
|
-
this._clearControlHooks();
|
|
367
|
-
this._setStatus("error");
|
|
368
|
-
this.emit("error", this.upload);
|
|
369
|
-
this.onChange();
|
|
370
|
-
}
|
|
371
|
-
cancel() {
|
|
372
|
-
if (this.terminated) return;
|
|
373
|
-
this.terminated = true;
|
|
374
|
-
this.controlHooks?.abort();
|
|
375
|
-
this.task?.cancel();
|
|
376
|
-
this._clearControlHooks();
|
|
377
|
-
this._setStatus("canceled");
|
|
378
|
-
this.emit("canceled", this.upload);
|
|
379
|
-
this.onChange();
|
|
380
|
-
}
|
|
381
|
-
pause() {
|
|
382
|
-
if (this.terminated) return;
|
|
383
|
-
if (this.upload.status === "uploading") {
|
|
384
|
-
this.task?.pause?.();
|
|
385
|
-
this._setStatus("paused");
|
|
386
|
-
this.onChange();
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (this.upload.status === "retrying") {
|
|
390
|
-
this.pausedDuringRetry = true;
|
|
391
|
-
this.controlHooks?.pauseDuringRetry?.();
|
|
392
|
-
this._setStatus("paused");
|
|
393
|
-
this.onChange();
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
resume() {
|
|
397
|
-
if (this.terminated) return;
|
|
398
|
-
if (this.upload.status !== "paused") return;
|
|
399
|
-
if (this.pausedDuringRetry) {
|
|
400
|
-
this.pausedDuringRetry = false;
|
|
401
|
-
this.controlHooks?.resumeDuringRetry?.();
|
|
402
|
-
this.onChange();
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
this.task?.resume?.();
|
|
406
|
-
this._setStatus("uploading");
|
|
407
|
-
this.onChange();
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
// src/core/storage-manager.ts
|
|
412
|
-
var StorageManager = class {
|
|
413
|
-
provider;
|
|
414
|
-
uploadHandles = [];
|
|
415
|
-
batches = [];
|
|
416
|
-
changeListeners = /* @__PURE__ */ new Set();
|
|
417
|
-
cachedState = null;
|
|
418
|
-
uploadOptionsByHandleId = /* @__PURE__ */ new Map();
|
|
419
|
-
constructor(provider) {
|
|
420
|
-
this.provider = provider;
|
|
421
|
-
}
|
|
422
|
-
/** Current `uploads` and `batches` state. */
|
|
423
|
-
getState = () => {
|
|
424
|
-
if (this.cachedState === null) {
|
|
425
|
-
this.cachedState = {
|
|
426
|
-
uploads: this.uploadHandles.map((h) => h.upload),
|
|
427
|
-
batches: this.batches.map((b) => b.snapshot())
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
return this.cachedState;
|
|
431
|
-
};
|
|
432
|
-
/** Runs `listener` after each change; returns a function — call it to unsubscribe. */
|
|
433
|
-
subscribe = (listener) => {
|
|
434
|
-
this.changeListeners.add(listener);
|
|
435
|
-
return () => {
|
|
436
|
-
this.changeListeners.delete(listener);
|
|
437
|
-
};
|
|
438
|
-
};
|
|
439
|
-
/** Returns `true` if the object exists. Returns `false` only for not-found; other errors are thrown. */
|
|
440
|
-
exists(path) {
|
|
441
|
-
return this.provider.exists(path);
|
|
442
|
-
}
|
|
443
|
-
/** Returns metadata for the object at `path`. Throws if the object does not exist. */
|
|
444
|
-
getMetadata(path) {
|
|
445
|
-
return this.provider.getMetadata(path);
|
|
446
|
-
}
|
|
447
|
-
/** Returns a download URL for the object at `path`. */
|
|
448
|
-
getDownloadURL(path) {
|
|
449
|
-
return this.provider.getDownloadURL(path);
|
|
450
|
-
}
|
|
451
|
-
/** Deletes the object at `path`. */
|
|
452
|
-
delete(path) {
|
|
453
|
-
return this.provider.delete(path);
|
|
454
|
-
}
|
|
455
|
-
/** Starts the file upload.`options.path` is the object path in storage (see {@link UploadOptions}).
|
|
456
|
-
*
|
|
457
|
-
* @returns An {@link UploadHandle} to control the upload: `pause`, `resume`, `cancel`, and listen for progress with `.on`.
|
|
458
|
-
*/
|
|
459
|
-
uploadFile(file, options) {
|
|
460
|
-
const handle = this.createHandle(file, { path: options.path });
|
|
461
|
-
this.uploadOptionsByHandleId.set(handle.upload.id, options);
|
|
462
|
-
this.uploadHandles.push(handle);
|
|
463
|
-
this.notifyChange();
|
|
464
|
-
this.startUpload(handle, options);
|
|
465
|
-
return handle;
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Upload several files as one batch. The optional third argument sets how many run at once
|
|
469
|
-
* and what happens when one file fails — see {@link BatchOptions}.
|
|
470
|
-
*
|
|
471
|
-
* @remarks
|
|
472
|
-
* `optionsFor` picks storage options per file (same as `uploadFile`); index matches `files` order.
|
|
473
|
-
* If `continueOnError` is `false`, the first failure cancels the other files and the batch fires `error`.
|
|
474
|
-
* If it stays `true` (default), other files keep going; when every file has finished, the batch fires `success`.
|
|
475
|
-
*
|
|
476
|
-
*/
|
|
477
|
-
uploadFiles(files, optionsFor, batchOptions = {}) {
|
|
478
|
-
const id = crypto.randomUUID();
|
|
479
|
-
const handles = files.map((file, i) => {
|
|
480
|
-
const options = optionsFor(file, i);
|
|
481
|
-
const handle = this.createHandle(file, {
|
|
482
|
-
batchId: id,
|
|
483
|
-
path: options.path
|
|
484
|
-
});
|
|
485
|
-
this.uploadOptionsByHandleId.set(handle.upload.id, options);
|
|
486
|
-
return handle;
|
|
487
|
-
});
|
|
488
|
-
this.uploadHandles.push(...handles);
|
|
489
|
-
const batch = new BatchHandle({
|
|
490
|
-
id,
|
|
491
|
-
uploads: handles,
|
|
492
|
-
options: batchOptions,
|
|
493
|
-
startNext: (handle) => {
|
|
494
|
-
const options = this.uploadOptionsByHandleId.get(handle.upload.id) ?? {
|
|
495
|
-
path: handle.upload.path
|
|
496
|
-
};
|
|
497
|
-
this.startUpload(handle, options);
|
|
498
|
-
},
|
|
499
|
-
onChange: () => this.notifyChange()
|
|
500
|
-
});
|
|
501
|
-
this.batches.push(batch);
|
|
502
|
-
this.notifyChange();
|
|
503
|
-
batch._start();
|
|
504
|
-
return batch;
|
|
505
|
-
}
|
|
506
|
-
createHandle(file, options) {
|
|
507
|
-
const upload = {
|
|
508
|
-
id: crypto.randomUUID(),
|
|
509
|
-
file,
|
|
510
|
-
path: options.path,
|
|
511
|
-
progress: 0,
|
|
512
|
-
bytesTransferred: 0,
|
|
513
|
-
totalBytes: file.size,
|
|
514
|
-
status: "queued",
|
|
515
|
-
...options.batchId !== void 0 ? { batchId: options.batchId } : {}
|
|
516
|
-
};
|
|
517
|
-
return new UploadHandle(upload, () => this.notifyChange());
|
|
518
|
-
}
|
|
519
|
-
startUpload(handle, options) {
|
|
520
|
-
const retryOptions = resolveRetryOptions(options.retry);
|
|
521
|
-
const maxAttempts = retryOptions ? retryOptions.maxRetries + 1 : 1;
|
|
522
|
-
let attempt = 0;
|
|
523
|
-
let retryTimeout = null;
|
|
524
|
-
let currentTask = null;
|
|
525
|
-
let aborted = false;
|
|
526
|
-
let pausedDuringRetry = false;
|
|
527
|
-
let pendingRetryError = null;
|
|
528
|
-
const clearRetryTimeout = () => {
|
|
529
|
-
if (retryTimeout !== null) {
|
|
530
|
-
clearTimeout(retryTimeout);
|
|
531
|
-
retryTimeout = null;
|
|
532
|
-
}
|
|
533
|
-
};
|
|
534
|
-
const abort = () => {
|
|
535
|
-
aborted = true;
|
|
536
|
-
clearRetryTimeout();
|
|
537
|
-
currentTask?.cancel();
|
|
538
|
-
currentTask = null;
|
|
539
|
-
pendingRetryError = null;
|
|
540
|
-
};
|
|
541
|
-
const scheduleRetry = (error, delayMs) => {
|
|
542
|
-
if (aborted) return;
|
|
543
|
-
pendingRetryError = error;
|
|
544
|
-
handle._enterRetryBackoff({
|
|
545
|
-
attempt: attempt + 1,
|
|
546
|
-
maxAttempts,
|
|
547
|
-
delayMs,
|
|
548
|
-
error
|
|
549
|
-
});
|
|
550
|
-
retryTimeout = setTimeout(() => {
|
|
551
|
-
retryTimeout = null;
|
|
552
|
-
if (aborted || pausedDuringRetry) return;
|
|
553
|
-
pendingRetryError = null;
|
|
554
|
-
runAttempt();
|
|
555
|
-
}, delayMs);
|
|
556
|
-
};
|
|
557
|
-
const runAttempt = () => {
|
|
558
|
-
if (aborted) return;
|
|
559
|
-
attempt += 1;
|
|
560
|
-
handle._prepareRetryAttempt(attempt);
|
|
561
|
-
currentTask = this.provider.upload(handle.upload.file, options, {
|
|
562
|
-
onProgress: (bytesTransferred, totalBytes) => handle._reportProgress(bytesTransferred, totalBytes),
|
|
563
|
-
onError: (error) => {
|
|
564
|
-
currentTask = null;
|
|
565
|
-
if (aborted) return;
|
|
566
|
-
if (attempt < maxAttempts && isRetryableStorageError(error, retryOptions)) {
|
|
567
|
-
const delayMs = retryOptions ? computeRetryDelay(attempt, retryOptions) : 0;
|
|
568
|
-
scheduleRetry(error, delayMs);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
handle._reportError(error);
|
|
572
|
-
},
|
|
573
|
-
onSuccess: (downloadURL) => {
|
|
574
|
-
currentTask = null;
|
|
575
|
-
if (aborted) return;
|
|
576
|
-
handle._reportSuccess(downloadURL);
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
handle._attachTask(currentTask);
|
|
580
|
-
};
|
|
581
|
-
handle._registerControlHooks({
|
|
582
|
-
abort,
|
|
583
|
-
pauseDuringRetry: () => {
|
|
584
|
-
pausedDuringRetry = true;
|
|
585
|
-
clearRetryTimeout();
|
|
586
|
-
},
|
|
587
|
-
resumeDuringRetry: () => {
|
|
588
|
-
if (aborted) return;
|
|
589
|
-
pausedDuringRetry = false;
|
|
590
|
-
if (pendingRetryError) {
|
|
591
|
-
scheduleRetry(pendingRetryError, 0);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
runAttempt();
|
|
596
|
-
}
|
|
597
|
-
notifyChange() {
|
|
598
|
-
this.cachedState = null;
|
|
599
|
-
const state = this.getState();
|
|
600
|
-
for (const listener of this.changeListeners) {
|
|
601
|
-
listener(state);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
var FirebaseStorageProvider = class {
|
|
606
|
-
constructor(storage) {
|
|
607
|
-
this.storage = storage;
|
|
608
|
-
}
|
|
609
|
-
storage;
|
|
610
|
-
upload(file, options, callbacks) {
|
|
611
|
-
const storageRef = ref(this.storage, options.path);
|
|
612
|
-
const task = uploadBytesResumable(storageRef, file);
|
|
613
|
-
task.on(
|
|
614
|
-
"state_changed",
|
|
615
|
-
(snapshot) => {
|
|
616
|
-
callbacks.onProgress(
|
|
617
|
-
snapshot.bytesTransferred,
|
|
618
|
-
snapshot.totalBytes
|
|
619
|
-
);
|
|
620
|
-
},
|
|
621
|
-
(error) => {
|
|
622
|
-
callbacks.onError(error);
|
|
623
|
-
},
|
|
624
|
-
async () => {
|
|
625
|
-
try {
|
|
626
|
-
const downloadURL = await getDownloadURL(storageRef);
|
|
627
|
-
callbacks.onSuccess(downloadURL);
|
|
628
|
-
} catch (error) {
|
|
629
|
-
callbacks.onError(error);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
);
|
|
633
|
-
return {
|
|
634
|
-
cancel: () => {
|
|
635
|
-
task.cancel();
|
|
636
|
-
},
|
|
637
|
-
pause: () => {
|
|
638
|
-
task.pause();
|
|
639
|
-
},
|
|
640
|
-
resume: () => {
|
|
641
|
-
task.resume();
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
async exists(path) {
|
|
646
|
-
try {
|
|
647
|
-
await getMetadata(ref(this.storage, path));
|
|
648
|
-
return true;
|
|
649
|
-
} catch (err) {
|
|
650
|
-
if (err.code === "storage/object-not-found") {
|
|
651
|
-
return false;
|
|
652
|
-
}
|
|
653
|
-
throw err;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
async getMetadata(path) {
|
|
657
|
-
const meta = await getMetadata(ref(this.storage, path));
|
|
658
|
-
return {
|
|
659
|
-
path,
|
|
660
|
-
size: meta.size,
|
|
661
|
-
contentType: meta.contentType,
|
|
662
|
-
createdAt: new Date(meta.timeCreated),
|
|
663
|
-
updatedAt: new Date(meta.updated),
|
|
664
|
-
customMetadata: meta.customMetadata
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
getDownloadURL(path) {
|
|
668
|
-
return getDownloadURL(ref(this.storage, path));
|
|
669
|
-
}
|
|
670
|
-
async delete(path) {
|
|
671
|
-
await deleteObject(ref(this.storage, path));
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
|
|
675
|
-
// src/firebase-storage-manager.ts
|
|
676
|
-
var StorageManager2 = class extends StorageManager {
|
|
677
|
-
constructor(storage) {
|
|
678
|
-
super(new FirebaseStorageProvider(storage));
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
export { BatchHandle, DEFAULT_RETRY_OPTIONS, StorageManager2 as StorageManager, UploadHandle, computeRetryDelay, getStorageErrorCode, isRetryableStorageError, resolveRetryOptions };
|
|
1
|
+
export { BatchHandle, DEFAULT_RETRY_OPTIONS, StorageManager, UploadHandle, computeRetryDelay, getStorageErrorCode, isRetryableStorageError, resolveRetryOptions } from './chunk-PBO3CMOD.js';
|
|
683
2
|
//# sourceMappingURL=index.js.map
|
|
684
3
|
//# sourceMappingURL=index.js.map
|