@ttt-productions/upload-core 0.0.2
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/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence/localstorage.d.ts +6 -0
- package/dist/persistence/localstorage.d.ts.map +1 -0
- package/dist/persistence/localstorage.js +54 -0
- package/dist/persistence/localstorage.js.map +1 -0
- package/dist/queue/upload-queue.d.ts +16 -0
- package/dist/queue/upload-queue.d.ts.map +1 -0
- package/dist/queue/upload-queue.js +115 -0
- package/dist/queue/upload-queue.js.map +1 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +4 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/use-upload-controller.d.ts +10 -0
- package/dist/react/use-upload-controller.d.ts.map +1 -0
- package/dist/react/use-upload-controller.js +32 -0
- package/dist/react/use-upload-controller.js.map +1 -0
- package/dist/react/use-upload-file.d.ts +8 -0
- package/dist/react/use-upload-file.d.ts.map +1 -0
- package/dist/react/use-upload-file.js +44 -0
- package/dist/react/use-upload-file.js.map +1 -0
- package/dist/react/use-upload-sessions.d.ts +5 -0
- package/dist/react/use-upload-sessions.d.ts.map +1 -0
- package/dist/react/use-upload-sessions.js +14 -0
- package/dist/react/use-upload-sessions.js.map +1 -0
- package/dist/storage/delete.d.ts +3 -0
- package/dist/storage/delete.d.ts.map +1 -0
- package/dist/storage/delete.js +6 -0
- package/dist/storage/delete.js.map +1 -0
- package/dist/storage/upload.d.ts +15 -0
- package/dist/storage/upload.d.ts.map +1 -0
- package/dist/storage/upload.js +157 -0
- package/dist/storage/upload.js.map +1 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/file-size.d.ts +2 -0
- package/dist/utils/file-size.d.ts.map +1 -0
- package/dist/utils/file-size.js +6 -0
- package/dist/utils/file-size.js.map +1 -0
- package/dist/utils/filename.d.ts +2 -0
- package/dist/utils/filename.d.ts.map +1 -0
- package/dist/utils/filename.js +17 -0
- package/dist/utils/filename.js.map +1 -0
- package/dist/utils/path.d.ts +7 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +22 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/retry.d.ts +3 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +31 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/upload-store.d.ts +19 -0
- package/dist/utils/upload-store.d.ts.map +1 -0
- package/dist/utils/upload-store.js +162 -0
- package/dist/utils/upload-store.js.map +1 -0
- package/package.json +45 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./storage/upload";
|
|
3
|
+
export * from "./storage/delete";
|
|
4
|
+
export * from "./utils/filename";
|
|
5
|
+
export * from "./utils/path";
|
|
6
|
+
export * from "./utils/upload-store";
|
|
7
|
+
export * from "./queue/upload-queue";
|
|
8
|
+
export * from "./persistence/localstorage";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AAExB,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AAEjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AAErC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./storage/upload";
|
|
3
|
+
export * from "./storage/delete";
|
|
4
|
+
export * from "./utils/filename";
|
|
5
|
+
export * from "./utils/path";
|
|
6
|
+
export * from "./utils/upload-store";
|
|
7
|
+
export * from "./queue/upload-queue";
|
|
8
|
+
export * from "./persistence/localstorage";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AAExB,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AAEjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AAErC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UploadSessionPersistenceAdapter } from "../types";
|
|
2
|
+
export declare function createLocalStorageUploadSessionPersistence(opts?: {
|
|
3
|
+
/** Prefix for keys. Default: "ttt_upload_session:" */
|
|
4
|
+
prefix?: string;
|
|
5
|
+
}): UploadSessionPersistenceAdapter;
|
|
6
|
+
//# sourceMappingURL=localstorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localstorage.d.ts","sourceRoot":"","sources":["../../src/persistence/localstorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAsB,MAAM,UAAU,CAAC;AAUpF,wBAAgB,0CAA0C,CAAC,IAAI,CAAC,EAAE;IAChE,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,+BAA+B,CA0ClC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function hasLocalStorage() {
|
|
2
|
+
try {
|
|
3
|
+
return typeof window !== "undefined" && !!window.localStorage;
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function createLocalStorageUploadSessionPersistence(opts) {
|
|
10
|
+
const prefix = opts?.prefix ?? "ttt_upload_session:";
|
|
11
|
+
const k = (id) => `${prefix}${id}`;
|
|
12
|
+
const safeParse = (raw) => {
|
|
13
|
+
if (!raw)
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
listIds: () => {
|
|
24
|
+
if (!hasLocalStorage())
|
|
25
|
+
return [];
|
|
26
|
+
const ids = [];
|
|
27
|
+
for (let i = 0; i < window.localStorage.length; i += 1) {
|
|
28
|
+
const key = window.localStorage.key(i);
|
|
29
|
+
if (!key)
|
|
30
|
+
continue;
|
|
31
|
+
if (!key.startsWith(prefix))
|
|
32
|
+
continue;
|
|
33
|
+
ids.push(key.slice(prefix.length));
|
|
34
|
+
}
|
|
35
|
+
return ids;
|
|
36
|
+
},
|
|
37
|
+
get: (id) => {
|
|
38
|
+
if (!hasLocalStorage())
|
|
39
|
+
return null;
|
|
40
|
+
return safeParse(window.localStorage.getItem(k(id)));
|
|
41
|
+
},
|
|
42
|
+
set: (id, state) => {
|
|
43
|
+
if (!hasLocalStorage())
|
|
44
|
+
return;
|
|
45
|
+
window.localStorage.setItem(k(id), JSON.stringify(state));
|
|
46
|
+
},
|
|
47
|
+
remove: (id) => {
|
|
48
|
+
if (!hasLocalStorage())
|
|
49
|
+
return;
|
|
50
|
+
window.localStorage.removeItem(k(id));
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=localstorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localstorage.js","sourceRoot":"","sources":["../../src/persistence/localstorage.ts"],"names":[],"mappings":"AAEA,SAAS,eAAe;IACtB,IAAI,CAAC;QACH,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0CAA0C,CAAC,IAG1D;IACC,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,qBAAqB,CAAC;IAErD,MAAM,CAAC,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,EAAE,CAAC;IAE3C,MAAM,SAAS,GAAG,CAAC,GAAkB,EAA6B,EAAE;QAClE,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO,EAAE,CAAC;YAClC,MAAM,GAAG,GAAa,EAAE,CAAC;YACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvC,IAAI,CAAC,GAAG;oBAAE,SAAS;gBACnB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;oBAAE,SAAS;gBACtC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,GAAG,EAAE,CAAC,EAAU,EAAE,EAAE;YAClB,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO,IAAI,CAAC;YACpC,OAAO,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;QAED,GAAG,EAAE,CAAC,EAAU,EAAE,KAAyB,EAAE,EAAE;YAC7C,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO;YAC/B,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,EAAE,CAAC,EAAU,EAAE,EAAE;YACrB,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO;YAC/B,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { UploadController, UploadQueueOptions, StartUploadArgs } from "../types";
|
|
2
|
+
export declare class UploadQueue {
|
|
3
|
+
private concurrency;
|
|
4
|
+
private running;
|
|
5
|
+
private pending;
|
|
6
|
+
private seq;
|
|
7
|
+
constructor(opts?: UploadQueueOptions);
|
|
8
|
+
setConcurrency(n: number): void;
|
|
9
|
+
getPendingCount(): number;
|
|
10
|
+
getRunningCount(): number;
|
|
11
|
+
enqueue(args: StartUploadArgs & {
|
|
12
|
+
priority?: number;
|
|
13
|
+
}): UploadController;
|
|
14
|
+
private pump;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=upload-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-queue.d.ts","sourceRoot":"","sources":["../../src/queue/upload-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAA6B,kBAAkB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAqBjH,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,GAAG,CAAK;gBAEJ,IAAI,CAAC,EAAE,kBAAkB;IAIrC,cAAc,CAAC,CAAC,EAAE,MAAM;IAKxB,eAAe;IAIf,eAAe;IAIf,OAAO,CAAC,IAAI,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,gBAAgB;IA+DxE,OAAO,CAAC,IAAI;CAoCb"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { getFileSize } from "../utils/file-size";
|
|
2
|
+
import { upsertUploadSession, removeUploadSession } from "../utils/upload-store";
|
|
3
|
+
import { startResumableUpload } from "../storage/upload";
|
|
4
|
+
function safeId() {
|
|
5
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
6
|
+
}
|
|
7
|
+
function noopBool() {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
export class UploadQueue {
|
|
11
|
+
concurrency;
|
|
12
|
+
running = 0;
|
|
13
|
+
pending = [];
|
|
14
|
+
seq = 0;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.concurrency = Math.max(1, opts?.concurrency ?? 2);
|
|
17
|
+
}
|
|
18
|
+
setConcurrency(n) {
|
|
19
|
+
this.concurrency = Math.max(1, n);
|
|
20
|
+
this.pump();
|
|
21
|
+
}
|
|
22
|
+
getPendingCount() {
|
|
23
|
+
return this.pending.length;
|
|
24
|
+
}
|
|
25
|
+
getRunningCount() {
|
|
26
|
+
return this.running;
|
|
27
|
+
}
|
|
28
|
+
enqueue(args) {
|
|
29
|
+
const id = args.id ?? `upl_${safeId()}`;
|
|
30
|
+
const priority = args.priority ?? 0;
|
|
31
|
+
const startedAt = Date.now();
|
|
32
|
+
upsertUploadSession({
|
|
33
|
+
id,
|
|
34
|
+
status: "queued",
|
|
35
|
+
path: args.path,
|
|
36
|
+
transferred: 0,
|
|
37
|
+
total: getFileSize(args.file),
|
|
38
|
+
percent: 0,
|
|
39
|
+
startedAt,
|
|
40
|
+
updatedAt: startedAt,
|
|
41
|
+
});
|
|
42
|
+
let real = null;
|
|
43
|
+
let resolveDone;
|
|
44
|
+
let rejectDone;
|
|
45
|
+
const done = new Promise((res, rej) => {
|
|
46
|
+
resolveDone = res;
|
|
47
|
+
rejectDone = rej;
|
|
48
|
+
});
|
|
49
|
+
const controller = {
|
|
50
|
+
id,
|
|
51
|
+
task: null,
|
|
52
|
+
pause: () => (real ? real.pause() : noopBool()),
|
|
53
|
+
resume: () => (real ? real.resume() : noopBool()),
|
|
54
|
+
cancel: () => {
|
|
55
|
+
if (real)
|
|
56
|
+
return real.cancel();
|
|
57
|
+
this.pending = this.pending.filter((j) => j.id !== id);
|
|
58
|
+
removeUploadSession(id);
|
|
59
|
+
rejectDone(new DOMException("Aborted", "AbortError"));
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
done,
|
|
63
|
+
};
|
|
64
|
+
const job = {
|
|
65
|
+
id,
|
|
66
|
+
args: { ...args, id },
|
|
67
|
+
priority,
|
|
68
|
+
seq: this.seq++,
|
|
69
|
+
resolveController: (c) => {
|
|
70
|
+
real = c;
|
|
71
|
+
controller.task = c.task;
|
|
72
|
+
controller.pause = c.pause;
|
|
73
|
+
controller.resume = c.resume;
|
|
74
|
+
controller.cancel = c.cancel;
|
|
75
|
+
c.done.then(resolveDone, rejectDone);
|
|
76
|
+
},
|
|
77
|
+
rejectController: (e) => rejectDone(e),
|
|
78
|
+
};
|
|
79
|
+
this.pending.push(job);
|
|
80
|
+
this.pump();
|
|
81
|
+
return controller;
|
|
82
|
+
}
|
|
83
|
+
pump() {
|
|
84
|
+
while (this.running < this.concurrency && this.pending.length > 0) {
|
|
85
|
+
this.pending.sort((a, b) => (b.priority - a.priority) || (a.seq - b.seq));
|
|
86
|
+
const job = this.pending.shift();
|
|
87
|
+
this.running += 1;
|
|
88
|
+
try {
|
|
89
|
+
const { priority: _p, ...rest } = job.args;
|
|
90
|
+
const c = startResumableUpload({
|
|
91
|
+
...rest,
|
|
92
|
+
id: job.id, // guarantee
|
|
93
|
+
});
|
|
94
|
+
job.resolveController(c);
|
|
95
|
+
c.done.finally(() => {
|
|
96
|
+
this.running -= 1;
|
|
97
|
+
this.pump();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
this.running -= 1;
|
|
102
|
+
upsertUploadSession({
|
|
103
|
+
id: job.id,
|
|
104
|
+
status: "error",
|
|
105
|
+
path: job.args.path,
|
|
106
|
+
error: e,
|
|
107
|
+
updatedAt: Date.now(),
|
|
108
|
+
});
|
|
109
|
+
job.rejectController(e);
|
|
110
|
+
this.pump();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=upload-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-queue.js","sourceRoot":"","sources":["../../src/queue/upload-queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StartUploadArgs, UploadController, UploadSessionState } from "../types";
|
|
2
|
+
export declare function useUploadController(): {
|
|
3
|
+
start: (args: StartUploadArgs) => UploadController;
|
|
4
|
+
controller: UploadController | null;
|
|
5
|
+
session: UploadSessionState | null;
|
|
6
|
+
pause: () => boolean;
|
|
7
|
+
resume: () => boolean;
|
|
8
|
+
cancel: () => boolean;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=use-upload-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-upload-controller.d.ts","sourceRoot":"","sources":["../../src/react/use-upload-controller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAItF,wBAAgB,mBAAmB;kBAWA,eAAe;;;;;;EAkBjD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { startResumableUpload } from "../storage/upload";
|
|
4
|
+
import { getUploadSession, subscribeUploadSession } from "../utils/upload-store";
|
|
5
|
+
export function useUploadController() {
|
|
6
|
+
const [controller, setController] = useState(null);
|
|
7
|
+
const [session, setSession] = useState(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!controller)
|
|
10
|
+
return;
|
|
11
|
+
const id = controller.id;
|
|
12
|
+
setSession(getUploadSession(id) ?? null);
|
|
13
|
+
return subscribeUploadSession(id, (s) => setSession(s));
|
|
14
|
+
}, [controller]);
|
|
15
|
+
const start = useCallback((args) => {
|
|
16
|
+
const c = startResumableUpload({ ...args, id: args.id ?? `upl_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}` });
|
|
17
|
+
setController(c);
|
|
18
|
+
return c;
|
|
19
|
+
}, []);
|
|
20
|
+
const api = useMemo(() => {
|
|
21
|
+
return {
|
|
22
|
+
start,
|
|
23
|
+
controller,
|
|
24
|
+
session,
|
|
25
|
+
pause: () => controller?.pause() ?? false,
|
|
26
|
+
resume: () => controller?.resume() ?? false,
|
|
27
|
+
cancel: () => controller?.cancel() ?? false,
|
|
28
|
+
};
|
|
29
|
+
}, [start, controller, session]);
|
|
30
|
+
return api;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=use-upload-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-upload-controller.js","sourceRoot":"","sources":["../../src/react/use-upload-controller.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAElE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEjF,MAAM,UAAU,mBAAmB;IACjC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAA0B,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAA4B,IAAI,CAAC,CAAC;IAExE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE,CAAC;QACzB,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC;QACzC,OAAO,sBAAsB,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,IAAqB,EAAE,EAAE;QAClD,MAAM,CAAC,GAAG,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACnI,aAAa,CAAC,CAAC,CAAC,CAAC;QACjB,OAAO,CAAC,CAAC;IACX,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;QACvB,OAAO;YACL,KAAK;YACL,UAAU;YACV,OAAO;YACP,KAAK,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,KAAK;YACzC,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,KAAK;YAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,KAAK;SAC5C,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAEjC,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UploadFileResumableArgs, UploadFileResumableResult } from "../types";
|
|
2
|
+
export declare function useUploadFile(): {
|
|
3
|
+
upload: (args: Omit<UploadFileResumableArgs, "onProgress">) => Promise<UploadFileResumableResult>;
|
|
4
|
+
progress: number;
|
|
5
|
+
isUploading: boolean;
|
|
6
|
+
error: unknown;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=use-upload-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-upload-file.d.ts","sourceRoot":"","sources":["../../src/react/use-upload-file.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAGnF,wBAAgB,aAAa;mBAeZ,IAAI,CAAC,uBAAuB,EAAE,YAAY,CAAC,KAAG,OAAO,CAAC,yBAAyB,CAAC;;;;EA+BhG"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { uploadFileResumable } from "../storage/upload";
|
|
4
|
+
export function useUploadFile() {
|
|
5
|
+
const [progress, setProgress] = useState(0);
|
|
6
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
const mountedRef = useRef(true);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
mountedRef.current = true;
|
|
11
|
+
return () => {
|
|
12
|
+
mountedRef.current = false;
|
|
13
|
+
};
|
|
14
|
+
}, []);
|
|
15
|
+
const upload = useCallback(async (args) => {
|
|
16
|
+
setIsUploading(true);
|
|
17
|
+
setError(null);
|
|
18
|
+
setProgress(0);
|
|
19
|
+
try {
|
|
20
|
+
const res = await uploadFileResumable({
|
|
21
|
+
...args,
|
|
22
|
+
onProgress: ({ percent }) => {
|
|
23
|
+
if (!mountedRef.current)
|
|
24
|
+
return;
|
|
25
|
+
setProgress(percent);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
if (!mountedRef.current)
|
|
29
|
+
return res;
|
|
30
|
+
setProgress(100);
|
|
31
|
+
setIsUploading(false);
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
if (mountedRef.current) {
|
|
36
|
+
setError(e);
|
|
37
|
+
setIsUploading(false);
|
|
38
|
+
}
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
return { upload, progress, isUploading, error };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=use-upload-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-upload-file.js","sourceRoot":"","sources":["../../src/react/use-upload-file.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAU,IAAI,CAAC,CAAC;IAElD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAEhC,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,OAAO,GAAG,EAAE;YACV,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;QAC7B,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,MAAM,GAAG,WAAW,CACxB,KAAK,EAAE,IAAiD,EAAsC,EAAE;QAC9F,cAAc,CAAC,IAAI,CAAC,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,WAAW,CAAC,CAAC,CAAC,CAAC;QAEf,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,mBAAmB,CAAC;gBACpC,GAAG,IAAI;gBACP,UAAU,EAAE,CAAC,EAAE,OAAO,EAAuB,EAAE,EAAE;oBAC/C,IAAI,CAAC,UAAU,CAAC,OAAO;wBAAE,OAAO;oBAChC,WAAW,CAAC,OAAO,CAAC,CAAC;gBACvB,CAAC;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,UAAU,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC;YAEpC,WAAW,CAAC,GAAG,CAAC,CAAC;YACjB,cAAc,CAAC,KAAK,CAAC,CAAC;YACtB,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACZ,cAAc,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;YACD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-upload-sessions.d.ts","sourceRoot":"","sources":["../../src/react/use-upload-sessions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGnD,wBAAgB,iBAAiB;;EAYhC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { listUploadSessions, subscribeUploadSessionsList } from "../utils/upload-store";
|
|
4
|
+
export function useUploadSessions() {
|
|
5
|
+
const [sessions, setSessions] = useState(() => listUploadSessions());
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const sync = () => setSessions(listUploadSessions());
|
|
8
|
+
sync();
|
|
9
|
+
const unsub = subscribeUploadSessionsList(sync);
|
|
10
|
+
return () => unsub();
|
|
11
|
+
}, []);
|
|
12
|
+
return { sessions };
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=use-upload-sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-upload-sessions.js","sourceRoot":"","sources":["../../src/react/use-upload-sessions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAExF,MAAM,UAAU,iBAAiB;IAC/B,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAuB,GAAG,EAAE,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAE3F,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACrD,IAAI,EAAE,CAAC;QAEP,MAAM,KAAK,GAAG,2BAA2B,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/storage/delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG/C,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/storage/delete.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAoB;IACnD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IAC/B,MAAM,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { UploadFileResumableArgs, UploadController, UploadFileResumableResult } from "../types";
|
|
2
|
+
import { type FirebaseStorage, type UploadMetadata } from "firebase/storage";
|
|
3
|
+
export declare function startResumableUpload(args: {
|
|
4
|
+
id: string;
|
|
5
|
+
storage: FirebaseStorage;
|
|
6
|
+
path: string;
|
|
7
|
+
file: Blob;
|
|
8
|
+
metadata?: UploadMetadata;
|
|
9
|
+
onProgress?: UploadFileResumableArgs["onProgress"];
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}): UploadController;
|
|
12
|
+
export declare function uploadFileResumable(args: Omit<Parameters<typeof startResumableUpload>[0], "id"> & {
|
|
13
|
+
id?: string;
|
|
14
|
+
}): Promise<UploadFileResumableResult>;
|
|
15
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/storage/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAIrG,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,gBAAgB,CA4JnB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E,OAAO,CAAC,yBAAyB,CAAC,CAIpC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { getFileSize } from "../utils/file-size";
|
|
2
|
+
import { upsertUploadSession } from "../utils/upload-store";
|
|
3
|
+
import { getDownloadURL, ref, uploadBytesResumable, } from "firebase/storage";
|
|
4
|
+
function isAbortError(e) {
|
|
5
|
+
return e instanceof DOMException && e.name === "AbortError";
|
|
6
|
+
}
|
|
7
|
+
export function startResumableUpload(args) {
|
|
8
|
+
const { id, storage, path, file, metadata, onProgress, signal } = args;
|
|
9
|
+
const r = ref(storage, path);
|
|
10
|
+
const task = uploadBytesResumable(r, file, metadata);
|
|
11
|
+
const startedAt = Date.now();
|
|
12
|
+
upsertUploadSession({
|
|
13
|
+
id,
|
|
14
|
+
status: "uploading",
|
|
15
|
+
path,
|
|
16
|
+
transferred: 0,
|
|
17
|
+
total: getFileSize(file),
|
|
18
|
+
percent: 0,
|
|
19
|
+
startedAt,
|
|
20
|
+
updatedAt: startedAt,
|
|
21
|
+
});
|
|
22
|
+
let unsub = null;
|
|
23
|
+
let resolveDone;
|
|
24
|
+
let rejectDone;
|
|
25
|
+
const done = new Promise((res, rej) => {
|
|
26
|
+
resolveDone = res;
|
|
27
|
+
rejectDone = rej;
|
|
28
|
+
});
|
|
29
|
+
const onAbort = () => {
|
|
30
|
+
try {
|
|
31
|
+
task.cancel();
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
};
|
|
35
|
+
if (signal) {
|
|
36
|
+
if (signal.aborted)
|
|
37
|
+
onAbort();
|
|
38
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
39
|
+
}
|
|
40
|
+
unsub = task.on("state_changed", (snap) => {
|
|
41
|
+
const transferred = snap.bytesTransferred ?? 0;
|
|
42
|
+
const total = snap.totalBytes ?? getFileSize(file);
|
|
43
|
+
const percent = total > 0 ? (transferred / total) * 100 : 0;
|
|
44
|
+
upsertUploadSession({
|
|
45
|
+
id,
|
|
46
|
+
status: snap.state === "paused"
|
|
47
|
+
? "paused"
|
|
48
|
+
: snap.state === "running"
|
|
49
|
+
? "uploading"
|
|
50
|
+
: "uploading",
|
|
51
|
+
path,
|
|
52
|
+
transferred,
|
|
53
|
+
total,
|
|
54
|
+
percent,
|
|
55
|
+
updatedAt: Date.now(),
|
|
56
|
+
});
|
|
57
|
+
onProgress?.({ transferred, total, percent, snapshot: snap });
|
|
58
|
+
}, async (err) => {
|
|
59
|
+
if (signal)
|
|
60
|
+
signal.removeEventListener("abort", onAbort);
|
|
61
|
+
if (unsub)
|
|
62
|
+
unsub();
|
|
63
|
+
// normalize aborts as "canceled" (not "error")
|
|
64
|
+
if (isAbortError(err)) {
|
|
65
|
+
upsertUploadSession({
|
|
66
|
+
id,
|
|
67
|
+
status: "canceled",
|
|
68
|
+
path,
|
|
69
|
+
updatedAt: Date.now(),
|
|
70
|
+
error: err,
|
|
71
|
+
});
|
|
72
|
+
return rejectDone(err);
|
|
73
|
+
}
|
|
74
|
+
upsertUploadSession({
|
|
75
|
+
id,
|
|
76
|
+
status: "error",
|
|
77
|
+
path,
|
|
78
|
+
updatedAt: Date.now(),
|
|
79
|
+
error: err,
|
|
80
|
+
});
|
|
81
|
+
rejectDone(err);
|
|
82
|
+
}, async () => {
|
|
83
|
+
try {
|
|
84
|
+
const downloadURL = await getDownloadURL(task.snapshot.ref);
|
|
85
|
+
const contentType = task.snapshot.metadata?.contentType ?? null;
|
|
86
|
+
const size = task.snapshot.totalBytes ?? getFileSize(file);
|
|
87
|
+
const result = {
|
|
88
|
+
downloadURL,
|
|
89
|
+
fullPath: task.snapshot.ref.fullPath,
|
|
90
|
+
contentType,
|
|
91
|
+
size,
|
|
92
|
+
};
|
|
93
|
+
upsertUploadSession({
|
|
94
|
+
id,
|
|
95
|
+
status: "success",
|
|
96
|
+
path,
|
|
97
|
+
transferred: size,
|
|
98
|
+
total: size,
|
|
99
|
+
percent: 100,
|
|
100
|
+
updatedAt: Date.now(),
|
|
101
|
+
result,
|
|
102
|
+
});
|
|
103
|
+
resolveDone(result);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
upsertUploadSession({
|
|
107
|
+
id,
|
|
108
|
+
status: "error",
|
|
109
|
+
path,
|
|
110
|
+
updatedAt: Date.now(),
|
|
111
|
+
error: e,
|
|
112
|
+
});
|
|
113
|
+
rejectDone(e);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
if (signal)
|
|
117
|
+
signal.removeEventListener("abort", onAbort);
|
|
118
|
+
if (unsub)
|
|
119
|
+
unsub();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
id,
|
|
124
|
+
task,
|
|
125
|
+
pause: () => {
|
|
126
|
+
try {
|
|
127
|
+
return task.pause();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
resume: () => {
|
|
134
|
+
try {
|
|
135
|
+
return task.resume();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
cancel: () => {
|
|
142
|
+
try {
|
|
143
|
+
return task.cancel();
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
done,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export async function uploadFileResumable(args) {
|
|
153
|
+
const id = args.id ?? `upl_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
154
|
+
const c = startResumableUpload({ ...args, id });
|
|
155
|
+
return c.done;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/storage/upload.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { FirebaseStorage, UploadMetadata, UploadTask, UploadTaskSnapshot } from "firebase/storage";
|
|
2
|
+
export type UploadProgressHandler = (p: {
|
|
3
|
+
transferred: number;
|
|
4
|
+
total: number;
|
|
5
|
+
percent: number;
|
|
6
|
+
snapshot?: UploadTaskSnapshot;
|
|
7
|
+
}) => void;
|
|
8
|
+
export interface UploadFileResumableArgs {
|
|
9
|
+
storage: FirebaseStorage;
|
|
10
|
+
path: string;
|
|
11
|
+
file: Blob | File;
|
|
12
|
+
metadata?: UploadMetadata;
|
|
13
|
+
onProgress?: UploadProgressHandler;
|
|
14
|
+
/** Optional retry policy for transient failures. */
|
|
15
|
+
retry?: {
|
|
16
|
+
maxRetries?: number;
|
|
17
|
+
baseDelayMs?: number;
|
|
18
|
+
maxDelayMs?: number;
|
|
19
|
+
};
|
|
20
|
+
/** Optional cancellation signal. */
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
export interface UploadFileResumableResult {
|
|
24
|
+
downloadURL: string;
|
|
25
|
+
fullPath: string;
|
|
26
|
+
contentType: string | null;
|
|
27
|
+
size: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DeleteFileArgs {
|
|
30
|
+
storage: FirebaseStorage;
|
|
31
|
+
path: string;
|
|
32
|
+
}
|
|
33
|
+
export type UploadSessionStatus = "idle" | "queued" | "uploading" | "paused" | "success" | "error" | "canceled";
|
|
34
|
+
export interface UploadSessionPersistenceAdapter {
|
|
35
|
+
/** Return all session ids currently stored. */
|
|
36
|
+
listIds: () => Promise<string[]> | string[];
|
|
37
|
+
get: (id: string) => Promise<UploadSessionState | null> | UploadSessionState | null;
|
|
38
|
+
set: (id: string, state: UploadSessionState) => Promise<void> | void;
|
|
39
|
+
remove: (id: string) => Promise<void> | void;
|
|
40
|
+
}
|
|
41
|
+
export interface UploadQueueOptions {
|
|
42
|
+
/** Maximum concurrent uploads. Default: 3 */
|
|
43
|
+
concurrency?: number;
|
|
44
|
+
/** Optional persistence adapter for session state. */
|
|
45
|
+
persistence?: UploadSessionPersistenceAdapter;
|
|
46
|
+
}
|
|
47
|
+
export interface UploadSessionState {
|
|
48
|
+
id: string;
|
|
49
|
+
status: UploadSessionStatus;
|
|
50
|
+
path: string;
|
|
51
|
+
/** Monotonic version counter for state updates. */
|
|
52
|
+
version: number;
|
|
53
|
+
transferred: number;
|
|
54
|
+
total: number;
|
|
55
|
+
percent: number;
|
|
56
|
+
startedAt: number;
|
|
57
|
+
updatedAt: number;
|
|
58
|
+
error?: unknown;
|
|
59
|
+
result?: UploadFileResumableResult;
|
|
60
|
+
}
|
|
61
|
+
export interface DisposeUploadSessionArgs {
|
|
62
|
+
/** Session id to dispose. */
|
|
63
|
+
id: string;
|
|
64
|
+
/** If true, cancel any active upload task before disposal. Default true. */
|
|
65
|
+
cancel?: boolean;
|
|
66
|
+
/** If true, remove session state from the in-memory store. Default true. */
|
|
67
|
+
remove?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface UploadController {
|
|
70
|
+
id: string;
|
|
71
|
+
task: UploadTask;
|
|
72
|
+
pause: () => boolean;
|
|
73
|
+
resume: () => boolean;
|
|
74
|
+
cancel: () => boolean;
|
|
75
|
+
/** Resolves on success, rejects on error/cancel. */
|
|
76
|
+
done: Promise<UploadFileResumableResult>;
|
|
77
|
+
}
|
|
78
|
+
export interface StartUploadArgs extends Omit<UploadFileResumableArgs, "onProgress"> {
|
|
79
|
+
/** Optional stable id for UI tracking. */
|
|
80
|
+
id?: string;
|
|
81
|
+
/**
|
|
82
|
+
* UploadQueue-only: higher numbers run sooner.
|
|
83
|
+
* Ignored by startResumableUpload/uploadFileResumable.
|
|
84
|
+
*/
|
|
85
|
+
priority?: number;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-size.d.ts","sourceRoot":"","sources":["../../src/utils/file-size.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAIrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-size.js","sourceRoot":"","sources":["../../src/utils/file-size.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,MAAM,OAAO,GAAG,IAAW,CAAC;IAC5B,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;IAC3B,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBpD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function normalizeFilename(name) {
|
|
2
|
+
const trimmed = (name || "").trim();
|
|
3
|
+
if (!trimmed)
|
|
4
|
+
return "file";
|
|
5
|
+
// keep extension if present
|
|
6
|
+
const lastDot = trimmed.lastIndexOf(".");
|
|
7
|
+
const base = lastDot > 0 ? trimmed.slice(0, lastDot) : trimmed;
|
|
8
|
+
const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() : "";
|
|
9
|
+
const safeBase = base
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9-_ ]/g, "")
|
|
12
|
+
.replace(/\s+/g, "-")
|
|
13
|
+
.replace(/-+/g, "-")
|
|
14
|
+
.replace(/^-|-$/g, "");
|
|
15
|
+
return `${safeBase || "file"}${ext}`;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=filename.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC1C,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IAE5B,4BAA4B;IAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/D,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpE,MAAM,QAAQ,GAAG,IAAI;SAClB,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;SAC5B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEzB,OAAO,GAAG,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAaA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,UAYA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { joinPath } from "@ttt-productions/firebase-helpers";
|
|
2
|
+
import { normalizeFilename } from "./filename";
|
|
3
|
+
function sanitizeSegment(seg) {
|
|
4
|
+
const s = String(seg ?? "").trim();
|
|
5
|
+
// Remove slashes and collapse dot-runs to avoid traversal.
|
|
6
|
+
const noSlashes = s.replace(/[\\/]+/g, "_");
|
|
7
|
+
const noDots = noSlashes.replace(/\.+/g, ".");
|
|
8
|
+
const cleaned = noDots.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
9
|
+
const trimmed = cleaned.replace(/^\.+/, "").replace(/\.+$/, "");
|
|
10
|
+
return trimmed.slice(0, 80) || "_";
|
|
11
|
+
}
|
|
12
|
+
export function buildUploadPath(args) {
|
|
13
|
+
const { basePath, ownerId, contentId, filename } = args;
|
|
14
|
+
// Sanitize segments for security (traversal prevention)
|
|
15
|
+
const segments = [basePath, ownerId, contentId]
|
|
16
|
+
.filter((p) => !!p)
|
|
17
|
+
.map(sanitizeSegment);
|
|
18
|
+
const filePart = filename ? normalizeFilename(filename) : "file";
|
|
19
|
+
// Use shared helper to robustly join them
|
|
20
|
+
return joinPath(...segments, filePart);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,SAAS,eAAe,CAAC,GAAW;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,2DAA2D;IAC3D,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAK/B;IACC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAExD,wDAAwD;IACxD,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC;SAC5C,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;SAC/B,GAAG,CAAC,eAAe,CAAC,CAAC;IAExB,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEjE,0CAA0C;IAC1C,OAAO,QAAQ,CAAC,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBrE;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB,MAAM,CAKR"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function sleep(ms, signal) {
|
|
2
|
+
if (ms <= 0)
|
|
3
|
+
return Promise.resolve();
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const t = setTimeout(() => {
|
|
6
|
+
cleanup();
|
|
7
|
+
resolve();
|
|
8
|
+
}, ms);
|
|
9
|
+
const onAbort = () => {
|
|
10
|
+
cleanup();
|
|
11
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
12
|
+
};
|
|
13
|
+
const cleanup = () => {
|
|
14
|
+
clearTimeout(t);
|
|
15
|
+
if (signal)
|
|
16
|
+
signal.removeEventListener("abort", onAbort);
|
|
17
|
+
};
|
|
18
|
+
if (signal) {
|
|
19
|
+
if (signal.aborted)
|
|
20
|
+
return onAbort();
|
|
21
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function backoffDelayMs(attempt, baseDelayMs, maxDelayMs) {
|
|
26
|
+
const exp = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, Math.max(0, attempt - 1)));
|
|
27
|
+
// jitter: 0.75x..1.25x
|
|
28
|
+
const jitter = exp * (0.75 + Math.random() * 0.5);
|
|
29
|
+
return Math.floor(Math.min(maxDelayMs, jitter));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,IAAI,EAAE,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE;YACxB,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,IAAI,MAAM;gBAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3D,CAAC,CAAC;QAEF,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,OAAO,EAAE,CAAC;YACrC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,WAAmB,EACnB,UAAkB;IAElB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,uBAAuB;IACvB,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { UploadSessionState, UploadSessionPersistenceAdapter } from "../types";
|
|
2
|
+
type Listener = (s: UploadSessionState) => void;
|
|
3
|
+
type ListListener = () => void;
|
|
4
|
+
export declare function setUploadSessionPersistence(adapter: UploadSessionPersistenceAdapter | null): void;
|
|
5
|
+
export type PersistenceErrorHandler = (err: unknown, op: "set" | "remove" | "get", id: string) => void;
|
|
6
|
+
export declare function setUploadSessionPersistenceErrorHandler(handler: PersistenceErrorHandler | null): void;
|
|
7
|
+
export declare function pruneOldUploadSessions(): number;
|
|
8
|
+
/** Load persisted sessions into the in-memory store. */
|
|
9
|
+
export declare function rehydrateUploadSessions(): Promise<void>;
|
|
10
|
+
export declare function getUploadSession(id: string): UploadSessionState | undefined;
|
|
11
|
+
export declare function listUploadSessions(): UploadSessionState[];
|
|
12
|
+
export declare function upsertUploadSession(partial: Partial<UploadSessionState> & Pick<UploadSessionState, "id">): void;
|
|
13
|
+
export declare function removeUploadSession(id: string): void;
|
|
14
|
+
export declare function clearUploadSessionListeners(id: string): void;
|
|
15
|
+
export declare function subscribeUploadSession(id: string, fn: Listener): () => void;
|
|
16
|
+
/** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */
|
|
17
|
+
export declare function subscribeUploadSessionsList(fn: ListListener): () => void;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=upload-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-store.d.ts","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,UAAU,CAAC;AAEpF,KAAK,QAAQ,GAAG,CAAC,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAChD,KAAK,YAAY,GAAG,MAAM,IAAI,CAAC;AAQ/B,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,+BAA+B,GAAG,IAAI,QAE1F;AAGD,MAAM,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;AAIvG,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,QAE9F;AA6BD,wBAAgB,sBAAsB,WAyBrC;AAED,wDAAwD;AACxD,wBAAsB,uBAAuB,kBAgB5C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE3E;AAED,wBAAgB,kBAAkB,IAAI,kBAAkB,EAAE,CAEzD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,QA8CxG;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,QAK7C;AAED,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,MAAM,QAErD;AAED,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI,CAiB3E;AAED,4EAA4E;AAC5E,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,IAAI,CAGxE"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { now } from "@ttt-productions/firebase-helpers";
|
|
2
|
+
const sessions = new Map();
|
|
3
|
+
const listeners = new Map();
|
|
4
|
+
const listListeners = new Set();
|
|
5
|
+
let persistence = null;
|
|
6
|
+
export function setUploadSessionPersistence(adapter) {
|
|
7
|
+
persistence = adapter;
|
|
8
|
+
}
|
|
9
|
+
let persistenceErrorHandler = null;
|
|
10
|
+
export function setUploadSessionPersistenceErrorHandler(handler) {
|
|
11
|
+
persistenceErrorHandler = handler;
|
|
12
|
+
}
|
|
13
|
+
async function persistUpsert(state) {
|
|
14
|
+
try {
|
|
15
|
+
await persistence?.set(state.id, state);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
console.error("[upload-store] Failed to persist session:", state.id, err);
|
|
19
|
+
persistenceErrorHandler?.(err, "set", state.id);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function persistRemove(id) {
|
|
23
|
+
try {
|
|
24
|
+
await persistence?.remove(id);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
console.error("[upload-store] Failed to remove persisted session:", id, err);
|
|
28
|
+
persistenceErrorHandler?.(err, "remove", id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function notifyList() {
|
|
32
|
+
for (const fn of listListeners)
|
|
33
|
+
fn();
|
|
34
|
+
}
|
|
35
|
+
// ---- Session pruning ----
|
|
36
|
+
const MAX_SESSIONS = 100;
|
|
37
|
+
const SUCCESS_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
38
|
+
const ERROR_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d
|
|
39
|
+
export function pruneOldUploadSessions() {
|
|
40
|
+
const t = now();
|
|
41
|
+
const all = listUploadSessions();
|
|
42
|
+
let pruned = 0;
|
|
43
|
+
for (const s of all) {
|
|
44
|
+
if (s.status === "success" && t - s.updatedAt > SUCCESS_TTL_MS) {
|
|
45
|
+
removeUploadSession(s.id);
|
|
46
|
+
pruned++;
|
|
47
|
+
}
|
|
48
|
+
else if ((s.status === "error" || s.status === "canceled") && t - s.updatedAt > ERROR_TTL_MS) {
|
|
49
|
+
removeUploadSession(s.id);
|
|
50
|
+
pruned++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const remaining = listUploadSessions();
|
|
54
|
+
if (remaining.length > MAX_SESSIONS) {
|
|
55
|
+
for (const s of remaining.slice(MAX_SESSIONS)) {
|
|
56
|
+
removeUploadSession(s.id);
|
|
57
|
+
pruned++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return pruned;
|
|
61
|
+
}
|
|
62
|
+
/** Load persisted sessions into the in-memory store. */
|
|
63
|
+
export async function rehydrateUploadSessions() {
|
|
64
|
+
if (!persistence)
|
|
65
|
+
return;
|
|
66
|
+
const ids = await persistence.listIds();
|
|
67
|
+
for (const id of ids) {
|
|
68
|
+
try {
|
|
69
|
+
const s = await persistence.get(id);
|
|
70
|
+
if (s)
|
|
71
|
+
sessions.set(id, s);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error("[upload-store] Failed to read persisted session:", id, err);
|
|
75
|
+
persistenceErrorHandler?.(err, "get", id);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
pruneOldUploadSessions();
|
|
79
|
+
notifyList();
|
|
80
|
+
}
|
|
81
|
+
export function getUploadSession(id) {
|
|
82
|
+
return sessions.get(id);
|
|
83
|
+
}
|
|
84
|
+
export function listUploadSessions() {
|
|
85
|
+
return Array.from(sessions.values()).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
86
|
+
}
|
|
87
|
+
export function upsertUploadSession(partial) {
|
|
88
|
+
const prev = sessions.get(partial.id);
|
|
89
|
+
// Guard against out-of-order async updates.
|
|
90
|
+
if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt)
|
|
91
|
+
return;
|
|
92
|
+
const prevStatus = prev?.status ?? "idle";
|
|
93
|
+
const nextStatus = partial.status ?? prevStatus;
|
|
94
|
+
const isTerminal = (s) => s === "success" || s === "error" || s === "canceled";
|
|
95
|
+
const prevIsTerminal = isTerminal(prevStatus);
|
|
96
|
+
const nextIsTerminal = isTerminal(nextStatus);
|
|
97
|
+
// Once terminal, stay terminal. If we receive a terminal nextStatus first, accept it.
|
|
98
|
+
const status = prevIsTerminal ? prevStatus : nextIsTerminal ? nextStatus : nextStatus;
|
|
99
|
+
// Progress values should never go backwards.
|
|
100
|
+
const transferred = Math.max(partial.transferred ?? 0, prev?.transferred ?? 0);
|
|
101
|
+
const total = Math.max(partial.total ?? 0, prev?.total ?? 0);
|
|
102
|
+
const percent = Math.max(partial.percent ?? 0, prev?.percent ?? 0);
|
|
103
|
+
const version = (prev?.version ?? 0) + 1;
|
|
104
|
+
const next = {
|
|
105
|
+
id: partial.id,
|
|
106
|
+
status,
|
|
107
|
+
path: partial.path ?? prev?.path ?? "",
|
|
108
|
+
version,
|
|
109
|
+
transferred,
|
|
110
|
+
total,
|
|
111
|
+
percent,
|
|
112
|
+
startedAt: partial.startedAt ?? prev?.startedAt ?? now(),
|
|
113
|
+
updatedAt: partial.updatedAt ?? now(),
|
|
114
|
+
error: partial.error ?? prev?.error,
|
|
115
|
+
result: partial.result ?? prev?.result,
|
|
116
|
+
};
|
|
117
|
+
const wasMissing = !sessions.has(partial.id);
|
|
118
|
+
sessions.set(partial.id, next);
|
|
119
|
+
void persistUpsert(next);
|
|
120
|
+
const ls = listeners.get(partial.id);
|
|
121
|
+
if (ls)
|
|
122
|
+
for (const fn of ls)
|
|
123
|
+
fn(next);
|
|
124
|
+
if (wasMissing)
|
|
125
|
+
notifyList();
|
|
126
|
+
}
|
|
127
|
+
export function removeUploadSession(id) {
|
|
128
|
+
const existed = sessions.delete(id);
|
|
129
|
+
clearUploadSessionListeners(id);
|
|
130
|
+
void persistRemove(id);
|
|
131
|
+
if (existed)
|
|
132
|
+
notifyList();
|
|
133
|
+
}
|
|
134
|
+
export function clearUploadSessionListeners(id) {
|
|
135
|
+
if (listeners.has(id))
|
|
136
|
+
listeners.delete(id);
|
|
137
|
+
}
|
|
138
|
+
export function subscribeUploadSession(id, fn) {
|
|
139
|
+
let set = listeners.get(id);
|
|
140
|
+
if (!set) {
|
|
141
|
+
set = new Set();
|
|
142
|
+
listeners.set(id, set);
|
|
143
|
+
}
|
|
144
|
+
set.add(fn);
|
|
145
|
+
const current = sessions.get(id);
|
|
146
|
+
if (current)
|
|
147
|
+
fn(current);
|
|
148
|
+
return () => {
|
|
149
|
+
const s = listeners.get(id);
|
|
150
|
+
if (!s)
|
|
151
|
+
return;
|
|
152
|
+
s.delete(fn);
|
|
153
|
+
if (s.size === 0)
|
|
154
|
+
listeners.delete(id);
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */
|
|
158
|
+
export function subscribeUploadSessionsList(fn) {
|
|
159
|
+
listListeners.add(fn);
|
|
160
|
+
return () => listListeners.delete(fn);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=upload-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-store.js","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ttt-productions/upload-core",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/ttt-productions/ttt-packages.git",
|
|
7
|
+
"directory": "packages/upload-core"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"module": "dist/index.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./react": {
|
|
23
|
+
"types": "./dist/react/index.d.ts",
|
|
24
|
+
"default": "./dist/react/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@ttt-productions/firebase-helpers": "^0.2.14"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"firebase": ">=10.0.0",
|
|
38
|
+
"react": ">=18.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.8.3"
|
|
42
|
+
},
|
|
43
|
+
"author": "DJ (TTT Productions)",
|
|
44
|
+
"license": "MIT"
|
|
45
|
+
}
|