@maas/payload-plugin-media-cloud 0.0.37 → 0.0.39
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/handleUpload.mjs +9 -9
- package/dist/adapter/handleUpload.mjs.map +1 -1
- package/dist/adapter/staticHandler.mjs +1 -1
- package/dist/adapter/staticHandler.mjs.map +1 -1
- package/dist/adapter/storageAdapter.mjs +1 -1
- package/dist/collectionHooks/afterChange.mjs +14 -3
- package/dist/collectionHooks/afterChange.mjs.map +1 -1
- package/dist/collections/mediaCollection.mjs +2 -3
- package/dist/collections/mediaCollection.mjs.map +1 -1
- package/dist/components/uploadHandler/uploadHandler.mjs +117 -99
- package/dist/components/uploadHandler/uploadHandler.mjs.map +1 -1
- package/dist/components/uploadManager/uploadManager.mjs +2 -2
- package/dist/components/uploadManager/uploadManager.mjs.map +1 -1
- package/dist/endpoints/fileExistsHandler.mjs +3 -3
- package/dist/endpoints/fileExistsHandler.mjs.map +1 -1
- package/dist/endpoints/muxAssetHandler.mjs +2 -2
- package/dist/endpoints/muxAssetHandler.mjs.map +1 -1
- package/dist/endpoints/muxCreateUploadHandler.mjs +2 -2
- package/dist/endpoints/muxCreateUploadHandler.mjs.map +1 -1
- package/dist/fields/mux.mjs +0 -3
- package/dist/fields/mux.mjs.map +1 -1
- package/dist/tus/stores/s3/s3Store.mjs +15 -1
- package/dist/tus/stores/s3/s3Store.mjs.map +1 -1
- package/dist/types/errors.d.mts +8 -0
- package/dist/types/errors.mjs +8 -0
- package/dist/types/errors.mjs.map +1 -1
- package/dist/types/index.d.mts +5 -1
- package/dist/utils/defaultOptions.mjs +5 -1
- package/dist/utils/defaultOptions.mjs.map +1 -1
- package/dist/utils/file.mjs +1 -1
- package/dist/utils/file.mjs.map +1 -1
- package/package.json +7 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadManager.mjs","names":["c","_c","createContext","use","useState","useCallback","useEffect","useRef","Button","useMediaCloudEmitter","useErrorHandler","MediaCloudErrors","jsx","_jsx","jsxs","_jsxs","UploadManagerContext","showUploadManager","activeUploads","addUpload","updateUpload","UploadManagerProvider","args","$","children","setShowUploadManager","t0","Symbol","for","setActiveUploads","activeTab","setActiveTab","emitter","activeTabRef","activeUploadsRef","logError","t1","t2","current","t3","t4","t5","uploads","hasActiveUploads","some","_temp","hasProcessingUploads","_temp2","hasCompletedUploads","_temp3","checkAutoSwitchToCompleted","t6","t7","pollingUploads","filter","_temp4","length","pollAssets","pollingUpload","response","fetch","pollingUrl","uploadId","method","credentials","ok","data","json","ready","prev","updatedUploads","map","upload_3","upload","polling","progress","status","setTimeout","t8","UPLOAD_POLLING_ERROR","message","intervalId","setInterval","clearInterval","event","filename","error","TUS_UPLOAD_ERROR","prev_0","upload_4","onUploadError","t9","event_0","upload_5","prev_1","onAddUpload","t10","event_1","filename_0","prev_2","upload_6","undefined","onUpdateUpload","t11","event_2","prev_3","upload_7","onRemoveUpload","t12","event_3","upload_9","find","upload_8","prev_4","upload_10","prev_5","updatedUploads_0","upload_11","onUploadComplete","t13","t14","on","off","t15","args_0","filename_1","t16","polling_0","upload_12","prev_6","args_1","filename_2","progress_0","polling_1","prev_7","upload_13","T0","t17","t18","uploadingFiles","_temp5","processingFiles","_temp6","completedFiles","_temp7","t19","renderUploadList","args_2","uploads_0","_temp8","t20","closeUploadManager","hasPollingUploads","_temp9","t21","value","Provider","className","buttonStyle","icon","margin","onClick","size","_temp0","window","location","reload","upload_18","upload_17","Math","ceil","style","upload_16","upload_15","upload_14","upload_2","upload_1","upload_0","useUploadManagerContext"],"sources":["../../../src/components/uploadManager/uploadManager.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 './uploadManager.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":"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"}
|
|
1
|
+
{"version":3,"file":"uploadManager.mjs","names":["c","_c","createContext","use","useState","useCallback","useEffect","useRef","Button","useMediaCloudEmitter","useErrorHandler","MediaCloudErrors","jsx","_jsx","jsxs","_jsxs","UploadManagerContext","showUploadManager","activeUploads","addUpload","updateUpload","UploadManagerProvider","args","$","children","setShowUploadManager","t0","Symbol","for","setActiveUploads","activeTab","setActiveTab","emitter","activeTabRef","activeUploadsRef","logError","t1","t2","current","t3","t4","t5","uploads","hasActiveUploads","some","_temp","hasProcessingUploads","_temp2","hasCompletedUploads","_temp3","checkAutoSwitchToCompleted","t6","t7","pollingUploads","filter","_temp4","length","pollAssets","pollingUpload","response","fetch","pollingUrl","uploadId","method","credentials","ok","data","json","ready","prev","updatedUploads","map","upload_3","upload","polling","progress","status","setTimeout","t8","UPLOAD_POLLING_ERROR","message","intervalId","setInterval","clearInterval","event","filename","error","TUS_UPLOAD_ERROR","prev_0","upload_4","onUploadError","t9","event_0","upload_5","prev_1","onAddUpload","t10","event_1","filename_0","prev_2","upload_6","undefined","onUpdateUpload","t11","event_2","prev_3","upload_7","onRemoveUpload","t12","event_3","upload_9","find","upload_8","prev_4","upload_10","prev_5","updatedUploads_0","upload_11","onUploadComplete","t13","t14","on","off","t15","args_0","filename_1","t16","polling_0","upload_12","prev_6","args_1","filename_2","progress_0","polling_1","prev_7","upload_13","T0","t17","t18","uploadingFiles","_temp5","processingFiles","_temp6","completedFiles","_temp7","t19","renderUploadList","args_2","uploads_0","_temp8","t20","closeUploadManager","hasPollingUploads","_temp9","t21","value","Provider","className","buttonStyle","icon","margin","onClick","size","_temp0","window","location","reload","upload_18","upload_17","index","Math","ceil","style","upload_16","upload_15","upload_14","upload_2","upload_1","upload_0","useUploadManagerContext"],"sources":["../../../src/components/uploadManager/uploadManager.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 './uploadManager.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, index) => (\n <li key={`${upload.filename}-${index}`} 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":"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"}
|
|
@@ -16,11 +16,11 @@ function getFileExistsHandler(args) {
|
|
|
16
16
|
limit: 1,
|
|
17
17
|
pagination: false
|
|
18
18
|
});
|
|
19
|
-
if (docs.length > 0) return Response.json({ message: "File found [
|
|
19
|
+
if (docs.length > 0) return Response.json({ message: "File found [Payload]" }, { status: 200 });
|
|
20
20
|
const url = getS3Store().getUrl(filename);
|
|
21
21
|
if ((await fetch(url, { method: "HEAD" })).status === 200) return Response.json({ message: "File found [S3]" }, { status: 200 });
|
|
22
|
-
return Response
|
|
23
|
-
} catch (
|
|
22
|
+
return new Response(null, { status: 204 });
|
|
23
|
+
} catch (error) {
|
|
24
24
|
logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message);
|
|
25
25
|
return Response.json({ message: "Failed to process asset" }, { status: 500 });
|
|
26
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileExistsHandler.mjs","names":[],"sources":["../../src/endpoints/fileExistsHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface GetFileExistsHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getFileExistsHandler(\n args: GetFileExistsHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = 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,\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 [
|
|
1
|
+
{"version":3,"file":"fileExistsHandler.mjs","names":[],"sources":["../../src/endpoints/fileExistsHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface GetFileExistsHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getFileExistsHandler(\n args: GetFileExistsHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = 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,\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 = getS3Store().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 new Response(null, {\n status: 204,\n })\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":";;;;AAWA,SAAgB,qBACd,MACgB;CAChB,MAAM,EAAE,YAAY,eAAe;CACnC,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;IACA,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,YAAY,CAAC,OAAO,SAAS;AAGzC,QAFmB,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,CAAC,EAExC,WAAW,IACxB,QAAO,SAAS,KAAK,EAAE,SAAS,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGvE,UAAO,IAAI,SAAS,MAAM,EACxB,QAAQ,KACT,CAAC;WACK,OAAO;AACd,YAAS,iBAAiB,sBAAsB,QAAQ;AACxD,UAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -3,11 +3,11 @@ function getMuxAssetHandler(args) {
|
|
|
3
3
|
const { getMuxClient, collection } = args;
|
|
4
4
|
return async (req) => {
|
|
5
5
|
try {
|
|
6
|
-
const
|
|
6
|
+
const muxClient = getMuxClient();
|
|
7
7
|
const { query } = req;
|
|
8
8
|
const uploadId = query.upload_id;
|
|
9
9
|
if (!uploadId) return Response.json({ message: "Upload ID is required" }, { status: 400 });
|
|
10
|
-
const asset = (await
|
|
10
|
+
const asset = (await muxClient.video.assets.list({
|
|
11
11
|
limit: 1,
|
|
12
12
|
upload_id: uploadId
|
|
13
13
|
}))?.data[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muxAssetHandler.mjs","names":[],"sources":["../../src/endpoints/muxAssetHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\nimport type { PayloadHandler } from 'payload'\nimport type { StaticRenditions } from '../types'\n\ninterface GetMuxAssetHandlerArgs {\n getMuxClient: () => Mux\n collection: string\n}\n\nexport function getMuxAssetHandler(\n args: GetMuxAssetHandlerArgs\n): PayloadHandler {\n const { getMuxClient, collection } = args\n\n return async (req) => {\n try {\n const
|
|
1
|
+
{"version":3,"file":"muxAssetHandler.mjs","names":[],"sources":["../../src/endpoints/muxAssetHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\nimport type { PayloadHandler } from 'payload'\nimport type { StaticRenditions } from '../types'\n\ninterface GetMuxAssetHandlerArgs {\n getMuxClient: () => Mux\n collection: string\n}\n\nexport function getMuxAssetHandler(\n args: GetMuxAssetHandlerArgs\n): PayloadHandler {\n const { getMuxClient, collection } = args\n\n return async (req) => {\n try {\n const muxClient = getMuxClient()\n const { query } = req\n\n const uploadId = query.upload_id as string\n\n if (!uploadId) {\n return Response.json(\n { message: 'Upload ID is required' },\n { status: 400 }\n )\n }\n\n const assets = await muxClient.video.assets.list({\n limit: 1,\n upload_id: uploadId,\n })\n\n const asset = assets?.data[0]\n\n if (!asset) {\n return Response.json(\n { message: 'No asset found for the given upload ID' },\n { status: 404 }\n )\n }\n\n if (asset.status === 'ready') {\n const { payload } = req\n\n const { docs } = await payload.find({\n collection,\n where: {\n 'mux.uploadId': {\n equals: uploadId,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n if (docs.length > 0) {\n const { id } = docs[0]\n\n const width = Array.from(asset.tracks ?? []).reduce(\n (max, track) =>\n track.type === 'video' && track.max_width\n ? Math.max(max, track.max_width)\n : max,\n 0\n )\n\n const height = Array.from(asset.tracks ?? []).reduce(\n (max, track) =>\n track.type === 'video' && track.max_height\n ? Math.max(max, track.max_height)\n : max,\n 0\n )\n\n await payload.update({\n collection,\n id,\n data: {\n mux: {\n status: asset.status,\n assetId: asset.id,\n playbackId: asset.playback_ids?.[0]?.id,\n aspectRatio: asset.aspect_ratio,\n duration: asset.duration,\n tracks: asset.tracks,\n maxResolutionTier: asset.max_resolution_tier,\n videoQuality: asset.video_quality,\n staticRenditions: asset.static_renditions as StaticRenditions,\n },\n width,\n height,\n },\n })\n }\n\n return Response.json(\n {\n ready: asset.status === 'ready',\n asset,\n },\n { status: 200 }\n )\n } else {\n return Response.json(\n {\n ready: false,\n asset,\n },\n { status: 200 }\n )\n }\n } catch (_error) {\n return Response.json(\n { message: 'Failed to fetch Mux asset' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";AASA,SAAgB,mBACd,MACgB;CAChB,MAAM,EAAE,cAAc,eAAe;AAErC,QAAO,OAAO,QAAQ;AACpB,MAAI;GACF,MAAM,YAAY,cAAc;GAChC,MAAM,EAAE,UAAU;GAElB,MAAM,WAAW,MAAM;AAEvB,OAAI,CAAC,SACH,QAAO,SAAS,KACd,EAAE,SAAS,yBAAyB,EACpC,EAAE,QAAQ,KAAK,CAChB;GAQH,MAAM,SALS,MAAM,UAAU,MAAM,OAAO,KAAK;IAC/C,OAAO;IACP,WAAW;IACZ,CAAC,GAEoB,KAAK;AAE3B,OAAI,CAAC,MACH,QAAO,SAAS,KACd,EAAE,SAAS,0CAA0C,EACrD,EAAE,QAAQ,KAAK,CAChB;AAGH,OAAI,MAAM,WAAW,SAAS;IAC5B,MAAM,EAAE,YAAY;IAEpB,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;KAClC;KACA,OAAO,EACL,gBAAgB,EACd,QAAQ,UACT,EACF;KACD,OAAO;KACP,YAAY;KACb,CAAC;AAEF,QAAI,KAAK,SAAS,GAAG;KACnB,MAAM,EAAE,OAAO,KAAK;KAEpB,MAAM,QAAQ,MAAM,KAAK,MAAM,UAAU,EAAE,CAAC,CAAC,QAC1C,KAAK,UACJ,MAAM,SAAS,WAAW,MAAM,YAC5B,KAAK,IAAI,KAAK,MAAM,UAAU,GAC9B,KACN,EACD;KAED,MAAM,SAAS,MAAM,KAAK,MAAM,UAAU,EAAE,CAAC,CAAC,QAC3C,KAAK,UACJ,MAAM,SAAS,WAAW,MAAM,aAC5B,KAAK,IAAI,KAAK,MAAM,WAAW,GAC/B,KACN,EACD;AAED,WAAM,QAAQ,OAAO;MACnB;MACA;MACA,MAAM;OACJ,KAAK;QACH,QAAQ,MAAM;QACd,SAAS,MAAM;QACf,YAAY,MAAM,eAAe,IAAI;QACrC,aAAa,MAAM;QACnB,UAAU,MAAM;QAChB,QAAQ,MAAM;QACd,mBAAmB,MAAM;QACzB,cAAc,MAAM;QACpB,kBAAkB,MAAM;QACzB;OACD;OACA;OACD;MACF,CAAC;;AAGJ,WAAO,SAAS,KACd;KACE,OAAO,MAAM,WAAW;KACxB;KACD,EACD,EAAE,QAAQ,KAAK,CAChB;SAED,QAAO,SAAS,KACd;IACE,OAAO;IACP;IACD,EACD,EAAE,QAAQ,KAAK,CAChB;WAEI,QAAQ;AACf,UAAO,SAAS,KACd,EAAE,SAAS,6BAA6B,EACxC,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -12,9 +12,9 @@ function getMuxCreateUploadHandler(args) {
|
|
|
12
12
|
throw new Error();
|
|
13
13
|
}
|
|
14
14
|
const { filename } = req.json ? await req.json() : "";
|
|
15
|
-
const
|
|
15
|
+
const muxClient = getMuxClient();
|
|
16
16
|
const assetOptions = pluginOptions.mux?.assetOptions || {};
|
|
17
|
-
const upload = await
|
|
17
|
+
const upload = await muxClient.video.uploads.create({
|
|
18
18
|
cors_origin: "*",
|
|
19
19
|
new_asset_settings: {
|
|
20
20
|
playback_policies: ["public"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muxCreateUploadHandler.mjs","names":[],"sources":["../../src/endpoints/muxCreateUploadHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\
|
|
1
|
+
{"version":3,"file":"muxCreateUploadHandler.mjs","names":[],"sources":["../../src/endpoints/muxCreateUploadHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport type { PayloadHandler } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types'\n\ninterface GetMuxCreateUploadHandlerArgs {\n getMuxClient: () => Mux\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function getMuxCreateUploadHandler(\n args: GetMuxCreateUploadHandlerArgs\n): PayloadHandler {\n const { getMuxClient, pluginOptions } = args\n\n return async (req) => {\n const { throwError, logError } = useErrorHandler()\n\n try {\n if (!req.json) {\n throwError(MediaCloudErrors.MUX_REQUEST_NO_JSON)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n const body = req.json ? await req.json() : ''\n const { filename } = body\n const muxClient = getMuxClient()\n const assetOptions = pluginOptions.mux?.assetOptions || {}\n const upload = await muxClient.video.uploads.create({\n cors_origin: '*',\n new_asset_settings: {\n playback_policies: ['public'],\n meta: {\n title: filename,\n },\n ...assetOptions,\n },\n test: pluginOptions.mux?.testMode ?? false,\n })\n return Response.json({ url: upload.url, uploadId: upload.id })\n } catch (_error) {\n logError(MediaCloudErrors.MUX_CREATE_UPLOAD_ERROR.message)\n return Response.json(\n { message: 'Failed to create upload' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";;;;AAYA,SAAgB,0BACd,MACgB;CAChB,MAAM,EAAE,cAAc,kBAAkB;AAExC,QAAO,OAAO,QAAQ;EACpB,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,MAAI;AACF,OAAI,CAAC,IAAI,MAAM;AACb,eAAW,iBAAiB,oBAAoB;AAChD,UAAM,IAAI,OAAO;;GAGnB,MAAM,EAAE,aADK,IAAI,OAAO,MAAM,IAAI,MAAM,GAAG;GAE3C,MAAM,YAAY,cAAc;GAChC,MAAM,eAAe,cAAc,KAAK,gBAAgB,EAAE;GAC1D,MAAM,SAAS,MAAM,UAAU,MAAM,QAAQ,OAAO;IAClD,aAAa;IACb,oBAAoB;KAClB,mBAAmB,CAAC,SAAS;KAC7B,MAAM,EACJ,OAAO,UACR;KACD,GAAG;KACJ;IACD,MAAM,cAAc,KAAK,YAAY;IACtC,CAAC;AACF,UAAO,SAAS,KAAK;IAAE,KAAK,OAAO;IAAK,UAAU,OAAO;IAAI,CAAC;WACvD,QAAQ;AACf,YAAS,iBAAiB,wBAAwB,QAAQ;AAC1D,UAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB"}
|
package/dist/fields/mux.mjs
CHANGED
package/dist/fields/mux.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux.mjs","names":["muxField: GroupField"],"sources":["../../src/fields/mux.ts"],"sourcesContent":["import type { GroupField } from 'payload'\n\nexport const muxField: GroupField = {\n name: 'mux',\n label: 'Mux',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableBulkEdit: true,\n disableListFilter: true,\n readOnly: true,\n condition: (data) => {\n return data.storage === 'mux'\n },\n },\n fields: [\n {\n name: 'preview',\n type: 'ui',\n admin: {\n
|
|
1
|
+
{"version":3,"file":"mux.mjs","names":["muxField: GroupField"],"sources":["../../src/fields/mux.ts"],"sourcesContent":["import type { GroupField } from 'payload'\n\nexport const muxField: GroupField = {\n name: 'mux',\n label: 'Mux',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableBulkEdit: true,\n disableListFilter: true,\n readOnly: true,\n condition: (data) => {\n return data.storage === 'mux'\n },\n },\n fields: [\n {\n name: 'preview',\n type: 'ui',\n admin: {\n disableListColumn: true,\n components: {\n Field: '@maas/payload-plugin-media-cloud/components#MuxPreview',\n },\n },\n },\n {\n name: 'status',\n label: 'Status',\n type: 'text',\n },\n {\n name: 'uploadId',\n label: 'Upload ID',\n type: 'text',\n },\n {\n name: 'assetId',\n label: 'Asset ID',\n type: 'text',\n },\n {\n name: 'playbackId',\n label: 'Playback ID',\n type: 'text',\n },\n {\n name: 'aspectRatio',\n label: 'Aspect Ratio',\n type: 'text',\n },\n {\n name: 'duration',\n label: 'Duration',\n type: 'number',\n },\n {\n name: 'tracks',\n label: 'Tracks',\n type: 'json',\n typescriptSchema: [\n ({ jsonSchema }) => ({\n ...jsonSchema,\n type: 'array',\n items: [\n {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n enum: ['audio', 'video'],\n },\n id: {\n type: 'string',\n },\n duration: {\n type: 'number',\n },\n primary: {\n type: 'boolean',\n },\n max_channels: {\n type: 'number',\n },\n max_channel_layout: {\n type: 'string',\n },\n max_width: {\n type: 'number',\n },\n max_height: {\n type: 'number',\n },\n max_frame_rate: {\n type: 'number',\n },\n },\n required: [\n 'type',\n 'id',\n 'duration',\n 'primary',\n 'max_channels',\n 'max_channel_layout',\n 'max_width',\n 'max_height',\n 'max_frame_rate',\n ],\n },\n ],\n }),\n ],\n },\n {\n name: 'maxResolutionTier',\n label: 'Max Resolution Tier',\n type: 'text',\n },\n {\n name: 'videoQuality',\n label: 'Video Quality',\n type: 'text',\n },\n {\n name: 'staticRenditions',\n label: 'Static Renditions',\n type: 'json',\n typescriptSchema: [\n ({ jsonSchema }) => ({\n ...jsonSchema,\n type: 'object',\n properties: {\n files: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n },\n status: {\n type: 'string',\n },\n resolution: {\n type: 'string',\n },\n name: {\n type: 'string',\n },\n id: {\n type: 'string',\n },\n ext: {\n type: 'string',\n },\n width: {\n type: 'number',\n },\n height: {\n type: 'number',\n },\n filesize: {\n type: 'string',\n },\n bitrate: {\n type: 'number',\n },\n },\n },\n required: [\n 'type',\n 'status',\n 'resolution',\n 'name',\n 'id',\n 'ext',\n 'width',\n 'height',\n 'filesize',\n 'bitrate',\n ],\n },\n },\n required: ['files'],\n }),\n ],\n },\n ],\n}\n"],"mappings":";AAEA,MAAaA,WAAuB;CAClC,MAAM;CACN,OAAO;CACP,MAAM;CACN,OAAO;EACL,mBAAmB;EACnB,iBAAiB;EACjB,mBAAmB;EACnB,UAAU;EACV,YAAY,SAAS;AACnB,UAAO,KAAK,YAAY;;EAE3B;CACD,QAAQ;EACN;GACE,MAAM;GACN,MAAM;GACN,OAAO;IACL,mBAAmB;IACnB,YAAY,EACV,OAAO,0DACR;IACF;GACF;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACN,kBAAkB,EACf,EAAE,kBAAkB;IACnB,GAAG;IACH,MAAM;IACN,OAAO,CACL;KACE,MAAM;KACN,YAAY;MACV,MAAM;OACJ,MAAM;OACN,MAAM,CAAC,SAAS,QAAQ;OACzB;MACD,IAAI,EACF,MAAM,UACP;MACD,UAAU,EACR,MAAM,UACP;MACD,SAAS,EACP,MAAM,WACP;MACD,cAAc,EACZ,MAAM,UACP;MACD,oBAAoB,EAClB,MAAM,UACP;MACD,WAAW,EACT,MAAM,UACP;MACD,YAAY,EACV,MAAM,UACP;MACD,gBAAgB,EACd,MAAM,UACP;MACF;KACD,UAAU;MACR;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KACF,CACF;IACF,EACF;GACF;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACN,kBAAkB,EACf,EAAE,kBAAkB;IACnB,GAAG;IACH,MAAM;IACN,YAAY,EACV,OAAO;KACL,MAAM;KACN,OAAO;MACL,MAAM;MACN,YAAY;OACV,MAAM,EACJ,MAAM,UACP;OACD,QAAQ,EACN,MAAM,UACP;OACD,YAAY,EACV,MAAM,UACP;OACD,MAAM,EACJ,MAAM,UACP;OACD,IAAI,EACF,MAAM,UACP;OACD,KAAK,EACH,MAAM,UACP;OACD,OAAO,EACL,MAAM,UACP;OACD,QAAQ,EACN,MAAM,UACP;OACD,UAAU,EACR,MAAM,UACP;OACD,SAAS,EACP,MAAM,UACP;OACF;MACF;KACD,UAAU;MACR;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KACF,EACF;IACD,UAAU,CAAC,QAAQ;IACpB,EACF;GACF;EACF;CACF"}
|
|
@@ -7,6 +7,8 @@ import { S3MetadataManager } from "./metadataManager.mjs";
|
|
|
7
7
|
import { S3PartsManager } from "./partsManager.mjs";
|
|
8
8
|
import stream from "node:stream";
|
|
9
9
|
import { NoSuchKey, NotFound, S3 } from "@aws-sdk/client-s3";
|
|
10
|
+
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
|
11
|
+
import { Agent } from "https";
|
|
10
12
|
import { DataStore, ERRORS, TUS_RESUMABLE, Upload } from "@tus/utils";
|
|
11
13
|
|
|
12
14
|
//#region src/tus/stores/s3/s3Store.ts
|
|
@@ -29,9 +31,21 @@ var S3Store = class extends DataStore {
|
|
|
29
31
|
"termination",
|
|
30
32
|
"expiration"
|
|
31
33
|
];
|
|
34
|
+
const mappedS3ClientConfig = {
|
|
35
|
+
...restS3ClientConfig,
|
|
36
|
+
requestHandler: new NodeHttpHandler({
|
|
37
|
+
connectionTimeout: 6e4,
|
|
38
|
+
requestTimeout: 3e5,
|
|
39
|
+
httpsAgent: new Agent({
|
|
40
|
+
maxSockets: 1e3,
|
|
41
|
+
keepAlive: true,
|
|
42
|
+
keepAliveMsecs: 6e4
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
};
|
|
32
46
|
this.bucket = bucket;
|
|
33
47
|
this.acl = acl;
|
|
34
|
-
this.client = new S3(
|
|
48
|
+
this.client = new S3(mappedS3ClientConfig);
|
|
35
49
|
this.customEndpoint = String(restS3ClientConfig.endpoint);
|
|
36
50
|
this.partSize = partSize ?? this.partSize;
|
|
37
51
|
this.minPartSize = minPartSize ?? this.minPartSize;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3Store.mjs","names":["request: AWS.CreateMultipartUploadCommandInput","metadata: TusUploadMetadata","offset: number","lastError: Error | null","region: string"],"sources":["../../../../src/tus/stores/s3/s3Store.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 './expirationManager'\nimport { S3FileOperations } from './fileOperations'\nimport { S3MetadataManager } from './metadataManager'\nimport { S3PartsManager } from './partsManager'\n\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { AWSError, S3StoreConfig, TusUploadMetadata } from '../../../types'\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.metadataManager.generateInfoKey.bind(this.metadataManager),\n this.metadataManager.generatePartKey.bind(this.metadataManager)\n )\n\n // Cleanup expired uploads when the store is initialized\n this.deleteExpired()\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 * @return Promise that resolves to the created upload object with storage information\n */\n public async create(upload: Upload): Promise<Upload> {\n log(MediaCloudLogs.S3_STORE_MULTIPART_INIT)\n\n const request: AWS.CreateMultipartUploadCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: upload.id,\n prefix: upload.metadata?.prefix ?? undefined,\n }),\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\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 * 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 * @return Promise that resolves to the new offset after writing the data\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 * @param id - The upload ID to retrieve\n * @returns Promise that resolves to the upload object with current offset and storage information\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 * @param id - The upload ID to read\n * @returns Promise that resolves to a readable stream of the file's contents\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 *\n * Moves the file specified by its `oldKey` to `newKey`.\n * @param oldKey - The current S3 key of the file to be moved\n * @param newKey - The new S3 key to move the file to\n * @return Promise that resolves when the file has been successfully moved, or rejects with an error if the move operation fails\n */\n public async copy(oldKey: string, newKey: string): Promise<void> {\n try {\n await this.client.copyObject({\n Bucket: this.bucket,\n CopySource: encodeURI(`${this.bucket}/${oldKey}`),\n Key: newKey,\n })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: oldKey,\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 as Error\n }\n }\n\n /**\n * Removes files specified by the upload’s `id`\n * @param id - The upload ID to remove\n * @returns Promise that resolves when the file and its metadata have been removed\n */\n public async remove(id: string): Promise<void> {\n try {\n const { 'upload-id': uploadId, file } =\n await this.metadataManager.getMetadata({ id })\n if (uploadId) {\n await this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: id,\n UploadId: uploadId,\n })\n }\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n },\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\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\n /**\n * Removes the .info file specified by the upload’s `id`\n * @param id - The upload ID to clean up\n * @returns Promise that resolves when the metadata file has been removed\n */\n public async cleanup(id: string): Promise<void> {\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\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\n /**\n * Combine all multipart uploads into a single object\n * @param id - The upload ID to complete\n * @returns Promise that resolves to the completed upload object with storage information\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 * @param id - The upload ID to get the URL for\n * @returns The full URL to access the uploaded file on S3\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 * @returns Promise that resolves to 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 * @return The expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationManager.getExpiration()\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"s3Store.mjs","names":["mappedS3ClientConfig: AWS.S3ClientConfig","HttpsAgent","request: AWS.CreateMultipartUploadCommandInput","metadata: TusUploadMetadata","offset: number","lastError: Error | null","region: string"],"sources":["../../../../src/tus/stores/s3/s3Store.ts"],"sourcesContent":["import { NoSuchKey, NotFound, S3 } from '@aws-sdk/client-s3'\nimport { NodeHttpHandler } from '@smithy/node-http-handler'\nimport { Agent as HttpsAgent } from 'https'\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 './expirationManager'\nimport { S3FileOperations } from './fileOperations'\nimport { S3MetadataManager } from './metadataManager'\nimport { S3PartsManager } from './partsManager'\n\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { AWSError, S3StoreConfig, TusUploadMetadata } from '../../../types'\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 const mappedS3ClientConfig: AWS.S3ClientConfig = {\n ...restS3ClientConfig,\n requestHandler: new NodeHttpHandler({\n connectionTimeout: 60000,\n requestTimeout: 300000,\n httpsAgent: new HttpsAgent({\n maxSockets: 1000,\n keepAlive: true,\n keepAliveMsecs: 60000,\n }),\n }),\n }\n\n this.bucket = bucket\n this.acl = acl\n this.client = new S3(mappedS3ClientConfig)\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.metadataManager.generateInfoKey.bind(this.metadataManager),\n this.metadataManager.generatePartKey.bind(this.metadataManager)\n )\n\n // Cleanup expired uploads when the store is initialized\n this.deleteExpired()\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 * @return Promise that resolves to the created upload object with storage information\n */\n public async create(upload: Upload): Promise<Upload> {\n log(MediaCloudLogs.S3_STORE_MULTIPART_INIT)\n\n const request: AWS.CreateMultipartUploadCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: upload.id,\n prefix: upload.metadata?.prefix ?? undefined,\n }),\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\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 * 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 * @return Promise that resolves to the new offset after writing the data\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 * @param id - The upload ID to retrieve\n * @returns Promise that resolves to the upload object with current offset and storage information\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 * @param id - The upload ID to read\n * @returns Promise that resolves to a readable stream of the file's contents\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 *\n * Moves the file specified by its `oldKey` to `newKey`.\n * @param oldKey - The current S3 key of the file to be moved\n * @param newKey - The new S3 key to move the file to\n * @return Promise that resolves when the file has been successfully moved, or rejects with an error if the move operation fails\n */\n public async copy(oldKey: string, newKey: string): Promise<void> {\n try {\n await this.client.copyObject({\n Bucket: this.bucket,\n CopySource: encodeURI(`${this.bucket}/${oldKey}`),\n Key: newKey,\n })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: oldKey,\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 as Error\n }\n }\n\n /**\n * Removes files specified by the upload’s `id`\n * @param id - The upload ID to remove\n * @returns Promise that resolves when the file and its metadata have been removed\n */\n public async remove(id: string): Promise<void> {\n try {\n const { 'upload-id': uploadId, file } =\n await this.metadataManager.getMetadata({ id })\n if (uploadId) {\n await this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: id,\n UploadId: uploadId,\n })\n }\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n },\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\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\n /**\n * Removes the .info file specified by the upload’s `id`\n * @param id - The upload ID to clean up\n * @returns Promise that resolves when the metadata file has been removed\n */\n public async cleanup(id: string): Promise<void> {\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\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\n /**\n * Combine all multipart uploads into a single object\n * @param id - The upload ID to complete\n * @returns Promise that resolves to the completed upload object with storage information\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 * @param id - The upload ID to get the URL for\n * @returns The full URL to access the uploaded file on S3\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 * @returns Promise that resolves to 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 * @return The expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationManager.getExpiration()\n }\n}\n"],"mappings":"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"}
|
package/dist/types/errors.d.mts
CHANGED
|
@@ -80,6 +80,10 @@ declare const MediaCloudErrors: {
|
|
|
80
80
|
readonly message: "A collection must be specified in the plugin options";
|
|
81
81
|
readonly errorCode: 400;
|
|
82
82
|
};
|
|
83
|
+
readonly THUMBNAIL_GENERATION_ERROR: {
|
|
84
|
+
readonly message: "Error generating thumbnail URL";
|
|
85
|
+
readonly errorCode: 500;
|
|
86
|
+
};
|
|
83
87
|
readonly FILE_TYPE_UNKNOWN: {
|
|
84
88
|
readonly message: "Unable to determine file type";
|
|
85
89
|
readonly errorCode: 400;
|
|
@@ -128,6 +132,10 @@ declare const MediaCloudErrors: {
|
|
|
128
132
|
readonly message: "Polling error for upload";
|
|
129
133
|
readonly errorCode: 500;
|
|
130
134
|
};
|
|
135
|
+
readonly UPLOAD_QUEUE_NOT_INITIALIZED: {
|
|
136
|
+
readonly message: "Upload queue not initialized";
|
|
137
|
+
readonly errorCode: 500;
|
|
138
|
+
};
|
|
131
139
|
readonly PLUGIN_NOT_CONFIGURED: {
|
|
132
140
|
readonly message: "Payload Media Cloud plugin is not configured";
|
|
133
141
|
readonly errorCode: 500;
|
package/dist/types/errors.mjs
CHANGED
|
@@ -80,6 +80,10 @@ const MediaCloudErrors = {
|
|
|
80
80
|
message: "A collection must be specified in the plugin options",
|
|
81
81
|
errorCode: 400
|
|
82
82
|
},
|
|
83
|
+
THUMBNAIL_GENERATION_ERROR: {
|
|
84
|
+
message: "Error generating thumbnail URL",
|
|
85
|
+
errorCode: 500
|
|
86
|
+
},
|
|
83
87
|
FILE_TYPE_UNKNOWN: {
|
|
84
88
|
message: "Unable to determine file type",
|
|
85
89
|
errorCode: 400
|
|
@@ -128,6 +132,10 @@ const MediaCloudErrors = {
|
|
|
128
132
|
message: "Polling error for upload",
|
|
129
133
|
errorCode: 500
|
|
130
134
|
},
|
|
135
|
+
UPLOAD_QUEUE_NOT_INITIALIZED: {
|
|
136
|
+
message: "Upload queue not initialized",
|
|
137
|
+
errorCode: 500
|
|
138
|
+
},
|
|
131
139
|
PLUGIN_NOT_CONFIGURED: {
|
|
132
140
|
message: "Payload Media Cloud plugin is not configured",
|
|
133
141
|
errorCode: 500
|
|
@@ -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_RENAME_ERROR: {\n message: 'Error renaming file in S3',\n errorCode: 500,\n },\n S3_MOVE_ERROR: {\n message: 'Error moving file in S3',\n errorCode: 500,\n },\n S3_PREFIX_ERROR: {\n message: 'Error updating S3 prefix',\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 TUS_CLEANUP_ERROR: {\n message: 'Error during cleanup',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n COLLECTION_REQUIRED: {\n message: 'A collection must be specified in the plugin options',\n errorCode: 400,\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 FILE_TYPE_UNSUPPORTED_ERROR: {\n message: 'Unsupported file type',\n errorCode: 400,\n },\n FILE_TYPE_NOT_ALLOWED: {\n message: 'File type not allowed',\n errorCode: 400,\n },\n FILE_SIZE_EXCEEDED: {\n message: 'File size exceeds the maximum allowed limit',\n errorCode: 400,\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\n // HOOK Errors\n FOLDER_FETCH_ERROR: {\n message: 'Error fetching folder data',\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_FILE_FOUND = '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_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,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,eAAe;EACb,SAAS;EACT,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;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,6BAA6B;EAC3B,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,oBAAoB;EAClB,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;CAGD,oBAAoB;EAClB,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_RENAME_ERROR: {\n message: 'Error renaming file in S3',\n errorCode: 500,\n },\n S3_MOVE_ERROR: {\n message: 'Error moving file in S3',\n errorCode: 500,\n },\n S3_PREFIX_ERROR: {\n message: 'Error updating S3 prefix',\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 TUS_CLEANUP_ERROR: {\n message: 'Error during cleanup',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n COLLECTION_REQUIRED: {\n message: 'A collection must be specified in the plugin options',\n errorCode: 400,\n },\n THUMBNAIL_GENERATION_ERROR: {\n message: 'Error generating thumbnail URL',\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 FILE_TYPE_UNSUPPORTED_ERROR: {\n message: 'Unsupported file type',\n errorCode: 400,\n },\n FILE_TYPE_NOT_ALLOWED: {\n message: 'File type not allowed',\n errorCode: 400,\n },\n FILE_SIZE_EXCEEDED: {\n message: 'File size exceeds the maximum allowed limit',\n errorCode: 400,\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 UPLOAD_QUEUE_NOT_INITIALIZED: {\n message: 'Upload queue not initialized',\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\n // HOOK Errors\n FOLDER_FETCH_ERROR: {\n message: 'Error fetching folder data',\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_FILE_FOUND = '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_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,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,eAAe;EACb,SAAS;EACT,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;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,4BAA4B;EAC1B,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,6BAA6B;EAC3B,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,oBAAoB;EAClB,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;CACD,8BAA8B;EAC5B,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
package/dist/types/index.d.mts
CHANGED
|
@@ -33,6 +33,7 @@ interface MediaCloudPluginOptions {
|
|
|
33
33
|
limits?: {
|
|
34
34
|
mimeTypes?: (MimeType | 'image/*' | 'video/*')[];
|
|
35
35
|
fileSize?: number;
|
|
36
|
+
concurrency?: number;
|
|
36
37
|
};
|
|
37
38
|
folders?: boolean;
|
|
38
39
|
mux?: {
|
|
@@ -63,6 +64,8 @@ interface MediaCloudDefaultPluginOptions {
|
|
|
63
64
|
};
|
|
64
65
|
limits: {
|
|
65
66
|
mimeTypes: (MimeType | 'image/*' | 'video/*')[];
|
|
67
|
+
fileSize: number;
|
|
68
|
+
concurrency: number;
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
type Storage = 'mux' | 's3';
|
|
@@ -137,6 +140,7 @@ interface NodeFSError extends Error {
|
|
|
137
140
|
code?: string;
|
|
138
141
|
}
|
|
139
142
|
type StaticRenditions = Record<string, unknown> | null | undefined;
|
|
143
|
+
type RequireAllNested<T> = { [P in keyof T]-?: RequireAllNested<T[P]> };
|
|
140
144
|
//#endregion
|
|
141
|
-
export { AWSError, ImageMimeType, IncompletePartInfo, MediaCloudDefaultPluginOptions, MediaCloudEmitterEvents, MediaCloudPluginOptions, MimeType, NodeFSError, S3StoreConfig, StaticRenditions, Storage, TusUploadMetadata, VideoMimeType };
|
|
145
|
+
export { AWSError, ImageMimeType, IncompletePartInfo, MediaCloudDefaultPluginOptions, MediaCloudEmitterEvents, MediaCloudPluginOptions, MimeType, NodeFSError, RequireAllNested, S3StoreConfig, StaticRenditions, Storage, TusUploadMetadata, VideoMimeType };
|
|
142
146
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -5,7 +5,11 @@ const defaultOptions = {
|
|
|
5
5
|
view: "grid",
|
|
6
6
|
folders: true,
|
|
7
7
|
storage: { "video/*": "mux" },
|
|
8
|
-
limits: {
|
|
8
|
+
limits: {
|
|
9
|
+
mimeTypes: ["image/*", "video/*"],
|
|
10
|
+
fileSize: Infinity,
|
|
11
|
+
concurrency: 10
|
|
12
|
+
}
|
|
9
13
|
};
|
|
10
14
|
|
|
11
15
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultOptions.mjs","names":["defaultOptions: MediaCloudDefaultPluginOptions"],"sources":["../../src/utils/defaultOptions.ts"],"sourcesContent":["import { MediaCloudDefaultPluginOptions } from '../types'\n\nexport const defaultOptions: MediaCloudDefaultPluginOptions = {\n enabled: true,\n collection: 'media',\n view: 'grid',\n folders: true,\n storage: {\n 'video/*': 'mux',\n },\n limits: {\n mimeTypes: ['image/*', 'video/*'],\n },\n}\n"],"mappings":";AAEA,MAAaA,iBAAiD;CAC5D,SAAS;CACT,YAAY;CACZ,MAAM;CACN,SAAS;CACT,SAAS,EACP,WAAW,OACZ;CACD,QAAQ
|
|
1
|
+
{"version":3,"file":"defaultOptions.mjs","names":["defaultOptions: MediaCloudDefaultPluginOptions"],"sources":["../../src/utils/defaultOptions.ts"],"sourcesContent":["import { MediaCloudDefaultPluginOptions } from '../types'\n\nexport const defaultOptions: MediaCloudDefaultPluginOptions = {\n enabled: true,\n collection: 'media',\n view: 'grid',\n folders: true,\n storage: {\n 'video/*': 'mux',\n },\n limits: {\n mimeTypes: ['image/*', 'video/*'],\n fileSize: Infinity,\n concurrency: 10,\n },\n}\n"],"mappings":";AAEA,MAAaA,iBAAiD;CAC5D,SAAS;CACT,YAAY;CACZ,MAAM;CACN,SAAS;CACT,SAAS,EACP,WAAW,OACZ;CACD,QAAQ;EACN,WAAW,CAAC,WAAW,UAAU;EACjC,UAAU;EACV,aAAa;EACd;CACF"}
|
package/dist/utils/file.mjs
CHANGED
|
@@ -95,7 +95,7 @@ async function getMimeType(file) {
|
|
|
95
95
|
function generateUniqueFilename(originalFilename) {
|
|
96
96
|
const timestamp = Date.now().toString(36);
|
|
97
97
|
const { name, ext } = parse(originalFilename);
|
|
98
|
-
return `${
|
|
98
|
+
return `${name}-${timestamp}${ext}`;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Sanitizes a filename by removing/replacing invalid characters and handling edge cases
|