{"version":3,"file":"uploadHandler.mjs","names":["PQueue","upchunk","tus","createClientUploadHandler","MediaCloudErrors","MediaCloudLogs","useMediaCloudEmitter","isVideo","getMimeType","generateUniqueFilename","sanitizeFilename","useMagicError","queue","MUX_CHUNK_SIZE","TUS_CHUNK_SIZE","TUS_RETRY_DELAYS","magicError","prefix","throwError","log","emitter","muxUpload","args","file","serverURL","apiRoute","mimeType","onResolve","endpoint","filename","name","response","fetch","body","JSON","stringify","credentials","method","headers","url","uploadId","json","uploader","createUpload","chunkSize","emit","polling","pollingUrl","storage","Promise","resolve","reject","on","error","MUX_UPLOAD_ERROR","cause","progress","detail","MUX_DIRECT_UPLOAD_ERROR","tusUpload","filetype","type","filesize","size","toString","uploadUrlAvailable","upload","Upload","retryDelays","storeFingerprintForResuming","metadata","contentType","contentLength","contentDisposition","onError","navigator","sendBeacon","message","TUS_UPLOAD_ERROR","onProgress","bytesUploaded","bytesTotal","percentage","Math","round","onSuccess","console","onUploadUrlAvailable","start","UploadHandler","props","handler","updateFilename","extra","pluginOptions","concurrency","limits","allowedMimeTypes","mimeTypes","assert","FILE_TYPE_UNKNOWN","mimeTypeMatches","some","allowed","endsWith","startsWith","replace","FILE_TYPE_NOT_ALLOWED","fileSize","FILE_SIZE_EXCEEDED","mappedFile","sanitizedFilename","status","S3_STORE_FILE_FOUND","S3_STORE_FILE_NOT_FOUND","newFilename","File","NAMING_FUNCTION_ERROR","uploadArgs","storageForMimeType","Object","prototype","hasOwnProperty","call","undefined","isVideoFile","storeOnMux","UPLOAD_HANDLER_ERROR","UPLOAD_QUEUE_NOT_INITIALIZED","add","catch"],"sources":["../../../src/components/uploadHandler/uploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport PQueue from 'p-queue'\n\nimport * as upchunk from '@mux/upchunk'\nimport * as tus from 'tus-js-client'\n\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\n\nimport { MediaCloudErrors, MediaCloudLogs } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport {\n  isVideo,\n  getMimeType,\n  generateUniqueFilename,\n  sanitizeFilename,\n} from '../../utils/file'\n\nimport type {\n  MediaCloudPluginOptions,\n  Storage,\n  MimeType,\n  RequireAllNested,\n} from '../../types/index'\nimport type { ReactNode } from 'react'\nimport type { UploadCollectionSlug } from 'payload'\nimport { useMagicError, UseMagicErrorReturn } from '@maas/error-handler'\n\ninterface UploadResult {\n  storage: Storage\n  mimeType: MimeType\n  filename: string\n  uploadId?: string\n}\n\ninterface UploadArgs {\n  serverURL: string\n  apiRoute: string\n  file: File\n  mimeType: MimeType\n  prefix?: string\n  onResolve: (result: UploadResult | null) => void\n}\n\ninterface MuxCreateUploadResponse {\n  url: string\n  uploadId: string\n  filename: string\n}\n\nlet queue: PQueue | undefined\n\nconst MUX_CHUNK_SIZE = 30720\nconst TUS_CHUNK_SIZE = 1024 * 1024\nconst TUS_RETRY_DELAYS = [0, 1000, 2000, 5000]\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n  prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\nconst { throwError, log } = magicError\nconst emitter = useMediaCloudEmitter()\n\n/**\n * Handles Mux video upload with progress tracking\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function muxUpload(args: UploadArgs): Promise<void> {\n  const { file, serverURL, apiRoute, mimeType, onResolve } = args\n\n  const endpoint = `${serverURL}${apiRoute}`\n  const filename = file.name\n\n  try {\n    // Request upload URL from Mux\n    const response = await fetch(`${endpoint}/mux/upload`, {\n      body: JSON.stringify({ filename, mimeType }),\n      credentials: 'include',\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    })\n\n    const { url, uploadId } = (await response.json()) as MuxCreateUploadResponse\n\n    // Create upchunk uploader\n    const uploader = await upchunk.createUpload({\n      endpoint: url,\n      file,\n      chunkSize: MUX_CHUNK_SIZE,\n    })\n\n    // Add upload to tracker\n    emitter.emit('addUpload', {\n      filename,\n      uploadId,\n      polling: false,\n      pollingUrl: `${endpoint}/mux/asset`,\n    })\n\n    // Update collection entry\n    await onResolve({\n      storage: 'mux',\n      filename,\n      uploadId,\n      mimeType,\n    })\n\n    return new Promise((resolve, reject) => {\n      // Set up event handlers\n      uploader.on('error', function (error) {\n        emitter.emit('removeUpload', { uploadId })\n        throwError({ ...MediaCloudErrors.MUX_UPLOAD_ERROR, cause: error })\n        reject(error)\n      })\n\n      uploader.on('progress', function (progress) {\n        emitter.emit('updateUpload', {\n          filename,\n          progress: progress.detail,\n        })\n      })\n\n      uploader.on('success', function () {\n        emitter.emit('uploadComplete', { filename })\n        resolve()\n      })\n    })\n  } catch {\n    throwError(MediaCloudErrors.MUX_DIRECT_UPLOAD_ERROR)\n    emitter.emit('uploadError', { filename, error: 'Video upload failed' })\n  }\n}\n\n/**\n * Handles TUS file upload with resumable capabilities\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function tusUpload(args: UploadArgs): Promise<void> {\n  const { apiRoute, serverURL, file, prefix = '', mimeType, onResolve } = args\n\n  const filename = file.name\n  const filetype = file.type\n  const filesize = file.size.toString()\n\n  const endpoint = `${serverURL}${apiRoute}/uploads`\n\n  // Flag to prevent multiple onUploadUrlAvailable calls\n  let uploadUrlAvailable = false\n\n  return new Promise((resolve, reject) => {\n    const upload = new tus.Upload(file, {\n      endpoint,\n      retryDelays: TUS_RETRY_DELAYS,\n      chunkSize: TUS_CHUNK_SIZE,\n      storeFingerprintForResuming: false,\n      metadata: {\n        filetype,\n        filesize,\n        filename,\n        prefix,\n        contentType: filetype,\n        contentLength: filesize,\n        contentDisposition: 'inline',\n      },\n      onError(error) {\n        // Clean up\n        navigator.sendBeacon(\n          `${endpoint}/cleanup`,\n          JSON.stringify({ filename })\n        )\n\n        // Inform user\n        emitter.emit('uploadError', { filename, error: error.message })\n        emitter.emit('removeUpload', { filename })\n        throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n        reject(error)\n      },\n      onProgress: function (bytesUploaded, bytesTotal) {\n        const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n        emitter.emit('updateUpload', {\n          filename,\n          progress: percentage,\n        })\n      },\n      async onSuccess() {\n        try {\n          // Mark upload as complete in UI\n          emitter.emit('uploadComplete', { filename })\n\n          // Trigger post upload processing\n          await fetch(`${endpoint}/${filename}/process`)\n\n          // Move file to correct folder\n          await fetch(`${endpoint}/${filename}/folder`)\n        } catch (error) {\n          console.error(error)\n        } finally {\n          resolve()\n        }\n      },\n      onUploadUrlAvailable: async function () {\n        try {\n          // Prevent multiple callbacks\n          if (uploadUrlAvailable) {\n            return\n          }\n\n          // Update flag\n          uploadUrlAvailable = true\n\n          // Add upload to UI\n          emitter.emit('addUpload', { filename })\n\n          // Update collection entry\n          onResolve({\n            filename,\n            mimeType,\n            storage: 's3',\n          })\n        } catch (error) {\n          // Handled elsewhere\n          console.error(error)\n        }\n      },\n    })\n\n    upload.start()\n  })\n}\n\ninterface Extra {\n  pluginOptions: RequireAllNested<MediaCloudPluginOptions>\n}\n\ntype ClientUploadHandlerProps<T extends Record<string, unknown>> = {\n  children: ReactNode\n  collectionSlug: UploadCollectionSlug\n  enabled?: boolean\n  extra: T\n  prefix?: string\n  serverHandlerPath: `/${string}`\n}\n\nexport function UploadHandler(\n  props: ClientUploadHandlerProps<{ pluginOptions: MediaCloudPluginOptions }>\n) {\n  return createClientUploadHandler({\n    handler: async function (args) {\n      const { serverURL, apiRoute, file, prefix, updateFilename, extra } = args\n      const { pluginOptions } = extra as unknown as Extra\n\n      // Initialize upload queue\n      if (!queue) {\n        queue = new PQueue({\n          concurrency: pluginOptions.limits.concurrency,\n        })\n      }\n\n      // Update concurrency\n      if (queue && queue.concurrency !== pluginOptions.limits.concurrency) {\n        queue.concurrency = pluginOptions.limits.concurrency\n      }\n\n      async function upload(onResolve: UploadArgs['onResolve']) {\n        // Check mime type\n        const mimeType = await getMimeType(file)\n        const allowedMimeTypes = pluginOptions.limits?.mimeTypes ?? []\n\n        magicError.assert(mimeType, MediaCloudErrors.FILE_TYPE_UNKNOWN)\n\n        const mimeTypeMatches = allowedMimeTypes.some((allowed) =>\n          allowed.endsWith('/*')\n            ? mimeType?.startsWith(allowed.replace('/*', '/'))\n            : mimeType === allowed\n        )\n\n        if (!mimeTypeMatches) {\n          throwError(MediaCloudErrors.FILE_TYPE_NOT_ALLOWED)\n        }\n\n        // Check file size limit\n        if (file.size > pluginOptions.limits.fileSize) {\n          throwError(MediaCloudErrors.FILE_SIZE_EXCEEDED)\n        }\n\n        // Clone file\n        let mappedFile = file\n\n        try {\n          // Check if file with same name exists\n          const sanitizedFilename = sanitizeFilename(file.name)\n          const endpoint = `${serverURL}${apiRoute}/uploads`\n          const response = await fetch(\n            `${endpoint}/${sanitizedFilename}/exists`\n          )\n\n          switch (response?.status) {\n            case 200:\n              log(MediaCloudLogs.S3_STORE_FILE_FOUND)\n              break\n            case 204:\n              log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n              break\n          }\n\n          // If file exists, generate a cloned file with a unique filename\n          // If not, generate a cloned file with a sanitized filename\n          const newFilename =\n            response?.status === 200\n              ? generateUniqueFilename(sanitizedFilename)\n              : sanitizedFilename\n\n          mappedFile = new File([file], newFilename, {\n            type: file.type,\n          })\n\n          await updateFilename(newFilename)\n        } catch (error) {\n          throwError({\n            ...MediaCloudErrors.NAMING_FUNCTION_ERROR,\n            cause: error,\n          })\n        }\n\n        try {\n          const uploadArgs: UploadArgs = {\n            file: mappedFile,\n            serverURL,\n            apiRoute,\n            mimeType,\n            prefix,\n            onResolve,\n          }\n\n          // Check storage mapping for mime type\n          type StorageMap = NonNullable<typeof pluginOptions.storage>\n\n          const storageForMimeType =\n            pluginOptions.storage &&\n            Object.prototype.hasOwnProperty.call(\n              pluginOptions.storage,\n              mimeType\n            )\n              ? pluginOptions.storage[mimeType as keyof StorageMap]\n              : undefined\n\n          const isVideoFile = await isVideo(file)\n          const storeOnMux =\n            pluginOptions.storage?.['video/*'] === 'mux' ||\n            storageForMimeType === 'mux'\n\n          switch (true) {\n            case storeOnMux && isVideoFile:\n              return await muxUpload(uploadArgs)\n            default:\n              return await tusUpload(uploadArgs)\n          }\n        } catch (error) {\n          throwError({\n            ...MediaCloudErrors.UPLOAD_HANDLER_ERROR,\n            cause: error,\n          })\n        }\n      }\n\n      return new Promise((resolve, reject) => {\n        magicError.assert(queue, MediaCloudErrors.UPLOAD_QUEUE_NOT_INITIALIZED)\n        queue.add(() => upload(resolve)).catch(reject)\n      })\n    },\n  })(props)\n}\n"],"mappings":"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"}