UNPKG

@maas/payload-plugin-mux

Version:

> [!CAUTION] > > # This plugin is not ready for production use! > > This plugin is under experimental development and is not yet ready for production use. It is intended for testing and feedback purposes only. Please use it at your own risk and be aware t

466 lines (455 loc) 13.5 kB
var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/endpoints/upload.ts import Mux from "@mux/mux-node"; import { NextResponse } from "next/server"; function createUpload(options) { const mux = new Mux({ tokenId: options.credentials.tokenId, tokenSecret: options.credentials.tokenSecret }); return () => __async(this, null, function* () { try { const upload = yield mux.video.uploads.create({ cors_origin: process.env.NEXT_PUBLIC_PAYLOAD_SERVER_URL || "http://localhost:3000", new_asset_settings: __spreadValues({ playback_policy: ["public"] }, options.newAssetSettings) }); return NextResponse.json(upload, { status: 200 }); } catch (error) { return NextResponse.json(error, { status: 500 }); } }); } function getUpload(options) { const mux = new Mux({ tokenId: options.credentials.tokenId, tokenSecret: options.credentials.tokenSecret }); return (req) => __async(this, null, function* () { try { const id = req.query.id; const upload = yield mux.video.uploads.retrieve(id); return NextResponse.json(upload, { status: 200 }); } catch (error) { return NextResponse.json(error, { status: 500 }); } }); } // src/endpoints/webhook.ts import Mux2 from "@mux/mux-node"; import { NextResponse as NextResponse2 } from "next/server"; // src/utils/getAssetMetadata.ts function getAssetMetadata(asset) { var _a, _b; const videoTrack = (_a = asset == null ? void 0 : asset.tracks) == null ? void 0 : _a.find((track) => track.type === "video"); return __spreadValues({ playbackId: asset.playback_ids[0].id, /* Reformat Mux's aspect ratio (e.g. 16:9) to be CSS-friendly (e.g. 16/9) */ aspectRatio: asset.aspect_ratio.replace(":", "/"), duration: asset.duration, mimeType: "video/mp4", files: (_b = asset.static_renditions) == null ? void 0 : _b.files }, videoTrack ? { maxWidth: videoTrack.max_width, maxHeight: videoTrack.max_height } : {}); } // src/endpoints/webhook.ts function webhook(options) { const mux = new Mux2({ tokenId: options.credentials.tokenId, tokenSecret: options.credentials.tokenSecret, webhookSecret: options.credentials.webhookSecret }); return (req) => __async(this, null, function* () { try { const body = req.text ? yield req.text() : ""; const headers = req.headers; mux.webhooks.verifySignature(body, headers); const event = JSON.parse(body); console.log(`Received webhook event: ${event.type}`); switch (event.type) { case "video.asset.created": console.log(`Asset created: ${event.object.id}`); break; case "video.asset.ready": yield updateVideoAsset(event.object.id, options, req.payload); break; case "video.asset.errored": console.log(`Asset errored: ${event.object.id}`); break; case "video.asset.deleted": console.log(`Asset deleted: ${event.object.id}`); break; case "video.asset.static_renditions.ready": yield updateVideoAsset(event.object.id, options, req.payload); console.log(`Static renditions ready: ${event.object.id}`); break; case "video.asset.static_renditions.deleted": console.log(`Static renditions deleted: ${event.object.id}`); break; default: console.log(`Unknown event type: ${event.type}`); break; } return NextResponse2.json( { message: "Webhook received" }, { status: 200 } ); } catch (error) { console.error("Error verifying webhook", error); return NextResponse2.json( { message: "Error verifying webhook" }, { status: 500 } ); } }); } function updateVideoAsset(assetId, options, payload) { return __async(this, null, function* () { const mux = new Mux2({ tokenId: options.credentials.tokenId, tokenSecret: options.credentials.tokenSecret }); console.log(`Updating video with assetId: ${assetId}`); const videos = yield payload.find({ collection: options.collection, where: { assetId: { equals: assetId } }, limit: 1, pagination: false }); if (videos.totalDocs === 0) { console.log(`No video found with assetId: ${assetId}`); } else { const video = videos.docs[0]; console.log(`Updating video ${video.id} with asset previewData...`); const asset = yield mux.video.assets.retrieve(assetId); yield payload.update({ collection: options.collection, id: video.id, data: __spreadValues({ id: video.id }, getAssetMetadata(asset)) }); } }); } // src/hooks/beforeChange.ts import Mux3 from "@mux/mux-node"; // src/utils/delay.ts function delay(ms) { return __async(this, null, function* () { return new Promise((resolve) => setTimeout(resolve, ms)); }); } // src/hooks/beforeChange.ts var beforeChangeHook = (_0) => __async(void 0, [_0], function* ({ collection, req, data: incomingData, operation, originalDoc }) { const mux = new Mux3(); let data = __spreadValues({}, incomingData); console.log(`beforeChangeHook: ${operation}`); console.log("data"); try { if (!(originalDoc == null ? void 0 : originalDoc.assetId) || originalDoc.assetId !== data.assetId) { console.log( `[payload-mux] Asset ID created for the first time or changed. Creating or updating...` ); if (operation === "update" && originalDoc.assetId !== data.assetId) { console.log( `[payload-mux] Deleting original asset: ${originalDoc.assetId}...` ); const response = yield mux.video.assets.delete(originalDoc.assetId); console.log(response); } let asset = yield mux.video.assets.retrieve(data.assetId); const delayDuration = 1500; const pollingLimit = 6; const timeout = Date.now() + pollingLimit * 1e3; while (asset.status === "preparing") { if (Date.now() > timeout) { console.log( `[payload-mux] Asset is still preparing after ${pollingLimit} seconds, giving up and letting the webhook handle it...` ); break; } console.log( `[payload-mux] Asset is preparing, trying again in ${delayDuration}ms` ); yield delay(delayDuration); asset = yield mux.video.assets.retrieve(data.assetId); } if (asset.status === "errored") { console.log( "Error while preparing asset. Deleting it and throwing error..." ); const response = yield mux.video.assets.retrieve(data.assetId); console.log(response); throw new Error( `Unable to prepare asset: ${asset.status}. It's been deleted, please try again.` ); } if (asset.status === "ready") { console.log(`[payload-mux] Asset is ready, getting previewData...`); data = __spreadValues(__spreadValues({}, data), getAssetMetadata(asset)); } else { console.log( `[payload-mux] Asset is not ready, letting the webhook handle it...` ); } data.url = ""; const existingVideo = yield req.payload.find({ collection: collection.slug, where: { filename: { contains: data.filename } } }); const uniqueFilename = `${data.filename}${existingVideo.totalDocs > 0 ? ` (${existingVideo.totalDocs})` : ""}`; data.filename = uniqueFilename; } } catch (err) { req.payload.logger.error( `[payload-mux] There was an error while uploading files corresponding to the collection with filename ${data.filename}:` ); req.payload.logger.error(err); throw err; } return __spreadValues({}, data); }); // src/hooks/afterDelete.ts import Mux4 from "@mux/mux-node"; var afterDeleteHook = (_0) => __async(void 0, [_0], function* ({ id, doc }) { const mux = new Mux4(); const { assetId } = doc; try { console.log(`[payload-mux] Checking if asset ${assetId} exists in Mux...`); const video = yield mux.video.assets.retrieve(assetId); if (video) { console.log(`[payload-mux] Asset ${id} exists in Mux, deleting...`); yield mux.video.assets.delete(assetId); } } catch (err) { if (err.type === "not_found") { console.log(`[payload-mux] Asset ${id} not found in Mux, continuing...`); } else { console.error(`[payload-mux] Error deleting asset ${id} from Mux...`); console.error(err); throw err; } } }); // src/collections/extendCollection.ts import { NextResponse as NextResponse3 } from "next/server"; import { deepmerge } from "@fastify/deepmerge"; function extendCollection(config) { const muxFields = [ { name: "muxUploader", type: "ui", admin: { disableListColumn: true, components: { Field: "@maas/payload-plugin-mux/components#MuxUploaderField" } } }, { name: "assetId", type: "text", label: "Asset ID", admin: { readOnly: true } }, { name: "playbackId", type: "text", label: "Playback ID", admin: { readOnly: true } }, { name: "duration", type: "number", label: "Duration", admin: { readOnly: true } }, { name: "aspectRatio", type: "text", label: "Aspect Ratio", admin: { readOnly: true } }, { name: "maxWidth", type: "number", label: "Max Width", admin: { readOnly: true } }, { name: "maxHeight", type: "number", label: "Max Height", admin: { readOnly: true } }, { name: "files", type: "json", label: "Files", admin: { readOnly: true } } ]; const muxEndpoints = [ { path: "/file/:filename", method: "get", handler: () => __async(this, null, function* () { return NextResponse3.json(null, { headers: { "Content-Type": "video/*" } }); }) } ]; const defaultConfig = { access: { read: () => true, delete: () => true }, admin: { useAsTitle: "filename", defaultColumns: ["filename", "playbackId", "duration"] }, upload: { disableLocalStorage: true, filesRequiredOnCreate: false, displayPreview: true, adminThumbnail({ doc }) { return `https://image.mux.com/${doc.playbackId}/thumbnail.jpg?width=200&height=200&fit_mode=pad`; }, bulkUpload: false, mimeTypes: ["video/*"] }, hooks: { beforeChange: [beforeChangeHook], afterDelete: [afterDeleteHook] }, fields: muxFields, endpoints: muxEndpoints }; return deepmerge({ all: true })(defaultConfig, config); } // src/plugin.ts var muxPlugin = (pluginOptions) => (incomingConfig) => { var _a, _b; const { enabled, collection } = pluginOptions; const config = __spreadValues({}, incomingConfig); if (enabled === false) { return config; } const videoCollection = (_a = config.collections) == null ? void 0 : _a.find( (c) => c.slug === collection ); if (!videoCollection) { throw new Error(`Could not find a collection with the slug ${collection}`); } const otherCollections = (_b = config.collections) == null ? void 0 : _b.filter( (c) => c.slug !== collection ); config.collections = [ ...otherCollections || [], extendCollection(videoCollection) ]; config.endpoints = [ ...config.endpoints || [], { path: "/_mux/upload", method: "post", handler: createUpload(pluginOptions) }, { path: "/_mux/upload", method: "get", handler: getUpload(pluginOptions) }, { path: "/_mux/upload", method: "get", handler: getUpload(pluginOptions) }, { path: "/_mux/webhook", method: "post", handler: webhook(pluginOptions) } ]; return config; }; export { muxPlugin };