@maas/payload-plugin-media-cloud 0.0.30 → 0.0.32
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/adapter/handleDelete.d.mts +3 -3
- package/dist/adapter/handleDelete.mjs +6 -6
- package/dist/adapter/handleDelete.mjs.map +1 -1
- package/dist/adapter/staticHandler.d.mts +5 -3
- package/dist/adapter/staticHandler.mjs +8 -7
- package/dist/adapter/staticHandler.mjs.map +1 -1
- package/dist/adapter/storageAdapter.d.mts +3 -3
- package/dist/adapter/storageAdapter.mjs +4 -4
- package/dist/adapter/storageAdapter.mjs.map +1 -1
- package/dist/collectionHooks/afterChange.d.mts +7 -0
- package/dist/collectionHooks/afterChange.mjs +39 -0
- package/dist/collectionHooks/afterChange.mjs.map +1 -0
- package/dist/collectionHooks/beforeChange.d.mts +7 -0
- package/dist/collectionHooks/beforeChange.mjs +33 -0
- package/dist/collectionHooks/beforeChange.mjs.map +1 -0
- package/dist/collections/mediaCollection.d.mts +3 -2
- package/dist/collections/mediaCollection.mjs +46 -198
- package/dist/collections/mediaCollection.mjs.map +1 -1
- package/dist/components/folderFileCard/folderFileCard.d.mts +13 -0
- package/dist/components/folderFileCard/folderFileCard.mjs +30 -0
- package/dist/components/folderFileCard/folderFileCard.mjs.map +1 -0
- package/dist/components/gridContext/gridContext.d.mts +51 -0
- package/dist/components/gridContext/gridContext.mjs +227 -0
- package/dist/components/gridContext/gridContext.mjs.map +1 -0
- package/dist/components/gridView/gridView.css +50 -0
- package/dist/components/gridView/gridView.d.mts +16 -0
- package/dist/components/gridView/gridView.mjs +124 -0
- package/dist/components/gridView/gridView.mjs.map +1 -0
- package/dist/components/gridView/index.d.mts +2 -0
- package/dist/components/gridView/index.mjs +3 -0
- package/dist/components/index.d.mts +9 -7
- package/dist/components/index.mjs +5 -4
- package/dist/components/itemCardGrid/itemCardGrid.css +12 -0
- package/dist/components/itemCardGrid/itemCardGrid.d.mts +18 -0
- package/dist/components/itemCardGrid/itemCardGrid.mjs +22 -0
- package/dist/components/itemCardGrid/itemCardGrid.mjs.map +1 -0
- package/dist/components/muxPreview/index.d.mts +2 -0
- package/dist/components/muxPreview/index.mjs +3 -0
- package/dist/components/{mux-preview/mux-preview.d.mts → muxPreview/muxPreview.d.mts} +2 -2
- package/dist/components/{mux-preview/mux-preview.mjs → muxPreview/muxPreview.mjs} +2 -2
- package/dist/components/muxPreview/muxPreview.mjs.map +1 -0
- package/dist/components/uploadHandler/index.d.mts +2 -0
- package/dist/components/uploadHandler/index.mjs +3 -0
- package/dist/components/uploadHandler/uploadHandler.d.mts +20 -0
- package/dist/components/{upload-handler/upload-handler.mjs → uploadHandler/uploadHandler.mjs} +82 -52
- package/dist/components/uploadHandler/uploadHandler.mjs.map +1 -0
- package/dist/components/uploadManager/index.d.mts +2 -0
- package/dist/components/uploadManager/index.mjs +3 -0
- package/dist/components/{upload-manager/upload-manager.d.mts → uploadManager/uploadManager.d.mts} +3 -3
- package/dist/components/{upload-manager/upload-manager.mjs → uploadManager/uploadManager.mjs} +3 -3
- package/dist/components/uploadManager/uploadManager.mjs.map +1 -0
- package/dist/endpoints/fileExistsHandler.d.mts +12 -0
- package/dist/endpoints/{tusFileExistsHandler.mjs → fileExistsHandler.mjs} +7 -7
- package/dist/endpoints/fileExistsHandler.mjs.map +1 -0
- package/dist/endpoints/muxAssetHandler.d.mts +1 -0
- package/dist/endpoints/muxAssetHandler.mjs +3 -3
- package/dist/endpoints/muxAssetHandler.mjs.map +1 -1
- package/dist/endpoints/muxWebhookHandler.d.mts +1 -0
- package/dist/endpoints/muxWebhookHandler.mjs +6 -5
- package/dist/endpoints/muxWebhookHandler.mjs.map +1 -1
- package/dist/endpoints/tusCleanupHandler.d.mts +3 -2
- package/dist/endpoints/tusCleanupHandler.mjs +4 -4
- package/dist/endpoints/tusCleanupHandler.mjs.map +1 -1
- package/dist/endpoints/tusFolderHandler.d.mts +12 -0
- package/dist/endpoints/tusFolderHandler.mjs +39 -0
- package/dist/endpoints/tusFolderHandler.mjs.map +1 -0
- package/dist/endpoints/tusPostProcessorHandler.d.mts +3 -2
- package/dist/endpoints/tusPostProcessorHandler.mjs +6 -5
- package/dist/endpoints/tusPostProcessorHandler.mjs.map +1 -1
- package/dist/fields/alt.d.mts +7 -0
- package/dist/fields/alt.mjs +10 -0
- package/dist/fields/alt.mjs.map +1 -0
- package/dist/fields/filename.d.mts +7 -0
- package/dist/fields/filename.mjs +14 -0
- package/dist/fields/filename.mjs.map +1 -0
- package/dist/fields/height.d.mts +7 -0
- package/dist/fields/height.mjs +15 -0
- package/dist/fields/height.mjs.map +1 -0
- package/dist/fields/mux.d.mts +7 -0
- package/dist/fields/mux.mjs +149 -0
- package/dist/fields/mux.mjs.map +1 -0
- package/dist/fields/path.d.mts +7 -0
- package/dist/fields/path.mjs +14 -0
- package/dist/fields/path.mjs.map +1 -0
- package/dist/fields/storage.d.mts +7 -0
- package/dist/fields/storage.mjs +18 -0
- package/dist/fields/storage.mjs.map +1 -0
- package/dist/fields/thumbnail.d.mts +7 -0
- package/dist/fields/thumbnail.mjs +17 -0
- package/dist/fields/thumbnail.mjs.map +1 -0
- package/dist/fields/width.d.mts +7 -0
- package/dist/fields/width.mjs +15 -0
- package/dist/fields/width.mjs.map +1 -0
- package/dist/hooks/useErrorHandler.d.mts +1 -1
- package/dist/hooks/useErrorHandler.mjs +4 -2
- package/dist/hooks/useErrorHandler.mjs.map +1 -1
- package/dist/plugin.d.mts +5 -4
- package/dist/plugin.mjs +53 -29
- package/dist/plugin.mjs.map +1 -1
- package/dist/tus/stores/s3/{expiration-manager.d.mts → expirationManager.d.mts} +4 -3
- package/dist/tus/stores/s3/{expiration-manager.mjs → expirationManager.mjs} +6 -3
- package/dist/tus/stores/s3/expirationManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{file-operations.d.mts → fileOperations.d.mts} +2 -2
- package/dist/tus/stores/s3/{file-operations.mjs → fileOperations.mjs} +2 -2
- package/dist/tus/stores/s3/fileOperations.mjs.map +1 -0
- package/dist/tus/stores/s3/index.d.mts +1 -1
- package/dist/tus/stores/s3/index.mjs +20 -9
- package/dist/tus/stores/s3/index.mjs.map +1 -1
- package/dist/tus/stores/s3/{metadata-manager.d.mts → metadataManager.d.mts} +4 -2
- package/dist/tus/stores/s3/{metadata-manager.mjs → metadataManager.mjs} +6 -5
- package/dist/tus/stores/s3/metadataManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{parts-manager.d.mts → partsManager.d.mts} +4 -4
- package/dist/tus/stores/s3/{parts-manager.mjs → partsManager.mjs} +67 -29
- package/dist/tus/stores/s3/partsManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{s3-store.d.mts → s3Store.d.mts} +38 -32
- package/dist/tus/stores/s3/{s3-store.mjs → s3Store.mjs} +102 -57
- package/dist/tus/stores/s3/s3Store.mjs.map +1 -0
- package/dist/types/errors.d.mts +32 -0
- package/dist/types/errors.mjs +32 -0
- package/dist/types/errors.mjs.map +1 -1
- package/dist/types/index.d.mts +42 -4
- package/dist/utils/buildS3Path.d.mts +10 -0
- package/dist/utils/buildS3Path.mjs +16 -0
- package/dist/utils/buildS3Path.mjs.map +1 -0
- package/dist/utils/buildThumbnailURL.d.mts +14 -0
- package/dist/utils/buildThumbnailURL.mjs +10 -0
- package/dist/utils/buildThumbnailURL.mjs.map +1 -0
- package/dist/utils/defaultOptions.d.mts +7 -0
- package/dist/utils/defaultOptions.mjs +12 -0
- package/dist/utils/defaultOptions.mjs.map +1 -0
- package/dist/utils/file.d.mts +16 -2
- package/dist/utils/file.mjs +58 -6
- package/dist/utils/file.mjs.map +1 -1
- package/dist/utils/mux.mjs +19 -8
- package/dist/utils/mux.mjs.map +1 -1
- package/dist/utils/tus.d.mts +9 -6
- package/dist/utils/tus.mjs +31 -11
- package/dist/utils/tus.mjs.map +1 -1
- package/package.json +9 -4
- package/dist/components/mux-preview/index.d.mts +0 -2
- package/dist/components/mux-preview/index.mjs +0 -3
- package/dist/components/mux-preview/mux-preview.mjs.map +0 -1
- package/dist/components/upload-handler/index.d.mts +0 -2
- package/dist/components/upload-handler/index.mjs +0 -3
- package/dist/components/upload-handler/upload-handler.d.mts +0 -22
- package/dist/components/upload-handler/upload-handler.mjs.map +0 -1
- package/dist/components/upload-manager/index.d.mts +0 -2
- package/dist/components/upload-manager/index.mjs +0 -3
- package/dist/components/upload-manager/upload-manager.mjs.map +0 -1
- package/dist/endpoints/tusFileExistsHandler.d.mts +0 -11
- package/dist/endpoints/tusFileExistsHandler.mjs.map +0 -1
- package/dist/tus/stores/s3/expiration-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/file-operations.mjs.map +0 -1
- package/dist/tus/stores/s3/metadata-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/parts-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/s3-store.mjs.map +0 -1
- /package/dist/components/{upload-manager/upload-manager.css → uploadManager/uploadManager.css} +0 -0
package/dist/utils/tus.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
|
-
import { S3Store } from "../tus/stores/s3/
|
|
2
|
+
import { S3Store } from "../tus/stores/s3/s3Store.mjs";
|
|
3
3
|
import { Server } from "@tus/server";
|
|
4
4
|
import * as payload2 from "payload";
|
|
5
5
|
import { PayloadRequest } from "payload";
|
|
@@ -8,22 +8,25 @@ import { PayloadRequest } from "payload";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Creates a TUS server instance with S3 storage
|
|
11
|
-
* @param args -
|
|
11
|
+
* @param args.getS3Store - Function that returns an S3 client instance
|
|
12
|
+
* @param args.pluginOptions - Media cloud plugin options
|
|
12
13
|
* @returns A configured TusServer instance
|
|
13
14
|
*/
|
|
14
15
|
interface CreateTusServerArgs {
|
|
15
|
-
|
|
16
|
+
getS3Store: () => S3Store;
|
|
16
17
|
pluginOptions: MediaCloudPluginOptions;
|
|
17
18
|
}
|
|
18
19
|
declare function createTusServer(args: CreateTusServerArgs): Server;
|
|
19
20
|
/**
|
|
20
21
|
* Creates TUS upload endpoints for file handling
|
|
21
|
-
* @param
|
|
22
|
+
* @param args.getTusServer - Function that returns a TUS server instance
|
|
23
|
+
* @param args.getS3Store - Function that returns an S3 client instance
|
|
22
24
|
* @returns An array of endpoint configurations
|
|
23
25
|
*/
|
|
24
26
|
interface CreateTusEndpointsArgs {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
getTusServer: () => Server;
|
|
28
|
+
getS3Store: () => S3Store;
|
|
29
|
+
pluginOptions: MediaCloudPluginOptions;
|
|
27
30
|
}
|
|
28
31
|
declare function createTusEndpoints(args: CreateTusEndpointsArgs): ({
|
|
29
32
|
handler: (req: PayloadRequest) => Promise<Response>;
|
package/dist/utils/tus.mjs
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
|
+
import { MediaCloudErrors } from "../types/errors.mjs";
|
|
2
|
+
import { useMagicError } from "../error-handler/dist/index.mjs";
|
|
1
3
|
import { generateUniqueFilename } from "./file.mjs";
|
|
2
4
|
import { getTusPostProcessorHandler } from "../endpoints/tusPostProcessorHandler.mjs";
|
|
3
|
-
import {
|
|
5
|
+
import { getTusFolderHandler } from "../endpoints/tusFolderHandler.mjs";
|
|
4
6
|
import { getTusCleanupHandler } from "../endpoints/tusCleanupHandler.mjs";
|
|
5
7
|
import { Server } from "@tus/server";
|
|
6
8
|
|
|
7
9
|
//#region src/utils/tus.ts
|
|
10
|
+
const magicError = useMagicError({ prefix: "PLUGIN-MEDIA-CLOUD" });
|
|
8
11
|
function createTusServer(args) {
|
|
9
|
-
const {
|
|
12
|
+
const { getS3Store, pluginOptions } = args;
|
|
13
|
+
const s3Store = getS3Store();
|
|
10
14
|
return new Server({
|
|
11
15
|
datastore: s3Store,
|
|
16
|
+
path: "/api/uploads",
|
|
17
|
+
respectForwardedHeaders: pluginOptions.s3?.respectForwardedHeaders ?? true,
|
|
12
18
|
namingFunction: async (_req, metadata) => {
|
|
13
19
|
return metadata?.filename ?? generateUniqueFilename("");
|
|
14
20
|
},
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
onUploadFinish: async (_req, upload) => {
|
|
22
|
+
const filename = upload.metadata?.filename ?? "";
|
|
23
|
+
await s3Store.cleanup(filename);
|
|
24
|
+
return Promise.resolve({});
|
|
25
|
+
}
|
|
17
26
|
});
|
|
18
27
|
}
|
|
19
28
|
function createTusEndpoints(args) {
|
|
20
|
-
const {
|
|
29
|
+
const { getTusServer, getS3Store, pluginOptions } = args;
|
|
30
|
+
const collection = pluginOptions.collection;
|
|
31
|
+
magicError.assert(collection, MediaCloudErrors.COLLECTION_REQUIRED);
|
|
21
32
|
/**
|
|
22
33
|
* Handles TUS requests through the server
|
|
23
34
|
* @param req - The payload request object
|
|
24
35
|
* @returns The server response
|
|
25
36
|
*/
|
|
26
37
|
function tusHandler(req) {
|
|
27
|
-
return
|
|
38
|
+
return getTusServer().handleWeb(req);
|
|
28
39
|
}
|
|
29
40
|
return [
|
|
30
41
|
{
|
|
@@ -58,17 +69,26 @@ function createTusEndpoints(args) {
|
|
|
58
69
|
path: "/uploads/:id"
|
|
59
70
|
},
|
|
60
71
|
{
|
|
61
|
-
handler:
|
|
72
|
+
handler: getTusPostProcessorHandler({
|
|
73
|
+
getS3Store,
|
|
74
|
+
collection
|
|
75
|
+
}),
|
|
62
76
|
method: "get",
|
|
63
|
-
path: "/uploads/:filename/
|
|
77
|
+
path: "/uploads/:filename/process"
|
|
64
78
|
},
|
|
65
79
|
{
|
|
66
|
-
handler:
|
|
80
|
+
handler: getTusFolderHandler({
|
|
81
|
+
getS3Store,
|
|
82
|
+
collection
|
|
83
|
+
}),
|
|
67
84
|
method: "get",
|
|
68
|
-
path: "/uploads/:filename/
|
|
85
|
+
path: "/uploads/:filename/folder"
|
|
69
86
|
},
|
|
70
87
|
{
|
|
71
|
-
handler: getTusCleanupHandler({
|
|
88
|
+
handler: getTusCleanupHandler({
|
|
89
|
+
getS3Store,
|
|
90
|
+
collection
|
|
91
|
+
}),
|
|
72
92
|
method: "post",
|
|
73
93
|
path: "/uploads/cleanup"
|
|
74
94
|
}
|
package/dist/utils/tus.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tus.mjs","names":["TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import { Server as TusServer } from '@tus/server'\n\nimport { getTusPostProcessorHandler } from '../endpoints/tusPostProcessorHandler'\nimport {
|
|
1
|
+
{"version":3,"file":"tus.mjs","names":["magicError: UseMagicErrorReturn","TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import { Server as TusServer } from '@tus/server'\n\nimport { getTusPostProcessorHandler } from '../endpoints/tusPostProcessorHandler'\nimport { getTusFolderHandler } from '../endpoints/tusFolderHandler'\nimport { getTusCleanupHandler } from '../endpoints/tusCleanupHandler'\n\nimport { generateUniqueFilename } from './file'\nimport { useMagicError, type UseMagicErrorReturn } from '@maas/error-handler'\n\nimport type { S3Store } from '../tus/stores/s3/s3Store'\nimport type { PayloadRequest } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types'\nimport { MediaCloudErrors } from '../types/errors'\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\n/**\n * Creates a TUS server instance with S3 storage\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.pluginOptions - Media cloud plugin options\n * @returns A configured TusServer instance\n */\ninterface CreateTusServerArgs {\n getS3Store: () => S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusServer(args: CreateTusServerArgs): TusServer {\n const { getS3Store, pluginOptions } = args\n\n const s3Store = getS3Store()\n\n return new TusServer({\n datastore: s3Store,\n path: '/api/uploads',\n respectForwardedHeaders: pluginOptions.s3?.respectForwardedHeaders ?? true,\n namingFunction: async (_req, metadata) => {\n return metadata?.filename ?? generateUniqueFilename('')\n },\n onUploadFinish: async (_req, upload) => {\n // Clean up .part and .info files\n const filename = upload.metadata?.filename ?? ''\n await s3Store.cleanup(filename)\n\n // Prevent type error\n return Promise.resolve({})\n },\n })\n}\n\n/**\n * Creates TUS upload endpoints for file handling\n * @param args.getTusServer - Function that returns a TUS server instance\n * @param args.getS3Store - Function that returns an S3 client instance\n * @returns An array of endpoint configurations\n */\ninterface CreateTusEndpointsArgs {\n getTusServer: () => TusServer\n getS3Store: () => S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusEndpoints(args: CreateTusEndpointsArgs) {\n const { getTusServer, getS3Store, pluginOptions } = args\n const collection = pluginOptions.collection\n\n magicError.assert(collection, MediaCloudErrors.COLLECTION_REQUIRED)\n\n /**\n * Handles TUS requests through the server\n * @param req - The payload request object\n * @returns The server response\n */\n function tusHandler(req: PayloadRequest) {\n return getTusServer().handleWeb(req as Request)\n }\n\n return [\n { handler: tusHandler, method: 'options' as const, path: '/uploads' },\n { handler: tusHandler, method: 'post' as const, path: '/uploads' },\n { handler: tusHandler, method: 'get' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'put' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'patch' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'delete' as const, path: '/uploads/:id' },\n {\n handler: getTusPostProcessorHandler({ getS3Store, collection }),\n method: 'get' as const,\n path: '/uploads/:filename/process',\n },\n {\n handler: getTusFolderHandler({ getS3Store, collection }),\n method: 'get' as const,\n path: '/uploads/:filename/folder',\n },\n {\n handler: getTusCleanupHandler({ getS3Store, collection }),\n method: 'post' as const,\n path: '/uploads/cleanup',\n },\n ]\n}\n"],"mappings":";;;;;;;;;AAcA,MAAMA,aAAkC,cAAc,EACpD,QAAQ,sBACT,CAAC;AAaF,SAAgB,gBAAgB,MAAsC;CACpE,MAAM,EAAE,YAAY,kBAAkB;CAEtC,MAAM,UAAU,YAAY;AAE5B,QAAO,IAAIC,OAAU;EACnB,WAAW;EACX,MAAM;EACN,yBAAyB,cAAc,IAAI,2BAA2B;EACtE,gBAAgB,OAAO,MAAM,aAAa;AACxC,UAAO,UAAU,YAAY,uBAAuB,GAAG;;EAEzD,gBAAgB,OAAO,MAAM,WAAW;GAEtC,MAAM,WAAW,OAAO,UAAU,YAAY;AAC9C,SAAM,QAAQ,QAAQ,SAAS;AAG/B,UAAO,QAAQ,QAAQ,EAAE,CAAC;;EAE7B,CAAC;;AAeJ,SAAgB,mBAAmB,MAA8B;CAC/D,MAAM,EAAE,cAAc,YAAY,kBAAkB;CACpD,MAAM,aAAa,cAAc;AAEjC,YAAW,OAAO,YAAY,iBAAiB,oBAAoB;;;;;;CAOnE,SAAS,WAAW,KAAqB;AACvC,SAAO,cAAc,CAAC,UAAU,IAAe;;AAGjD,QAAO;EACL;GAAE,SAAS;GAAY,QAAQ;GAAoB,MAAM;GAAY;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAiB,MAAM;GAAY;EAClE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAkB,MAAM;GAAgB;EACvE;GAAE,SAAS;GAAY,QAAQ;GAAmB,MAAM;GAAgB;EACxE;GACE,SAAS,2BAA2B;IAAE;IAAY;IAAY,CAAC;GAC/D,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,oBAAoB;IAAE;IAAY;IAAY,CAAC;GACxD,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,qBAAqB;IAAE;IAAY;IAAY,CAAC;GACzD,QAAQ;GACR,MAAM;GACP;EACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maas/payload-plugin-media-cloud",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"contributors": [
|
|
6
6
|
{
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@mux/upchunk": "^3.5.0",
|
|
41
41
|
"@tus/server": "^2.3.0",
|
|
42
42
|
"@tus/utils": "^0.6.0",
|
|
43
|
+
"defu": "^6.1.4",
|
|
43
44
|
"file-type": "^21.1.1",
|
|
44
45
|
"image-size": "^2.0.2",
|
|
45
46
|
"mitt": "^3.0.1",
|
|
@@ -50,7 +51,9 @@
|
|
|
50
51
|
"peerDependencies": {
|
|
51
52
|
"@mux/mux-player-react": "^3",
|
|
52
53
|
"@payloadcms/plugin-cloud-storage": "^3.61",
|
|
54
|
+
"@payloadcms/translations": "^3.61",
|
|
53
55
|
"@payloadcms/ui": "^3.61",
|
|
56
|
+
"next": "^15.4.10",
|
|
54
57
|
"payload": "^3.61",
|
|
55
58
|
"react": "^19.1",
|
|
56
59
|
"react-dom": "^19.1"
|
|
@@ -65,14 +68,16 @@
|
|
|
65
68
|
},
|
|
66
69
|
"devDependencies": {
|
|
67
70
|
"@mux/mux-player-react": "^3.9.2",
|
|
68
|
-
"@payloadcms/plugin-cloud-storage": "3.
|
|
69
|
-
"@payloadcms/
|
|
71
|
+
"@payloadcms/plugin-cloud-storage": "3.75.0",
|
|
72
|
+
"@payloadcms/translations": "3.75.0",
|
|
73
|
+
"@payloadcms/ui": "3.75.0",
|
|
70
74
|
"@types/multistream": "^4.1.3",
|
|
71
75
|
"@types/node": "^22.19.1",
|
|
72
76
|
"@types/react": "19.2.1",
|
|
73
77
|
"@types/react-dom": "19.2.1",
|
|
74
78
|
"glob": "^13.0.0",
|
|
75
|
-
"
|
|
79
|
+
"next": "15.4.8",
|
|
80
|
+
"payload": "3.75.0",
|
|
76
81
|
"react": "19.2.1",
|
|
77
82
|
"react-dom": "19.2.1",
|
|
78
83
|
"tsdown": "^0.16.8",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mux-preview.mjs","names":[],"sources":["../../../src/components/mux-preview/mux-preview.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useState } from 'react'\nimport MuxPlayer from '@mux/mux-player-react'\n\nimport type React from 'react'\nimport type { UIFieldClientProps } from 'payload'\n\n/**\n * React component for previewing Mux video assets\n * @param props - The UI field client props containing field data\n * @returns JSX element for Mux video preview\n */\nexport function MuxPreview(props: UIFieldClientProps) {\n const [playbackId, setPlaybackId] = useState<string | null>(null)\n const [isClient, setIsClient] = useState(false)\n\n // Ensure we’re on the client side\n useEffect(() => {\n setIsClient(true)\n }, [])\n\n useEffect(() => {\n if (!isClient) {\n return\n }\n\n // Get collection from schemaPath and ID from URL\n const collection = props.schemaPath?.split('.')[0] // \"media.muxPlayer\" -> \"media\"\n const docId = window.location.pathname.split('/').pop() // Get last part of URL\n\n if (collection && docId && docId !== 'create' && docId !== 'admin') {\n fetch(`/api/${collection}/${docId}`)\n .then((res) => res.json())\n .then((data) => setPlaybackId(data.mux?.playbackId || null))\n .catch(() => setPlaybackId(null))\n }\n }, [props.schemaPath, isClient])\n\n // Only render on client with playbackId\n return isClient && playbackId ? (\n <MuxPlayer\n playbackId={playbackId}\n streamType=\"on-demand\"\n disableTracking={true}\n style={{ height: '60vh' }}\n nohotkeys={true}\n preload=\"metadata\"\n />\n ) : null\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,WAAW,OAA2B;CACpD,MAAM,CAAC,YAAY,iBAAiB,SAAwB,KAAK;CACjE,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;AAG/C,iBAAgB;AACd,cAAY,KAAK;IAChB,EAAE,CAAC;AAEN,iBAAgB;AACd,MAAI,CAAC,SACH;EAIF,MAAM,aAAa,MAAM,YAAY,MAAM,IAAI,CAAC;EAChD,MAAM,QAAQ,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,KAAK;AAEvD,MAAI,cAAc,SAAS,UAAU,YAAY,UAAU,QACzD,OAAM,QAAQ,WAAW,GAAG,QAAQ,CACjC,MAAM,QAAQ,IAAI,MAAM,CAAC,CACzB,MAAM,SAAS,cAAc,KAAK,KAAK,cAAc,KAAK,CAAC,CAC3D,YAAY,cAAc,KAAK,CAAC;IAEpC,CAAC,MAAM,YAAY,SAAS,CAAC;AAGhC,QAAO,YAAY,aACjB,CAAC,UACC,YAAY,YACZ,uBACA,iBAAiB,MACjB,OAAO,EAAE,QAAQ,QAAQ,EACzB,WAAW,MACX,wBAEA"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import * as react0 from "react";
|
|
2
|
-
import * as payload0 from "payload";
|
|
3
|
-
|
|
4
|
-
//#region src/components/upload-handler/upload-handler.d.ts
|
|
5
|
-
declare const UploadHandler: ({
|
|
6
|
-
children,
|
|
7
|
-
collectionSlug,
|
|
8
|
-
enabled,
|
|
9
|
-
extra,
|
|
10
|
-
prefix,
|
|
11
|
-
serverHandlerPath
|
|
12
|
-
}: {
|
|
13
|
-
children: react0.ReactNode;
|
|
14
|
-
collectionSlug: payload0.UploadCollectionSlug;
|
|
15
|
-
enabled?: boolean;
|
|
16
|
-
extra: Record<string, unknown>;
|
|
17
|
-
prefix?: string;
|
|
18
|
-
serverHandlerPath: string;
|
|
19
|
-
}) => react0.JSX.Element;
|
|
20
|
-
//#endregion
|
|
21
|
-
export { UploadHandler };
|
|
22
|
-
//# sourceMappingURL=upload-handler.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"upload-handler.mjs","names":["uploadArgs: UploadArgs"],"sources":["../../../src/components/upload-handler/upload-handler.tsx"],"sourcesContent":["'use client'\n\nimport * as upchunk from '@mux/upchunk'\nimport * as tus from 'tus-js-client'\n\nimport { toast } from '@payloadcms/ui'\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\n\nimport { MediaCloudErrors, MediaCloudLogs } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { isVideo, getFileType, generateUniqueFilename } from '../../utils/file'\n\ninterface UploadArgs {\n serverURL: string\n apiRoute: string\n file: File\n mimeType: string\n}\n\ninterface UploadResult {\n filename: string\n uploadId?: string\n mimeType: string\n storage: 'mux' | 's3'\n}\n\ninterface MuxCreateUploadResponse {\n url: string\n uploadId: string\n filename: string\n}\n\nconst { logError, throwError } = useErrorHandler()\nconst emitter = useMediaCloudEmitter()\n\nconst MUX_CHUNK_SIZE = 30720\nconst TUS_CHUNK_SIZE = 1024 * 1024\nconst TUS_RETRY_DELAYS = [0, 1000, 2000, 5000]\n\n/**\n * Handles Mux video upload with progress tracking\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function muxUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { file, serverURL, apiRoute, mimeType } = args\n\n const filename = file.name\n try {\n // Request upload URL from Mux\n const response = await fetch(`${serverURL}${apiRoute}/mux/upload`, {\n body: JSON.stringify({ filename, mimeType }),\n credentials: 'include',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n\n const { url, uploadId } = (await response.json()) as MuxCreateUploadResponse\n\n // Create upchunk uploader\n const uploader = await upchunk.createUpload({\n endpoint: url,\n file,\n chunkSize: MUX_CHUNK_SIZE,\n })\n\n // Add upload to tracker\n emitter.emit('addUpload', {\n filename,\n uploadId,\n polling: false,\n pollingUrl: `${serverURL}${apiRoute}/mux/asset`,\n })\n\n // Set up event handlers\n uploader.on('error', function () {\n logError(MediaCloudErrors.MUX_UPLOAD_ERROR.message)\n toast.error('Video upload failed')\n emitter.emit('removeUpload', { uploadId })\n })\n\n uploader.on('progress', function (progress) {\n emitter.emit('updateUpload', {\n filename,\n progress: progress.detail,\n })\n })\n\n uploader.on('success', function () {\n emitter.emit('uploadComplete', { filename })\n })\n\n // Update collection entry\n // with filename, uploadId, mimeType, and storage\n return {\n filename,\n uploadId,\n mimeType,\n storage: 'mux',\n }\n } catch (_error) {\n logError(MediaCloudErrors.MUX_DIRECT_UPLOAD_ERROR.message)\n toast.error('Video upload failed')\n return null\n }\n}\n\n/**\n * Handles TUS file upload with resumable capabilities\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function tusUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { apiRoute, serverURL, file, mimeType } = args\n\n const filename = file.name\n const filetype = file.type\n const filesize = file.size.toString()\n\n // Clear any stale TUS uploads from localStorage\n Object.keys(localStorage)\n .filter((key) => key.startsWith('tus::'))\n .forEach((key) => localStorage.removeItem(key))\n\n // Flag to prevent multiple onUploadUrlAvailable calls\n let uploadUrlAvailable = false\n let eventListenerAdded = false\n\n function onBeforeUnload(e: BeforeUnloadEvent) {\n window.removeEventListener('beforeunload', onBeforeUnload)\n e.preventDefault()\n }\n\n function onPageUnload() {\n window.removeEventListener('unload', onPageUnload)\n navigator.sendBeacon(\n `${serverURL}${apiRoute}/uploads/cleanup`,\n JSON.stringify({ filename })\n )\n }\n\n return new Promise((resolve) => {\n const upload = new tus.Upload(file, {\n endpoint: `${serverURL}${apiRoute}/uploads`,\n retryDelays: TUS_RETRY_DELAYS,\n chunkSize: TUS_CHUNK_SIZE,\n storeFingerprintForResuming: false,\n metadata: {\n filetype,\n filesize,\n filename,\n contentType: filetype,\n contentLength: filesize,\n contentDisposition: 'inline',\n },\n onError(error) {\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n logError(error.message)\n\n // Clean up\n navigator.sendBeacon(\n `${serverURL}${apiRoute}/uploads/cleanup`,\n JSON.stringify({ filename })\n )\n\n // Inform user\n emitter.emit('removeUpload', { filename })\n toast.error('File upload failed')\n resolve(null)\n },\n onBeforeRequest() {\n if (!eventListenerAdded) {\n window.addEventListener('beforeunload', onBeforeUnload)\n window.addEventListener('unload', onPageUnload)\n eventListenerAdded = true\n }\n },\n onProgress: function (bytesUploaded, bytesTotal) {\n const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n emitter.emit('updateUpload', {\n filename,\n progress: percentage,\n })\n },\n onSuccess() {\n // Clean up beforeunload listener\n window.removeEventListener('beforeunload', onBeforeUnload)\n window.removeEventListener('unload', onPageUnload)\n\n // Mark upload as complete in UI\n emitter.emit('uploadComplete', { filename })\n\n // Trigger post upload processing\n fetch(`${serverURL}${apiRoute}/uploads/${filename}/process`)\n },\n onUploadUrlAvailable: async function () {\n // Prevent multiple callbacks\n if (uploadUrlAvailable) {\n return\n }\n\n // Update flag\n uploadUrlAvailable = true\n\n // Add upload to UI\n emitter.emit('addUpload', { filename })\n\n // Update collection entry\n // with filename, mimeType, and storage\n resolve({\n filename,\n mimeType,\n storage: 's3',\n })\n },\n })\n\n upload.start()\n })\n}\n\nconst { log } = useErrorHandler()\n\nexport const UploadHandler = createClientUploadHandler({\n handler: async function (args) {\n const { serverURL, apiRoute, file, updateFilename } = args\n try {\n const mimeType =\n (await getFileType(file)) || file.type || 'application/octet-stream'\n if (!mimeType) {\n throwError(MediaCloudErrors.FILE_TYPE_UNKNOWN)\n return null\n }\n\n const isVideoFile = await isVideo(file)\n\n // Clone file\n let mappedFile = file\n\n if (!isVideoFile) {\n try {\n // Check if file with same name exists\n const response = await fetch(\n `${serverURL}${apiRoute}/uploads/${file.name}/exists`\n ).catch(() => {})\n\n // If file exists, generate a cloned file with a unique filename\n if (response?.status === 200) {\n log(MediaCloudLogs.S3_STORE_FILE_FOUND)\n\n const newFilename = generateUniqueFilename(file.name)\n mappedFile = new File([file], newFilename, { type: file.type })\n await updateFilename(newFilename)\n }\n } catch (_error) {\n logError(MediaCloudErrors.NAMING_FUNCTION_ERROR.message)\n }\n }\n\n const uploadArgs: UploadArgs = {\n file: mappedFile,\n serverURL,\n apiRoute,\n mimeType,\n }\n\n if (isVideoFile) {\n return await muxUpload(uploadArgs)\n } else {\n return await tusUpload(uploadArgs)\n }\n } catch (error) {\n console.error(\n '[PLUGIN-MEDIA-CLOUD] Upload handler detailed error:',\n error\n )\n logError(MediaCloudErrors.UPLOAD_HANDLER_ERROR.message)\n toast.error(\n `Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n return null\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;AAiCA,MAAM,EAAE,UAAU,eAAe,iBAAiB;AAClD,MAAM,UAAU,sBAAsB;AAEtC,MAAM,iBAAiB;AACvB,MAAM,iBAAiB,OAAO;AAC9B,MAAM,mBAAmB;CAAC;CAAG;CAAM;CAAM;CAAK;;;;;;AAO9C,eAAe,UAAU,MAAgD;CACvE,MAAM,EAAE,MAAM,WAAW,UAAU,aAAa;CAEhD,MAAM,WAAW,KAAK;AACtB,KAAI;EAWF,MAAM,EAAE,KAAK,aAAc,OATV,MAAM,MAAM,GAAG,YAAY,SAAS,cAAc;GACjE,MAAM,KAAK,UAAU;IAAE;IAAU;IAAU,CAAC;GAC5C,aAAa;GACb,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACF,CAAC,EAEwC,MAAM;EAGhD,MAAM,WAAW,MAAM,QAAQ,aAAa;GAC1C,UAAU;GACV;GACA,WAAW;GACZ,CAAC;AAGF,UAAQ,KAAK,aAAa;GACxB;GACA;GACA,SAAS;GACT,YAAY,GAAG,YAAY,SAAS;GACrC,CAAC;AAGF,WAAS,GAAG,SAAS,WAAY;AAC/B,YAAS,iBAAiB,iBAAiB,QAAQ;AACnD,SAAM,MAAM,sBAAsB;AAClC,WAAQ,KAAK,gBAAgB,EAAE,UAAU,CAAC;IAC1C;AAEF,WAAS,GAAG,YAAY,SAAU,UAAU;AAC1C,WAAQ,KAAK,gBAAgB;IAC3B;IACA,UAAU,SAAS;IACpB,CAAC;IACF;AAEF,WAAS,GAAG,WAAW,WAAY;AACjC,WAAQ,KAAK,kBAAkB,EAAE,UAAU,CAAC;IAC5C;AAIF,SAAO;GACL;GACA;GACA;GACA,SAAS;GACV;UACM,QAAQ;AACf,WAAS,iBAAiB,wBAAwB,QAAQ;AAC1D,QAAM,MAAM,sBAAsB;AAClC,SAAO;;;;;;;;AASX,eAAe,UAAU,MAAgD;CACvE,MAAM,EAAE,UAAU,WAAW,MAAM,aAAa;CAEhD,MAAM,WAAW,KAAK;CACtB,MAAM,WAAW,KAAK;CACtB,MAAM,WAAW,KAAK,KAAK,UAAU;AAGrC,QAAO,KAAK,aAAa,CACtB,QAAQ,QAAQ,IAAI,WAAW,QAAQ,CAAC,CACxC,SAAS,QAAQ,aAAa,WAAW,IAAI,CAAC;CAGjD,IAAI,qBAAqB;CACzB,IAAI,qBAAqB;CAEzB,SAAS,eAAe,GAAsB;AAC5C,SAAO,oBAAoB,gBAAgB,eAAe;AAC1D,IAAE,gBAAgB;;CAGpB,SAAS,eAAe;AACtB,SAAO,oBAAoB,UAAU,aAAa;AAClD,YAAU,WACR,GAAG,YAAY,SAAS,mBACxB,KAAK,UAAU,EAAE,UAAU,CAAC,CAC7B;;AAGH,QAAO,IAAI,SAAS,YAAY;AA4E9B,EA3Ee,IAAI,IAAI,OAAO,MAAM;GAClC,UAAU,GAAG,YAAY,SAAS;GAClC,aAAa;GACb,WAAW;GACX,6BAA6B;GAC7B,UAAU;IACR;IACA;IACA;IACA,aAAa;IACb,eAAe;IACf,oBAAoB;IACrB;GACD,QAAQ,OAAO;AACb,aAAS,iBAAiB,iBAAiB,QAAQ;AACnD,aAAS,MAAM,QAAQ;AAGvB,cAAU,WACR,GAAG,YAAY,SAAS,mBACxB,KAAK,UAAU,EAAE,UAAU,CAAC,CAC7B;AAGD,YAAQ,KAAK,gBAAgB,EAAE,UAAU,CAAC;AAC1C,UAAM,MAAM,qBAAqB;AACjC,YAAQ,KAAK;;GAEf,kBAAkB;AAChB,QAAI,CAAC,oBAAoB;AACvB,YAAO,iBAAiB,gBAAgB,eAAe;AACvD,YAAO,iBAAiB,UAAU,aAAa;AAC/C,0BAAqB;;;GAGzB,YAAY,SAAU,eAAe,YAAY;IAC/C,MAAM,aAAa,KAAK,MAAO,gBAAgB,aAAc,IAAI;AACjE,YAAQ,KAAK,gBAAgB;KAC3B;KACA,UAAU;KACX,CAAC;;GAEJ,YAAY;AAEV,WAAO,oBAAoB,gBAAgB,eAAe;AAC1D,WAAO,oBAAoB,UAAU,aAAa;AAGlD,YAAQ,KAAK,kBAAkB,EAAE,UAAU,CAAC;AAG5C,UAAM,GAAG,YAAY,SAAS,WAAW,SAAS,UAAU;;GAE9D,sBAAsB,iBAAkB;AAEtC,QAAI,mBACF;AAIF,yBAAqB;AAGrB,YAAQ,KAAK,aAAa,EAAE,UAAU,CAAC;AAIvC,YAAQ;KACN;KACA;KACA,SAAS;KACV,CAAC;;GAEL,CAAC,CAEK,OAAO;GACd;;AAGJ,MAAM,EAAE,QAAQ,iBAAiB;AAEjC,MAAa,gBAAgB,0BAA0B,EACrD,SAAS,eAAgB,MAAM;CAC7B,MAAM,EAAE,WAAW,UAAU,MAAM,mBAAmB;AACtD,KAAI;EACF,MAAM,WACH,MAAM,YAAY,KAAK,IAAK,KAAK,QAAQ;AAC5C,MAAI,CAAC,UAAU;AACb,cAAW,iBAAiB,kBAAkB;AAC9C,UAAO;;EAGT,MAAM,cAAc,MAAM,QAAQ,KAAK;EAGvC,IAAI,aAAa;AAEjB,MAAI,CAAC,YACH,KAAI;AAOF,QALiB,MAAM,MACrB,GAAG,YAAY,SAAS,WAAW,KAAK,KAAK,SAC9C,CAAC,YAAY,GAAG,GAGH,WAAW,KAAK;AAC5B,QAAI,eAAe,oBAAoB;IAEvC,MAAM,cAAc,uBAAuB,KAAK,KAAK;AACrD,iBAAa,IAAI,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC;AAC/D,UAAM,eAAe,YAAY;;WAE5B,QAAQ;AACf,YAAS,iBAAiB,sBAAsB,QAAQ;;EAI5D,MAAMA,aAAyB;GAC7B,MAAM;GACN;GACA;GACA;GACD;AAED,MAAI,YACF,QAAO,MAAM,UAAU,WAAW;MAElC,QAAO,MAAM,UAAU,WAAW;UAE7B,OAAO;AACd,UAAQ,MACN,uDACA,MACD;AACD,WAAS,iBAAiB,qBAAqB,QAAQ;AACvD,QAAM,MACJ,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,kBAC5D;AACD,SAAO;;GAGZ,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"upload-manager.mjs","names":["upload: Upload","args","value: UploadManagerContextType"],"sources":["../../../src/components/upload-manager/upload-manager.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n use,\n useState,\n useCallback,\n useEffect,\n useRef,\n} from 'react'\nimport { Button } from '@payloadcms/ui'\n\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../../types/errors'\n\nimport type { MediaCloudEmitterEvents } from '../../types'\nimport type React from 'react'\n\nimport './upload-manager.css'\n\ninterface Upload {\n filename: string\n progress: number\n uploadId?: string\n error?: string\n polling?: boolean\n pollingUrl?: string\n status?: 'uploading' | 'processing' | 'completed'\n}\n\ninterface UploadManagerContextType {\n showUploadManager?: boolean\n activeUploads: Upload[]\n addUpload: (args: MediaCloudEmitterEvents['addUpload']) => void\n updateUpload: (args: MediaCloudEmitterEvents['updateUpload']) => void\n}\n\ninterface UploadManagerProviderArgs {\n children: React.ReactNode\n}\n\ninterface RenderUploadListArgs {\n uploads: Upload[]\n}\n\nconst UploadManagerContext = createContext<UploadManagerContextType>({\n showUploadManager: false,\n activeUploads: [],\n addUpload: () => {},\n updateUpload: () => {},\n})\n\n/**\n * Provider component for upload management context\n * @param args - Arguments including children to wrap\n * @returns JSX element providing upload management context\n */\nexport function UploadManagerProvider(args: UploadManagerProviderArgs) {\n const { children } = args\n const [showUploadManager, setShowUploadManager] = useState(false)\n const [activeUploads, setActiveUploads] = useState<Upload[]>([])\n const [activeTab, setActiveTab] = useState<\n 'uploading' | 'processing' | 'completed'\n >('uploading')\n\n const emitter = useMediaCloudEmitter()\n const activeTabRef = useRef(activeTab)\n const activeUploadsRef = useRef(activeUploads)\n\n const { logError } = useErrorHandler()\n\n // Keep refs in sync with state\n useEffect(() => {\n activeTabRef.current = activeTab\n }, [activeTab])\n\n useEffect(() => {\n activeUploadsRef.current = activeUploads\n }, [activeUploads])\n\n // Helper function to check if we should auto-switch to completed tab\n const checkAutoSwitchToCompleted = useCallback((uploads: Upload[]) => {\n const hasActiveUploads = uploads.some(\n (upload) => upload.status === 'uploading'\n )\n const hasProcessingUploads = uploads.some(\n (upload) => upload.status === 'processing'\n )\n const hasCompletedUploads = uploads.some(\n (upload) => upload.status === 'completed'\n )\n\n // Auto-switch to completed tab if no uploading/processing uploads remain\n if (\n !hasActiveUploads &&\n !hasProcessingUploads &&\n hasCompletedUploads &&\n activeTabRef.current !== 'completed'\n ) {\n setActiveTab('completed')\n }\n }, [])\n\n // Polling logic\n useEffect(() => {\n const pollingUploads = activeUploads.filter(\n (upload) => upload.polling && upload.pollingUrl\n )\n\n if (pollingUploads.length === 0) {\n return\n }\n\n const pollAssets = async () => {\n for (const pollingUpload of pollingUploads) {\n try {\n const response = await fetch(\n `${pollingUpload.pollingUrl}?upload_id=${pollingUpload.uploadId}`,\n {\n method: 'GET',\n credentials: 'include',\n }\n )\n\n if (response.ok) {\n const data = await response.json()\n\n if (data.ready) {\n // Asset is ready, stop polling for this upload\n setActiveUploads((prev) => {\n const updatedUploads = prev.map((upload) =>\n upload.uploadId === pollingUpload.uploadId\n ? {\n ...upload,\n polling: false,\n progress: 100,\n status: 'completed' as const,\n }\n : upload\n )\n\n // Check if we should auto-switch to completed tab\n setTimeout(() => checkAutoSwitchToCompleted(updatedUploads), 0)\n\n return updatedUploads\n })\n }\n }\n } catch (_error) {\n logError(MediaCloudErrors.UPLOAD_POLLING_ERROR.message)\n }\n }\n }\n\n const intervalId = setInterval(pollAssets, 2000) // Poll every 2 seconds\n\n return () => clearInterval(intervalId)\n }, [activeUploads, checkAutoSwitchToCompleted, logError])\n\n /**\n * Handles the 'uploadError' event\n * @param event - The upload error event\n */\n const onUploadError = useCallback(\n (event: MediaCloudEmitterEvents['uploadError']) => {\n const { filename, error } = event\n\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? { ...upload, error: error, status: 'completed' }\n : upload\n )\n )\n },\n [logError]\n )\n\n /**\n * Handles the 'addUpload' event\n * @param event - The add upload event\n */\n const onAddUpload = useCallback(\n (event: MediaCloudEmitterEvents['addUpload']) => {\n const upload: Upload = {\n filename: event.filename,\n uploadId: event.uploadId,\n progress: 0,\n polling: event.polling,\n pollingUrl: event.pollingUrl,\n status: 'uploading',\n }\n\n setActiveUploads((prev) => [...prev, upload])\n\n // Show the upload manager when new upload starts\n setShowUploadManager(true)\n\n // Auto-switch to uploading tab when new upload starts\n if (activeTabRef.current !== 'uploading') {\n setActiveTab('uploading')\n }\n },\n []\n )\n\n /**\n * Handles the 'updateUpload' event\n * @param event - The update upload event\n */\n const onUpdateUpload = useCallback(\n (event: MediaCloudEmitterEvents['updateUpload']) => {\n const { filename, progress, polling } = event\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? {\n ...upload,\n progress,\n ...(polling !== undefined && { polling }),\n status: polling\n ? 'processing'\n : progress >= 100\n ? 'completed'\n : 'uploading',\n }\n : upload\n )\n )\n },\n []\n )\n\n /**\n * Handles the 'removeUpload' event\n * @param event - The remove upload event\n */\n const onRemoveUpload = useCallback(\n (event: MediaCloudEmitterEvents['removeUpload']) => {\n setActiveUploads((prev) =>\n prev.filter((upload) => upload.filename !== event.filename)\n )\n },\n []\n )\n\n /**\n * Handles the 'uploadComplete' event\n * @param event - The upload completed event\n */\n const onUploadComplete = useCallback(\n (event: MediaCloudEmitterEvents['uploadComplete']) => {\n // Check if this upload has a polling URL (Mux upload)\n const upload = activeUploadsRef.current.find(\n (upload) => upload.filename === event.filename\n )\n if (upload?.pollingUrl) {\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === event.filename\n ? {\n ...upload,\n polling: true,\n progress: 100,\n status: 'processing' as const,\n }\n : upload\n )\n )\n } else {\n // Regular upload completion\n setActiveUploads((prev) => {\n const updatedUploads = prev.map((upload) =>\n upload.filename === event.filename\n ? { ...upload, progress: 100, status: 'completed' as const }\n : upload\n )\n\n // Check if we should auto-switch to completed tab\n setTimeout(() => checkAutoSwitchToCompleted(updatedUploads), 0)\n\n return updatedUploads\n })\n }\n },\n [checkAutoSwitchToCompleted]\n )\n\n useEffect(() => {\n emitter.on('addUpload', onAddUpload)\n emitter.on('updateUpload', onUpdateUpload)\n emitter.on('removeUpload', onRemoveUpload)\n emitter.on('uploadError', onUploadError)\n emitter.on('uploadComplete', onUploadComplete)\n\n return () => {\n emitter.off('addUpload', onAddUpload)\n emitter.off('updateUpload', onUpdateUpload)\n emitter.off('removeUpload', onRemoveUpload)\n emitter.off('uploadError', onUploadError)\n emitter.off('uploadComplete', onUploadComplete)\n }\n }, [\n emitter,\n onAddUpload,\n onUpdateUpload,\n onRemoveUpload,\n onUploadError,\n onUploadComplete,\n ])\n\n /**\n * Adds a new upload to the manager\n * @param args - The upload arguments\n */\n const addUpload = useCallback(\n (args: MediaCloudEmitterEvents['addUpload']) => {\n const { filename, polling = false, pollingUrl } = args\n const upload: Upload = {\n filename,\n progress: 0,\n polling,\n pollingUrl,\n status: 'uploading',\n }\n\n setActiveUploads((prev) => [...prev, upload])\n },\n []\n )\n\n /**\n * Updates an existing upload in the manager\n * @param args - The update arguments\n */\n const updateUpload = useCallback(\n (args: MediaCloudEmitterEvents['updateUpload']) => {\n const { filename, progress, polling } = args\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? {\n ...upload,\n progress,\n ...(polling !== undefined && { polling }),\n status: polling\n ? 'processing'\n : progress >= 100\n ? 'completed'\n : 'uploading',\n }\n : upload\n )\n )\n },\n []\n )\n\n // Filter uploads by status\n const uploadingFiles = activeUploads.filter(\n (upload) => upload.status === 'uploading'\n )\n const processingFiles = activeUploads.filter(\n (upload) => upload.status === 'processing'\n )\n const completedFiles = activeUploads.filter(\n (upload) => upload.status === 'completed'\n )\n\n /**\n * Renders the upload list\n * @param args - The render arguments\n * @returns JSX element representing the upload list\n */\n function renderUploadList(args: RenderUploadListArgs) {\n const { uploads } = args\n return (\n <ul>\n {uploads.map((upload) => (\n <li key={upload.filename} data-status={upload.status}>\n <div className=\"upload-info\">\n <span className=\"upload-filename\">{upload.filename}</span>\n <span className=\"upload-meta\">\n {upload.status === 'processing'\n ? 'Processing...'\n : upload.progress < 100\n ? `${Math.ceil(upload.progress)}%`\n : 'Completed'}\n </span>\n </div>\n <div\n className=\"upload-progress-bar\"\n style={\n {\n ['--progress']:\n upload.status === 'processing'\n ? '1'\n : `${upload.progress / 100}`,\n } as React.CSSProperties\n }\n >\n <div\n data-active={upload.status === 'processing'}\n className=\"upload-progress\"\n />\n </div>\n </li>\n ))}\n </ul>\n )\n }\n\n /**\n * Closes the upload manager\n */\n function closeUploadManager() {\n // Only allow closing if no uploads are actively polling\n const hasPollingUploads = activeUploads.some((upload) => upload.polling)\n if (!hasPollingUploads) {\n setActiveUploads([])\n setShowUploadManager(false)\n }\n }\n\n const value: UploadManagerContextType = {\n activeUploads,\n addUpload,\n updateUpload,\n }\n\n return (\n <UploadManagerContext.Provider value={value}>\n {showUploadManager && (\n <div className=\"upload-manager\">\n <div className=\"upload-manager__header\">\n <h4>Uploads</h4>\n <Button\n buttonStyle=\"icon-label\"\n icon=\"x\"\n margin={false}\n onClick={closeUploadManager}\n />\n </div>\n\n <div className=\"upload-manager__tabs\">\n <button\n data-active={activeTab === 'uploading'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('uploading')}\n >\n Uploading ({uploadingFiles.length})\n </button>\n <button\n data-active={activeTab === 'processing'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('processing')}\n >\n Processing ({processingFiles.length})\n </button>\n <button\n data-active={activeTab === 'completed'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('completed')}\n >\n Completed ({completedFiles.length})\n </button>\n </div>\n\n <div className=\"upload-manager__content\">\n {activeTab === 'uploading' && uploadingFiles.length > 0 && (\n <div>{renderUploadList({ uploads: uploadingFiles })}</div>\n )}\n {activeTab === 'processing' && processingFiles.length > 0 && (\n <div>{renderUploadList({ uploads: processingFiles })}</div>\n )}\n {activeTab === 'completed' && completedFiles.length > 0 && (\n <div>\n {renderUploadList({ uploads: completedFiles })}\n <div className=\"upload-manager__footer\">\n <Button\n buttonStyle=\"subtle\"\n size=\"small\"\n margin={false}\n onClick={() => window?.location?.reload()}\n >\n Refresh\n </Button>\n </div>\n </div>\n )}\n {((activeTab === 'uploading' && uploadingFiles.length === 0) ||\n (activeTab === 'processing' && processingFiles.length === 0) ||\n (activeTab === 'completed' && completedFiles.length === 0)) && (\n <p className=\"upload-empty-state\">No {activeTab} files</p>\n )}\n </div>\n </div>\n )}\n {children}\n </UploadManagerContext.Provider>\n )\n}\n\nexport const useUploadManagerContext = () => use(UploadManagerContext)\n"],"mappings":";;;;;;;;;;AA8CA,MAAM,uBAAuB,cAAwC;CACnE,mBAAmB;CACnB,eAAe,EAAE;CACjB,iBAAiB;CACjB,oBAAoB;CACrB,CAAC;;;;;;AAOF,SAAgB,sBAAsB,MAAiC;CACrE,MAAM,EAAE,aAAa;CACrB,MAAM,CAAC,mBAAmB,wBAAwB,SAAS,MAAM;CACjE,MAAM,CAAC,eAAe,oBAAoB,SAAmB,EAAE,CAAC;CAChE,MAAM,CAAC,WAAW,gBAAgB,SAEhC,YAAY;CAEd,MAAM,UAAU,sBAAsB;CACtC,MAAM,eAAe,OAAO,UAAU;CACtC,MAAM,mBAAmB,OAAO,cAAc;CAE9C,MAAM,EAAE,aAAa,iBAAiB;AAGtC,iBAAgB;AACd,eAAa,UAAU;IACtB,CAAC,UAAU,CAAC;AAEf,iBAAgB;AACd,mBAAiB,UAAU;IAC1B,CAAC,cAAc,CAAC;CAGnB,MAAM,6BAA6B,aAAa,YAAsB;EACpE,MAAM,mBAAmB,QAAQ,MAC9B,WAAW,OAAO,WAAW,YAC/B;EACD,MAAM,uBAAuB,QAAQ,MAClC,WAAW,OAAO,WAAW,aAC/B;EACD,MAAM,sBAAsB,QAAQ,MACjC,WAAW,OAAO,WAAW,YAC/B;AAGD,MACE,CAAC,oBACD,CAAC,wBACD,uBACA,aAAa,YAAY,YAEzB,cAAa,YAAY;IAE1B,EAAE,CAAC;AAGN,iBAAgB;EACd,MAAM,iBAAiB,cAAc,QAClC,WAAW,OAAO,WAAW,OAAO,WACtC;AAED,MAAI,eAAe,WAAW,EAC5B;EAGF,MAAM,aAAa,YAAY;AAC7B,QAAK,MAAM,iBAAiB,eAC1B,KAAI;IACF,MAAM,WAAW,MAAM,MACrB,GAAG,cAAc,WAAW,aAAa,cAAc,YACvD;KACE,QAAQ;KACR,aAAa;KACd,CACF;AAED,QAAI,SAAS,IAGX;UAFa,MAAM,SAAS,MAAM,EAEzB,MAEP,mBAAkB,SAAS;MACzB,MAAM,iBAAiB,KAAK,KAAK,WAC/B,OAAO,aAAa,cAAc,WAC9B;OACE,GAAG;OACH,SAAS;OACT,UAAU;OACV,QAAQ;OACT,GACD,OACL;AAGD,uBAAiB,2BAA2B,eAAe,EAAE,EAAE;AAE/D,aAAO;OACP;;YAGC,QAAQ;AACf,aAAS,iBAAiB,qBAAqB,QAAQ;;;EAK7D,MAAM,aAAa,YAAY,YAAY,IAAK;AAEhD,eAAa,cAAc,WAAW;IACrC;EAAC;EAAe;EAA4B;EAAS,CAAC;;;;;CAMzD,MAAM,gBAAgB,aACnB,UAAkD;EACjD,MAAM,EAAE,UAAU,UAAU;AAE5B,WAAS,iBAAiB,iBAAiB,QAAQ;AAEnD,oBAAkB,SAChB,KAAK,KAAK,WACR,OAAO,aAAa,WAChB;GAAE,GAAG;GAAe;GAAO,QAAQ;GAAa,GAChD,OACL,CACF;IAEH,CAAC,SAAS,CACX;;;;;CAMD,MAAM,cAAc,aACjB,UAAgD;EAC/C,MAAMA,SAAiB;GACrB,UAAU,MAAM;GAChB,UAAU,MAAM;GAChB,UAAU;GACV,SAAS,MAAM;GACf,YAAY,MAAM;GAClB,QAAQ;GACT;AAED,oBAAkB,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC;AAG7C,uBAAqB,KAAK;AAG1B,MAAI,aAAa,YAAY,YAC3B,cAAa,YAAY;IAG7B,EAAE,CACH;;;;;CAMD,MAAM,iBAAiB,aACpB,UAAmD;EAClD,MAAM,EAAE,UAAU,UAAU,YAAY;AACxC,oBAAkB,SAChB,KAAK,KAAK,WACR,OAAO,aAAa,WAChB;GACE,GAAG;GACH;GACA,GAAI,YAAY,UAAa,EAAE,SAAS;GACxC,QAAQ,UACJ,eACA,YAAY,MACV,cACA;GACP,GACD,OACL,CACF;IAEH,EAAE,CACH;;;;;CAMD,MAAM,iBAAiB,aACpB,UAAmD;AAClD,oBAAkB,SAChB,KAAK,QAAQ,WAAW,OAAO,aAAa,MAAM,SAAS,CAC5D;IAEH,EAAE,CACH;;;;;CAMD,MAAM,mBAAmB,aACtB,UAAqD;AAKpD,MAHe,iBAAiB,QAAQ,MACrC,WAAW,OAAO,aAAa,MAAM,SACvC,EACW,WACV,mBAAkB,SAChB,KAAK,KAAK,WACR,OAAO,aAAa,MAAM,WACtB;GACE,GAAG;GACH,SAAS;GACT,UAAU;GACV,QAAQ;GACT,GACD,OACL,CACF;MAGD,mBAAkB,SAAS;GACzB,MAAM,iBAAiB,KAAK,KAAK,WAC/B,OAAO,aAAa,MAAM,WACtB;IAAE,GAAG;IAAQ,UAAU;IAAK,QAAQ;IAAsB,GAC1D,OACL;AAGD,oBAAiB,2BAA2B,eAAe,EAAE,EAAE;AAE/D,UAAO;IACP;IAGN,CAAC,2BAA2B,CAC7B;AAED,iBAAgB;AACd,UAAQ,GAAG,aAAa,YAAY;AACpC,UAAQ,GAAG,gBAAgB,eAAe;AAC1C,UAAQ,GAAG,gBAAgB,eAAe;AAC1C,UAAQ,GAAG,eAAe,cAAc;AACxC,UAAQ,GAAG,kBAAkB,iBAAiB;AAE9C,eAAa;AACX,WAAQ,IAAI,aAAa,YAAY;AACrC,WAAQ,IAAI,gBAAgB,eAAe;AAC3C,WAAQ,IAAI,gBAAgB,eAAe;AAC3C,WAAQ,IAAI,eAAe,cAAc;AACzC,WAAQ,IAAI,kBAAkB,iBAAiB;;IAEhD;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;;;;;CAMF,MAAM,YAAY,aACf,WAA+C;EAC9C,MAAM,EAAE,UAAU,UAAU,OAAO,eAAeC;EAClD,MAAMD,SAAiB;GACrB;GACA,UAAU;GACV;GACA;GACA,QAAQ;GACT;AAED,oBAAkB,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC;IAE/C,EAAE,CACH;;;;;CAMD,MAAM,eAAe,aAClB,WAAkD;EACjD,MAAM,EAAE,UAAU,UAAU,YAAYC;AACxC,oBAAkB,SAChB,KAAK,KAAK,WACR,OAAO,aAAa,WAChB;GACE,GAAG;GACH;GACA,GAAI,YAAY,UAAa,EAAE,SAAS;GACxC,QAAQ,UACJ,eACA,YAAY,MACV,cACA;GACP,GACD,OACL,CACF;IAEH,EAAE,CACH;CAGD,MAAM,iBAAiB,cAAc,QAClC,WAAW,OAAO,WAAW,YAC/B;CACD,MAAM,kBAAkB,cAAc,QACnC,WAAW,OAAO,WAAW,aAC/B;CACD,MAAM,iBAAiB,cAAc,QAClC,WAAW,OAAO,WAAW,YAC/B;;;;;;CAOD,SAAS,iBAAiB,QAA4B;EACpD,MAAM,EAAE,YAAYA;AACpB,SACE,CAAC,GAAG;SACD,QAAQ,KAAK,WACZ,CAAC,GAAG,KAAK,OAAO,UAAU,aAAa,OAAO,QAAQ;YACpD,CAAC,IAAI,wBAAwB;cAC3B,CAAC,KAAK,6BAA6B,OAAO,SAAS,EAAE,KAAK;cAC1D,CAAC,KAAK,wBAAwB;iBAC3B,OAAO,WAAW,eACf,kBACA,OAAO,WAAW,MAChB,GAAG,KAAK,KAAK,OAAO,SAAS,CAAC,KAC9B,YAAY;cACpB,EAAE,KAAK;YACT,EAAE,IAAI;YACN,CAAC,IACC,gCACA,OACE,GACG,eACC,OAAO,WAAW,eACd,MACA,GAAG,OAAO,WAAW,OAC5B,EAEJ;cACC,CAAC,IACC,aAAa,OAAO,WAAW,cAC/B,8BACA;YACJ,EAAE,IAAI;UACR,EAAE,IACF,CAAC;MACL,EAAE;;;;;CAON,SAAS,qBAAqB;AAG5B,MAAI,CADsB,cAAc,MAAM,WAAW,OAAO,QAAQ,EAChD;AACtB,oBAAiB,EAAE,CAAC;AACpB,wBAAqB,MAAM;;;CAI/B,MAAMC,QAAkC;EACtC;EACA;EACA;EACD;AAED,QACE,CAAC,qBAAqB,SAAS,OAAO,OAAO;OAC1C,qBACC,CAAC,IAAI,2BAA2B;UAC9B,CAAC,IAAI,mCAAmC;YACtC,CAAC,GAAG,OAAO,EAAE,GAAG;YAChB,CAAC,OACC,yBACA,SACA,QAAQ,OACR,SAAS,sBACT;UACJ,EAAE,IAAI;;UAEN,CAAC,IAAI,iCAAiC;YACpC,CAAC,OACC,aAAa,cAAc,aAC3B,uBACA,eAAe,aAAa,YAAY,EACzC;0BACa,eAAe,OAAO;YACpC,EAAE,OAAO;YACT,CAAC,OACC,aAAa,cAAc,cAC3B,uBACA,eAAe,aAAa,aAAa,EAC1C;2BACc,gBAAgB,OAAO;YACtC,EAAE,OAAO;YACT,CAAC,OACC,aAAa,cAAc,aAC3B,uBACA,eAAe,aAAa,YAAY,EACzC;0BACa,eAAe,OAAO;YACpC,EAAE,OAAO;UACX,EAAE,IAAI;;UAEN,CAAC,IAAI,oCAAoC;aACtC,cAAc,eAAe,eAAe,SAAS,KACpD,CAAC,KAAK,iBAAiB,EAAE,SAAS,gBAAgB,CAAC,CAAC,EAAE,KACtD;aACD,cAAc,gBAAgB,gBAAgB,SAAS,KACtD,CAAC,KAAK,iBAAiB,EAAE,SAAS,iBAAiB,CAAC,CAAC,EAAE,KACvD;aACD,cAAc,eAAe,eAAe,SAAS,KACpD,CAAC,IAAI;iBACF,iBAAiB,EAAE,SAAS,gBAAgB,CAAC,CAAC;gBAC/C,CAAC,IAAI,mCAAmC;kBACtC,CAAC,OACC,qBACA,aACA,QAAQ,OACR,eAAe,QAAQ,UAAU,QAAQ,EAC1C;;kBAED,EAAE,OAAO;gBACX,EAAE,IAAI;cACR,EAAE,KACF;cACC,cAAc,eAAe,eAAe,WAAW,KACvD,cAAc,gBAAgB,gBAAgB,WAAW,KACzD,cAAc,eAAe,eAAe,WAAW,MACxD,CAAC,EAAE,+BAA+B,IAAI,UAAU,MAAM,EAAE,GACxD;UACJ,EAAE,IAAI;QACR,EAAE,KACF;OACD,SAAS;IACZ,EAAE,qBAAqB;;AAI3B,MAAa,gCAAgC,IAAI,qBAAqB"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { S3Store } from "../tus/stores/s3/s3-store.mjs";
|
|
2
|
-
import { PayloadHandler } from "payload";
|
|
3
|
-
|
|
4
|
-
//#region src/endpoints/tusFileExistsHandler.d.ts
|
|
5
|
-
interface GetTusPostProcessorHandlerArgs {
|
|
6
|
-
s3Store: S3Store;
|
|
7
|
-
}
|
|
8
|
-
declare function getTusFileExistsHandler(args: GetTusPostProcessorHandlerArgs): PayloadHandler;
|
|
9
|
-
//#endregion
|
|
10
|
-
export { getTusFileExistsHandler };
|
|
11
|
-
//# sourceMappingURL=tusFileExistsHandler.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tusFileExistsHandler.mjs","names":[],"sources":["../../src/endpoints/tusFileExistsHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3-store'\n\ninterface GetTusPostProcessorHandlerArgs {\n s3Store: S3Store\n}\n\nexport function getTusFileExistsHandler(\n args: GetTusPostProcessorHandlerArgs\n): PayloadHandler {\n const { s3Store } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n // Check if file exists in Payload database\n const { docs } = await payload.find({\n collection: 'media',\n where: {\n filename: {\n equals: filename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n if (docs.length > 0) {\n return Response.json(\n { message: 'File found [payload]' },\n { status: 200 }\n )\n }\n\n // Check if completed file exists in S3\n const url = s3Store.getUrl(filename)\n const s3Response = await fetch(url, { method: 'HEAD' })\n\n if (s3Response.status === 200) {\n return Response.json({ message: 'File found [S3]' }, { status: 200 })\n }\n\n return Response.json({ message: 'File not found' }, { status: 404 })\n } catch (_error) {\n logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message)\n return Response.json(\n { message: 'Failed to process asset' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";;;;AAUA,SAAgB,wBACd,MACgB;CAChB,MAAM,EAAE,YAAY;CACpB,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,QAAO,eAAgB,KAAK;AAC1B,MAAI;GACF,MAAM,EAAE,aAAa,YAAY;GACjC,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACH,YAAW,iBAAiB,kBAAkB;GAIhD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;IAClC,YAAY;IACZ,OAAO,EACL,UAAU,EACR,QAAQ,UACT,EACF;IACD,OAAO;IACP,YAAY;IACb,CAAC;AAEF,OAAI,KAAK,SAAS,EAChB,QAAO,SAAS,KACd,EAAE,SAAS,wBAAwB,EACnC,EAAE,QAAQ,KAAK,CAChB;GAIH,MAAM,MAAM,QAAQ,OAAO,SAAS;AAGpC,QAFmB,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,CAAC,EAExC,WAAW,IACxB,QAAO,SAAS,KAAK,EAAE,SAAS,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGvE,UAAO,SAAS,KAAK,EAAE,SAAS,kBAAkB,EAAE,EAAE,QAAQ,KAAK,CAAC;WAC7D,QAAQ;AACf,YAAS,iBAAiB,sBAAsB,QAAQ;AACxD,UAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"expiration-manager.mjs","names":["client: S3","bucket: string","expirationPeriodInMilliseconds: number","generateInfoKey: (id: string) => string","generatePartKey: (id: string, isIncompletePart: boolean) => string","keyMarker: string | undefined","uploadIdMarker: string | undefined","listResponse: AWS.ListMultipartUploadsCommandOutput","deletions: Promise<AWS.DeleteObjectsCommandOutput>[]"],"sources":["../../../../src/tus/stores/s3/expiration-manager.ts"],"sourcesContent":["import type AWS from '@aws-sdk/client-s3'\nimport type { S3 } from '@aws-sdk/client-s3'\n\ntype GetExpirationDateArgs = {\n createdAt: string\n}\n\nexport class S3ExpirationManager {\n constructor(\n private client: S3,\n private bucket: string,\n private expirationPeriodInMilliseconds: number,\n private generateInfoKey: (id: string) => string,\n private generatePartKey: (id: string, isIncompletePart: boolean) => string\n ) {}\n\n /**\n * Calculates the expiration date for a file based on a creation date\n * @param args - The function arguments\n * @param args.createdAt - The creation date as a string\n * @returns The expiration date\n */\n getExpirationDate(args: GetExpirationDateArgs): Date {\n const { createdAt } = args\n const date = new Date(createdAt)\n return new Date(date.getTime() + this.expirationPeriodInMilliseconds)\n }\n\n /**\n * Gets the expiration period in milliseconds\n * @returns The expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationPeriodInMilliseconds\n }\n\n /**\n * Deletes expired incomplete uploads based on their initialization date.\n * Returns the number of deleted uploads.\n * @param args - The function arguments (empty object)\n * @returns Promise that resolves to the number of deleted uploads\n */\n async deleteExpired(): Promise<number> {\n // No arguments to destructure\n if (this.getExpiration() === 0) {\n return 0\n }\n\n let keyMarker: string | undefined = undefined\n let uploadIdMarker: string | undefined = undefined\n let isTruncated = true\n let deleted = 0\n\n while (isTruncated) {\n const listResponse: AWS.ListMultipartUploadsCommandOutput =\n await this.client.listMultipartUploads({\n Bucket: this.bucket,\n KeyMarker: keyMarker,\n UploadIdMarker: uploadIdMarker,\n })\n\n const expiredUploads =\n listResponse.Uploads?.filter((multiPartUpload: AWS.MultipartUpload) => {\n const initiatedDate = multiPartUpload.Initiated\n return (\n initiatedDate &&\n new Date().getTime() >\n this.getExpirationDate({\n createdAt: initiatedDate.toISOString(),\n }).getTime()\n )\n }) || []\n\n const objectsToDelete = expiredUploads.reduce(\n (all: { Key: string }[], expiredUpload: AWS.MultipartUpload) => {\n all.push(\n {\n Key: this.generateInfoKey(expiredUpload.Key as string),\n },\n {\n Key: this.generatePartKey(expiredUpload.Key as string, true),\n }\n )\n return all\n },\n [] as { Key: string }[]\n )\n\n const deletions: Promise<AWS.DeleteObjectsCommandOutput>[] = []\n\n if (objectsToDelete.length > 0) {\n deletions.push(\n this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: objectsToDelete,\n },\n })\n )\n }\n\n const abortions = expiredUploads.map(\n (expiredUpload: AWS.MultipartUpload) =>\n this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: expiredUpload.Key,\n UploadId: expiredUpload.UploadId,\n })\n )\n\n await Promise.all([...deletions, ...abortions])\n\n deleted += expiredUploads.length\n\n isTruncated = listResponse.IsTruncated || false\n keyMarker = listResponse.NextKeyMarker\n uploadIdMarker = listResponse.NextUploadIdMarker\n }\n\n return deleted\n }\n}\n"],"mappings":";AAOA,IAAa,sBAAb,MAAiC;CAC/B,YACE,AAAQA,QACR,AAAQC,QACR,AAAQC,gCACR,AAAQC,iBACR,AAAQC,iBACR;EALQ;EACA;EACA;EACA;EACA;;;;;;;;CASV,kBAAkB,MAAmC;EACnD,MAAM,EAAE,cAAc;EACtB,MAAM,OAAO,IAAI,KAAK,UAAU;AAChC,SAAO,IAAI,KAAK,KAAK,SAAS,GAAG,KAAK,+BAA+B;;;;;;CAOvE,gBAAwB;AACtB,SAAO,KAAK;;;;;;;;CASd,MAAM,gBAAiC;AAErC,MAAI,KAAK,eAAe,KAAK,EAC3B,QAAO;EAGT,IAAIC,YAAgC;EACpC,IAAIC,iBAAqC;EACzC,IAAI,cAAc;EAClB,IAAI,UAAU;AAEd,SAAO,aAAa;GAClB,MAAMC,eACJ,MAAM,KAAK,OAAO,qBAAqB;IACrC,QAAQ,KAAK;IACb,WAAW;IACX,gBAAgB;IACjB,CAAC;GAEJ,MAAM,iBACJ,aAAa,SAAS,QAAQ,oBAAyC;IACrE,MAAM,gBAAgB,gBAAgB;AACtC,WACE,kCACA,IAAI,MAAM,EAAC,SAAS,GAClB,KAAK,kBAAkB,EACrB,WAAW,cAAc,aAAa,EACvC,CAAC,CAAC,SAAS;KAEhB,IAAI,EAAE;GAEV,MAAM,kBAAkB,eAAe,QACpC,KAAwB,kBAAuC;AAC9D,QAAI,KACF,EACE,KAAK,KAAK,gBAAgB,cAAc,IAAc,EACvD,EACD,EACE,KAAK,KAAK,gBAAgB,cAAc,KAAe,KAAK,EAC7D,CACF;AACD,WAAO;MAET,EAAE,CACH;GAED,MAAMC,YAAuD,EAAE;AAE/D,OAAI,gBAAgB,SAAS,EAC3B,WAAU,KACR,KAAK,OAAO,cAAc;IACxB,QAAQ,KAAK;IACb,QAAQ,EACN,SAAS,iBACV;IACF,CAAC,CACH;GAGH,MAAM,YAAY,eAAe,KAC9B,kBACC,KAAK,OAAO,qBAAqB;IAC/B,QAAQ,KAAK;IACb,KAAK,cAAc;IACnB,UAAU,cAAc;IACzB,CAAC,CACL;AAED,SAAM,QAAQ,IAAI,CAAC,GAAG,WAAW,GAAG,UAAU,CAAC;AAE/C,cAAW,eAAe;AAE1B,iBAAc,aAAa,eAAe;AAC1C,eAAY,aAAa;AACzB,oBAAiB,aAAa;;AAGhC,SAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-operations.mjs","names":["maxMultipartParts: number","maxUploadSize: number","minPartSize: number","preferredPartSize: number","optimalPartSize: number"],"sources":["../../../../src/tus/stores/s3/file-operations.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../../../types/errors'\n\nimport type { NodeFSError } from '../../../types'\nimport type AWS from '@aws-sdk/client-s3'\n\ntype CalculateOptimalPartSizeArgs = {\n size?: number\n}\n\ntype CalculateOffsetFromPartsArgs = {\n parts?: Array<AWS.Part>\n}\n\ntype CalculatePartNumberArgs = {\n parts: Array<AWS.Part>\n}\n\ntype GenerateUniqueTmpFileNameArgs = {\n template: string\n}\n\ntype CalculateOffsetFromPartsExportArgs = {\n parts?: Array<{ Size?: number }>\n}\n\nconst { throwError } = useErrorHandler()\n\nexport class S3FileOperations {\n constructor(\n private maxMultipartParts: number,\n private maxUploadSize: number,\n private minPartSize: number,\n private preferredPartSize: number\n ) {}\n\n /**\n * Calculates the optimal part size for S3 multipart upload\n * @param args - The function arguments\n * @param args.size - The upload size in bytes (optional)\n * @returns The optimal part size in bytes\n */\n calculateOptimalPartSize(args: CalculateOptimalPartSizeArgs): number {\n const { size } = args\n // When upload size is not known, default to preferredPartSize\n // Assuming maxUploadSize can lead to extremely large chunks (hundreds of GB),\n // which breaks streaming/resumable uploads in practice\n let uploadSize = size\n if (uploadSize === undefined) {\n uploadSize = this.preferredPartSize\n }\n\n let optimalPartSize: number\n\n // When upload is smaller or equal to preferredPartSize, upload in just one part\n if (uploadSize <= this.preferredPartSize) {\n optimalPartSize = uploadSize\n }\n // Does the upload fit in maxMultipartParts parts or less with preferredPartSize\n else if (uploadSize <= this.preferredPartSize * this.maxMultipartParts) {\n optimalPartSize = this.preferredPartSize\n // The upload is too big for the preferred size\n // We divide the size with the max amount of parts and round it up\n } else {\n optimalPartSize = Math.ceil(uploadSize / this.maxMultipartParts)\n }\n\n // Always ensure the part size is at least minPartSize\n return Math.max(optimalPartSize, this.minPartSize)\n }\n\n /**\n * Calculates the offset based on uploaded parts\n * @param args - The function arguments\n * @param args.parts - Array of uploaded parts (optional)\n * @returns The total offset in bytes\n */\n calculateOffsetFromParts(args: CalculateOffsetFromPartsArgs): number {\n const { parts } = args\n return parts && parts.length > 0\n ? parts.reduce((a, b) => a + (b.Size ?? 0), 0)\n : 0\n }\n\n /**\n * Calculates the next part number based on existing parts\n * @param args - The function arguments\n * @param args.parts - Array of uploaded parts\n * @returns The next part number to use\n */\n calculatePartNumber(args: CalculatePartNumberArgs): number {\n const { parts } = args\n return parts.length > 0 ? parts[parts.length - 1].PartNumber! + 1 : 1\n }\n\n /**\n * Generates a unique temporary file name\n * @param args - The function arguments\n * @param args.template - The template string for the file name\n * @returns Promise that resolves to the unique file path\n * @throws Error if unable to find unique name after max tries\n */\n async generateUniqueTmpFileName(\n args: GenerateUniqueTmpFileNameArgs\n ): Promise<string> {\n const { template } = args\n const tries = 5\n for (let i = 0; i < tries; i++) {\n const randomId = crypto.randomBytes(16).toString('hex')\n const filePath = path.join(os.tmpdir(), `${template}${randomId}`)\n try {\n await fs.promises.access(filePath, fs.constants.F_OK)\n } catch (error) {\n const nodeError = error as NodeFSError\n if (nodeError.code === 'ENOENT') {\n return filePath\n }\n }\n }\n throwError(MediaCloudErrors.S3_UNIQUE_NAME_ERROR)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n}\n\n/**\n * Calculates the total offset from uploaded parts\n * @param args - The function arguments\n * @param args.parts - Array of parts with size information (optional)\n * @returns The total offset in bytes\n */\nexport function calculateOffsetFromParts(\n args: CalculateOffsetFromPartsExportArgs\n) {\n const { parts } = args\n return parts && parts.length > 0\n ? parts.reduce((a, b) => a + (b.Size ?? 0), 0)\n : 0\n}\n"],"mappings":";;;;;;;;AA+BA,MAAM,EAAE,eAAe,iBAAiB;AAExC,IAAa,mBAAb,MAA8B;CAC5B,YACE,AAAQA,mBACR,AAAQC,eACR,AAAQC,aACR,AAAQC,mBACR;EAJQ;EACA;EACA;EACA;;;;;;;;CASV,yBAAyB,MAA4C;EACnE,MAAM,EAAE,SAAS;EAIjB,IAAI,aAAa;AACjB,MAAI,eAAe,OACjB,cAAa,KAAK;EAGpB,IAAIC;AAGJ,MAAI,cAAc,KAAK,kBACrB,mBAAkB;WAGX,cAAc,KAAK,oBAAoB,KAAK,kBACnD,mBAAkB,KAAK;MAIvB,mBAAkB,KAAK,KAAK,aAAa,KAAK,kBAAkB;AAIlE,SAAO,KAAK,IAAI,iBAAiB,KAAK,YAAY;;;;;;;;CASpD,yBAAyB,MAA4C;EACnE,MAAM,EAAE,UAAU;AAClB,SAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C;;;;;;;;CASN,oBAAoB,MAAuC;EACzD,MAAM,EAAE,UAAU;AAClB,SAAO,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,GAAG,aAAc,IAAI;;;;;;;;;CAUtE,MAAM,0BACJ,MACiB;EACjB,MAAM,EAAE,aAAa;EACrB,MAAM,QAAQ;AACd,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,WAAW,OAAO,YAAY,GAAG,CAAC,SAAS,MAAM;GACvD,MAAM,WAAW,KAAK,KAAK,GAAG,QAAQ,EAAE,GAAG,WAAW,WAAW;AACjE,OAAI;AACF,UAAM,GAAG,SAAS,OAAO,UAAU,GAAG,UAAU,KAAK;YAC9C,OAAO;AAEd,QADkB,MACJ,SAAS,SACrB,QAAO;;;AAIb,aAAW,iBAAiB,qBAAqB;AACjD,QAAM,IAAI,OAAO;;;;;;;;;AAUrB,SAAgB,yBACd,MACA;CACA,MAAM,EAAE,UAAU;AAClB,QAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"metadata-manager.mjs","names":["client: S3","bucket: string","shouldUseExpirationTags: () => boolean","generateCompleteTag: (value: 'false' | 'true') => string | undefined"],"sources":["../../../../src/tus/stores/s3/metadata-manager.ts"],"sourcesContent":["import { TUS_RESUMABLE, Upload } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type { S3 } from '@aws-sdk/client-s3'\nimport type { TusUploadMetadata } from '../../../types'\n\ntype GenerateInfoKeyArgs = {\n id: string\n}\n\ntype GeneratePartKeyArgs = {\n id: string\n isIncomplete?: boolean\n}\n\ntype GetMetadataArgs = {\n id: string\n}\n\ntype SaveMetadataArgs = {\n upload: Upload\n uploadId: string\n}\n\ntype CompleteMetadataArgs = {\n upload: Upload\n}\n\nconst { log } = useErrorHandler()\n\nexport class S3MetadataManager {\n constructor(\n private client: S3,\n private bucket: string,\n private shouldUseExpirationTags: () => boolean,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n /**\n * Generates the S3 key for metadata info files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @returns The S3 key for the metadata file\n */\n generateInfoKey(args: GenerateInfoKeyArgs): string {\n const { id } = args\n return `${id}.info`\n }\n\n /**\n * Generates the S3 key for part files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @param args.isIncomplete - Whether this is an incomplete part\n * @returns The S3 key for the part file\n */\n generatePartKey(args: GeneratePartKeyArgs): string {\n const { id, isIncomplete = false } = args\n let key = id\n if (isIncomplete) {\n key += '.part'\n }\n\n // TODO: introduce ObjectPrefixing for parts and incomplete parts\n // ObjectPrefix is prepended to the name of each S3 object that is created\n // to store uploaded files. It can be used to create a pseudo-directory\n // structure in the bucket, e.g. \"path/to/my/uploads\".\n return key\n }\n\n /**\n * Retrieves upload metadata previously saved in `${file_id}.info`.\n * Always fetches fresh data from S3 to ensure correctness.\n * @param args - The function arguments\n * @param args.id - The file ID to retrieve metadata for\n * @returns Promise that resolves to the upload metadata\n */\n async getMetadata(args: GetMetadataArgs): Promise<TusUploadMetadata> {\n const { id } = args\n\n const { Body, Metadata } = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id }),\n })\n\n const file = JSON.parse((await Body?.transformToString()) as string)\n\n const parsedSize =\n file.size === undefined || file.size === null\n ? undefined\n : Number(file.size)\n\n const metadata: TusUploadMetadata = {\n file: new Upload({\n id,\n creation_date: file.creation_date,\n metadata: file.metadata,\n offset: Number.parseInt(file.offset, 10),\n size: Number.isFinite(parsedSize) ? parsedSize : undefined,\n storage: file.storage,\n }),\n 'tus-version': Metadata?.['tus-version'] as string,\n 'upload-id': (Metadata?.['upload-id'] || file['upload-id']) as string,\n }\n\n return metadata\n }\n\n /**\n * Saves upload metadata to a `${file_id}.info` file on S3.\n * The upload-id is stored in the S3 object metadata.\n * @param args - The function arguments\n * @param args.upload - The upload object containing metadata\n * @param args.uploadId - The upload ID for the multipart upload\n * @returns Promise that resolves when metadata is saved\n */\n async saveMetadata(args: SaveMetadataArgs): Promise<void> {\n const { upload, uploadId } = args\n log(MediaCloudLogs.S3_STORE_METADATA_SAVING)\n\n await this.client.putObject({\n Body: JSON.stringify(upload),\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id: upload.id }),\n Metadata: {\n 'tus-version': TUS_RESUMABLE,\n 'upload-id': uploadId,\n },\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_METADATA_SAVED)\n }\n\n /**\n * Completes metadata by updating it with completion tags\n * @param args - The function arguments\n * @param args.upload - The completed upload object\n * @returns Promise that resolves when metadata is updated\n */\n async completeMetadata(args: CompleteMetadataArgs): Promise<void> {\n const { upload } = args\n\n if (!this.shouldUseExpirationTags()) {\n return\n }\n\n const metadata = await this.getMetadata({ id: upload.id })\n const { 'upload-id': uploadId } = metadata\n\n await this.client.putObject({\n Body: JSON.stringify(upload),\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id: upload.id }),\n Metadata: {\n 'tus-version': TUS_RESUMABLE,\n 'upload-id': uploadId,\n },\n Tagging: this.generateCompleteTag('true'),\n })\n }\n}\n"],"mappings":";;;;;AA8BA,MAAM,EAAE,QAAQ,iBAAiB;AAEjC,IAAa,oBAAb,MAA+B;CAC7B,YACE,AAAQA,QACR,AAAQC,QACR,AAAQC,yBACR,AAAQC,qBACR;EAJQ;EACA;EACA;EACA;;;;;;;;CAQV,gBAAgB,MAAmC;EACjD,MAAM,EAAE,OAAO;AACf,SAAO,GAAG,GAAG;;;;;;;;;CAUf,gBAAgB,MAAmC;EACjD,MAAM,EAAE,IAAI,eAAe,UAAU;EACrC,IAAI,MAAM;AACV,MAAI,aACF,QAAO;AAOT,SAAO;;;;;;;;;CAUT,MAAM,YAAY,MAAmD;EACnE,MAAM,EAAE,OAAO;EAEf,MAAM,EAAE,MAAM,aAAa,MAAM,KAAK,OAAO,UAAU;GACrD,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,CAAC;GAClC,CAAC;EAEF,MAAM,OAAO,KAAK,MAAO,MAAM,MAAM,mBAAmB,CAAY;EAEpE,MAAM,aACJ,KAAK,SAAS,UAAa,KAAK,SAAS,OACrC,SACA,OAAO,KAAK,KAAK;AAevB,SAboC;GAClC,MAAM,IAAI,OAAO;IACf;IACA,eAAe,KAAK;IACpB,UAAU,KAAK;IACf,QAAQ,OAAO,SAAS,KAAK,QAAQ,GAAG;IACxC,MAAM,OAAO,SAAS,WAAW,GAAG,aAAa;IACjD,SAAS,KAAK;IACf,CAAC;GACF,eAAe,WAAW;GAC1B,aAAc,WAAW,gBAAgB,KAAK;GAC/C;;;;;;;;;;CAaH,MAAM,aAAa,MAAuC;EACxD,MAAM,EAAE,QAAQ,aAAa;AAC7B,MAAI,eAAe,yBAAyB;AAE5C,QAAM,KAAK,OAAO,UAAU;GAC1B,MAAM,KAAK,UAAU,OAAO;GAC5B,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,OAAO,IAAI,CAAC;GAC5C,UAAU;IACR,eAAe;IACf,aAAa;IACd;GACD,SAAS,KAAK,oBAAoB,QAAQ;GAC3C,CAAC;AACF,MAAI,eAAe,wBAAwB;;;;;;;;CAS7C,MAAM,iBAAiB,MAA2C;EAChE,MAAM,EAAE,WAAW;AAEnB,MAAI,CAAC,KAAK,yBAAyB,CACjC;EAIF,MAAM,EAAE,aAAa,aADJ,MAAM,KAAK,YAAY,EAAE,IAAI,OAAO,IAAI,CAAC;AAG1D,QAAM,KAAK,OAAO,UAAU;GAC1B,MAAM,KAAK,UAAU,OAAO;GAC5B,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,OAAO,IAAI,CAAC;GAC5C,UAAU;IACR,eAAe;IACf,aAAa;IACd;GACD,SAAS,KAAK,oBAAoB,OAAO;GAC1C,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parts-manager.mjs","names":["client: S3","bucket: string","minPartSize: number","partUploadSemaphore: Semaphore","metadataManager: S3MetadataManager","fileOperations: S3FileOperations","generateCompleteTag: (value: 'false' | 'true') => string | undefined","params: AWS.ListPartsCommandInput","params: AWS.UploadPartCommandInput","promises: Promise<void>[]","pendingChunkFilepath: null | string","permit: SemaphorePermit | undefined"],"sources":["../../../../src/tus/stores/s3/parts-manager.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport stream from 'node:stream'\n\nimport { NoSuchKey, NotFound, type S3 } from '@aws-sdk/client-s3'\nimport { StreamSplitter } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../../../types/errors'\n\nimport type { Readable } from 'node:stream'\nimport type AWS from '@aws-sdk/client-s3'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './file-operations'\nimport type { S3MetadataManager } from './metadata-manager'\nimport type { Semaphore, SemaphorePermit } from './semaphore'\n\ntype RetrievePartsArgs = {\n id: string\n partNumberMarker?: string\n}\n\ntype FinishMultipartUploadArgs = {\n metadata: TusUploadMetadata\n parts: Array<AWS.Part>\n}\n\ntype GetIncompletePartArgs = {\n id: string\n}\n\ntype GetIncompletePartSizeArgs = {\n id: string\n}\n\ntype DeleteIncompletePartArgs = {\n id: string\n}\n\ntype DownloadIncompletePartArgs = {\n id: string\n}\n\ntype UploadIncompletePartArgs = {\n id: string\n readStream: fs.ReadStream | Readable\n}\n\ntype UploadPartArgs = {\n metadata: TusUploadMetadata\n readStream: fs.ReadStream | Readable\n partNumber: number\n}\n\ntype UploadPartsArgs = {\n metadata: TusUploadMetadata\n readStream: stream.Readable\n currentPartNumber: number\n offset: number\n}\n\nconst { log, throwError } = useErrorHandler()\n\nexport class S3PartsManager {\n constructor(\n private client: S3,\n private bucket: string,\n private minPartSize: number,\n private partUploadSemaphore: Semaphore,\n private metadataManager: S3MetadataManager,\n private fileOperations: S3FileOperations,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n\n /**\n * Gets the number of complete parts/chunks already uploaded to S3.\n * Retrieves only consecutive parts.\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.partNumberMarker - Marker for pagination (optional)\n * @returns Promise that resolves to array of uploaded parts\n */\n async retrieveParts(args: RetrievePartsArgs): Promise<Array<AWS.Part>> {\n const { id, partNumberMarker } = args\n const metadata = await this.metadataManager.getMetadata({ id })\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.ListPartsCommandInput = {\n Bucket: this.bucket,\n Key: id,\n PartNumberMarker: partNumberMarker,\n UploadId: metadata['upload-id'],\n }\n\n const data = await this.client.listParts(params)\n\n let parts = data.Parts ?? []\n\n if (data.IsTruncated) {\n const rest = await this.retrieveParts({\n id,\n partNumberMarker: data.NextPartNumberMarker,\n })\n parts = [...parts, ...rest]\n }\n\n if (!partNumberMarker) {\n parts.sort((a, b) => (a.PartNumber || 0) - (b.PartNumber || 0))\n }\n\n return parts\n }\n\n /**\n * Completes a multipart upload on S3.\n * This is where S3 concatenates all the uploaded parts.\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.parts - Array of uploaded parts to complete\n * @returns Promise that resolves to the location URL (optional)\n */\n async finishMultipartUpload(\n args: FinishMultipartUploadArgs\n ): Promise<string | undefined> {\n const { metadata, parts } = args\n const params = {\n Bucket: this.bucket,\n Key: metadata.file.id,\n MultipartUpload: {\n Parts: parts.map((part) => {\n return {\n ETag: part.ETag,\n PartNumber: part.PartNumber,\n }\n }),\n },\n UploadId: metadata['upload-id'],\n }\n\n try {\n const result = await this.client.completeMultipartUpload(params)\n return result.Location\n } catch (_error) {\n throwError(MediaCloudErrors.TUS_UPLOAD_ERROR)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n }\n\n /**\n * Gets incomplete part from S3\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to readable stream or undefined if not found\n */\n async getIncompletePart(\n args: GetIncompletePartArgs\n ): Promise<Readable | undefined> {\n const { id } = args\n try {\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\n })\n return data.Body as Readable\n } catch (error) {\n if (error instanceof NoSuchKey) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Gets the size of an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to part size or undefined if not found\n */\n async getIncompletePartSize(\n args: GetIncompletePartSizeArgs\n ): Promise<number | undefined> {\n const { id } = args\n try {\n const data = await this.client.headObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\n })\n return data.ContentLength\n } catch (error) {\n if (error instanceof NotFound) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Deletes an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves when deletion is complete\n */\n async deleteIncompletePart(args: DeleteIncompletePartArgs): Promise<void> {\n const { id } = args\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\n })\n }\n\n /**\n * Downloads incomplete part to temporary file\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to incomplete part info or undefined if not found\n */\n async downloadIncompletePart(\n args: DownloadIncompletePartArgs\n ): Promise<IncompletePartInfo | undefined> {\n const { id } = args\n const incompletePart = await this.getIncompletePart({ id })\n\n if (!incompletePart) {\n return\n }\n const filePath = await this.fileOperations.generateUniqueTmpFileName({\n template: 'tus-s3-incomplete-part-',\n })\n\n try {\n let incompletePartSize = 0\n\n const byteCounterTransform = new stream.Transform({\n transform(chunk, _, callback) {\n incompletePartSize += chunk.length\n callback(null, chunk)\n },\n })\n\n // Write to temporary file\n await stream.promises.pipeline(\n incompletePart,\n byteCounterTransform,\n fs.createWriteStream(filePath)\n )\n\n const createReadStream = (options: { cleanUpOnEnd: boolean }) => {\n const fileReader = fs.createReadStream(filePath)\n\n if (options.cleanUpOnEnd) {\n fileReader.on('end', () => {\n fs.unlink(filePath, () => {})\n })\n\n fileReader.on('error', (err) => {\n fileReader.destroy(err)\n fs.unlink(filePath, () => {})\n })\n }\n\n return fileReader\n }\n\n return {\n createReader: createReadStream,\n path: filePath,\n size: incompletePartSize,\n }\n } catch (err) {\n fs.promises.rm(filePath).catch(() => {})\n throw err\n }\n }\n\n /**\n * Uploads an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.readStream - The stream to read data from\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadIncompletePart(args: UploadIncompletePartArgs): Promise<string> {\n const { id, readStream } = args\n const data = await this.client.putObject({\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n return data.ETag as string\n }\n\n /**\n * Uploads a single part\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.partNumber - The part number to upload\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadPart(args: UploadPartArgs): Promise<AWS.Part> {\n const { metadata, readStream, partNumber } = args\n const permit = await this.partUploadSemaphore.acquire()\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.UploadPartCommandInput = {\n Body: readStream,\n Bucket: this.bucket,\n Key: metadata.file.id,\n PartNumber: partNumber,\n UploadId: metadata['upload-id'],\n }\n\n try {\n const data = await this.client.uploadPart(params)\n return { ETag: data.ETag, PartNumber: partNumber }\n } catch (_error) {\n throwError(MediaCloudErrors.TUS_UPLOAD_ERROR)\n throw new Error() // This will never execute but satisfies TypeScript\n } finally {\n permit()\n }\n }\n\n /**\n * Uploads a stream to s3 using multiple parts\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.currentPartNumber - The current part number to start from\n * @param args.offset - The byte offset to start from\n * @returns Promise that resolves to the number of bytes uploaded\n */\n async uploadParts(args: UploadPartsArgs): Promise<number> {\n const { metadata, readStream, offset: initialOffset } = args\n let { currentPartNumber } = args\n let offset = initialOffset\n const size = metadata.file.size\n const promises: Promise<void>[] = []\n let pendingChunkFilepath: null | string = null\n let bytesUploaded = 0\n let permit: SemaphorePermit | undefined = undefined\n\n const splitterStream = new StreamSplitter({\n chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),\n directory: os.tmpdir(),\n })\n .on('beforeChunkStarted', async () => {\n permit = await this.partUploadSemaphore.acquire()\n })\n .on('chunkStarted', (filepath) => {\n pendingChunkFilepath = filepath\n })\n .on('chunkFinished', ({ path, size: partSize }) => {\n pendingChunkFilepath = null\n\n const acquiredPermit = permit\n const partNumber = currentPartNumber++\n\n offset += partSize\n\n const isFinalPart = size === offset\n\n const uploadChunk = async () => {\n try {\n // Only the first chunk of each PATCH request can prepend\n // an incomplete part (last chunk) from the previous request.\n const readable = fs.createReadStream(path)\n readable.on('error', function (error) {\n throw error\n })\n\n switch (true) {\n case partSize >= this.minPartSize || isFinalPart:\n await this.uploadPart({\n metadata,\n readStream: readable,\n partNumber,\n })\n break\n default:\n await this.uploadIncompletePart({\n id: metadata.file.id,\n readStream: readable,\n })\n break\n }\n\n bytesUploaded += partSize\n } catch (error) {\n // Destroy the splitter to stop processing more chunks\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n splitterStream.destroy(mappedError)\n throw mappedError\n } finally {\n fs.promises.rm(path).catch(function () {})\n acquiredPermit?.()\n }\n }\n\n const deferred = uploadChunk()\n\n promises.push(deferred)\n })\n .on('chunkError', () => {\n permit?.()\n })\n\n try {\n await stream.promises.pipeline(readStream, splitterStream)\n } catch (error) {\n if (pendingChunkFilepath !== null) {\n try {\n await fs.promises.rm(pendingChunkFilepath)\n } catch {\n log(MediaCloudLogs.S3_STORE_CHUNK_REMOVAL_FAILED)\n }\n }\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n promises.push(Promise.reject(mappedError))\n } finally {\n // Wait for all promises\n await Promise.allSettled(promises)\n // Reject the promise if any of the promises reject\n await Promise.all(promises)\n }\n\n return bytesUploaded\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"s3-store.mjs","names":["request: AWS.CreateMultipartUploadCommandInput","metadata: TusUploadMetadata","offset: number","lastError: Error | null","region: string"],"sources":["../../../../src/tus/stores/s3/s3-store.ts"],"sourcesContent":["import { NoSuchKey, NotFound, S3 } from '@aws-sdk/client-s3'\nimport { DataStore, ERRORS, TUS_RESUMABLE, Upload } from '@tus/utils'\nimport stream, { type Readable } from 'node:stream'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { Semaphore } from './semaphore'\nimport { S3ExpirationManager } from './expiration-manager'\nimport { S3FileOperations } from './file-operations'\nimport { S3MetadataManager } from './metadata-manager'\nimport { S3PartsManager } from './parts-manager'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { AWSError, S3StoreConfig, TusUploadMetadata } from '../../../types'\nimport { MediaCloudLogs } from '../../../types/errors'\n\nconst { log } = useErrorHandler()\n\nexport class S3Store extends DataStore {\n public client: S3\n public bucket: string\n public partSize = 8 * 1024 * 1024 // 8MB preferred part size\n public minPartSize = 5 * 1024 * 1024 // 5MB minimum part size\n public maxMultipartParts = 10_000\n public maxUploadSize = 5_497_558_138_880 as const // 5TiB\n public useTags = false\n public expirationPeriodInMilliseconds = 0\n protected acl?: string\n\n protected partUploadSemaphore: Semaphore\n protected metadataManager: S3MetadataManager\n protected fileOperations: S3FileOperations\n protected partsManager: S3PartsManager\n protected expirationManager: S3ExpirationManager\n protected customEndpoint: string\n\n constructor(options: S3StoreConfig) {\n super()\n const {\n maxMultipartParts,\n minPartSize,\n partSize,\n s3ClientConfig,\n maxConcurrentPartUploads,\n useTags,\n expirationPeriodInMilliseconds,\n } = options\n const { acl, bucket, ...restS3ClientConfig } = s3ClientConfig\n\n this.extensions = [\n 'creation',\n 'creation-with-upload',\n 'creation-defer-length',\n 'termination',\n 'expiration',\n ]\n\n this.bucket = bucket\n this.acl = acl\n this.client = new S3(restS3ClientConfig)\n this.customEndpoint = String(restS3ClientConfig.endpoint)\n\n this.partSize = partSize ?? this.partSize\n this.minPartSize = minPartSize ?? this.minPartSize\n this.maxMultipartParts = maxMultipartParts ?? this.maxMultipartParts\n\n this.useTags = useTags ?? this.useTags\n this.expirationPeriodInMilliseconds =\n expirationPeriodInMilliseconds ?? this.expirationPeriodInMilliseconds\n this.partUploadSemaphore = new Semaphore(maxConcurrentPartUploads ?? 60)\n\n // Initialize component managers\n this.metadataManager = new S3MetadataManager(\n this.client,\n this.bucket,\n this.shouldUseExpirationTags.bind(this),\n this.generateCompleteTag.bind(this)\n )\n\n this.fileOperations = new S3FileOperations(\n this.maxMultipartParts,\n this.maxUploadSize,\n this.minPartSize,\n this.partSize\n )\n\n this.partsManager = new S3PartsManager(\n this.client,\n this.bucket,\n this.minPartSize,\n this.partUploadSemaphore,\n this.metadataManager,\n this.fileOperations,\n this.generateCompleteTag.bind(this)\n )\n\n this.expirationManager = new S3ExpirationManager(\n this.client,\n this.bucket,\n this.expirationPeriodInMilliseconds,\n this.generateInfoKey.bind(this),\n this.generatePartKey.bind(this)\n )\n\n // Cleanup expired uploads when the store is initialized\n this.deleteExpired()\n }\n\n /**\n * Generate the key name for the info file\n * @param id - The upload ID\n * @returns The info file key\n */\n protected generateInfoKey(id: string): string {\n return `${id}.info`\n }\n\n /**\n * Generate the key name for a part file\n * @param id - The upload ID\n * @param isIncompletePart - Whether this is an incomplete part (default: false)\n * @returns The part file key\n */\n protected generatePartKey(\n id: string,\n isIncompletePart: boolean = false\n ): string {\n return isIncompletePart ? `${id}.part` : id\n }\n\n /**\n * Helper method to check if expiration tags should be used\n * @returns True if expiration tags should be used\n */\n protected shouldUseExpirationTags(): boolean {\n return this.expirationPeriodInMilliseconds !== 0 && this.useTags\n }\n\n /**\n * Generates a tag for marking complete/incomplete uploads\n * @param value - Either 'false' or 'true' to mark completion status\n * @returns The tag string or undefined if tags shouldn't be used\n */\n protected generateCompleteTag(value: 'false' | 'true'): string | undefined {\n if (!this.shouldUseExpirationTags()) {\n return undefined\n }\n return `Tus-Completed=${value}`\n }\n\n /**\n * Creates a multipart upload on S3 attaching any metadata to it.\n * Also, a `${file_id}.info` file is created which holds some information\n * about the upload itself like: `upload-id`, `upload-length`, etc.\n * @param upload - The upload object to create\n * @returns Promise that resolves to the created upload\n */\n public async create(upload: Upload): Promise<Upload> {\n log(MediaCloudLogs.S3_STORE_MULTIPART_INIT)\n const request: AWS.CreateMultipartUploadCommandInput = {\n Bucket: this.bucket,\n Key: upload.id,\n Metadata: { 'tus-version': TUS_RESUMABLE },\n }\n\n if (upload.metadata?.contentType) {\n request.ContentType = upload.metadata.contentType as string\n }\n\n if (upload.metadata?.cacheControl) {\n request.CacheControl = upload.metadata.cacheControl as string\n }\n\n if (this.acl) {\n request.ACL = this.acl as AWS.ObjectCannedACL\n }\n\n upload.creation_date = new Date().toISOString()\n\n const response = await this.client.createMultipartUpload(request)\n upload.storage = {\n type: 's3',\n bucket: this.bucket,\n path: response.Key as string,\n }\n await this.metadataManager.saveMetadata({\n upload,\n uploadId: response.UploadId as string,\n })\n log(MediaCloudLogs.S3_STORE_MULTIPART_CREATED)\n\n return upload\n }\n\n /**\n * Declares the length of the upload\n * @param file_id - The file ID\n * @param upload_length - The length of the upload\n * @returns Promise that resolves when length is declared\n */\n public async declareUploadLength(\n file_id: string,\n upload_length: number\n ): Promise<void> {\n const { file, 'upload-id': uploadId } =\n await this.metadataManager.getMetadata({ id: file_id })\n if (!file) {\n throw ERRORS.FILE_NOT_FOUND\n }\n\n file.size = upload_length\n\n await this.metadataManager.saveMetadata({ upload: file, uploadId })\n }\n\n /**\n * Writes `buffer` to the file specified by the upload's `id` at `offset`\n * @param readable - The readable stream to write\n * @param id - The upload ID\n * @param offset - The byte offset to write at\n * @returns Promise that resolves to the number of bytes written\n */\n public async write(\n readable: stream.Readable,\n id: string,\n offset: number\n ): Promise<number> {\n const metadata = await this.metadataManager.getMetadata({ id })\n\n // TUS sends PATCH requests with an `upload-offset` header.\n // Offset the write by the offset in the PATCH request.\n const calculatedOffset = this.fileOperations.calculateOffsetFromParts({\n parts: await this.partsManager.retrieveParts({ id }),\n })\n const offsetDiff = offset - calculatedOffset\n const requestedOffset = offset\n\n let finalReadable = readable\n\n if (offsetDiff < 0) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n // If the offset given in the PATCH request is higher than\n // the expected offset, we need to prepend an incomplete\n // part to the readable stream, if one exists.\n if (offsetDiff > 0) {\n const incompletePart = await this.partsManager.downloadIncompletePart({\n id,\n })\n\n if (!incompletePart) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n if (incompletePart.size !== offsetDiff) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n // Clear the incomplete part from S3 since it's going to be combined with the current request\n await this.partsManager.deleteIncompletePart({ id })\n\n // Adjust offset to account for the incomplete part\n offset = requestedOffset - incompletePart.size\n\n finalReadable = stream.Readable.from(\n (async function* () {\n yield* incompletePart.createReader({ cleanUpOnEnd: true })\n yield* readable\n })()\n )\n }\n\n const partNumber = this.fileOperations.calculatePartNumber({\n parts: await this.partsManager.retrieveParts({ id }),\n })\n\n const bytesUploaded = await this.partsManager.uploadParts({\n metadata,\n readStream: finalReadable,\n currentPartNumber: partNumber,\n offset,\n })\n\n // The size of the incomplete part should not be counted, because the\n // process of the incomplete part should be fully transparent to the user.\n const newOffset =\n requestedOffset + bytesUploaded - (offsetDiff > 0 ? offsetDiff : 0)\n\n // Check if the upload is complete\n if (metadata.file.size === newOffset) {\n try {\n const parts = await this.partsManager.retrieveParts({ id })\n await this.partsManager.finishMultipartUpload({ metadata, parts })\n\n // Update the metadata with completed state\n const completedUpload = new Upload({\n ...metadata.file,\n offset: newOffset,\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n\n await this.metadataManager.completeMetadata({ upload: completedUpload })\n } catch (error) {\n log(MediaCloudLogs.S3_STORE_UPLOAD_FAILED)\n throw error\n }\n }\n\n return newOffset\n }\n\n /**\n * Returns the current state of the upload, i.e how much data has been\n * uploaded and if the upload is complete.\n */\n public async getUpload(id: string): Promise<Upload> {\n let metadata: TusUploadMetadata\n\n try {\n metadata = await this.metadataManager.getMetadata({ id })\n } catch (error) {\n if (\n error instanceof NoSuchKey ||\n error instanceof NotFound ||\n (error as AWSError)?.Code === 'NotFound' ||\n (error as AWSError)?.Code === 'NoSuchKey'\n ) {\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n\n let offset: number\n\n try {\n const parts = await this.partsManager.retrieveParts({ id })\n offset = this.fileOperations.calculateOffsetFromParts({ parts })\n } catch (error) {\n // Check if the error is caused by the upload not being found. This happens\n // when the multipart upload has already been completed or aborted.\n if (\n (error as AWSError)?.Code === 'NoSuchUpload' ||\n (error as AWSError)?.Code === 'NoSuchKey'\n ) {\n return new Upload({\n ...metadata.file,\n metadata: metadata.file.metadata,\n offset: metadata.file.size as number,\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n }\n\n log(MediaCloudLogs.S3_STORE_RETRIEVE_PARTS_ERROR)\n throw error\n }\n\n const incompletePartSize = await this.partsManager.getIncompletePartSize({\n id,\n })\n\n return new Upload({\n ...metadata.file,\n offset: offset + (incompletePartSize ?? 0),\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n }\n\n /**\n * Reads the file specified by the upload's `id` and returns a readable stream\n */\n async read(id: string): Promise<Readable> {\n log(MediaCloudLogs.S3_STORE_READ_ATTEMPT)\n let retries = 3\n let lastError: Error | null = null\n\n while (retries > 0) {\n try {\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: id,\n })\n log(MediaCloudLogs.S3_STORE_READ_SUCCESS)\n return data.Body as Readable\n } catch (error) {\n log(MediaCloudLogs.S3_STORE_READ_RETRY)\n lastError = error as Error\n retries--\n\n if (retries > 0) {\n // Wait a bit before retrying, in case S3 needs time for consistency\n await new Promise((resolve) => setTimeout(resolve, 100))\n }\n }\n }\n\n log(MediaCloudLogs.S3_STORE_READ_FAILED)\n throw lastError || new Error(`Failed to read file ${id}`)\n }\n\n /**\n * Removes the file specified by the upload's `id`\n */\n public async remove(id: string): Promise<void> {\n try {\n const { 'upload-id': uploadId } = await this.metadataManager.getMetadata({\n id,\n })\n if (uploadId) {\n await this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: id,\n UploadId: uploadId,\n })\n }\n } catch (error) {\n if (\n (error as AWSError)?.code &&\n ['NoSuchKey', 'NoSuchUpload', 'NotFound'].includes(\n (error as AWSError).Code || ''\n )\n ) {\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n { Key: id },\n { Key: this.metadataManager.generateInfoKey({ id }) },\n {\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n }),\n },\n ],\n },\n })\n }\n\n /**\n * Combine all multipart uploads into a single object\n */\n public async completeMultipartUpload(id: string): Promise<Upload> {\n const metadata = await this.metadataManager.getMetadata({ id })\n const parts = await this.partsManager.retrieveParts({ id })\n\n const incompletePartInfo = await this.partsManager.downloadIncompletePart({\n id,\n })\n\n if (incompletePartInfo) {\n // Upload the incomplete part as a regular part\n await this.partsManager.uploadPart({\n metadata,\n readStream: incompletePartInfo.createReader({ cleanUpOnEnd: true }),\n partNumber: parts.length + 1,\n })\n\n // Remove the incomplete part\n await this.partsManager.deleteIncompletePart({ id })\n\n // Re-fetch parts to include the newly uploaded part\n const updatedParts = await this.partsManager.retrieveParts({ id })\n await this.partsManager.finishMultipartUpload({\n metadata,\n parts: updatedParts,\n })\n } else {\n await this.partsManager.finishMultipartUpload({ metadata, parts })\n }\n\n const completedUpload = new Upload({\n ...metadata.file,\n offset: metadata.file.size ?? 0,\n size: metadata.file.size ?? 0,\n storage: metadata.file.storage,\n })\n\n await this.metadataManager.completeMetadata({ upload: completedUpload })\n\n return completedUpload\n }\n\n /**\n * Get the full S3 URL for an uploaded file\n */\n public getUrl(id: string): string {\n // Use the custom endpoint if available\n if (this.customEndpoint) {\n return `${this.customEndpoint}/${this.bucket}/${id}`\n }\n\n // Fallback to standard AWS S3 URL format\n const regionConfig = this.client.config.region\n let region: string\n\n // If region is a function, we can't resolve it synchronously, so use a fallback\n if (typeof regionConfig === 'function') {\n region = 'us-east-1' // fallback for sync calls\n } else {\n region = regionConfig || 'us-east-1'\n }\n\n // Standard AWS S3 URL format\n if (region === 'us-east-1') {\n return `https://${this.bucket}.s3.amazonaws.com/${id}`\n } else {\n return `https://${this.bucket}.s3.${region}.amazonaws.com/${id}`\n }\n }\n\n /**\n * Deletes expired incomplete uploads.\n * Returns the number of deleted uploads.\n */\n async deleteExpired(): Promise<number> {\n return this.expirationManager.deleteExpired()\n }\n\n /**\n * Returns the expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationManager.getExpiration()\n }\n}\n"],"mappings":"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"}
|
/package/dist/components/{upload-manager/upload-manager.css → uploadManager/uploadManager.css}
RENAMED
|
File without changes
|