@ttt-productions/upload-core 0.7.4 → 0.7.6
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/browser/delete.d.ts +3 -0
- package/dist/browser/delete.d.ts.map +1 -0
- package/dist/{storage → browser}/delete.js +1 -1
- package/dist/{storage → browser}/delete.js.map +1 -1
- package/dist/browser/index.d.ts +5 -5
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +5 -5
- package/dist/browser/index.js.map +1 -1
- package/dist/{persistence → browser}/localstorage.d.ts +1 -1
- package/dist/browser/localstorage.d.ts.map +1 -0
- package/dist/{persistence → browser}/localstorage.js +2 -2
- package/dist/browser/localstorage.js.map +1 -0
- package/dist/browser/types.d.ts +3 -3
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/browser/types.js.map +1 -1
- package/dist/{queue → browser}/upload-queue.d.ts +2 -2
- package/dist/{queue → browser}/upload-queue.d.ts.map +1 -1
- package/dist/{queue → browser}/upload-queue.js +8 -8
- package/dist/browser/upload-queue.js.map +1 -0
- package/dist/browser/upload.d.ts +26 -0
- package/dist/browser/upload.d.ts.map +1 -0
- package/dist/{storage → browser}/upload.js +43 -37
- package/dist/browser/upload.js.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/storage/upload-error.d.ts.map +1 -1
- package/dist/storage/upload-error.js +1 -1
- package/dist/storage/upload-error.js.map +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/file-size.js +1 -1
- package/dist/utils/file-size.js.map +1 -1
- package/dist/utils/filename.d.ts.map +1 -1
- package/dist/utils/filename.js +9 -9
- package/dist/utils/filename.js.map +1 -1
- package/dist/utils/path.js +9 -9
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/upload-store.d.ts +3 -3
- package/dist/utils/upload-store.d.ts.map +1 -1
- package/dist/utils/upload-store.js +13 -12
- package/dist/utils/upload-store.js.map +1 -1
- package/package.json +3 -6
- package/dist/persistence/localstorage.d.ts.map +0 -1
- package/dist/persistence/localstorage.js.map +0 -1
- package/dist/queue/upload-queue.js.map +0 -1
- package/dist/storage/delete.d.ts +0 -3
- package/dist/storage/delete.d.ts.map +0 -1
- package/dist/storage/upload.d.ts +0 -27
- package/dist/storage/upload.d.ts.map +0 -1
- package/dist/storage/upload.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/browser/delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/
|
|
1
|
+
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/browser/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","sourcesContent":["import type { DeleteFileArgs } from './types.js';\nimport { deleteObject, ref } from 'firebase/storage';\n\nexport async function deleteFile(args: DeleteFileArgs): Promise<void> {\n const { storage, path } = args;\n await deleteObject(ref(storage, path));\n}\n"]}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './upload.js';
|
|
3
|
+
export * from './delete.js';
|
|
4
|
+
export * from './upload-queue.js';
|
|
5
|
+
export * from './localstorage.js';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC"}
|
package/dist/browser/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './upload.js';
|
|
3
|
+
export * from './delete.js';
|
|
4
|
+
export * from './upload-queue.js';
|
|
5
|
+
export * from './localstorage.js';
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC","sourcesContent":["export * from './types.js';\nexport * from './upload.js';\nexport * from './delete.js';\nexport * from './upload-queue.js';\nexport * from './localstorage.js';\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UploadSessionPersistenceAdapter } from
|
|
1
|
+
import type { UploadSessionPersistenceAdapter } from '../types.js';
|
|
2
2
|
export declare function createLocalStorageUploadSessionPersistence(opts?: {
|
|
3
3
|
/** Prefix for keys. Default: "upload_session:" */
|
|
4
4
|
prefix?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localstorage.d.ts","sourceRoot":"","sources":["../../src/browser/localstorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAsB,MAAM,aAAa,CAAC;AAUvF,wBAAgB,0CAA0C,CAAC,IAAI,CAAC,EAAE;IAChE,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,+BAA+B,CA0ClC"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
function hasLocalStorage() {
|
|
2
2
|
try {
|
|
3
|
-
return typeof window !==
|
|
3
|
+
return typeof window !== 'undefined' && !!window.localStorage;
|
|
4
4
|
}
|
|
5
5
|
catch {
|
|
6
6
|
return false;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
export function createLocalStorageUploadSessionPersistence(opts) {
|
|
10
|
-
const prefix = opts?.prefix ??
|
|
10
|
+
const prefix = opts?.prefix ?? 'upload_session:';
|
|
11
11
|
const k = (id) => `${prefix}${id}`;
|
|
12
12
|
const safeParse = (raw) => {
|
|
13
13
|
if (!raw)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localstorage.js","sourceRoot":"","sources":["../../src/browser/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,iBAAiB,CAAC;IAEjD,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","sourcesContent":["import type { UploadSessionPersistenceAdapter, UploadSessionState } from '../types.js';\n\nfunction hasLocalStorage(): boolean {\n try {\n return typeof window !== 'undefined' && !!window.localStorage;\n } catch {\n return false;\n }\n}\n\nexport function createLocalStorageUploadSessionPersistence(opts?: {\n /** Prefix for keys. Default: \"upload_session:\" */\n prefix?: string;\n}): UploadSessionPersistenceAdapter {\n const prefix = opts?.prefix ?? 'upload_session:';\n\n const k = (id: string) => `${prefix}${id}`;\n\n const safeParse = (raw: string | null): UploadSessionState | null => {\n if (!raw) return null;\n try {\n return JSON.parse(raw) as UploadSessionState;\n } catch {\n return null;\n }\n };\n\n return {\n listIds: () => {\n if (!hasLocalStorage()) return [];\n const ids: string[] = [];\n for (let i = 0; i < window.localStorage.length; i += 1) {\n const key = window.localStorage.key(i);\n if (!key) continue;\n if (!key.startsWith(prefix)) continue;\n ids.push(key.slice(prefix.length));\n }\n return ids;\n },\n\n get: (id: string) => {\n if (!hasLocalStorage()) return null;\n return safeParse(window.localStorage.getItem(k(id)));\n },\n\n set: (id: string, state: UploadSessionState) => {\n if (!hasLocalStorage()) return;\n window.localStorage.setItem(k(id), JSON.stringify(state));\n },\n\n remove: (id: string) => {\n if (!hasLocalStorage()) return;\n window.localStorage.removeItem(k(id));\n },\n };\n}\n"]}
|
package/dist/browser/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FirebaseStorage, UploadMetadata, UploadTask, UploadTaskSnapshot } from
|
|
2
|
-
import type { UploadFileResumableResult } from
|
|
1
|
+
import type { FirebaseStorage, UploadMetadata, UploadTask, UploadTaskSnapshot } from 'firebase/storage';
|
|
2
|
+
import type { UploadFileResumableResult } from '../types.js';
|
|
3
3
|
export type UploadProgressHandler = (p: {
|
|
4
4
|
transferred: number;
|
|
5
5
|
total: number;
|
|
@@ -28,7 +28,7 @@ export interface UploadController {
|
|
|
28
28
|
/** Resolves on success, rejects on error/cancel. */
|
|
29
29
|
done: Promise<UploadFileResumableResult>;
|
|
30
30
|
}
|
|
31
|
-
export interface StartUploadArgs extends Omit<UploadFileResumableArgs,
|
|
31
|
+
export interface StartUploadArgs extends Omit<UploadFileResumableArgs, 'onProgress'> {
|
|
32
32
|
/** Optional stable id for UI tracking. */
|
|
33
33
|
id?: string;
|
|
34
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,UAAU,EACV,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,EAAE;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,KAAK,IAAI,CAAC;AAEX,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAEnC,oCAAoC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IAEjB,KAAK,EAAE,MAAM,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,MAAM,EAAE,MAAM,OAAO,CAAC;IAEtB,oDAAoD;IACpD,IAAI,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,YAAY,CAAC;IAClF,0CAA0C;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n FirebaseStorage,\n UploadMetadata,\n UploadTask,\n UploadTaskSnapshot,\n} from 'firebase/storage';\nimport type { UploadFileResumableResult } from '../types.js';\n\nexport type UploadProgressHandler = (p: {\n transferred: number;\n total: number;\n percent: number; // 0..100\n snapshot?: UploadTaskSnapshot;\n}) => void;\n\nexport interface UploadFileResumableArgs {\n storage: FirebaseStorage;\n path: string;\n file: Blob | File;\n metadata?: UploadMetadata;\n onProgress?: UploadProgressHandler;\n\n /** Optional cancellation signal. */\n signal?: AbortSignal;\n}\n\nexport interface DeleteFileArgs {\n storage: FirebaseStorage;\n path: string;\n}\n\nexport interface UploadController {\n id: string;\n task: UploadTask;\n\n pause: () => boolean;\n resume: () => boolean;\n cancel: () => boolean;\n\n /** Resolves on success, rejects on error/cancel. */\n done: Promise<UploadFileResumableResult>;\n}\n\nexport interface StartUploadArgs extends Omit<UploadFileResumableArgs, 'onProgress'> {\n /** Optional stable id for UI tracking. */\n id?: string;\n\n /**\n * UploadQueue-only: higher numbers run sooner.\n * Ignored by startResumableUpload/uploadFileResumable.\n */\n priority?: number;\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { UploadQueueOptions } from
|
|
2
|
-
import type { UploadController, StartUploadArgs } from
|
|
1
|
+
import type { UploadQueueOptions } from '../types.js';
|
|
2
|
+
import type { UploadController, StartUploadArgs } from './types.js';
|
|
3
3
|
export declare class UploadQueue {
|
|
4
4
|
private concurrency;
|
|
5
5
|
private running;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-queue.d.ts","sourceRoot":"","sources":["../../src/
|
|
1
|
+
{"version":3,"file":"upload-queue.d.ts","sourceRoot":"","sources":["../../src/browser/upload-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6B,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAqBpE,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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getFileSize } from
|
|
2
|
-
import { upsertUploadSession, removeUploadSession } from
|
|
3
|
-
import { startResumableUpload } from
|
|
1
|
+
import { getFileSize } from '../utils/file-size.js';
|
|
2
|
+
import { upsertUploadSession, removeUploadSession } from '../utils/upload-store.js';
|
|
3
|
+
import { startResumableUpload } from './upload.js';
|
|
4
4
|
function safeId() {
|
|
5
5
|
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
6
6
|
}
|
|
@@ -31,7 +31,7 @@ export class UploadQueue {
|
|
|
31
31
|
const startedAt = Date.now();
|
|
32
32
|
upsertUploadSession({
|
|
33
33
|
id,
|
|
34
|
-
status:
|
|
34
|
+
status: 'queued',
|
|
35
35
|
path: args.path,
|
|
36
36
|
transferred: 0,
|
|
37
37
|
total: getFileSize(args.file),
|
|
@@ -56,7 +56,7 @@ export class UploadQueue {
|
|
|
56
56
|
return real.cancel();
|
|
57
57
|
this.pending = this.pending.filter((j) => j.id !== id);
|
|
58
58
|
removeUploadSession(id);
|
|
59
|
-
rejectDone(new DOMException(
|
|
59
|
+
rejectDone(new DOMException('Aborted', 'AbortError'));
|
|
60
60
|
return true;
|
|
61
61
|
},
|
|
62
62
|
done,
|
|
@@ -82,14 +82,14 @@ export class UploadQueue {
|
|
|
82
82
|
}
|
|
83
83
|
pump() {
|
|
84
84
|
while (this.running < this.concurrency && this.pending.length > 0) {
|
|
85
|
-
this.pending.sort((a, b) =>
|
|
85
|
+
this.pending.sort((a, b) => b.priority - a.priority || a.seq - b.seq);
|
|
86
86
|
const job = this.pending.shift();
|
|
87
87
|
this.running += 1;
|
|
88
88
|
try {
|
|
89
89
|
const { priority: _p, ...rest } = job.args;
|
|
90
90
|
const c = startResumableUpload({
|
|
91
91
|
...rest,
|
|
92
|
-
id: job.id,
|
|
92
|
+
id: job.id,
|
|
93
93
|
});
|
|
94
94
|
job.resolveController(c);
|
|
95
95
|
c.done.finally(() => {
|
|
@@ -101,7 +101,7 @@ export class UploadQueue {
|
|
|
101
101
|
this.running -= 1;
|
|
102
102
|
upsertUploadSession({
|
|
103
103
|
id: job.id,
|
|
104
|
-
status:
|
|
104
|
+
status: 'error',
|
|
105
105
|
path: job.args.path,
|
|
106
106
|
error: e,
|
|
107
107
|
updatedAt: Date.now(),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-queue.js","sourceRoot":"","sources":["../../src/browser/upload-queue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,SAAS,MAAM;IACb,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,CAAC;AACvE,CAAC;AACD,SAAS,QAAQ;IACf,OAAO,KAAK,CAAC;AACf,CAAC;AAWD,MAAM,OAAO,WAAW;IACd,WAAW,CAAS;IACpB,OAAO,GAAG,CAAC,CAAC;IACZ,OAAO,GAAU,EAAE,CAAC;IACpB,GAAG,GAAG,CAAC,CAAC;IAEhB,YAAY,IAAyB;QACnC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,cAAc,CAAC,CAAS;QACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,OAAO,CAAC,IAA6C;QACnD,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,OAAO,MAAM,EAAE,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,mBAAmB,CAAC;YAClB,EAAE;YACF,MAAM,EAAE,QAAQ;YAChB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,WAAW,EAAE,CAAC;YACd,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;YAC7B,OAAO,EAAE,CAAC;YACV,SAAS;YACT,SAAS,EAAE,SAAS;SACrB,CAAC,CAAC;QAEH,IAAI,IAAI,GAA4B,IAAI,CAAC;QAEzC,IAAI,WAAoD,CAAC;QACzD,IAAI,UAAiC,CAAC;QAEtC,MAAM,IAAI,GAAG,IAAI,OAAO,CAA4B,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC/D,WAAW,GAAG,GAAG,CAAC;YAClB,UAAU,GAAG,GAAG,CAAC;QACnB,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,GAAqB;YACnC,EAAE;YACF,IAAI,EAAE,IAAW;YACjB,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC/C,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YACjD,MAAM,EAAE,GAAG,EAAE;gBACX,IAAI,IAAI;oBAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC/B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;gBACvD,mBAAmB,CAAC,EAAE,CAAC,CAAC;gBACxB,UAAU,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;gBACtD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI;SACL,CAAC;QAEF,MAAM,GAAG,GAAQ;YACf,EAAE;YACF,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;YACrB,QAAQ;YACR,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;YACf,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE;gBACvB,IAAI,GAAG,CAAC,CAAC;gBACT,UAAU,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;gBACzB,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;gBAC3B,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;gBAC7B,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;gBAC7B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;YACvC,CAAC;YACD,gBAAgB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;SACvC,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,EAAE,CAAC;QAEZ,OAAO,UAAU,CAAC;IACpB,CAAC;IAEO,IAAI;QACV,OAAO,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YAEtE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAG,CAAC;YAClC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;YAElB,IAAI,CAAC;gBACH,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;gBAC3C,MAAM,CAAC,GAAG,oBAAoB,CAAC;oBAC7B,GAAI,IAAY;oBAChB,EAAE,EAAE,GAAG,CAAC,EAAE;iBACX,CAAC,CAAC;gBAEH,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBAEzB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;oBAClB,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;oBAClB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;gBAElB,mBAAmB,CAAC;oBAClB,EAAE,EAAE,GAAG,CAAC,EAAE;oBACV,MAAM,EAAE,OAAO;oBACf,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI;oBACnB,KAAK,EAAE,CAAC;oBACR,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;iBACtB,CAAC,CAAC;gBAEH,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;gBACxB,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;CACF","sourcesContent":["import type { UploadFileResumableResult, UploadQueueOptions } from '../types.js';\nimport type { UploadController, StartUploadArgs } from './types.js';\nimport { getFileSize } from '../utils/file-size.js';\nimport { upsertUploadSession, removeUploadSession } from '../utils/upload-store.js';\nimport { startResumableUpload } from './upload.js';\n\nfunction safeId() {\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n}\nfunction noopBool() {\n return false;\n}\n\ntype Job = {\n id: string;\n args: StartUploadArgs & { priority?: number };\n priority: number; // higher = sooner\n seq: number; // FIFO within same priority\n resolveController: (c: UploadController) => void;\n rejectController: (e: unknown) => void;\n};\n\nexport class UploadQueue {\n private concurrency: number;\n private running = 0;\n private pending: Job[] = [];\n private seq = 0;\n\n constructor(opts?: UploadQueueOptions) {\n this.concurrency = Math.max(1, opts?.concurrency ?? 2);\n }\n\n setConcurrency(n: number) {\n this.concurrency = Math.max(1, n);\n this.pump();\n }\n\n getPendingCount() {\n return this.pending.length;\n }\n\n getRunningCount() {\n return this.running;\n }\n\n enqueue(args: StartUploadArgs & { priority?: number }): UploadController {\n const id = args.id ?? `upl_${safeId()}`;\n const priority = args.priority ?? 0;\n\n const startedAt = Date.now();\n upsertUploadSession({\n id,\n status: 'queued',\n path: args.path,\n transferred: 0,\n total: getFileSize(args.file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let real: UploadController | null = null;\n\n let resolveDone!: (r: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n const controller: UploadController = {\n id,\n task: null as any,\n pause: () => (real ? real.pause() : noopBool()),\n resume: () => (real ? real.resume() : noopBool()),\n cancel: () => {\n if (real) return real.cancel();\n this.pending = this.pending.filter((j) => j.id !== id);\n removeUploadSession(id);\n rejectDone(new DOMException('Aborted', 'AbortError'));\n return true;\n },\n done,\n };\n\n const job: Job = {\n id,\n args: { ...args, id },\n priority,\n seq: this.seq++,\n resolveController: (c) => {\n real = c;\n controller.task = c.task;\n controller.pause = c.pause;\n controller.resume = c.resume;\n controller.cancel = c.cancel;\n c.done.then(resolveDone, rejectDone);\n },\n rejectController: (e) => rejectDone(e),\n };\n\n this.pending.push(job);\n this.pump();\n\n return controller;\n }\n\n private pump() {\n while (this.running < this.concurrency && this.pending.length > 0) {\n this.pending.sort((a, b) => b.priority - a.priority || a.seq - b.seq);\n\n const job = this.pending.shift()!;\n this.running += 1;\n\n try {\n const { priority: _p, ...rest } = job.args;\n const c = startResumableUpload({\n ...(rest as any),\n id: job.id,\n });\n\n job.resolveController(c);\n\n c.done.finally(() => {\n this.running -= 1;\n this.pump();\n });\n } catch (e) {\n this.running -= 1;\n\n upsertUploadSession({\n id: job.id,\n status: 'error',\n path: job.args.path,\n error: e,\n updatedAt: Date.now(),\n });\n\n job.rejectController(e);\n this.pump();\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UploadFileResumableResult } from '../types.js';
|
|
2
|
+
import type { UploadFileResumableArgs, UploadController } from './types.js';
|
|
3
|
+
import { type FirebaseStorage, type UploadMetadata } from 'firebase/storage';
|
|
4
|
+
export declare function startResumableUpload(args: {
|
|
5
|
+
id: string;
|
|
6
|
+
storage: FirebaseStorage;
|
|
7
|
+
path: string;
|
|
8
|
+
file: Blob;
|
|
9
|
+
metadata?: UploadMetadata;
|
|
10
|
+
onProgress?: UploadFileResumableArgs['onProgress'];
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}): UploadController;
|
|
13
|
+
export declare function uploadFileResumable(args: Omit<Parameters<typeof startResumableUpload>[0], 'id'> & {
|
|
14
|
+
id?: string;
|
|
15
|
+
/**
|
|
16
|
+
* EXPLICIT opt-in for the NEUTRAL content type: a picker file with unknown
|
|
17
|
+
* browser metadata must reach the server AS unknown
|
|
18
|
+
* (`application/octet-stream`) so the server-side byte inspector — the only
|
|
19
|
+
* classification authority — sees it unrewritten. Default false keeps the
|
|
20
|
+
* media-only gate for every other consumer. A concrete media MIME is still validated either
|
|
21
|
+
* way; this flag never permits arbitrary declared types, only the one
|
|
22
|
+
* neutral value.
|
|
23
|
+
*/
|
|
24
|
+
allowNeutralContentType?: boolean;
|
|
25
|
+
}): Promise<UploadFileResumableResult>;
|
|
26
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/browser/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAM5E,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAC;AAY1B,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,CAiKnB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GACA,OAAO,CAAC,yBAAyB,CAAC,CAwBpC"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { getFileSize } from
|
|
2
|
-
import { upsertUploadSession } from
|
|
3
|
-
import { NEUTRAL_CONTENT_TYPE } from
|
|
4
|
-
import { UploadError, isValidMediaContentType } from
|
|
5
|
-
import {
|
|
1
|
+
import { getFileSize } from '../utils/file-size.js';
|
|
2
|
+
import { upsertUploadSession } from '../utils/upload-store.js';
|
|
3
|
+
import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';
|
|
4
|
+
import { UploadError, isValidMediaContentType } from '../storage/upload-error.js';
|
|
5
|
+
import { ref, uploadBytesResumable, } from 'firebase/storage';
|
|
6
6
|
function isAbortError(e) {
|
|
7
|
-
return e instanceof DOMException && e.name ===
|
|
7
|
+
return e instanceof DOMException && e.name === 'AbortError';
|
|
8
|
+
}
|
|
9
|
+
function isStorageCanceledError(e) {
|
|
10
|
+
return (typeof e === 'object' && e !== null && e.code === 'storage/canceled');
|
|
8
11
|
}
|
|
9
12
|
export function startResumableUpload(args) {
|
|
10
13
|
const { id, storage, path, file, metadata, onProgress, signal } = args;
|
|
@@ -13,7 +16,7 @@ export function startResumableUpload(args) {
|
|
|
13
16
|
const startedAt = Date.now();
|
|
14
17
|
upsertUploadSession({
|
|
15
18
|
id,
|
|
16
|
-
status:
|
|
19
|
+
status: 'uploading',
|
|
17
20
|
path,
|
|
18
21
|
transferred: 0,
|
|
19
22
|
total: getFileSize(file),
|
|
@@ -28,28 +31,36 @@ export function startResumableUpload(args) {
|
|
|
28
31
|
resolveDone = res;
|
|
29
32
|
rejectDone = rej;
|
|
30
33
|
});
|
|
31
|
-
|
|
34
|
+
// The Storage SDK rejects a cancelled task with its own `storage/canceled` error.
|
|
35
|
+
// When this upload asked for the cancel (its signal aborted, or its controller's
|
|
36
|
+
// cancel() ran), that rejection becomes the canonical AbortError every consumer
|
|
37
|
+
// already treats as a user cancel; a `storage/canceled` nobody here asked for
|
|
38
|
+
// stays a real failure.
|
|
39
|
+
let canceledLocally = false;
|
|
40
|
+
const cancelTask = () => {
|
|
41
|
+
canceledLocally = true;
|
|
32
42
|
try {
|
|
33
|
-
task.cancel();
|
|
43
|
+
return task.cancel();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
34
47
|
}
|
|
35
|
-
|
|
48
|
+
};
|
|
49
|
+
const onAbort = () => {
|
|
50
|
+
cancelTask();
|
|
36
51
|
};
|
|
37
52
|
if (signal) {
|
|
38
53
|
if (signal.aborted)
|
|
39
54
|
onAbort();
|
|
40
|
-
signal.addEventListener(
|
|
55
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
41
56
|
}
|
|
42
|
-
unsub = task.on(
|
|
57
|
+
unsub = task.on('state_changed', (snap) => {
|
|
43
58
|
const transferred = snap.bytesTransferred ?? 0;
|
|
44
59
|
const total = snap.totalBytes ?? getFileSize(file);
|
|
45
60
|
const percent = total > 0 ? (transferred / total) * 100 : 0;
|
|
46
61
|
upsertUploadSession({
|
|
47
62
|
id,
|
|
48
|
-
status: snap.state ===
|
|
49
|
-
? "paused"
|
|
50
|
-
: snap.state === "running"
|
|
51
|
-
? "uploading"
|
|
52
|
-
: "uploading",
|
|
63
|
+
status: snap.state === 'paused' ? 'paused' : snap.state === 'running' ? 'uploading' : 'uploading',
|
|
53
64
|
path,
|
|
54
65
|
transferred,
|
|
55
66
|
total,
|
|
@@ -59,23 +70,27 @@ export function startResumableUpload(args) {
|
|
|
59
70
|
onProgress?.({ transferred, total, percent, snapshot: snap });
|
|
60
71
|
}, async (err) => {
|
|
61
72
|
if (signal)
|
|
62
|
-
signal.removeEventListener(
|
|
73
|
+
signal.removeEventListener('abort', onAbort);
|
|
63
74
|
if (unsub)
|
|
64
75
|
unsub();
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
const cancelError = isAbortError(err)
|
|
77
|
+
? err
|
|
78
|
+
: canceledLocally && isStorageCanceledError(err)
|
|
79
|
+
? new DOMException('Aborted', 'AbortError')
|
|
80
|
+
: null;
|
|
81
|
+
if (cancelError) {
|
|
67
82
|
upsertUploadSession({
|
|
68
83
|
id,
|
|
69
|
-
status:
|
|
84
|
+
status: 'canceled',
|
|
70
85
|
path,
|
|
71
86
|
updatedAt: Date.now(),
|
|
72
|
-
error:
|
|
87
|
+
error: cancelError,
|
|
73
88
|
});
|
|
74
|
-
return rejectDone(
|
|
89
|
+
return rejectDone(cancelError);
|
|
75
90
|
}
|
|
76
91
|
upsertUploadSession({
|
|
77
92
|
id,
|
|
78
|
-
status:
|
|
93
|
+
status: 'error',
|
|
79
94
|
path,
|
|
80
95
|
updatedAt: Date.now(),
|
|
81
96
|
error: err,
|
|
@@ -83,18 +98,16 @@ export function startResumableUpload(args) {
|
|
|
83
98
|
rejectDone(err);
|
|
84
99
|
}, async () => {
|
|
85
100
|
try {
|
|
86
|
-
const downloadURL = await getDownloadURL(task.snapshot.ref);
|
|
87
101
|
const contentType = task.snapshot.metadata?.contentType ?? null;
|
|
88
102
|
const size = task.snapshot.totalBytes ?? getFileSize(file);
|
|
89
103
|
const result = {
|
|
90
|
-
downloadURL,
|
|
91
104
|
fullPath: task.snapshot.ref.fullPath,
|
|
92
105
|
contentType,
|
|
93
106
|
size,
|
|
94
107
|
};
|
|
95
108
|
upsertUploadSession({
|
|
96
109
|
id,
|
|
97
|
-
status:
|
|
110
|
+
status: 'success',
|
|
98
111
|
path,
|
|
99
112
|
transferred: size,
|
|
100
113
|
total: size,
|
|
@@ -107,7 +120,7 @@ export function startResumableUpload(args) {
|
|
|
107
120
|
catch (e) {
|
|
108
121
|
upsertUploadSession({
|
|
109
122
|
id,
|
|
110
|
-
status:
|
|
123
|
+
status: 'error',
|
|
111
124
|
path,
|
|
112
125
|
updatedAt: Date.now(),
|
|
113
126
|
error: e,
|
|
@@ -116,7 +129,7 @@ export function startResumableUpload(args) {
|
|
|
116
129
|
}
|
|
117
130
|
finally {
|
|
118
131
|
if (signal)
|
|
119
|
-
signal.removeEventListener(
|
|
132
|
+
signal.removeEventListener('abort', onAbort);
|
|
120
133
|
if (unsub)
|
|
121
134
|
unsub();
|
|
122
135
|
}
|
|
@@ -140,14 +153,7 @@ export function startResumableUpload(args) {
|
|
|
140
153
|
return false;
|
|
141
154
|
}
|
|
142
155
|
},
|
|
143
|
-
cancel:
|
|
144
|
-
try {
|
|
145
|
-
return task.cancel();
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
},
|
|
156
|
+
cancel: cancelTask,
|
|
151
157
|
done,
|
|
152
158
|
};
|
|
153
159
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/browser/upload.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult } from '../types.js';\nimport type { UploadFileResumableArgs, UploadController } from './types.js';\nimport { getFileSize } from '../utils/file-size.js';\nimport { upsertUploadSession } from '../utils/upload-store.js';\nimport { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';\nimport { UploadError, isValidMediaContentType } from '../storage/upload-error.js';\n\nimport {\n ref,\n uploadBytesResumable,\n type FirebaseStorage,\n type UploadMetadata,\n type UploadTaskSnapshot,\n} from 'firebase/storage';\n\nfunction isAbortError(e: unknown) {\n return e instanceof DOMException && e.name === 'AbortError';\n}\n\nfunction isStorageCanceledError(e: unknown) {\n return (\n typeof e === 'object' && e !== null && (e as { code?: unknown }).code === 'storage/canceled'\n );\n}\n\nexport function startResumableUpload(args: {\n id: string;\n storage: FirebaseStorage;\n path: string;\n file: Blob;\n metadata?: UploadMetadata;\n onProgress?: UploadFileResumableArgs['onProgress'];\n signal?: AbortSignal;\n}): UploadController {\n const { id, storage, path, file, metadata, onProgress, signal } = args;\n\n const r = ref(storage, path);\n const task = uploadBytesResumable(r, file, metadata);\n\n const startedAt = Date.now();\n\n upsertUploadSession({\n id,\n status: 'uploading',\n path,\n transferred: 0,\n total: getFileSize(file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let unsub: (() => void) | null = null;\n\n let resolveDone!: (v: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n // The Storage SDK rejects a cancelled task with its own `storage/canceled` error.\n // When this upload asked for the cancel (its signal aborted, or its controller's\n // cancel() ran), that rejection becomes the canonical AbortError every consumer\n // already treats as a user cancel; a `storage/canceled` nobody here asked for\n // stays a real failure.\n let canceledLocally = false;\n const cancelTask = () => {\n canceledLocally = true;\n try {\n return task.cancel();\n } catch {\n return false;\n }\n };\n\n const onAbort = () => {\n cancelTask();\n };\n\n if (signal) {\n if (signal.aborted) onAbort();\n signal.addEventListener('abort', onAbort, { once: true });\n }\n\n unsub = task.on(\n 'state_changed',\n (snap: UploadTaskSnapshot) => {\n const transferred = snap.bytesTransferred ?? 0;\n const total = snap.totalBytes ?? getFileSize(file);\n const percent = total > 0 ? (transferred / total) * 100 : 0;\n\n upsertUploadSession({\n id,\n status:\n snap.state === 'paused' ? 'paused' : snap.state === 'running' ? 'uploading' : 'uploading',\n path,\n transferred,\n total,\n percent,\n updatedAt: Date.now(),\n });\n\n onProgress?.({ transferred, total, percent, snapshot: snap });\n },\n async (err) => {\n if (signal) signal.removeEventListener('abort', onAbort);\n if (unsub) unsub();\n\n const cancelError = isAbortError(err)\n ? err\n : canceledLocally && isStorageCanceledError(err)\n ? new DOMException('Aborted', 'AbortError')\n : null;\n if (cancelError) {\n upsertUploadSession({\n id,\n status: 'canceled',\n path,\n updatedAt: Date.now(),\n error: cancelError,\n });\n return rejectDone(cancelError);\n }\n\n upsertUploadSession({\n id,\n status: 'error',\n path,\n updatedAt: Date.now(),\n error: err,\n });\n\n rejectDone(err);\n },\n async () => {\n try {\n const contentType = task.snapshot.metadata?.contentType ?? null;\n const size = task.snapshot.totalBytes ?? getFileSize(file);\n\n const result: UploadFileResumableResult = {\n fullPath: task.snapshot.ref.fullPath,\n contentType,\n size,\n };\n\n upsertUploadSession({\n id,\n status: 'success',\n path,\n transferred: size,\n total: size,\n percent: 100,\n updatedAt: Date.now(),\n result,\n });\n\n resolveDone(result);\n } catch (e) {\n upsertUploadSession({\n id,\n status: 'error',\n path,\n updatedAt: Date.now(),\n error: e,\n });\n rejectDone(e);\n } finally {\n if (signal) signal.removeEventListener('abort', onAbort);\n if (unsub) unsub();\n }\n },\n );\n\n return {\n id,\n task,\n pause: () => {\n try {\n return task.pause();\n } catch {\n return false;\n }\n },\n resume: () => {\n try {\n return task.resume();\n } catch {\n return false;\n }\n },\n cancel: cancelTask,\n done,\n };\n}\n\nexport async function uploadFileResumable(\n args: Omit<Parameters<typeof startResumableUpload>[0], 'id'> & {\n id?: string;\n /**\n * EXPLICIT opt-in for the NEUTRAL content type: a picker file with unknown\n * browser metadata must reach the server AS unknown\n * (`application/octet-stream`) so the server-side byte inspector — the only\n * classification authority — sees it unrewritten. Default false keeps the\n * media-only gate for every other consumer. A concrete media MIME is still validated either\n * way; this flag never permits arbitrary declared types, only the one\n * neutral value.\n */\n allowNeutralContentType?: boolean;\n },\n): Promise<UploadFileResumableResult> {\n // Defense-in-depth: reject missing or invalid contentType before any bytes are sent.\n // file-input should already guarantee a valid or explicitly-neutral MIME, but this\n // is the last gate before Firebase Storage.\n const ct = args.metadata?.contentType;\n if (!ct) {\n throw new UploadError(\n 'missing_content_type',\n 'Upload rejected: contentType metadata is required. Ensure file.type is set before uploading.',\n );\n }\n const neutralAllowed =\n args.allowNeutralContentType === true && ct.toLowerCase().trim() === NEUTRAL_CONTENT_TYPE;\n if (!neutralAllowed && !isValidMediaContentType(ct)) {\n throw new UploadError(\n 'invalid_content_type',\n `Upload rejected: contentType \"${ct}\" is not a supported media MIME (expected image/*, video/*, or audio/*).`,\n );\n }\n\n const { allowNeutralContentType: _allowNeutral, ...rest } = args;\n const id = args.id ?? `upl_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n const c = startResumableUpload({ ...rest, id });\n return c.done;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './storage/upload-error.js';
|
|
3
|
+
export * from './utils/filename.js';
|
|
4
|
+
export * from './utils/path.js';
|
|
5
|
+
export * from './utils/upload-store.js';
|
|
6
|
+
export * from './utils/file-size.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './storage/upload-error.js';
|
|
3
|
+
export * from './utils/filename.js';
|
|
4
|
+
export * from './utils/path.js';
|
|
5
|
+
export * from './utils/upload-store.js';
|
|
6
|
+
export * from './utils/file-size.js';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,cAAc,2BAA2B,CAAC;AAE1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC","sourcesContent":["export * from
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,cAAc,2BAA2B,CAAC;AAE1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC","sourcesContent":["export * from './types.js';\n\nexport * from './storage/upload-error.js';\n\nexport * from './utils/filename.js';\nexport * from './utils/path.js';\nexport * from './utils/upload-store.js';\nexport * from './utils/file-size.js';\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-error.d.ts","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"upload-error.d.ts","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAE9E;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM;CAOpD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAKvF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NEUTRAL_CONTENT_TYPE } from
|
|
1
|
+
import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';
|
|
2
2
|
/**
|
|
3
3
|
* Typed error thrown by upload-core when a client-side guard rejects an upload
|
|
4
4
|
* before it reaches Firebase Storage. Consumers can catch this and show a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-error.js","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"upload-error.js","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAItE;;;;GAIG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IAE/B,YAAY,IAAqB,EAAE,OAAgB;QACjD,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,uEAAuE;QACvE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAAsC;IAC5E,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,EAAE,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,EAAE,KAAK,oBAAoB;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,qCAAqC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';\n\nexport type UploadErrorCode = 'missing_content_type' | 'invalid_content_type';\n\n/**\n * Typed error thrown by upload-core when a client-side guard rejects an upload\n * before it reaches Firebase Storage. Consumers can catch this and show a\n * friendly message instead of parsing a storage 403.\n */\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message?: string) {\n super(message ?? code);\n this.name = 'UploadError';\n this.code = code;\n // Preserve prototype chain for `instanceof` checks after transpilation\n Object.setPrototypeOf(this, UploadError.prototype);\n }\n}\n\n/**\n * Returns true if the given string looks like a valid media MIME type\n * for an image, video, or audio upload: image/*, video/*, or audio/*.\n * Empty strings, application/octet-stream, and anything else → false.\n */\nexport function isValidMediaContentType(contentType: string | undefined | null): boolean {\n if (!contentType) return false;\n const ct = contentType.toLowerCase().trim();\n if (ct === NEUTRAL_CONTENT_TYPE) return false;\n return /^(image|video|audio)\\/[a-z0-9.+-]+$/.test(ct);\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a finished upload reports: where the bytes landed, their stored content type, and
|
|
3
|
+
* their size. No download URL is minted — a staged upload is read only by the backend.
|
|
4
|
+
*/
|
|
1
5
|
export interface UploadFileResumableResult {
|
|
2
|
-
downloadURL: string;
|
|
3
6
|
fullPath: string;
|
|
4
7
|
contentType: string | null;
|
|
5
8
|
size: number;
|
|
6
9
|
}
|
|
7
|
-
export type UploadSessionStatus =
|
|
10
|
+
export type UploadSessionStatus = 'idle' | 'queued' | 'uploading' | 'paused' | 'success' | 'error' | 'canceled';
|
|
8
11
|
export interface UploadSessionPersistenceAdapter {
|
|
9
12
|
/** Return all session ids currently stored. */
|
|
10
13
|
listIds: () => Promise<string[]> | string[];
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACxC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,mBAAmB,GAC7B,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAEhF,MAAM,WAAW,+BAA+B;IAC9C,+CAA+C;IAC/C,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;IAC5C,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACpF,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,kBAAkB;IACjC,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,WAAW,CAAC,EAAE,+BAA+B,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,mBAAmB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IAEb,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAEhB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAEhB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AAED,MAAM,WAAW,wBAAwB;IACvC,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * What a finished upload reports: where the bytes landed, their stored content type, and\n * their size. No download URL is minted — a staged upload is read only by the backend.\n */\nexport interface UploadFileResumableResult {\n fullPath: string;\n contentType: string | null;\n size: number;\n}\n\nexport type UploadSessionStatus =\n 'idle' | 'queued' | 'uploading' | 'paused' | 'success' | 'error' | 'canceled';\n\nexport interface UploadSessionPersistenceAdapter {\n /** Return all session ids currently stored. */\n listIds: () => Promise<string[]> | string[];\n get: (id: string) => Promise<UploadSessionState | null> | UploadSessionState | null;\n set: (id: string, state: UploadSessionState) => Promise<void> | void;\n remove: (id: string) => Promise<void> | void;\n}\n\nexport interface UploadQueueOptions {\n /** Maximum concurrent uploads. Default: 3 */\n concurrency?: number;\n /** Optional persistence adapter for session state. */\n persistence?: UploadSessionPersistenceAdapter;\n}\n\nexport interface UploadSessionState {\n id: string;\n status: UploadSessionStatus;\n path: string;\n\n /** Monotonic version counter for state updates. */\n version: number;\n\n transferred: number;\n total: number;\n percent: number; // 0..100\n\n startedAt: number;\n updatedAt: number;\n\n error?: unknown;\n result?: UploadFileResumableResult;\n}\n\nexport interface DisposeUploadSessionArgs {\n /** Session id to dispose. */\n id: string;\n /** If true, cancel any active upload task before disposal. Default true. */\n cancel?: boolean;\n /** If true, remove session state from the in-memory store. Default true. */\n remove?: boolean;\n}\n"]}
|
package/dist/utils/file-size.js
CHANGED
|
@@ -1 +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","sourcesContent":["export function getFileSize(file: Blob | File): number {\n const anyFile = file as any;\n const size = anyFile?.size;\n return typeof size ===
|
|
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","sourcesContent":["export function getFileSize(file: Blob | File): number {\n const anyFile = file as any;\n const size = anyFile?.size;\n return typeof size === 'number' && size >= 0 ? size : 0;\n}\n"]}
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBtD"}
|
package/dist/utils/filename.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export function normalizeFilename(name) {
|
|
2
|
-
const trimmed = (name ||
|
|
2
|
+
const trimmed = (name || '').trim();
|
|
3
3
|
if (!trimmed)
|
|
4
|
-
return
|
|
5
|
-
const lastDot = trimmed.lastIndexOf(
|
|
4
|
+
return 'file';
|
|
5
|
+
const lastDot = trimmed.lastIndexOf('.');
|
|
6
6
|
const base = lastDot > 0 ? trimmed.slice(0, lastDot) : trimmed;
|
|
7
|
-
const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() :
|
|
7
|
+
const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() : '';
|
|
8
8
|
const safeBase = base
|
|
9
9
|
.toLowerCase()
|
|
10
|
-
.replace(/[^a-z0-9-_ ]/g,
|
|
11
|
-
.replace(/\s+/g,
|
|
12
|
-
.replace(/-+/g,
|
|
13
|
-
.replace(/^-|-$/g,
|
|
14
|
-
return `${safeBase ||
|
|
10
|
+
.replace(/[^a-z0-9-_ ]/g, '')
|
|
11
|
+
.replace(/\s+/g, '-')
|
|
12
|
+
.replace(/-+/g, '-')
|
|
13
|
+
.replace(/^-|-$/g, '');
|
|
14
|
+
return `${safeBase || 'file'}${ext}`;
|
|
15
15
|
}
|
|
16
16
|
//# sourceMappingURL=filename.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,IAAY;
|
|
1
|
+
{"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IAE5B,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","sourcesContent":["export function normalizeFilename(name: string): string {\n const trimmed = (name || '').trim();\n if (!trimmed) return 'file';\n\n const lastDot = trimmed.lastIndexOf('.');\n const base = lastDot > 0 ? trimmed.slice(0, lastDot) : trimmed;\n const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() : '';\n\n const safeBase = base\n .toLowerCase()\n .replace(/[^a-z0-9-_ ]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n\n return `${safeBase || 'file'}${ext}`;\n}\n"]}
|
package/dist/utils/path.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { joinPath } from
|
|
2
|
-
import { normalizeFilename } from
|
|
1
|
+
import { joinPath } from '@ttt-productions/firebase-helpers';
|
|
2
|
+
import { normalizeFilename } from './filename.js';
|
|
3
3
|
function sanitizeSegment(seg) {
|
|
4
|
-
const s = String(seg ??
|
|
4
|
+
const s = String(seg ?? '').trim();
|
|
5
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(/^\.+/,
|
|
10
|
-
return trimmed.slice(0, 80) ||
|
|
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
11
|
}
|
|
12
12
|
export function buildUploadPath(args) {
|
|
13
13
|
const { basePath, ownerId, contentId, filename } = args;
|
|
@@ -15,7 +15,7 @@ export function buildUploadPath(args) {
|
|
|
15
15
|
const segments = [basePath, ownerId, contentId]
|
|
16
16
|
.filter((p) => !!p)
|
|
17
17
|
.map(sanitizeSegment);
|
|
18
|
-
const filePart = filename ? normalizeFilename(filename) :
|
|
18
|
+
const filePart = filename ? normalizeFilename(filename) : 'file';
|
|
19
19
|
return joinPath(...segments, filePart);
|
|
20
20
|
}
|
|
21
21
|
//# sourceMappingURL=path.js.map
|
package/dist/utils/path.js.map
CHANGED
|
@@ -1 +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,eAAe,CAAC;AAElD,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,OAAO,QAAQ,CAAC,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { joinPath } from
|
|
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,eAAe,CAAC;AAElD,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,OAAO,QAAQ,CAAC,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { joinPath } from '@ttt-productions/firebase-helpers';\nimport { normalizeFilename } from './filename.js';\n\nfunction sanitizeSegment(seg: string): string {\n const s = String(seg ?? '').trim();\n // Remove slashes and collapse dot-runs to avoid traversal.\n const noSlashes = s.replace(/[\\\\/]+/g, '_');\n const noDots = noSlashes.replace(/\\.+/g, '.');\n const cleaned = noDots.replace(/[^a-zA-Z0-9._-]/g, '_');\n const trimmed = cleaned.replace(/^\\.+/, '').replace(/\\.+$/, '');\n return trimmed.slice(0, 80) || '_';\n}\n\nexport function buildUploadPath(args: {\n basePath: string;\n ownerId?: string;\n contentId?: string;\n filename?: string;\n}) {\n const { basePath, ownerId, contentId, filename } = args;\n\n // Sanitize segments for security (traversal prevention)\n const segments = [basePath, ownerId, contentId]\n .filter((p): p is string => !!p)\n .map(sanitizeSegment);\n\n const filePart = filename ? normalizeFilename(filename) : 'file';\n\n return joinPath(...segments, filePart);\n}\n"]}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { UploadSessionState, UploadSessionPersistenceAdapter } from
|
|
1
|
+
import type { UploadSessionState, UploadSessionPersistenceAdapter } from '../types.js';
|
|
2
2
|
type Listener = (s: UploadSessionState) => void;
|
|
3
3
|
type ListListener = () => void;
|
|
4
4
|
export declare function setUploadSessionPersistence(adapter: UploadSessionPersistenceAdapter | null): void;
|
|
5
|
-
export type PersistenceErrorHandler = (err: unknown, op:
|
|
5
|
+
export type PersistenceErrorHandler = (err: unknown, op: 'set' | 'remove' | 'get', id: string) => void;
|
|
6
6
|
export declare function setUploadSessionPersistenceErrorHandler(handler: PersistenceErrorHandler | null): void;
|
|
7
7
|
export declare function pruneOldUploadSessions(): number;
|
|
8
8
|
/** Load persisted sessions into the in-memory store. */
|
|
9
9
|
export declare function rehydrateUploadSessions(): Promise<void>;
|
|
10
10
|
export declare function getUploadSession(id: string): UploadSessionState | undefined;
|
|
11
11
|
export declare function listUploadSessions(): UploadSessionState[];
|
|
12
|
-
export declare function upsertUploadSession(partial: Partial<UploadSessionState> & Pick<UploadSessionState,
|
|
12
|
+
export declare function upsertUploadSession(partial: Partial<UploadSessionState> & Pick<UploadSessionState, 'id'>): void;
|
|
13
13
|
export declare function removeUploadSession(id: string): void;
|
|
14
14
|
export declare function subscribeUploadSession(id: string, fn: Listener): () => void;
|
|
15
15
|
/** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */
|
|
@@ -1 +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,aAAa,CAAC;AAEvF,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;AAED,MAAM,MAAM,uBAAuB,GAAG,
|
|
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,aAAa,CAAC;AAEvF,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;AAED,MAAM,MAAM,uBAAuB,GAAG,CACpC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,EAC5B,EAAE,EAAE,MAAM,KACP,IAAI,CAAC;AAIV,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,QAE9F;AA4BD,wBAAgB,sBAAsB,WA4BrC;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,CACjC,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,QA+CtE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,QAK7C;AAMD,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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { now } from
|
|
1
|
+
import { now } from '@ttt-productions/firebase-helpers';
|
|
2
2
|
const sessions = new Map();
|
|
3
3
|
const listeners = new Map();
|
|
4
4
|
const listListeners = new Set();
|
|
@@ -15,8 +15,8 @@ async function persistUpsert(state) {
|
|
|
15
15
|
await persistence?.set(state.id, state);
|
|
16
16
|
}
|
|
17
17
|
catch (err) {
|
|
18
|
-
console.error(
|
|
19
|
-
persistenceErrorHandler?.(err,
|
|
18
|
+
console.error('[upload-store] Failed to persist session:', state.id, err);
|
|
19
|
+
persistenceErrorHandler?.(err, 'set', state.id);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
async function persistRemove(id) {
|
|
@@ -24,8 +24,8 @@ async function persistRemove(id) {
|
|
|
24
24
|
await persistence?.remove(id);
|
|
25
25
|
}
|
|
26
26
|
catch (err) {
|
|
27
|
-
console.error(
|
|
28
|
-
persistenceErrorHandler?.(err,
|
|
27
|
+
console.error('[upload-store] Failed to remove persisted session:', id, err);
|
|
28
|
+
persistenceErrorHandler?.(err, 'remove', id);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
function notifyList() {
|
|
@@ -40,11 +40,12 @@ export function pruneOldUploadSessions() {
|
|
|
40
40
|
const all = listUploadSessions();
|
|
41
41
|
let pruned = 0;
|
|
42
42
|
for (const s of all) {
|
|
43
|
-
if (s.status ===
|
|
43
|
+
if (s.status === 'success' && t - s.updatedAt > SUCCESS_TTL_MS) {
|
|
44
44
|
removeUploadSession(s.id);
|
|
45
45
|
pruned++;
|
|
46
46
|
}
|
|
47
|
-
else if ((s.status ===
|
|
47
|
+
else if ((s.status === 'error' || s.status === 'canceled') &&
|
|
48
|
+
t - s.updatedAt > ERROR_TTL_MS) {
|
|
48
49
|
removeUploadSession(s.id);
|
|
49
50
|
pruned++;
|
|
50
51
|
}
|
|
@@ -70,8 +71,8 @@ export async function rehydrateUploadSessions() {
|
|
|
70
71
|
sessions.set(id, s);
|
|
71
72
|
}
|
|
72
73
|
catch (err) {
|
|
73
|
-
console.error(
|
|
74
|
-
persistenceErrorHandler?.(err,
|
|
74
|
+
console.error('[upload-store] Failed to read persisted session:', id, err);
|
|
75
|
+
persistenceErrorHandler?.(err, 'get', id);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
pruneOldUploadSessions();
|
|
@@ -88,9 +89,9 @@ export function upsertUploadSession(partial) {
|
|
|
88
89
|
// Guard against out-of-order async updates.
|
|
89
90
|
if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt)
|
|
90
91
|
return;
|
|
91
|
-
const prevStatus = prev?.status ??
|
|
92
|
+
const prevStatus = prev?.status ?? 'idle';
|
|
92
93
|
const nextStatus = partial.status ?? prevStatus;
|
|
93
|
-
const isTerminal = (s) => s ===
|
|
94
|
+
const isTerminal = (s) => s === 'success' || s === 'error' || s === 'canceled';
|
|
94
95
|
const prevIsTerminal = isTerminal(prevStatus);
|
|
95
96
|
const nextIsTerminal = isTerminal(nextStatus);
|
|
96
97
|
// Once terminal, stay terminal. If we receive a terminal nextStatus first, accept it.
|
|
@@ -103,7 +104,7 @@ export function upsertUploadSession(partial) {
|
|
|
103
104
|
const next = {
|
|
104
105
|
id: partial.id,
|
|
105
106
|
status,
|
|
106
|
-
path: partial.path ?? prev?.path ??
|
|
107
|
+
path: partial.path ?? prev?.path ?? '',
|
|
107
108
|
version,
|
|
108
109
|
transferred,
|
|
109
110
|
total,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-store.js","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { now } from \"@ttt-productions/firebase-helpers\";\nimport type { UploadSessionState, UploadSessionPersistenceAdapter } from \"../types.js\";\n\ntype Listener = (s: UploadSessionState) => void;\ntype ListListener = () => void;\n\nconst sessions = new Map<string, UploadSessionState>();\nconst listeners = new Map<string, Set<Listener>>();\nconst listListeners = new Set<ListListener>();\n\nlet persistence: UploadSessionPersistenceAdapter | null = null;\n\nexport function setUploadSessionPersistence(adapter: UploadSessionPersistenceAdapter | null) {\n persistence = adapter;\n}\n\nexport type PersistenceErrorHandler = (err: unknown, op: \"set\" | \"remove\" | \"get\", id: string) => void;\n\nlet persistenceErrorHandler: PersistenceErrorHandler | null = null;\n\nexport function setUploadSessionPersistenceErrorHandler(handler: PersistenceErrorHandler | null) {\n persistenceErrorHandler = handler;\n}\n\nasync function persistUpsert(state: UploadSessionState) {\n try {\n await persistence?.set(state.id, state);\n } catch (err) {\n console.error(\"[upload-store] Failed to persist session:\", state.id, err);\n persistenceErrorHandler?.(err, \"set\", state.id);\n }\n}\n\nasync function persistRemove(id: string) {\n try {\n await persistence?.remove(id);\n } catch (err) {\n console.error(\"[upload-store] Failed to remove persisted session:\", id, err);\n persistenceErrorHandler?.(err, \"remove\", id);\n }\n}\n\nfunction notifyList() {\n for (const fn of listListeners) fn();\n}\n\nconst MAX_SESSIONS = 100;\nconst SUCCESS_TTL_MS = 24 * 60 * 60 * 1000; // 24h\nconst ERROR_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d\n\nexport function pruneOldUploadSessions() {\n const t = now();\n const all = listUploadSessions();\n\n let pruned = 0;\n\n for (const s of all) {\n if (s.status === \"success\" && t - s.updatedAt > SUCCESS_TTL_MS) {\n removeUploadSession(s.id);\n pruned++;\n } else if ((s.status === \"error\" || s.status === \"canceled\") && t - s.updatedAt > ERROR_TTL_MS) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n const remaining = listUploadSessions();\n if (remaining.length > MAX_SESSIONS) {\n for (const s of remaining.slice(MAX_SESSIONS)) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n return pruned;\n}\n\n/** Load persisted sessions into the in-memory store. */\nexport async function rehydrateUploadSessions() {\n if (!persistence) return;\n\n const ids = await persistence.listIds();\n for (const id of ids) {\n try {\n const s = await persistence.get(id);\n if (s) sessions.set(id, s);\n } catch (err) {\n console.error(\"[upload-store] Failed to read persisted session:\", id, err);\n persistenceErrorHandler?.(err, \"get\", id);\n }\n }\n\n pruneOldUploadSessions();\n notifyList();\n}\n\nexport function getUploadSession(id: string): UploadSessionState | undefined {\n return sessions.get(id);\n}\n\nexport function listUploadSessions(): UploadSessionState[] {\n return Array.from(sessions.values()).sort((a, b) => b.updatedAt - a.updatedAt);\n}\n\nexport function upsertUploadSession(partial: Partial<UploadSessionState> & Pick<UploadSessionState, \"id\">) {\n const prev = sessions.get(partial.id);\n\n // Guard against out-of-order async updates.\n if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt) return;\n\n const prevStatus = prev?.status ?? \"idle\";\n const nextStatus = partial.status ?? prevStatus;\n\n const isTerminal = (s: string) => s === \"success\" || s === \"error\" || s === \"canceled\";\n const prevIsTerminal = isTerminal(prevStatus);\n const nextIsTerminal = isTerminal(nextStatus);\n\n // Once terminal, stay terminal. If we receive a terminal nextStatus first, accept it.\n const status = prevIsTerminal ? prevStatus : nextIsTerminal ? nextStatus : nextStatus;\n\n // Progress values should never go backwards.\n const transferred = Math.max(partial.transferred ?? 0, prev?.transferred ?? 0);\n const total = Math.max(partial.total ?? 0, prev?.total ?? 0);\n const percent = Math.max(partial.percent ?? 0, prev?.percent ?? 0);\n\n const version = (prev?.version ?? 0) + 1;\n\n const next: UploadSessionState = {\n id: partial.id,\n status,\n path: partial.path ?? prev?.path ?? \"\",\n version,\n transferred,\n total,\n percent,\n startedAt: partial.startedAt ?? prev?.startedAt ?? now(),\n updatedAt: partial.updatedAt ?? now(),\n error: partial.error ?? prev?.error,\n result: partial.result ?? prev?.result,\n };\n\n const wasMissing = !sessions.has(partial.id);\n\n sessions.set(partial.id, next);\n void persistUpsert(next);\n\n const ls = listeners.get(partial.id);\n if (ls) for (const fn of ls) fn(next);\n\n if (wasMissing) notifyList();\n}\n\nexport function removeUploadSession(id: string) {\n const existed = sessions.delete(id);\n clearUploadSessionListeners(id);\n void persistRemove(id);\n if (existed) notifyList();\n}\n\nfunction clearUploadSessionListeners(id: string) {\n if (listeners.has(id)) listeners.delete(id);\n}\n\nexport function subscribeUploadSession(id: string, fn: Listener): () => void {\n let set = listeners.get(id);\n if (!set) {\n set = new Set();\n listeners.set(id, set);\n }\n set.add(fn);\n\n const current = sessions.get(id);\n if (current) fn(current);\n\n return () => {\n const s = listeners.get(id);\n if (!s) return;\n s.delete(fn);\n if (s.size === 0) listeners.delete(id);\n };\n}\n\n/** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */\nexport function subscribeUploadSessionsList(fn: ListListener): () => void {\n listListeners.add(fn);\n return () => listListeners.delete(fn);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"upload-store.js","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { now } from '@ttt-productions/firebase-helpers';\nimport type { UploadSessionState, UploadSessionPersistenceAdapter } from '../types.js';\n\ntype Listener = (s: UploadSessionState) => void;\ntype ListListener = () => void;\n\nconst sessions = new Map<string, UploadSessionState>();\nconst listeners = new Map<string, Set<Listener>>();\nconst listListeners = new Set<ListListener>();\n\nlet persistence: UploadSessionPersistenceAdapter | null = null;\n\nexport function setUploadSessionPersistence(adapter: UploadSessionPersistenceAdapter | null) {\n persistence = adapter;\n}\n\nexport type PersistenceErrorHandler = (\n err: unknown,\n op: 'set' | 'remove' | 'get',\n id: string,\n) => void;\n\nlet persistenceErrorHandler: PersistenceErrorHandler | null = null;\n\nexport function setUploadSessionPersistenceErrorHandler(handler: PersistenceErrorHandler | null) {\n persistenceErrorHandler = handler;\n}\n\nasync function persistUpsert(state: UploadSessionState) {\n try {\n await persistence?.set(state.id, state);\n } catch (err) {\n console.error('[upload-store] Failed to persist session:', state.id, err);\n persistenceErrorHandler?.(err, 'set', state.id);\n }\n}\n\nasync function persistRemove(id: string) {\n try {\n await persistence?.remove(id);\n } catch (err) {\n console.error('[upload-store] Failed to remove persisted session:', id, err);\n persistenceErrorHandler?.(err, 'remove', id);\n }\n}\n\nfunction notifyList() {\n for (const fn of listListeners) fn();\n}\n\nconst MAX_SESSIONS = 100;\nconst SUCCESS_TTL_MS = 24 * 60 * 60 * 1000; // 24h\nconst ERROR_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d\n\nexport function pruneOldUploadSessions() {\n const t = now();\n const all = listUploadSessions();\n\n let pruned = 0;\n\n for (const s of all) {\n if (s.status === 'success' && t - s.updatedAt > SUCCESS_TTL_MS) {\n removeUploadSession(s.id);\n pruned++;\n } else if (\n (s.status === 'error' || s.status === 'canceled') &&\n t - s.updatedAt > ERROR_TTL_MS\n ) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n const remaining = listUploadSessions();\n if (remaining.length > MAX_SESSIONS) {\n for (const s of remaining.slice(MAX_SESSIONS)) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n return pruned;\n}\n\n/** Load persisted sessions into the in-memory store. */\nexport async function rehydrateUploadSessions() {\n if (!persistence) return;\n\n const ids = await persistence.listIds();\n for (const id of ids) {\n try {\n const s = await persistence.get(id);\n if (s) sessions.set(id, s);\n } catch (err) {\n console.error('[upload-store] Failed to read persisted session:', id, err);\n persistenceErrorHandler?.(err, 'get', id);\n }\n }\n\n pruneOldUploadSessions();\n notifyList();\n}\n\nexport function getUploadSession(id: string): UploadSessionState | undefined {\n return sessions.get(id);\n}\n\nexport function listUploadSessions(): UploadSessionState[] {\n return Array.from(sessions.values()).sort((a, b) => b.updatedAt - a.updatedAt);\n}\n\nexport function upsertUploadSession(\n partial: Partial<UploadSessionState> & Pick<UploadSessionState, 'id'>,\n) {\n const prev = sessions.get(partial.id);\n\n // Guard against out-of-order async updates.\n if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt) return;\n\n const prevStatus = prev?.status ?? 'idle';\n const nextStatus = partial.status ?? prevStatus;\n\n const isTerminal = (s: string) => s === 'success' || s === 'error' || s === 'canceled';\n const prevIsTerminal = isTerminal(prevStatus);\n const nextIsTerminal = isTerminal(nextStatus);\n\n // Once terminal, stay terminal. If we receive a terminal nextStatus first, accept it.\n const status = prevIsTerminal ? prevStatus : nextIsTerminal ? nextStatus : nextStatus;\n\n // Progress values should never go backwards.\n const transferred = Math.max(partial.transferred ?? 0, prev?.transferred ?? 0);\n const total = Math.max(partial.total ?? 0, prev?.total ?? 0);\n const percent = Math.max(partial.percent ?? 0, prev?.percent ?? 0);\n\n const version = (prev?.version ?? 0) + 1;\n\n const next: UploadSessionState = {\n id: partial.id,\n status,\n path: partial.path ?? prev?.path ?? '',\n version,\n transferred,\n total,\n percent,\n startedAt: partial.startedAt ?? prev?.startedAt ?? now(),\n updatedAt: partial.updatedAt ?? now(),\n error: partial.error ?? prev?.error,\n result: partial.result ?? prev?.result,\n };\n\n const wasMissing = !sessions.has(partial.id);\n\n sessions.set(partial.id, next);\n void persistUpsert(next);\n\n const ls = listeners.get(partial.id);\n if (ls) for (const fn of ls) fn(next);\n\n if (wasMissing) notifyList();\n}\n\nexport function removeUploadSession(id: string) {\n const existed = sessions.delete(id);\n clearUploadSessionListeners(id);\n void persistRemove(id);\n if (existed) notifyList();\n}\n\nfunction clearUploadSessionListeners(id: string) {\n if (listeners.has(id)) listeners.delete(id);\n}\n\nexport function subscribeUploadSession(id: string, fn: Listener): () => void {\n let set = listeners.get(id);\n if (!set) {\n set = new Set();\n listeners.set(id, set);\n }\n set.add(fn);\n\n const current = sessions.get(id);\n if (current) fn(current);\n\n return () => {\n const s = listeners.get(id);\n if (!s) return;\n s.delete(fn);\n if (s.size === 0) listeners.delete(id);\n };\n}\n\n/** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */\nexport function subscribeUploadSessionsList(fn: ListListener): () => void {\n listListeners.add(fn);\n return () => listListeners.delete(fn);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttt-productions/upload-core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ttt-productions/ttt-packages.git",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"prepublishOnly": "npm run clean && npm run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ttt-productions/firebase-helpers": "^0.12.
|
|
35
|
-
"@ttt-productions/media-schemas": "^0.14.
|
|
34
|
+
"@ttt-productions/firebase-helpers": "^0.12.13",
|
|
35
|
+
"@ttt-productions/media-schemas": "^0.14.11"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"firebase": ">=12.0.0"
|
|
@@ -42,9 +42,6 @@
|
|
|
42
42
|
"optional": true
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"typescript": "^5.8.3"
|
|
47
|
-
},
|
|
48
45
|
"author": "DJ (TTT Productions)",
|
|
49
46
|
"license": "MIT"
|
|
50
47
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"localstorage.d.ts","sourceRoot":"","sources":["../../src/persistence/localstorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAsB,MAAM,aAAa,CAAC;AAUvF,wBAAgB,0CAA0C,CAAC,IAAI,CAAC,EAAE;IAChE,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,+BAA+B,CA0ClC"}
|
|
@@ -1 +0,0 @@
|
|
|
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,iBAAiB,CAAC;IAEjD,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","sourcesContent":["import type { UploadSessionPersistenceAdapter, UploadSessionState } from \"../types.js\";\n\nfunction hasLocalStorage(): boolean {\n try {\n return typeof window !== \"undefined\" && !!window.localStorage;\n } catch {\n return false;\n }\n}\n\nexport function createLocalStorageUploadSessionPersistence(opts?: {\n /** Prefix for keys. Default: \"upload_session:\" */\n prefix?: string;\n}): UploadSessionPersistenceAdapter {\n const prefix = opts?.prefix ?? \"upload_session:\";\n\n const k = (id: string) => `${prefix}${id}`;\n\n const safeParse = (raw: string | null): UploadSessionState | null => {\n if (!raw) return null;\n try {\n return JSON.parse(raw) as UploadSessionState;\n } catch {\n return null;\n }\n };\n\n return {\n listIds: () => {\n if (!hasLocalStorage()) return [];\n const ids: string[] = [];\n for (let i = 0; i < window.localStorage.length; i += 1) {\n const key = window.localStorage.key(i);\n if (!key) continue;\n if (!key.startsWith(prefix)) continue;\n ids.push(key.slice(prefix.length));\n }\n return ids;\n },\n\n get: (id: string) => {\n if (!hasLocalStorage()) return null;\n return safeParse(window.localStorage.getItem(k(id)));\n },\n\n set: (id: string, state: UploadSessionState) => {\n if (!hasLocalStorage()) return;\n window.localStorage.setItem(k(id), JSON.stringify(state));\n },\n\n remove: (id: string) => {\n if (!hasLocalStorage()) return;\n window.localStorage.removeItem(k(id));\n },\n };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"upload-queue.js","sourceRoot":"","sources":["../../src/queue/upload-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult, UploadQueueOptions } from \"../types.js\";\nimport type { UploadController, StartUploadArgs } from \"../browser/types.js\";\nimport { getFileSize } from \"../utils/file-size.js\";\nimport { upsertUploadSession, removeUploadSession } from \"../utils/upload-store.js\";\nimport { startResumableUpload } from \"../storage/upload.js\";\n\nfunction safeId() {\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n}\nfunction noopBool() {\n return false;\n}\n\ntype Job = {\n id: string;\n args: StartUploadArgs & { priority?: number };\n priority: number; // higher = sooner\n seq: number; // FIFO within same priority\n resolveController: (c: UploadController) => void;\n rejectController: (e: unknown) => void;\n};\n\nexport class UploadQueue {\n private concurrency: number;\n private running = 0;\n private pending: Job[] = [];\n private seq = 0;\n\n constructor(opts?: UploadQueueOptions) {\n this.concurrency = Math.max(1, opts?.concurrency ?? 2);\n }\n\n setConcurrency(n: number) {\n this.concurrency = Math.max(1, n);\n this.pump();\n }\n\n getPendingCount() {\n return this.pending.length;\n }\n\n getRunningCount() {\n return this.running;\n }\n\n enqueue(args: StartUploadArgs & { priority?: number }): UploadController {\n const id = args.id ?? `upl_${safeId()}`;\n const priority = args.priority ?? 0;\n\n const startedAt = Date.now();\n upsertUploadSession({\n id,\n status: \"queued\",\n path: args.path,\n transferred: 0,\n total: getFileSize(args.file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let real: UploadController | null = null;\n\n let resolveDone!: (r: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n const controller: UploadController = {\n id,\n task: null as any,\n pause: () => (real ? real.pause() : noopBool()),\n resume: () => (real ? real.resume() : noopBool()),\n cancel: () => {\n if (real) return real.cancel();\n this.pending = this.pending.filter((j) => j.id !== id);\n removeUploadSession(id);\n rejectDone(new DOMException(\"Aborted\", \"AbortError\"));\n return true;\n },\n done,\n };\n\n const job: Job = {\n id,\n args: { ...args, id },\n priority,\n seq: this.seq++,\n resolveController: (c) => {\n real = c;\n controller.task = c.task;\n controller.pause = c.pause;\n controller.resume = c.resume;\n controller.cancel = c.cancel;\n c.done.then(resolveDone, rejectDone);\n },\n rejectController: (e) => rejectDone(e),\n };\n\n this.pending.push(job);\n this.pump();\n\n return controller;\n }\n\n private pump() {\n while (this.running < this.concurrency && this.pending.length > 0) {\n this.pending.sort((a, b) => (b.priority - a.priority) || (a.seq - b.seq));\n\n const job = this.pending.shift()!;\n this.running += 1;\n\n try {\n const { priority: _p, ...rest } = job.args;\n const c = startResumableUpload({\n ...(rest as any),\n id: job.id, // guarantee\n });\n\n job.resolveController(c);\n\n c.done.finally(() => {\n this.running -= 1;\n this.pump();\n });\n } catch (e) {\n this.running -= 1;\n\n upsertUploadSession({\n id: job.id,\n status: \"error\",\n path: job.args.path,\n error: e,\n updatedAt: Date.now(),\n });\n\n job.rejectController(e);\n this.pump();\n }\n }\n }\n}\n"]}
|
package/dist/storage/delete.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/storage/delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
|
package/dist/storage/upload.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { UploadFileResumableResult } from "../types.js";
|
|
2
|
-
import type { UploadFileResumableArgs, UploadController } from "../browser/types.js";
|
|
3
|
-
import { type FirebaseStorage, type UploadMetadata } from "firebase/storage";
|
|
4
|
-
export declare function startResumableUpload(args: {
|
|
5
|
-
id: string;
|
|
6
|
-
storage: FirebaseStorage;
|
|
7
|
-
path: string;
|
|
8
|
-
file: Blob;
|
|
9
|
-
metadata?: UploadMetadata;
|
|
10
|
-
onProgress?: UploadFileResumableArgs["onProgress"];
|
|
11
|
-
signal?: AbortSignal;
|
|
12
|
-
}): UploadController;
|
|
13
|
-
export declare function uploadFileResumable(args: Omit<Parameters<typeof startResumableUpload>[0], "id"> & {
|
|
14
|
-
id?: string;
|
|
15
|
-
/**
|
|
16
|
-
* EXPLICIT opt-in for the NEUTRAL content type (canonical-upload-content-
|
|
17
|
-
* classification, 2026-07-31): a picker file with unknown browser metadata
|
|
18
|
-
* must reach the server AS unknown (`application/octet-stream`) so the
|
|
19
|
-
* server-side byte inspector — the only classification authority — sees it
|
|
20
|
-
* unrewritten. Default false preserves the historical media-only gate for
|
|
21
|
-
* every other consumer. A concrete media MIME is still validated either
|
|
22
|
-
* way; this flag never permits arbitrary declared types, only the one
|
|
23
|
-
* neutral value.
|
|
24
|
-
*/
|
|
25
|
-
allowNeutralContentType?: boolean;
|
|
26
|
-
}): Promise<UploadFileResumableResult>;
|
|
27
|
-
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/storage/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAMrF,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;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;;;;;OASG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GACA,OAAO,CAAC,yBAAyB,CAAC,CAwBpC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/storage/upload.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult } from \"../types.js\";\nimport type { UploadFileResumableArgs, UploadController } from \"../browser/types.js\";\nimport { getFileSize } from \"../utils/file-size.js\";\nimport { upsertUploadSession } from \"../utils/upload-store.js\";\nimport { NEUTRAL_CONTENT_TYPE } from \"@ttt-productions/media-schemas\";\nimport { UploadError, isValidMediaContentType } from \"./upload-error.js\";\n\nimport {\n getDownloadURL,\n ref,\n uploadBytesResumable,\n type FirebaseStorage,\n type UploadMetadata,\n type UploadTaskSnapshot,\n} from \"firebase/storage\";\n\nfunction isAbortError(e: unknown) {\n return e instanceof DOMException && e.name === \"AbortError\";\n}\n\nexport function startResumableUpload(args: {\n id: string;\n storage: FirebaseStorage;\n path: string;\n file: Blob;\n metadata?: UploadMetadata;\n onProgress?: UploadFileResumableArgs[\"onProgress\"];\n signal?: AbortSignal;\n}): UploadController {\n const { id, storage, path, file, metadata, onProgress, signal } = args;\n\n const r = ref(storage, path);\n const task = uploadBytesResumable(r, file, metadata);\n\n const startedAt = Date.now();\n\n upsertUploadSession({\n id,\n status: \"uploading\",\n path,\n transferred: 0,\n total: getFileSize(file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let unsub: (() => void) | null = null;\n\n let resolveDone!: (v: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n const onAbort = () => {\n try {\n task.cancel();\n } catch {}\n };\n\n if (signal) {\n if (signal.aborted) onAbort();\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n unsub = task.on(\n \"state_changed\",\n (snap: UploadTaskSnapshot) => {\n const transferred = snap.bytesTransferred ?? 0;\n const total = snap.totalBytes ?? getFileSize(file);\n const percent = total > 0 ? (transferred / total) * 100 : 0;\n\n upsertUploadSession({\n id,\n status:\n snap.state === \"paused\"\n ? \"paused\"\n : snap.state === \"running\"\n ? \"uploading\"\n : \"uploading\",\n path,\n transferred,\n total,\n percent,\n updatedAt: Date.now(),\n });\n\n onProgress?.({ transferred, total, percent, snapshot: snap });\n },\n async (err) => {\n if (signal) signal.removeEventListener(\"abort\", onAbort);\n if (unsub) unsub();\n\n // normalize aborts as \"canceled\" (not \"error\")\n if (isAbortError(err)) {\n upsertUploadSession({\n id,\n status: \"canceled\",\n path,\n updatedAt: Date.now(),\n error: err,\n });\n return rejectDone(err);\n }\n\n upsertUploadSession({\n id,\n status: \"error\",\n path,\n updatedAt: Date.now(),\n error: err,\n });\n\n rejectDone(err);\n },\n async () => {\n try {\n const downloadURL = await getDownloadURL(task.snapshot.ref);\n const contentType = task.snapshot.metadata?.contentType ?? null;\n const size = task.snapshot.totalBytes ?? getFileSize(file);\n\n const result: UploadFileResumableResult = {\n downloadURL,\n fullPath: task.snapshot.ref.fullPath,\n contentType,\n size,\n };\n\n upsertUploadSession({\n id,\n status: \"success\",\n path,\n transferred: size,\n total: size,\n percent: 100,\n updatedAt: Date.now(),\n result,\n });\n\n resolveDone(result);\n } catch (e) {\n upsertUploadSession({\n id,\n status: \"error\",\n path,\n updatedAt: Date.now(),\n error: e,\n });\n rejectDone(e);\n } finally {\n if (signal) signal.removeEventListener(\"abort\", onAbort);\n if (unsub) unsub();\n }\n }\n );\n\n return {\n id,\n task,\n pause: () => {\n try {\n return task.pause();\n } catch {\n return false;\n }\n },\n resume: () => {\n try {\n return task.resume();\n } catch {\n return false;\n }\n },\n cancel: () => {\n try {\n return task.cancel();\n } catch {\n return false;\n }\n },\n done,\n };\n}\n\nexport async function uploadFileResumable(\n args: Omit<Parameters<typeof startResumableUpload>[0], \"id\"> & {\n id?: string;\n /**\n * EXPLICIT opt-in for the NEUTRAL content type (canonical-upload-content-\n * classification, 2026-07-31): a picker file with unknown browser metadata\n * must reach the server AS unknown (`application/octet-stream`) so the\n * server-side byte inspector — the only classification authority — sees it\n * unrewritten. Default false preserves the historical media-only gate for\n * every other consumer. A concrete media MIME is still validated either\n * way; this flag never permits arbitrary declared types, only the one\n * neutral value.\n */\n allowNeutralContentType?: boolean;\n }\n): Promise<UploadFileResumableResult> {\n // Defense-in-depth: reject missing or invalid contentType before any bytes are sent.\n // file-input should already guarantee a valid or explicitly-neutral MIME, but this\n // is the last gate before Firebase Storage.\n const ct = args.metadata?.contentType;\n if (!ct) {\n throw new UploadError(\n 'missing_content_type',\n 'Upload rejected: contentType metadata is required. Ensure file.type is set before uploading.'\n );\n }\n const neutralAllowed =\n args.allowNeutralContentType === true && ct.toLowerCase().trim() === NEUTRAL_CONTENT_TYPE;\n if (!neutralAllowed && !isValidMediaContentType(ct)) {\n throw new UploadError(\n 'invalid_content_type',\n `Upload rejected: contentType \"${ct}\" is not a supported media MIME (expected image/*, video/*, or audio/*).`\n );\n }\n\n const { allowNeutralContentType: _allowNeutral, ...rest } = args;\n const id = args.id ?? `upl_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n const c = startResumableUpload({ ...rest, id });\n return c.done;\n}\n"]}
|