@maas/payload-plugin-media-cloud 0.0.19 → 0.0.20
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/components/upload-handler/upload-handler.d.mts +2 -2
- package/dist/components/upload-handler/upload-handler.mjs +9 -4
- package/dist/components/upload-handler/upload-handler.mjs.map +1 -1
- package/dist/types/errors.d.mts +2 -0
- package/dist/types/errors.mjs +2 -0
- package/dist/types/errors.mjs.map +1 -1
- package/dist/utils/tus.d.mts +2 -2
- package/dist/utils/tus.mjs +6 -4
- package/dist/utils/tus.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react0 from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as payload2 from "payload";
|
|
3
3
|
|
|
4
4
|
//#region src/components/upload-handler/upload-handler.d.ts
|
|
5
5
|
declare const UploadHandler: ({
|
|
@@ -11,7 +11,7 @@ declare const UploadHandler: ({
|
|
|
11
11
|
serverHandlerPath
|
|
12
12
|
}: {
|
|
13
13
|
children: react0.ReactNode;
|
|
14
|
-
collectionSlug:
|
|
14
|
+
collectionSlug: payload2.UploadCollectionSlug;
|
|
15
15
|
enabled?: boolean;
|
|
16
16
|
extra: Record<string, unknown>;
|
|
17
17
|
prefix?: string;
|
|
@@ -40,7 +40,7 @@ function parseFilename(uploadUrl) {
|
|
|
40
40
|
async function muxUpload(args) {
|
|
41
41
|
const { file, serverURL, apiRoute, mimeType, updateFilename } = args;
|
|
42
42
|
const filename = sanitizeFilename(file.name);
|
|
43
|
-
updateFilename(filename);
|
|
43
|
+
await updateFilename(filename);
|
|
44
44
|
try {
|
|
45
45
|
const { url, uploadId } = await (await fetch(`${serverURL}${apiRoute}/mux/upload`, {
|
|
46
46
|
body: JSON.stringify({
|
|
@@ -53,7 +53,10 @@ async function muxUpload(args) {
|
|
|
53
53
|
})).json();
|
|
54
54
|
const uploader = await upchunk.createUpload({
|
|
55
55
|
endpoint: url,
|
|
56
|
-
file
|
|
56
|
+
file: {
|
|
57
|
+
...file,
|
|
58
|
+
name: filename
|
|
59
|
+
},
|
|
57
60
|
chunkSize: MUX_CHUNK_SIZE
|
|
58
61
|
});
|
|
59
62
|
emitter.emit("addUpload", {
|
|
@@ -129,10 +132,11 @@ async function tusUpload(args) {
|
|
|
129
132
|
emitter.emit("uploadComplete", { filename: filename$1 });
|
|
130
133
|
fetch(`${serverURL}${apiRoute}/uploads/${filename$1}/process`);
|
|
131
134
|
},
|
|
132
|
-
onUploadUrlAvailable: function() {
|
|
135
|
+
onUploadUrlAvailable: async function() {
|
|
133
136
|
const filename$1 = parseFilename(upload?.url);
|
|
134
|
-
updateFilename(filename$1);
|
|
137
|
+
await updateFilename(filename$1);
|
|
135
138
|
emitter.emit("addUpload", { filename: filename$1 });
|
|
139
|
+
console.log("🚀 ~ filename:", filename$1, mimeType);
|
|
136
140
|
resolve({
|
|
137
141
|
filename: filename$1,
|
|
138
142
|
mimeType,
|
|
@@ -145,6 +149,7 @@ async function tusUpload(args) {
|
|
|
145
149
|
}
|
|
146
150
|
const UploadHandler = createClientUploadHandler({ handler: async function(args) {
|
|
147
151
|
const { serverURL, apiRoute, file, updateFilename } = args;
|
|
152
|
+
console.log("upload handler called", file);
|
|
148
153
|
try {
|
|
149
154
|
const mimeType = await getFileType(file) || file.type || "application/octet-stream";
|
|
150
155
|
if (!mimeType) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-handler.mjs","names":["filename","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 } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { isVideo, getFileType, sanitizeFilename } from '../../utils/file'\n\ninterface UploadArgs {\n serverURL: string\n apiRoute: string\n file: File\n mimeType: string\n updateFilename: (filename: string) => void\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 * Utility function to parse upload ID from URL\n * @param uploadUrl - The upload URL to parse\n * @returns The extracted upload ID or empty string if parsing fails\n */\nfunction parseFilename(uploadUrl?: string | null): string {\n if (!uploadUrl) {\n logError(MediaCloudErrors.UPLOAD_NO_URL.message)\n return ''\n }\n const url = new URL(uploadUrl)\n return url.pathname.split('/').pop() || ''\n}\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, updateFilename } = args\n\n const filename = sanitizeFilename(file.name)\n updateFilename(filename)\n\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, updateFilename } = args\n\n const filename = sanitizeFilename(file.name)\n const filetype = file.type\n const filesize = file.size.toString()\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 metadata: {\n filetype,\n filesize,\n filename,\n contentType: filetype,\n contentDisposition: 'inline',\n contentLength: filesize,\n },\n onError: function () {\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n toast.error('File upload failed')\n resolve(null)\n },\n onProgress: function (bytesUploaded, bytesTotal) {\n const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n const filename = parseFilename(upload?.url)\n emitter.emit('updateUpload', {\n filename,\n progress: percentage,\n })\n },\n onSuccess: function () {\n const filename = parseFilename(upload?.url)\n emitter.emit('uploadComplete', { filename })\n\n // Trigger post upload processing\n fetch(`${serverURL}${apiRoute}/uploads/${filename}/process`)\n },\n onUploadUrlAvailable: function () {\n const filename = parseFilename(upload?.url)\n updateFilename(filename)\n\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\nexport const UploadHandler = createClientUploadHandler({\n handler: async function (args) {\n const { serverURL, apiRoute, file, updateFilename } = args\n\n try {\n const mimeType =\n (await getFileType(file)) || file.type || 'application/octet-stream'\n\n if (!mimeType) {\n throwError(MediaCloudErrors.FILE_TYPE_UNKNOWN)\n return null\n }\n\n const isVideoFile = await isVideo(file)\n const uploadArgs: UploadArgs = {\n file,\n serverURL,\n apiRoute,\n mimeType,\n updateFilename,\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":";;;;;;;;;;;;AAkCA,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,SAAS,cAAc,WAAmC;AACxD,KAAI,CAAC,WAAW;AACd,WAAS,iBAAiB,cAAc,QAAQ;AAChD,SAAO;;AAGT,QADY,IAAI,IAAI,UAAU,CACnB,SAAS,MAAM,IAAI,CAAC,KAAK,IAAI;;;;;;;AAQ1C,eAAe,UAAU,MAAgD;CACvE,MAAM,EAAE,MAAM,WAAW,UAAU,UAAU,mBAAmB;
|
|
1
|
+
{"version":3,"file":"upload-handler.mjs","names":["filename","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 } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { isVideo, getFileType, sanitizeFilename } from '../../utils/file'\n\ninterface UploadArgs {\n serverURL: string\n apiRoute: string\n file: File\n mimeType: string\n updateFilename: (filename: string) => void\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 * Utility function to parse upload ID from URL\n * @param uploadUrl - The upload URL to parse\n * @returns The extracted upload ID or empty string if parsing fails\n */\nfunction parseFilename(uploadUrl?: string | null): string {\n if (!uploadUrl) {\n logError(MediaCloudErrors.UPLOAD_NO_URL.message)\n return ''\n }\n const url = new URL(uploadUrl)\n return url.pathname.split('/').pop() || ''\n}\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, updateFilename } = args\n\n // Sanitize filename, inform payload about final filename\n const filename = sanitizeFilename(file.name)\n await updateFilename(filename)\n\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 ...file,\n name: filename,\n },\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, updateFilename } = args\n\n const filename = sanitizeFilename(file.name)\n const filetype = file.type\n const filesize = file.size.toString()\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 metadata: {\n filetype,\n filesize,\n filename,\n contentType: filetype,\n contentDisposition: 'inline',\n contentLength: filesize,\n },\n onError: function () {\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n toast.error('File upload failed')\n resolve(null)\n },\n onProgress: function (bytesUploaded, bytesTotal) {\n const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n const filename = parseFilename(upload?.url)\n emitter.emit('updateUpload', {\n filename,\n progress: percentage,\n })\n },\n onSuccess: function () {\n const filename = parseFilename(upload?.url)\n emitter.emit('uploadComplete', { filename })\n\n // Trigger post upload processing\n fetch(`${serverURL}${apiRoute}/uploads/${filename}/process`)\n },\n onUploadUrlAvailable: async function () {\n // Get filename from upload URL\n // This ensures we have the final filename that is used in storage\n // Call updateFilename callback to inform payload about the final filename\n const filename = parseFilename(upload?.url)\n await updateFilename(filename)\n\n // Add upload to UI\n emitter.emit('addUpload', { filename })\n\n // Update collection entry\n // with filename, mimeType, and storage\n console.log('🚀 ~ filename:', filename, mimeType)\n resolve({\n filename,\n mimeType,\n storage: 's3',\n })\n },\n })\n\n upload.start()\n })\n}\n\nexport const UploadHandler = createClientUploadHandler({\n handler: async function (args) {\n const { serverURL, apiRoute, file, updateFilename } = args\n\n console.log('upload handler called', file)\n\n try {\n const mimeType =\n (await getFileType(file)) || file.type || 'application/octet-stream'\n\n if (!mimeType) {\n throwError(MediaCloudErrors.FILE_TYPE_UNKNOWN)\n return null\n }\n\n const isVideoFile = await isVideo(file)\n const uploadArgs: UploadArgs = {\n file,\n serverURL,\n apiRoute,\n mimeType,\n updateFilename,\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":"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"}
|
package/dist/types/errors.d.mts
CHANGED
|
@@ -106,6 +106,8 @@ declare const MediaCloudErrors: {
|
|
|
106
106
|
};
|
|
107
107
|
};
|
|
108
108
|
declare enum MediaCloudLogs {
|
|
109
|
+
S3_STORE_NO_FILE_FOUND = "No file found",
|
|
110
|
+
S3_STORE_FILE_FOUND = "File found",
|
|
109
111
|
S3_STORE_MULTIPART_INIT = "Initializing multipart upload",
|
|
110
112
|
S3_STORE_MULTIPART_CREATED = "Multipart upload created",
|
|
111
113
|
S3_STORE_UPLOAD_FAILED = "Failed to finish upload",
|
package/dist/types/errors.mjs
CHANGED
|
@@ -106,6 +106,8 @@ const MediaCloudErrors = {
|
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
108
|
let MediaCloudLogs = /* @__PURE__ */ function(MediaCloudLogs$1) {
|
|
109
|
+
MediaCloudLogs$1["S3_STORE_NO_FILE_FOUND"] = "No file found";
|
|
110
|
+
MediaCloudLogs$1["S3_STORE_FILE_FOUND"] = "File found";
|
|
109
111
|
MediaCloudLogs$1["S3_STORE_MULTIPART_INIT"] = "Initializing multipart upload";
|
|
110
112
|
MediaCloudLogs$1["S3_STORE_MULTIPART_CREATED"] = "Multipart upload created";
|
|
111
113
|
MediaCloudLogs$1["S3_STORE_UPLOAD_FAILED"] = "Failed to finish upload";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_METADATA_CACHE_CLEARED = 'Removing cached data',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_NO_FILE_FOUND = 'No file found',\n S3_STORE_FILE_FOUND = 'File found',\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_METADATA_CACHE_CLEARED = 'Removing cached data',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
package/dist/utils/tus.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
2
|
import { S3Store } from "../tus/stores/s3/s3-store.mjs";
|
|
3
3
|
import { Server } from "@tus/server";
|
|
4
|
-
import * as
|
|
4
|
+
import * as payload1 from "payload";
|
|
5
5
|
import { PayloadRequest } from "payload";
|
|
6
6
|
|
|
7
7
|
//#region src/utils/tus.d.ts
|
|
@@ -46,7 +46,7 @@ declare function createTusEndpoints(args: CreateTusEndpointsArgs): ({
|
|
|
46
46
|
method: "delete";
|
|
47
47
|
path: string;
|
|
48
48
|
} | {
|
|
49
|
-
handler:
|
|
49
|
+
handler: payload1.PayloadHandler;
|
|
50
50
|
method: "get";
|
|
51
51
|
path: string;
|
|
52
52
|
})[];
|
package/dist/utils/tus.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { MediaCloudErrors } from "../types/errors.mjs";
|
|
1
|
+
import { MediaCloudErrors, MediaCloudLogs } from "../types/errors.mjs";
|
|
2
2
|
import { useErrorHandler } from "../hooks/useErrorHandler.mjs";
|
|
3
3
|
import { generateUniqueFilename } from "./file.mjs";
|
|
4
4
|
import { getTusPostProcessorHandler } from "../endpoints/tusPostProcessorHandler.mjs";
|
|
5
5
|
import { Server } from "@tus/server";
|
|
6
6
|
|
|
7
7
|
//#region src/utils/tus.ts
|
|
8
|
-
const { logError } = useErrorHandler();
|
|
8
|
+
const { logError, log } = useErrorHandler();
|
|
9
9
|
function createTusServer(args) {
|
|
10
10
|
const { s3Store, pluginOptions } = args;
|
|
11
11
|
return new Server({
|
|
@@ -17,8 +17,10 @@ function createTusServer(args) {
|
|
|
17
17
|
if (await client.getObject({
|
|
18
18
|
Key: metadata?.filename ?? "",
|
|
19
19
|
Bucket: bucket
|
|
20
|
-
}).catch(() =>
|
|
21
|
-
|
|
20
|
+
}).catch(() => log(MediaCloudLogs.S3_STORE_NO_FILE_FOUND))) {
|
|
21
|
+
log(MediaCloudLogs.S3_STORE_FILE_FOUND);
|
|
22
|
+
return generateUniqueFilename(metadata?.filename ?? "");
|
|
23
|
+
} else return metadata.filename;
|
|
22
24
|
} catch (_error) {
|
|
23
25
|
logError(MediaCloudErrors.NAMING_FUNCTION_ERROR.message);
|
|
24
26
|
return metadata?.filename ?? generateUniqueFilename("");
|
package/dist/utils/tus.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tus.mjs","names":["TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"tus.mjs","names":["TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import { Server as TusServer } from '@tus/server'\n\nimport { MediaCloudLogs, MediaCloudErrors } from '../types/errors'\nimport { generateUniqueFilename } from '../utils/file'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\n\nimport { getTusPostProcessorHandler } from '../endpoints/tusPostProcessorHandler'\n\nimport type { S3Store } from '../tus/stores/s3/s3-store'\nimport type { PayloadRequest } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types'\n\nconst { logError, log } = useErrorHandler()\n\n/**\n * Creates a TUS server instance with S3 storage\n * @param args - The arguments for creating the TUS server\n * @returns A configured TusServer instance\n */\ninterface CreateTusServerArgs {\n s3Store: S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusServer(args: CreateTusServerArgs): TusServer {\n const { s3Store, pluginOptions } = args\n\n return new TusServer({\n datastore: s3Store,\n namingFunction: async (_req, metadata) => {\n try {\n const { client, bucket } = s3Store\n if (!metadata?.filename) {\n return generateUniqueFilename('')\n }\n\n // Check if a file with the same name already exists\n const file = await client\n .getObject({\n Key: metadata?.filename ?? '',\n Bucket: bucket,\n })\n .catch(() => log(MediaCloudLogs.S3_STORE_NO_FILE_FOUND))\n\n if (file) {\n log(MediaCloudLogs.S3_STORE_FILE_FOUND)\n return generateUniqueFilename(metadata?.filename ?? '')\n } else {\n return metadata.filename\n }\n } catch (_error) {\n logError(MediaCloudErrors.NAMING_FUNCTION_ERROR.message)\n return metadata?.filename ?? generateUniqueFilename('')\n }\n },\n path: '/api/uploads',\n respectForwardedHeaders: pluginOptions.s3.respectForwardedHeaders ?? true,\n })\n}\n\n/**\n * Creates TUS upload endpoints for file handling\n * @param tusServer - The TUS server instance\n * @returns An array of endpoint configurations\n */\ninterface CreateTusEndpointsArgs {\n tusServer: TusServer\n s3Store: S3Store\n}\n\nexport function createTusEndpoints(args: CreateTusEndpointsArgs) {\n const { tusServer, s3Store } = args\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 tusServer.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({ s3Store }),\n method: 'get' as const,\n path: '/uploads/:filename/process',\n },\n ]\n}\n"],"mappings":";;;;;;;AAYA,MAAM,EAAE,UAAU,QAAQ,iBAAiB;AAY3C,SAAgB,gBAAgB,MAAsC;CACpE,MAAM,EAAE,SAAS,kBAAkB;AAEnC,QAAO,IAAIA,OAAU;EACnB,WAAW;EACX,gBAAgB,OAAO,MAAM,aAAa;AACxC,OAAI;IACF,MAAM,EAAE,QAAQ,WAAW;AAC3B,QAAI,CAAC,UAAU,SACb,QAAO,uBAAuB,GAAG;AAWnC,QAPa,MAAM,OAChB,UAAU;KACT,KAAK,UAAU,YAAY;KAC3B,QAAQ;KACT,CAAC,CACD,YAAY,IAAI,eAAe,uBAAuB,CAAC,EAEhD;AACR,SAAI,eAAe,oBAAoB;AACvC,YAAO,uBAAuB,UAAU,YAAY,GAAG;UAEvD,QAAO,SAAS;YAEX,QAAQ;AACf,aAAS,iBAAiB,sBAAsB,QAAQ;AACxD,WAAO,UAAU,YAAY,uBAAuB,GAAG;;;EAG3D,MAAM;EACN,yBAAyB,cAAc,GAAG,2BAA2B;EACtE,CAAC;;AAaJ,SAAgB,mBAAmB,MAA8B;CAC/D,MAAM,EAAE,WAAW,YAAY;;;;;;CAO/B,SAAS,WAAW,KAAqB;AACvC,SAAO,UAAU,UAAU,IAAe;;AAG5C,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,EAAE,SAAS,CAAC;GAChD,QAAQ;GACR,MAAM;GACP;EACF"}
|