@pro-laico/payload-mux 0.0.0
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/LICENSE.md +52 -0
- package/README.md +11 -0
- package/dist/collections/MuxVideo.d.ts +7 -0
- package/dist/collections/MuxVideo.d.ts.map +1 -0
- package/dist/collections/MuxVideo.js +266 -0
- package/dist/collections/MuxVideo.js.map +1 -0
- package/dist/components/MuxUploaderField.d.ts +10 -0
- package/dist/components/MuxUploaderField.d.ts.map +1 -0
- package/dist/components/MuxUploaderField.js +143 -0
- package/dist/components/MuxUploaderField.js.map +1 -0
- package/dist/components/MuxVideoGifCell.d.ts +5 -0
- package/dist/components/MuxVideoGifCell.d.ts.map +1 -0
- package/dist/components/MuxVideoGifCell.js +21 -0
- package/dist/components/MuxVideoGifCell.js.map +1 -0
- package/dist/components/MuxVideoImageCell.d.ts +5 -0
- package/dist/components/MuxVideoImageCell.d.ts.map +1 -0
- package/dist/components/MuxVideoImageCell.js +21 -0
- package/dist/components/MuxVideoImageCell.js.map +1 -0
- package/dist/components/mux-uploader.css +23 -0
- package/dist/endpoints/upload.d.ts +10 -0
- package/dist/endpoints/upload.d.ts.map +1 -0
- package/dist/endpoints/upload.js +49 -0
- package/dist/endpoints/upload.js.map +1 -0
- package/dist/endpoints/webhook.d.ts +16 -0
- package/dist/endpoints/webhook.d.ts.map +1 -0
- package/dist/endpoints/webhook.js +148 -0
- package/dist/endpoints/webhook.js.map +1 -0
- package/dist/hooks/afterDelete.d.ts +6 -0
- package/dist/hooks/afterDelete.d.ts.map +1 -0
- package/dist/hooks/afterDelete.js +12 -0
- package/dist/hooks/afterDelete.js.map +1 -0
- package/dist/hooks/beforeChange.d.ts +11 -0
- package/dist/hooks/beforeChange.d.ts.map +1 -0
- package/dist/hooks/beforeChange.js +95 -0
- package/dist/hooks/beforeChange.js.map +1 -0
- package/dist/hooks/beforeValidate.d.ts +17 -0
- package/dist/hooks/beforeValidate.d.ts.map +1 -0
- package/dist/hooks/beforeValidate.js +61 -0
- package/dist/hooks/beforeValidate.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/defaultAccess.d.ts +5 -0
- package/dist/lib/defaultAccess.d.ts.map +1 -0
- package/dist/lib/defaultAccess.js +4 -0
- package/dist/lib/defaultAccess.js.map +1 -0
- package/dist/lib/delay.d.ts +3 -0
- package/dist/lib/delay.d.ts.map +1 -0
- package/dist/lib/delay.js +3 -0
- package/dist/lib/delay.js.map +1 -0
- package/dist/lib/getAssetMetadata.d.ts +34 -0
- package/dist/lib/getAssetMetadata.d.ts.map +1 -0
- package/dist/lib/getAssetMetadata.js +21 -0
- package/dist/lib/getAssetMetadata.js.map +1 -0
- package/dist/lib/ingest.d.ts +53 -0
- package/dist/lib/ingest.d.ts.map +1 -0
- package/dist/lib/ingest.js +109 -0
- package/dist/lib/ingest.js.map +1 -0
- package/dist/lib/isAllowed.d.ts +7 -0
- package/dist/lib/isAllowed.d.ts.map +1 -0
- package/dist/lib/isAllowed.js +6 -0
- package/dist/lib/isAllowed.js.map +1 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +80 -0
- package/dist/plugin.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.mux-uploader {
|
|
2
|
+
margin-bottom: var(--base);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.mux-uploader__error {
|
|
6
|
+
color: var(--theme-error-500);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.mux-uploader__processing::after {
|
|
10
|
+
display: inline-block;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
width: 0;
|
|
13
|
+
vertical-align: bottom;
|
|
14
|
+
/* ascii code for the ellipsis character */
|
|
15
|
+
content: "\2026";
|
|
16
|
+
animation: mux-ellipsis steps(5, end) 1.5s infinite;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@keyframes mux-ellipsis {
|
|
20
|
+
to {
|
|
21
|
+
width: 2ch;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type Mux from '@mux/mux-node';
|
|
2
|
+
import type { PayloadHandler } from 'payload';
|
|
3
|
+
import type { MuxVideoPluginOptions } from '../types';
|
|
4
|
+
/** `POST /api/mux/upload` — create a Mux direct-upload and return it (the admin uploader
|
|
5
|
+
* POSTs files straight to the returned URL). Gated by the plugin's `access` option. */
|
|
6
|
+
export declare const createMuxUploadHandler: (mux: Mux, pluginOptions: MuxVideoPluginOptions) => PayloadHandler;
|
|
7
|
+
/** `GET /api/mux/upload?id=…` — retrieve a direct-upload by id so the client can read the
|
|
8
|
+
* `asset_id` once Mux assigns it. Gated by the plugin's `access` option. */
|
|
9
|
+
export declare const getMuxUploadHandler: (mux: Mux, pluginOptions: MuxVideoPluginOptions) => PayloadHandler;
|
|
10
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/endpoints/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAErD;wFACwF;AACxF,eAAO,MAAM,sBAAsB,GAChC,KAAK,GAAG,EAAE,eAAe,qBAAqB,KAAG,cAUjD,CAAA;AAEH;6EAC6E;AAC7E,eAAO,MAAM,mBAAmB,GAC7B,KAAK,GAAG,EAAE,eAAe,qBAAqB,KAAG,cAcjD,CAAA"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isAllowed } from "../lib/isAllowed.js";
|
|
2
|
+
/** `POST /api/mux/upload` — create a Mux direct-upload and return it (the admin uploader
|
|
3
|
+
* POSTs files straight to the returned URL). Gated by the plugin's `access` option. */ export const createMuxUploadHandler = (mux, pluginOptions)=>async (req)=>{
|
|
4
|
+
if (!await isAllowed(pluginOptions, req)) return Response.json({
|
|
5
|
+
error: 'Forbidden.'
|
|
6
|
+
}, {
|
|
7
|
+
status: 403
|
|
8
|
+
});
|
|
9
|
+
const upload = await mux.video.uploads.create({
|
|
10
|
+
cors_origin: pluginOptions.uploadSettings?.cors_origin ?? process.env.NEXT_PUBLIC_SERVER_URL ?? '*',
|
|
11
|
+
new_asset_settings: {
|
|
12
|
+
playback_policy: [
|
|
13
|
+
pluginOptions.playbackPolicy ?? 'public'
|
|
14
|
+
],
|
|
15
|
+
...pluginOptions.uploadSettings?.new_asset_settings
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return Response.json(upload);
|
|
19
|
+
};
|
|
20
|
+
/** `GET /api/mux/upload?id=…` — retrieve a direct-upload by id so the client can read the
|
|
21
|
+
* `asset_id` once Mux assigns it. Gated by the plugin's `access` option. */ export const getMuxUploadHandler = (mux, pluginOptions)=>async (req)=>{
|
|
22
|
+
if (!await isAllowed(pluginOptions, req)) return Response.json({
|
|
23
|
+
error: 'Forbidden.'
|
|
24
|
+
}, {
|
|
25
|
+
status: 403
|
|
26
|
+
});
|
|
27
|
+
const id = req.query?.id;
|
|
28
|
+
if (!id) return Response.json({
|
|
29
|
+
error: 'Missing upload id.'
|
|
30
|
+
}, {
|
|
31
|
+
status: 400
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const upload = await mux.video.uploads.retrieve(id);
|
|
35
|
+
return Response.json(upload);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
req.payload.logger.error({
|
|
38
|
+
err,
|
|
39
|
+
msg: `[payload-mux] Failed to retrieve upload '${id}'`
|
|
40
|
+
});
|
|
41
|
+
return Response.json({
|
|
42
|
+
error: 'Failed to retrieve upload.'
|
|
43
|
+
}, {
|
|
44
|
+
status: 500
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/upload.ts"],"sourcesContent":["import type Mux from '@mux/mux-node'\nimport type { PayloadHandler } from 'payload'\nimport { isAllowed } from '../lib/isAllowed'\nimport type { MuxVideoPluginOptions } from '../types'\n\n/** `POST /api/mux/upload` — create a Mux direct-upload and return it (the admin uploader\n * POSTs files straight to the returned URL). Gated by the plugin's `access` option. */\nexport const createMuxUploadHandler =\n (mux: Mux, pluginOptions: MuxVideoPluginOptions): PayloadHandler =>\n async (req) => {\n if (!(await isAllowed(pluginOptions, req))) return Response.json({ error: 'Forbidden.' }, { status: 403 })\n\n const upload = await mux.video.uploads.create({\n cors_origin: pluginOptions.uploadSettings?.cors_origin ?? process.env.NEXT_PUBLIC_SERVER_URL ?? '*',\n new_asset_settings: { playback_policy: [pluginOptions.playbackPolicy ?? 'public'], ...pluginOptions.uploadSettings?.new_asset_settings },\n })\n\n return Response.json(upload)\n }\n\n/** `GET /api/mux/upload?id=…` — retrieve a direct-upload by id so the client can read the\n * `asset_id` once Mux assigns it. Gated by the plugin's `access` option. */\nexport const getMuxUploadHandler =\n (mux: Mux, pluginOptions: MuxVideoPluginOptions): PayloadHandler =>\n async (req) => {\n if (!(await isAllowed(pluginOptions, req))) return Response.json({ error: 'Forbidden.' }, { status: 403 })\n\n const id = req.query?.id as string | undefined\n if (!id) return Response.json({ error: 'Missing upload id.' }, { status: 400 })\n\n try {\n const upload = await mux.video.uploads.retrieve(id)\n return Response.json(upload)\n } catch (err) {\n req.payload.logger.error({ err, msg: `[payload-mux] Failed to retrieve upload '${id}'` })\n return Response.json({ error: 'Failed to retrieve upload.' }, { status: 500 })\n }\n }\n"],"names":["isAllowed","createMuxUploadHandler","mux","pluginOptions","req","Response","json","error","status","upload","video","uploads","create","cors_origin","uploadSettings","process","env","NEXT_PUBLIC_SERVER_URL","new_asset_settings","playback_policy","playbackPolicy","getMuxUploadHandler","id","query","retrieve","err","payload","logger","msg"],"mappings":"AAEA,SAASA,SAAS,QAAQ,sBAAkB;AAG5C;sFACsF,GACtF,OAAO,MAAMC,yBACX,CAACC,KAAUC,gBACX,OAAOC;QACL,IAAI,CAAE,MAAMJ,UAAUG,eAAeC,MAAO,OAAOC,SAASC,IAAI,CAAC;YAAEC,OAAO;QAAa,GAAG;YAAEC,QAAQ;QAAI;QAExG,MAAMC,SAAS,MAAMP,IAAIQ,KAAK,CAACC,OAAO,CAACC,MAAM,CAAC;YAC5CC,aAAaV,cAAcW,cAAc,EAAED,eAAeE,QAAQC,GAAG,CAACC,sBAAsB,IAAI;YAChGC,oBAAoB;gBAAEC,iBAAiB;oBAAChB,cAAciB,cAAc,IAAI;iBAAS;gBAAE,GAAGjB,cAAcW,cAAc,EAAEI,kBAAkB;YAAC;QACzI;QAEA,OAAOb,SAASC,IAAI,CAACG;IACvB,EAAC;AAEH;2EAC2E,GAC3E,OAAO,MAAMY,sBACX,CAACnB,KAAUC,gBACX,OAAOC;QACL,IAAI,CAAE,MAAMJ,UAAUG,eAAeC,MAAO,OAAOC,SAASC,IAAI,CAAC;YAAEC,OAAO;QAAa,GAAG;YAAEC,QAAQ;QAAI;QAExG,MAAMc,KAAKlB,IAAImB,KAAK,EAAED;QACtB,IAAI,CAACA,IAAI,OAAOjB,SAASC,IAAI,CAAC;YAAEC,OAAO;QAAqB,GAAG;YAAEC,QAAQ;QAAI;QAE7E,IAAI;YACF,MAAMC,SAAS,MAAMP,IAAIQ,KAAK,CAACC,OAAO,CAACa,QAAQ,CAACF;YAChD,OAAOjB,SAASC,IAAI,CAACG;QACvB,EAAE,OAAOgB,KAAK;YACZrB,IAAIsB,OAAO,CAACC,MAAM,CAACpB,KAAK,CAAC;gBAAEkB;gBAAKG,KAAK,CAAC,yCAAyC,EAAEN,GAAG,CAAC,CAAC;YAAC;YACvF,OAAOjB,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAA6B,GAAG;gBAAEC,QAAQ;YAAI;QAC9E;IACF,EAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type Mux from '@mux/mux-node';
|
|
2
|
+
import type { PayloadHandler } from 'payload';
|
|
3
|
+
import type { MuxVideoPluginOptions } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* `POST /api/mux/webhook` — keep Payload in sync with Mux. `unwrap()` verifies the Mux
|
|
6
|
+
* signature and returns a typed event; we then set metadata + status on `video.asset.ready` /
|
|
7
|
+
* `updated`, delete the doc on `video.asset.deleted`, mark the doc errored on
|
|
8
|
+
* `video.asset.errored`, and (when `autoCreateOnWebhook`) backfill a doc for an asset Payload
|
|
9
|
+
* doesn't have yet. 200s after a handled event so Mux stops retrying; 500s on a failed
|
|
10
|
+
* handler so Mux retries it.
|
|
11
|
+
*
|
|
12
|
+
* Note: Payload pre-parses the request body, so the signature is verified against a
|
|
13
|
+
* re-serialized payload rather than the original bytes — unavoidable at the endpoint layer.
|
|
14
|
+
*/
|
|
15
|
+
export declare const muxWebhookHandler: (mux: Mux, pluginOptions: MuxVideoPluginOptions) => PayloadHandler;
|
|
16
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/endpoints/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAQrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC3B,KAAK,GAAG,EAAE,eAAe,qBAAqB,KAAG,cAuEjD,CAAA"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { getAssetMetadata } from "../lib/getAssetMetadata.js";
|
|
2
|
+
const ok = ()=>new Response('Success!', {
|
|
3
|
+
status: 200
|
|
4
|
+
});
|
|
5
|
+
const fail = ()=>new Response('Error', {
|
|
6
|
+
status: 500
|
|
7
|
+
});
|
|
8
|
+
// One info line per process so users can confirm the webhook wiring end to end.
|
|
9
|
+
let verifiedLogged = false;
|
|
10
|
+
/**
|
|
11
|
+
* `POST /api/mux/webhook` — keep Payload in sync with Mux. `unwrap()` verifies the Mux
|
|
12
|
+
* signature and returns a typed event; we then set metadata + status on `video.asset.ready` /
|
|
13
|
+
* `updated`, delete the doc on `video.asset.deleted`, mark the doc errored on
|
|
14
|
+
* `video.asset.errored`, and (when `autoCreateOnWebhook`) backfill a doc for an asset Payload
|
|
15
|
+
* doesn't have yet. 200s after a handled event so Mux stops retrying; 500s on a failed
|
|
16
|
+
* handler so Mux retries it.
|
|
17
|
+
*
|
|
18
|
+
* Note: Payload pre-parses the request body, so the signature is verified against a
|
|
19
|
+
* re-serialized payload rather than the original bytes — unavoidable at the endpoint layer.
|
|
20
|
+
*/ export const muxWebhookHandler = (mux, pluginOptions)=>async (req)=>{
|
|
21
|
+
if (!req.json) return Response.json({
|
|
22
|
+
error: 'Bad request.'
|
|
23
|
+
}, {
|
|
24
|
+
status: 400
|
|
25
|
+
});
|
|
26
|
+
const body = await req.json();
|
|
27
|
+
if (!body) return Response.json({
|
|
28
|
+
error: 'Bad request.'
|
|
29
|
+
}, {
|
|
30
|
+
status: 400
|
|
31
|
+
});
|
|
32
|
+
let event;
|
|
33
|
+
try {
|
|
34
|
+
event = await mux.webhooks.unwrap(JSON.stringify(body), req.headers);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
const hint = 'check that MUX_WEBHOOK_SECRET matches the signing secret in the Mux dashboard';
|
|
37
|
+
req.payload.logger.error({
|
|
38
|
+
err,
|
|
39
|
+
msg: `[payload-mux] Webhook signature verification failed — ${hint}`
|
|
40
|
+
});
|
|
41
|
+
return Response.json({
|
|
42
|
+
error: `Invalid signature — ${hint}.`
|
|
43
|
+
}, {
|
|
44
|
+
status: 401
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (!verifiedLogged) {
|
|
48
|
+
verifiedLogged = true;
|
|
49
|
+
req.payload.logger.info('[payload-mux] webhook verified — receiving Mux events');
|
|
50
|
+
}
|
|
51
|
+
const collection = pluginOptions.extendCollection ?? 'mux-video';
|
|
52
|
+
const assetId = event.object.id;
|
|
53
|
+
const videos = await req.payload.find({
|
|
54
|
+
collection,
|
|
55
|
+
where: {
|
|
56
|
+
assetId: {
|
|
57
|
+
equals: assetId
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
limit: 1,
|
|
61
|
+
pagination: false
|
|
62
|
+
});
|
|
63
|
+
const video = videos.totalDocs > 0 ? videos.docs[0] : null;
|
|
64
|
+
if (!video) {
|
|
65
|
+
const backfillable = event.type === 'video.asset.created' || event.type === 'video.asset.ready' || event.type === 'video.asset.updated';
|
|
66
|
+
if (pluginOptions.autoCreateOnWebhook && backfillable) {
|
|
67
|
+
try {
|
|
68
|
+
const title = event.data.meta?.title || assetId;
|
|
69
|
+
const metadata = getAssetMetadata(event.data);
|
|
70
|
+
const status = metadata.playbackOptions?.length ? 'ready' : 'preparing';
|
|
71
|
+
await req.payload.create({
|
|
72
|
+
collection,
|
|
73
|
+
data: {
|
|
74
|
+
title,
|
|
75
|
+
assetId,
|
|
76
|
+
...metadata,
|
|
77
|
+
status
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
req.payload.logger.error({
|
|
82
|
+
err,
|
|
83
|
+
msg: `[payload-mux] Failed to backfill asset '${assetId}'`
|
|
84
|
+
});
|
|
85
|
+
return fail();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return ok();
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
switch(event.type){
|
|
92
|
+
case 'video.asset.ready':
|
|
93
|
+
case 'video.asset.updated':
|
|
94
|
+
{
|
|
95
|
+
const metadata = getAssetMetadata(event.data);
|
|
96
|
+
// `updated` can fire before a playback id exists — only playback-bearing events mean 'ready'.
|
|
97
|
+
const status = metadata.playbackOptions?.length ? {
|
|
98
|
+
status: 'ready',
|
|
99
|
+
error: null
|
|
100
|
+
} : {};
|
|
101
|
+
await req.payload.update({
|
|
102
|
+
collection,
|
|
103
|
+
id: video.id,
|
|
104
|
+
data: {
|
|
105
|
+
...metadata,
|
|
106
|
+
...status
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'video.asset.deleted':
|
|
112
|
+
await req.payload.delete({
|
|
113
|
+
collection,
|
|
114
|
+
id: video.id
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
case 'video.asset.errored':
|
|
118
|
+
{
|
|
119
|
+
req.payload.logger.error({
|
|
120
|
+
assetId,
|
|
121
|
+
errors: event.data.errors,
|
|
122
|
+
msg: '[payload-mux] Asset errored'
|
|
123
|
+
});
|
|
124
|
+
const error = event.data.errors?.messages?.join('; ') || event.data.errors?.type || 'Unknown Mux error';
|
|
125
|
+
await req.payload.update({
|
|
126
|
+
collection,
|
|
127
|
+
id: video.id,
|
|
128
|
+
data: {
|
|
129
|
+
status: 'errored',
|
|
130
|
+
error
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
default:
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
req.payload.logger.error({
|
|
140
|
+
err,
|
|
141
|
+
msg: `[payload-mux] Failed to handle '${event.type}' for asset '${assetId}'`
|
|
142
|
+
});
|
|
143
|
+
return fail();
|
|
144
|
+
}
|
|
145
|
+
return ok();
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/webhook.ts"],"sourcesContent":["import type Mux from '@mux/mux-node'\nimport type { CollectionSlug, PayloadHandler } from 'payload'\nimport { type AssetLike, getAssetMetadata } from '../lib/getAssetMetadata'\nimport type { MuxVideoPluginOptions } from '../types'\n\nconst ok = () => new Response('Success!', { status: 200 })\nconst fail = () => new Response('Error', { status: 500 })\n\n// One info line per process so users can confirm the webhook wiring end to end.\nlet verifiedLogged = false\n\n/**\n * `POST /api/mux/webhook` — keep Payload in sync with Mux. `unwrap()` verifies the Mux\n * signature and returns a typed event; we then set metadata + status on `video.asset.ready` /\n * `updated`, delete the doc on `video.asset.deleted`, mark the doc errored on\n * `video.asset.errored`, and (when `autoCreateOnWebhook`) backfill a doc for an asset Payload\n * doesn't have yet. 200s after a handled event so Mux stops retrying; 500s on a failed\n * handler so Mux retries it.\n *\n * Note: Payload pre-parses the request body, so the signature is verified against a\n * re-serialized payload rather than the original bytes — unavoidable at the endpoint layer.\n */\nexport const muxWebhookHandler =\n (mux: Mux, pluginOptions: MuxVideoPluginOptions): PayloadHandler =>\n async (req) => {\n if (!req.json) return Response.json({ error: 'Bad request.' }, { status: 400 })\n\n const body = await req.json()\n if (!body) return Response.json({ error: 'Bad request.' }, { status: 400 })\n\n let event: Awaited<ReturnType<typeof mux.webhooks.unwrap>>\n try {\n event = await mux.webhooks.unwrap(JSON.stringify(body), req.headers)\n } catch (err) {\n const hint = 'check that MUX_WEBHOOK_SECRET matches the signing secret in the Mux dashboard'\n req.payload.logger.error({ err, msg: `[payload-mux] Webhook signature verification failed — ${hint}` })\n return Response.json({ error: `Invalid signature — ${hint}.` }, { status: 401 })\n }\n\n if (!verifiedLogged) {\n verifiedLogged = true\n req.payload.logger.info('[payload-mux] webhook verified — receiving Mux events')\n }\n\n const collection = ((pluginOptions.extendCollection as string) ?? 'mux-video') as CollectionSlug\n const assetId = event.object.id\n\n const videos = await req.payload.find({ collection, where: { assetId: { equals: assetId } }, limit: 1, pagination: false })\n const video = videos.totalDocs > 0 ? videos.docs[0] : null\n\n if (!video) {\n const backfillable = event.type === 'video.asset.created' || event.type === 'video.asset.ready' || event.type === 'video.asset.updated'\n if (pluginOptions.autoCreateOnWebhook && backfillable) {\n try {\n const title = (event.data as { meta?: { title?: string } }).meta?.title || assetId\n const metadata = getAssetMetadata(event.data as AssetLike)\n const status = metadata.playbackOptions?.length ? 'ready' : 'preparing'\n await req.payload.create({ collection, data: { title, assetId, ...metadata, status } as never })\n } catch (err) {\n req.payload.logger.error({ err, msg: `[payload-mux] Failed to backfill asset '${assetId}'` })\n return fail()\n }\n }\n return ok()\n }\n\n try {\n switch (event.type) {\n case 'video.asset.ready':\n case 'video.asset.updated': {\n const metadata = getAssetMetadata(event.data)\n // `updated` can fire before a playback id exists — only playback-bearing events mean 'ready'.\n const status = metadata.playbackOptions?.length ? { status: 'ready', error: null } : {}\n await req.payload.update({ collection, id: video.id, data: { ...metadata, ...status } as never })\n break\n }\n case 'video.asset.deleted':\n await req.payload.delete({ collection, id: video.id })\n break\n case 'video.asset.errored': {\n req.payload.logger.error({ assetId, errors: event.data.errors, msg: '[payload-mux] Asset errored' })\n const error = event.data.errors?.messages?.join('; ') || event.data.errors?.type || 'Unknown Mux error'\n await req.payload.update({ collection, id: video.id, data: { status: 'errored', error } as never })\n break\n }\n default:\n break\n }\n } catch (err) {\n req.payload.logger.error({ err, msg: `[payload-mux] Failed to handle '${event.type}' for asset '${assetId}'` })\n return fail()\n }\n\n return ok()\n }\n"],"names":["getAssetMetadata","ok","Response","status","fail","verifiedLogged","muxWebhookHandler","mux","pluginOptions","req","json","error","body","event","webhooks","unwrap","JSON","stringify","headers","err","hint","payload","logger","msg","info","collection","extendCollection","assetId","object","id","videos","find","where","equals","limit","pagination","video","totalDocs","docs","backfillable","type","autoCreateOnWebhook","title","data","meta","metadata","playbackOptions","length","create","update","delete","errors","messages","join"],"mappings":"AAEA,SAAyBA,gBAAgB,QAAQ,6BAAyB;AAG1E,MAAMC,KAAK,IAAM,IAAIC,SAAS,YAAY;QAAEC,QAAQ;IAAI;AACxD,MAAMC,OAAO,IAAM,IAAIF,SAAS,SAAS;QAAEC,QAAQ;IAAI;AAEvD,gFAAgF;AAChF,IAAIE,iBAAiB;AAErB;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,oBACX,CAACC,KAAUC,gBACX,OAAOC;QACL,IAAI,CAACA,IAAIC,IAAI,EAAE,OAAOR,SAASQ,IAAI,CAAC;YAAEC,OAAO;QAAe,GAAG;YAAER,QAAQ;QAAI;QAE7E,MAAMS,OAAO,MAAMH,IAAIC,IAAI;QAC3B,IAAI,CAACE,MAAM,OAAOV,SAASQ,IAAI,CAAC;YAAEC,OAAO;QAAe,GAAG;YAAER,QAAQ;QAAI;QAEzE,IAAIU;QACJ,IAAI;YACFA,QAAQ,MAAMN,IAAIO,QAAQ,CAACC,MAAM,CAACC,KAAKC,SAAS,CAACL,OAAOH,IAAIS,OAAO;QACrE,EAAE,OAAOC,KAAK;YACZ,MAAMC,OAAO;YACbX,IAAIY,OAAO,CAACC,MAAM,CAACX,KAAK,CAAC;gBAAEQ;gBAAKI,KAAK,CAAC,sDAAsD,EAAEH,MAAM;YAAC;YACrG,OAAOlB,SAASQ,IAAI,CAAC;gBAAEC,OAAO,CAAC,oBAAoB,EAAES,KAAK,CAAC,CAAC;YAAC,GAAG;gBAAEjB,QAAQ;YAAI;QAChF;QAEA,IAAI,CAACE,gBAAgB;YACnBA,iBAAiB;YACjBI,IAAIY,OAAO,CAACC,MAAM,CAACE,IAAI,CAAC;QAC1B;QAEA,MAAMC,aAAc,AAACjB,cAAckB,gBAAgB,IAAe;QAClE,MAAMC,UAAUd,MAAMe,MAAM,CAACC,EAAE;QAE/B,MAAMC,SAAS,MAAMrB,IAAIY,OAAO,CAACU,IAAI,CAAC;YAAEN;YAAYO,OAAO;gBAAEL,SAAS;oBAAEM,QAAQN;gBAAQ;YAAE;YAAGO,OAAO;YAAGC,YAAY;QAAM;QACzH,MAAMC,QAAQN,OAAOO,SAAS,GAAG,IAAIP,OAAOQ,IAAI,CAAC,EAAE,GAAG;QAEtD,IAAI,CAACF,OAAO;YACV,MAAMG,eAAe1B,MAAM2B,IAAI,KAAK,yBAAyB3B,MAAM2B,IAAI,KAAK,uBAAuB3B,MAAM2B,IAAI,KAAK;YAClH,IAAIhC,cAAciC,mBAAmB,IAAIF,cAAc;gBACrD,IAAI;oBACF,MAAMG,QAAQ,AAAC7B,MAAM8B,IAAI,CAAmCC,IAAI,EAAEF,SAASf;oBAC3E,MAAMkB,WAAW7C,iBAAiBa,MAAM8B,IAAI;oBAC5C,MAAMxC,SAAS0C,SAASC,eAAe,EAAEC,SAAS,UAAU;oBAC5D,MAAMtC,IAAIY,OAAO,CAAC2B,MAAM,CAAC;wBAAEvB;wBAAYkB,MAAM;4BAAED;4BAAOf;4BAAS,GAAGkB,QAAQ;4BAAE1C;wBAAO;oBAAW;gBAChG,EAAE,OAAOgB,KAAK;oBACZV,IAAIY,OAAO,CAACC,MAAM,CAACX,KAAK,CAAC;wBAAEQ;wBAAKI,KAAK,CAAC,wCAAwC,EAAEI,QAAQ,CAAC,CAAC;oBAAC;oBAC3F,OAAOvB;gBACT;YACF;YACA,OAAOH;QACT;QAEA,IAAI;YACF,OAAQY,MAAM2B,IAAI;gBAChB,KAAK;gBACL,KAAK;oBAAuB;wBAC1B,MAAMK,WAAW7C,iBAAiBa,MAAM8B,IAAI;wBAC5C,8FAA8F;wBAC9F,MAAMxC,SAAS0C,SAASC,eAAe,EAAEC,SAAS;4BAAE5C,QAAQ;4BAASQ,OAAO;wBAAK,IAAI,CAAC;wBACtF,MAAMF,IAAIY,OAAO,CAAC4B,MAAM,CAAC;4BAAExB;4BAAYI,IAAIO,MAAMP,EAAE;4BAAEc,MAAM;gCAAE,GAAGE,QAAQ;gCAAE,GAAG1C,MAAM;4BAAC;wBAAW;wBAC/F;oBACF;gBACA,KAAK;oBACH,MAAMM,IAAIY,OAAO,CAAC6B,MAAM,CAAC;wBAAEzB;wBAAYI,IAAIO,MAAMP,EAAE;oBAAC;oBACpD;gBACF,KAAK;oBAAuB;wBAC1BpB,IAAIY,OAAO,CAACC,MAAM,CAACX,KAAK,CAAC;4BAAEgB;4BAASwB,QAAQtC,MAAM8B,IAAI,CAACQ,MAAM;4BAAE5B,KAAK;wBAA8B;wBAClG,MAAMZ,QAAQE,MAAM8B,IAAI,CAACQ,MAAM,EAAEC,UAAUC,KAAK,SAASxC,MAAM8B,IAAI,CAACQ,MAAM,EAAEX,QAAQ;wBACpF,MAAM/B,IAAIY,OAAO,CAAC4B,MAAM,CAAC;4BAAExB;4BAAYI,IAAIO,MAAMP,EAAE;4BAAEc,MAAM;gCAAExC,QAAQ;gCAAWQ;4BAAM;wBAAW;wBACjG;oBACF;gBACA;oBACE;YACJ;QACF,EAAE,OAAOQ,KAAK;YACZV,IAAIY,OAAO,CAACC,MAAM,CAACX,KAAK,CAAC;gBAAEQ;gBAAKI,KAAK,CAAC,gCAAgC,EAAEV,MAAM2B,IAAI,CAAC,aAAa,EAAEb,QAAQ,CAAC,CAAC;YAAC;YAC7G,OAAOvB;QACT;QAEA,OAAOH;IACT,EAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type Mux from '@mux/mux-node';
|
|
2
|
+
import type { CollectionAfterDeleteHook } from 'payload';
|
|
3
|
+
/** When a video doc is deleted in Payload, delete its asset in Mux too. If the asset was
|
|
4
|
+
* already removed in Mux (a `not_found`), that's fine — nothing to do. */
|
|
5
|
+
export declare const getAfterDeleteHook: (mux: Mux) => CollectionAfterDeleteHook;
|
|
6
|
+
//# sourceMappingURL=afterDelete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"afterDelete.d.ts","sourceRoot":"","sources":["../../src/hooks/afterDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAExD;2EAC2E;AAC3E,eAAO,MAAM,kBAAkB,GAC5B,KAAK,GAAG,KAAG,yBAUX,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** When a video doc is deleted in Payload, delete its asset in Mux too. If the asset was
|
|
2
|
+
* already removed in Mux (a `not_found`), that's fine — nothing to do. */ export const getAfterDeleteHook = (mux)=>async ({ doc })=>{
|
|
3
|
+
const assetId = doc.assetId;
|
|
4
|
+
if (!assetId) return;
|
|
5
|
+
try {
|
|
6
|
+
await mux.video.assets.delete(assetId);
|
|
7
|
+
} catch (err) {
|
|
8
|
+
if (err.type !== 'not_found') throw err;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=afterDelete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/afterDelete.ts"],"sourcesContent":["import type Mux from '@mux/mux-node'\nimport type { CollectionAfterDeleteHook } from 'payload'\n\n/** When a video doc is deleted in Payload, delete its asset in Mux too. If the asset was\n * already removed in Mux (a `not_found`), that's fine — nothing to do. */\nexport const getAfterDeleteHook =\n (mux: Mux): CollectionAfterDeleteHook =>\n async ({ doc }) => {\n const assetId = (doc as { assetId?: string }).assetId\n if (!assetId) return\n\n try {\n await mux.video.assets.delete(assetId)\n } catch (err) {\n if ((err as { type?: string }).type !== 'not_found') throw err\n }\n }\n"],"names":["getAfterDeleteHook","mux","doc","assetId","video","assets","delete","err","type"],"mappings":"AAGA;yEACyE,GACzE,OAAO,MAAMA,qBACX,CAACC,MACD,OAAO,EAAEC,GAAG,EAAE;QACZ,MAAMC,UAAU,AAACD,IAA6BC,OAAO;QACrD,IAAI,CAACA,SAAS;QAEd,IAAI;YACF,MAAMF,IAAIG,KAAK,CAACC,MAAM,CAACC,MAAM,CAACH;QAChC,EAAE,OAAOI,KAAK;YACZ,IAAI,AAACA,IAA0BC,IAAI,KAAK,aAAa,MAAMD;QAC7D;IACF,EAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type Mux from '@mux/mux-node';
|
|
2
|
+
import type { CollectionBeforeChangeHook } from 'payload';
|
|
3
|
+
/**
|
|
4
|
+
* When a doc's `assetId` is set or changed: delete the previous asset (on update), fetch the
|
|
5
|
+
* new asset, briefly poll while it's `preparing`, and fold its metadata + `status` into the
|
|
6
|
+
* doc. A still-`preparing` asset is left for the webhook to finish; an `errored` asset is deleted
|
|
7
|
+
* and the save rejected. The doc's `filename`/`title` are made unique (the upload collection
|
|
8
|
+
* requires a unique filename).
|
|
9
|
+
*/
|
|
10
|
+
export declare const getBeforeChangeHook: (mux: Mux) => CollectionBeforeChangeHook;
|
|
11
|
+
//# sourceMappingURL=beforeChange.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beforeChange.d.ts","sourceRoot":"","sources":["../../src/hooks/beforeChange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAE,0BAA0B,EAAkB,MAAM,SAAS,CAAA;AAQzE;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC7B,KAAK,GAAG,KAAG,0BA+DX,CAAA"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { delay } from "../lib/delay.js";
|
|
2
|
+
import { getAssetMetadata } from "../lib/getAssetMetadata.js";
|
|
3
|
+
/** Poll a freshly-uploaded asset for up to this long before handing off to the webhook. */ const POLL_INTERVAL_MS = 1500;
|
|
4
|
+
const POLL_LIMIT_MS = 6000;
|
|
5
|
+
/**
|
|
6
|
+
* When a doc's `assetId` is set or changed: delete the previous asset (on update), fetch the
|
|
7
|
+
* new asset, briefly poll while it's `preparing`, and fold its metadata + `status` into the
|
|
8
|
+
* doc. A still-`preparing` asset is left for the webhook to finish; an `errored` asset is deleted
|
|
9
|
+
* and the save rejected. The doc's `filename`/`title` are made unique (the upload collection
|
|
10
|
+
* requires a unique filename).
|
|
11
|
+
*/ export const getBeforeChangeHook = (mux)=>async ({ req, data: incomingData, operation, originalDoc, collection })=>{
|
|
12
|
+
let data = {
|
|
13
|
+
...incomingData
|
|
14
|
+
};
|
|
15
|
+
const previousAssetId = originalDoc?.assetId;
|
|
16
|
+
// Pre-resolved data (seed / import / webhook backfill): the doc already carries its
|
|
17
|
+
// assetId and playback options, so there's nothing to fetch from Mux. Pass it through.
|
|
18
|
+
if (data.assetId && Array.isArray(data.playbackOptions) && data.playbackOptions.length > 0) return data;
|
|
19
|
+
if (previousAssetId === data.assetId) return data;
|
|
20
|
+
try {
|
|
21
|
+
if (operation === 'update' && previousAssetId) {
|
|
22
|
+
await mux.video.assets.delete(previousAssetId);
|
|
23
|
+
}
|
|
24
|
+
let asset = await mux.video.assets.retrieve(data.assetId);
|
|
25
|
+
const timeout = Date.now() + POLL_LIMIT_MS;
|
|
26
|
+
while(asset.status === 'preparing' && Date.now() < timeout){
|
|
27
|
+
await delay(POLL_INTERVAL_MS);
|
|
28
|
+
asset = await mux.video.assets.retrieve(data.assetId);
|
|
29
|
+
}
|
|
30
|
+
if (asset.status === 'errored') {
|
|
31
|
+
await mux.video.assets.delete(data.assetId);
|
|
32
|
+
throw new Error(`Unable to prepare Mux asset (status: ${asset.status}). It's been deleted, please try again.`);
|
|
33
|
+
}
|
|
34
|
+
if (asset.status === 'ready') {
|
|
35
|
+
data = {
|
|
36
|
+
...data,
|
|
37
|
+
...getAssetMetadata(asset),
|
|
38
|
+
status: 'ready',
|
|
39
|
+
error: null
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
// Still preparing after the short poll — the webhook flips the status to 'ready'.
|
|
43
|
+
data = {
|
|
44
|
+
...data,
|
|
45
|
+
status: 'preparing',
|
|
46
|
+
error: null
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// `title` is unique: probe `base`, then `base (1)`, `base (2)` … until one is free.
|
|
50
|
+
// Exact-match probing avoids counting unrelated titles, and excluding the current doc
|
|
51
|
+
// (on update) keeps a replace-the-video save from renaming itself.
|
|
52
|
+
const base = data.title;
|
|
53
|
+
let uniqueTitle = base;
|
|
54
|
+
for(let n = 1;; n++){
|
|
55
|
+
const titleClause = {
|
|
56
|
+
title: {
|
|
57
|
+
equals: uniqueTitle
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const where = operation === 'update' && originalDoc?.id != null ? {
|
|
61
|
+
and: [
|
|
62
|
+
titleClause,
|
|
63
|
+
{
|
|
64
|
+
id: {
|
|
65
|
+
not_equals: originalDoc.id
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
} : titleClause;
|
|
70
|
+
const { totalDocs } = await req.payload.count({
|
|
71
|
+
collection: collection.slug,
|
|
72
|
+
where
|
|
73
|
+
});
|
|
74
|
+
if (totalDocs === 0) break;
|
|
75
|
+
uniqueTitle = `${base} (${n})`;
|
|
76
|
+
}
|
|
77
|
+
data.title = uniqueTitle;
|
|
78
|
+
// When extending an actual upload collection, the Mux asset stands in for the file: blank
|
|
79
|
+
// the url and mirror the unique title onto the (required, unique) filename. No-op for the
|
|
80
|
+
// default `mux-video` collection, which is not an upload collection and has neither field.
|
|
81
|
+
if (collection.upload) {
|
|
82
|
+
data.url = '';
|
|
83
|
+
data.filename = uniqueTitle;
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
req.payload.logger.error({
|
|
87
|
+
err,
|
|
88
|
+
msg: `[payload-mux] Error preparing Mux asset for '${data.filename ?? data.title}'`
|
|
89
|
+
});
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
return data;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=beforeChange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/beforeChange.ts"],"sourcesContent":["import type Mux from '@mux/mux-node'\nimport type { CollectionBeforeChangeHook, CollectionSlug } from 'payload'\nimport { delay } from '../lib/delay'\nimport { getAssetMetadata } from '../lib/getAssetMetadata'\n\n/** Poll a freshly-uploaded asset for up to this long before handing off to the webhook. */\nconst POLL_INTERVAL_MS = 1500\nconst POLL_LIMIT_MS = 6000\n\n/**\n * When a doc's `assetId` is set or changed: delete the previous asset (on update), fetch the\n * new asset, briefly poll while it's `preparing`, and fold its metadata + `status` into the\n * doc. A still-`preparing` asset is left for the webhook to finish; an `errored` asset is deleted\n * and the save rejected. The doc's `filename`/`title` are made unique (the upload collection\n * requires a unique filename).\n */\nexport const getBeforeChangeHook =\n (mux: Mux): CollectionBeforeChangeHook =>\n async ({ req, data: incomingData, operation, originalDoc, collection }) => {\n let data = { ...incomingData }\n const previousAssetId: string | undefined = originalDoc?.assetId\n\n // Pre-resolved data (seed / import / webhook backfill): the doc already carries its\n // assetId and playback options, so there's nothing to fetch from Mux. Pass it through.\n if (data.assetId && Array.isArray(data.playbackOptions) && data.playbackOptions.length > 0) return data\n\n if (previousAssetId === data.assetId) return data\n\n try {\n if (operation === 'update' && previousAssetId) {\n await mux.video.assets.delete(previousAssetId)\n }\n\n let asset = await mux.video.assets.retrieve(data.assetId)\n const timeout = Date.now() + POLL_LIMIT_MS\n while (asset.status === 'preparing' && Date.now() < timeout) {\n await delay(POLL_INTERVAL_MS)\n asset = await mux.video.assets.retrieve(data.assetId)\n }\n\n if (asset.status === 'errored') {\n await mux.video.assets.delete(data.assetId)\n throw new Error(`Unable to prepare Mux asset (status: ${asset.status}). It's been deleted, please try again.`)\n }\n\n if (asset.status === 'ready') {\n data = { ...data, ...getAssetMetadata(asset), status: 'ready', error: null }\n } else {\n // Still preparing after the short poll — the webhook flips the status to 'ready'.\n data = { ...data, status: 'preparing', error: null }\n }\n\n // `title` is unique: probe `base`, then `base (1)`, `base (2)` … until one is free.\n // Exact-match probing avoids counting unrelated titles, and excluding the current doc\n // (on update) keeps a replace-the-video save from renaming itself.\n const base = data.title as string\n let uniqueTitle = base\n for (let n = 1; ; n++) {\n const titleClause = { title: { equals: uniqueTitle } }\n const where =\n operation === 'update' && originalDoc?.id != null ? { and: [titleClause, { id: { not_equals: originalDoc.id } }] } : titleClause\n const { totalDocs } = await req.payload.count({ collection: collection.slug as CollectionSlug, where })\n if (totalDocs === 0) break\n uniqueTitle = `${base} (${n})`\n }\n data.title = uniqueTitle\n\n // When extending an actual upload collection, the Mux asset stands in for the file: blank\n // the url and mirror the unique title onto the (required, unique) filename. No-op for the\n // default `mux-video` collection, which is not an upload collection and has neither field.\n if (collection.upload) {\n data.url = ''\n data.filename = uniqueTitle\n }\n } catch (err) {\n req.payload.logger.error({ err, msg: `[payload-mux] Error preparing Mux asset for '${data.filename ?? data.title}'` })\n throw err\n }\n\n return data\n }\n"],"names":["delay","getAssetMetadata","POLL_INTERVAL_MS","POLL_LIMIT_MS","getBeforeChangeHook","mux","req","data","incomingData","operation","originalDoc","collection","previousAssetId","assetId","Array","isArray","playbackOptions","length","video","assets","delete","asset","retrieve","timeout","Date","now","status","Error","error","base","title","uniqueTitle","n","titleClause","equals","where","id","and","not_equals","totalDocs","payload","count","slug","upload","url","filename","err","logger","msg"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type Mux from '@mux/mux-node';
|
|
2
|
+
import type { CollectionBeforeValidateHook } from 'payload';
|
|
3
|
+
import type { MuxVideoPluginOptions } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Server-side ingest. When a doc is created (or updated) with a transient `source` (a local
|
|
6
|
+
* path or `http(s)` URL) and no `assetId` yet, upload it to Mux, wait until the asset is
|
|
7
|
+
* `ready`, and fold the asset id + metadata into the doc — running in `beforeValidate` so it
|
|
8
|
+
* lands before `assetId`'s required check. The `source` key is always stripped so it never
|
|
9
|
+
* persists. Docs that already carry an `assetId` (the admin direct-upload flow, webhook
|
|
10
|
+
* backfill, or pre-resolved seed data) skip ingest entirely.
|
|
11
|
+
*
|
|
12
|
+
* Playback policy (and the rest of `new_asset_settings`) come from the plugin's `uploadSettings`
|
|
13
|
+
* — the same config the admin direct-upload path uses — so a seeded video matches an admin-uploaded
|
|
14
|
+
* one. A per-video `source.playbackPolicy` overrides it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const getBeforeValidateHook: (mux: Mux, options?: MuxVideoPluginOptions) => CollectionBeforeValidateHook;
|
|
17
|
+
//# sourceMappingURL=beforeValidate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beforeValidate.d.ts","sourceRoot":"","sources":["../../src/hooks/beforeValidate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAA;AAG3D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AA6BrD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GAC/B,KAAK,GAAG,EAAE,UAAS,qBAA0B,KAAG,4BA0BhD,CAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getAssetMetadata } from "../lib/getAssetMetadata.js";
|
|
2
|
+
import { ingestMuxAsset } from "../lib/ingest.js";
|
|
3
|
+
/** Normalize a `source` value (string or object) to a ref + options, or null if unusable. */ function normalizeSource(source) {
|
|
4
|
+
if (!source) return null;
|
|
5
|
+
if (typeof source === 'string') return {
|
|
6
|
+
ref: source
|
|
7
|
+
};
|
|
8
|
+
const s = source;
|
|
9
|
+
const ref = s.url ?? s.file;
|
|
10
|
+
if (!ref) return null;
|
|
11
|
+
return {
|
|
12
|
+
ref,
|
|
13
|
+
playbackPolicy: s.playbackPolicy,
|
|
14
|
+
corsOrigin: s.corsOrigin,
|
|
15
|
+
posterTimestamp: s.posterTimestamp
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Server-side ingest. When a doc is created (or updated) with a transient `source` (a local
|
|
20
|
+
* path or `http(s)` URL) and no `assetId` yet, upload it to Mux, wait until the asset is
|
|
21
|
+
* `ready`, and fold the asset id + metadata into the doc — running in `beforeValidate` so it
|
|
22
|
+
* lands before `assetId`'s required check. The `source` key is always stripped so it never
|
|
23
|
+
* persists. Docs that already carry an `assetId` (the admin direct-upload flow, webhook
|
|
24
|
+
* backfill, or pre-resolved seed data) skip ingest entirely.
|
|
25
|
+
*
|
|
26
|
+
* Playback policy (and the rest of `new_asset_settings`) come from the plugin's `uploadSettings`
|
|
27
|
+
* — the same config the admin direct-upload path uses — so a seeded video matches an admin-uploaded
|
|
28
|
+
* one. A per-video `source.playbackPolicy` overrides it.
|
|
29
|
+
*/ export const getBeforeValidateHook = (mux, options = {})=>async ({ data })=>{
|
|
30
|
+
const d = data ?? {};
|
|
31
|
+
if (!('source' in d)) return data;
|
|
32
|
+
const { source, ...rest } = d;
|
|
33
|
+
const norm = d.assetId ? null : normalizeSource(source);
|
|
34
|
+
if (!norm) return rest;
|
|
35
|
+
const asset = await ingestMuxAsset(mux, norm.ref, {
|
|
36
|
+
// Mirror the admin direct-upload path (`upload.ts`): the plugin-level `playbackPolicy`
|
|
37
|
+
// shorthand is the base default, overlaid by the full `uploadSettings.new_asset_settings`,
|
|
38
|
+
// with a per-video `source.playbackPolicy` winning last — so seeded and admin-uploaded
|
|
39
|
+
// videos share one policy.
|
|
40
|
+
newAssetSettings: {
|
|
41
|
+
playback_policy: [
|
|
42
|
+
options.playbackPolicy ?? 'public'
|
|
43
|
+
],
|
|
44
|
+
...options.uploadSettings?.new_asset_settings
|
|
45
|
+
},
|
|
46
|
+
playbackPolicy: norm.playbackPolicy,
|
|
47
|
+
corsOrigin: norm.corsOrigin ?? options.uploadSettings?.cors_origin
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
...rest,
|
|
51
|
+
assetId: asset.id,
|
|
52
|
+
...getAssetMetadata(asset),
|
|
53
|
+
// Ingest waits until the asset is ready, so the status can be stamped directly.
|
|
54
|
+
status: 'ready',
|
|
55
|
+
...norm.posterTimestamp !== undefined ? {
|
|
56
|
+
posterTimestamp: norm.posterTimestamp
|
|
57
|
+
} : {}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=beforeValidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/beforeValidate.ts"],"sourcesContent":["import type Mux from '@mux/mux-node'\nimport type { CollectionBeforeValidateHook } from 'payload'\nimport { getAssetMetadata } from '../lib/getAssetMetadata'\nimport { ingestMuxAsset, type MuxSource } from '../lib/ingest'\nimport type { MuxVideoPluginOptions } from '../types'\n\n/** The transient `source` a caller sets to ingest server-side: a string path/URL, or an\n * object carrying the path/URL plus per-video options. Never persisted. */\ninterface MuxSourceInput {\n file?: string\n url?: string\n playbackPolicy?: 'public' | 'signed'\n posterTimestamp?: number\n corsOrigin?: string\n}\n\ninterface NormalizedSource {\n ref: MuxSource\n playbackPolicy?: 'public' | 'signed'\n corsOrigin?: string\n posterTimestamp?: number\n}\n\n/** Normalize a `source` value (string or object) to a ref + options, or null if unusable. */\nfunction normalizeSource(source: unknown): NormalizedSource | null {\n if (!source) return null\n if (typeof source === 'string') return { ref: source }\n const s = source as MuxSourceInput\n const ref = s.url ?? s.file\n if (!ref) return null\n return { ref, playbackPolicy: s.playbackPolicy, corsOrigin: s.corsOrigin, posterTimestamp: s.posterTimestamp }\n}\n\n/**\n * Server-side ingest. When a doc is created (or updated) with a transient `source` (a local\n * path or `http(s)` URL) and no `assetId` yet, upload it to Mux, wait until the asset is\n * `ready`, and fold the asset id + metadata into the doc — running in `beforeValidate` so it\n * lands before `assetId`'s required check. The `source` key is always stripped so it never\n * persists. Docs that already carry an `assetId` (the admin direct-upload flow, webhook\n * backfill, or pre-resolved seed data) skip ingest entirely.\n *\n * Playback policy (and the rest of `new_asset_settings`) come from the plugin's `uploadSettings`\n * — the same config the admin direct-upload path uses — so a seeded video matches an admin-uploaded\n * one. A per-video `source.playbackPolicy` overrides it.\n */\nexport const getBeforeValidateHook =\n (mux: Mux, options: MuxVideoPluginOptions = {}): CollectionBeforeValidateHook =>\n async ({ data }) => {\n const d = (data ?? {}) as Record<string, unknown>\n if (!('source' in d)) return data\n\n const { source, ...rest } = d\n const norm = d.assetId ? null : normalizeSource(source)\n if (!norm) return rest\n\n const asset = await ingestMuxAsset(mux, norm.ref, {\n // Mirror the admin direct-upload path (`upload.ts`): the plugin-level `playbackPolicy`\n // shorthand is the base default, overlaid by the full `uploadSettings.new_asset_settings`,\n // with a per-video `source.playbackPolicy` winning last — so seeded and admin-uploaded\n // videos share one policy.\n newAssetSettings: { playback_policy: [options.playbackPolicy ?? 'public'], ...options.uploadSettings?.new_asset_settings },\n playbackPolicy: norm.playbackPolicy,\n corsOrigin: norm.corsOrigin ?? options.uploadSettings?.cors_origin,\n })\n return {\n ...rest,\n assetId: asset.id,\n ...getAssetMetadata(asset),\n // Ingest waits until the asset is ready, so the status can be stamped directly.\n status: 'ready',\n ...(norm.posterTimestamp !== undefined ? { posterTimestamp: norm.posterTimestamp } : {}),\n }\n }\n"],"names":["getAssetMetadata","ingestMuxAsset","normalizeSource","source","ref","s","url","file","playbackPolicy","corsOrigin","posterTimestamp","getBeforeValidateHook","mux","options","data","d","rest","norm","assetId","asset","newAssetSettings","playback_policy","uploadSettings","new_asset_settings","cors_origin","id","status","undefined"],"mappings":"AAEA,SAASA,gBAAgB,QAAQ,6BAAyB;AAC1D,SAASC,cAAc,QAAwB,mBAAe;AAoB9D,2FAA2F,GAC3F,SAASC,gBAAgBC,MAAe;IACtC,IAAI,CAACA,QAAQ,OAAO;IACpB,IAAI,OAAOA,WAAW,UAAU,OAAO;QAAEC,KAAKD;IAAO;IACrD,MAAME,IAAIF;IACV,MAAMC,MAAMC,EAAEC,GAAG,IAAID,EAAEE,IAAI;IAC3B,IAAI,CAACH,KAAK,OAAO;IACjB,OAAO;QAAEA;QAAKI,gBAAgBH,EAAEG,cAAc;QAAEC,YAAYJ,EAAEI,UAAU;QAAEC,iBAAiBL,EAAEK,eAAe;IAAC;AAC/G;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,wBACX,CAACC,KAAUC,UAAiC,CAAC,CAAC,GAC9C,OAAO,EAAEC,IAAI,EAAE;QACb,MAAMC,IAAKD,QAAQ,CAAC;QACpB,IAAI,CAAE,CAAA,YAAYC,CAAAA,GAAI,OAAOD;QAE7B,MAAM,EAAEX,MAAM,EAAE,GAAGa,MAAM,GAAGD;QAC5B,MAAME,OAAOF,EAAEG,OAAO,GAAG,OAAOhB,gBAAgBC;QAChD,IAAI,CAACc,MAAM,OAAOD;QAElB,MAAMG,QAAQ,MAAMlB,eAAeW,KAAKK,KAAKb,GAAG,EAAE;YAChD,uFAAuF;YACvF,2FAA2F;YAC3F,uFAAuF;YACvF,2BAA2B;YAC3BgB,kBAAkB;gBAAEC,iBAAiB;oBAACR,QAAQL,cAAc,IAAI;iBAAS;gBAAE,GAAGK,QAAQS,cAAc,EAAEC,kBAAkB;YAAC;YACzHf,gBAAgBS,KAAKT,cAAc;YACnCC,YAAYQ,KAAKR,UAAU,IAAII,QAAQS,cAAc,EAAEE;QACzD;QACA,OAAO;YACL,GAAGR,IAAI;YACPE,SAASC,MAAMM,EAAE;YACjB,GAAGzB,iBAAiBmB,MAAM;YAC1B,gFAAgF;YAChFO,QAAQ;YACR,GAAIT,KAAKP,eAAe,KAAKiB,YAAY;gBAAEjB,iBAAiBO,KAAKP,eAAe;YAAC,IAAI,CAAC,CAAC;QACzF;IACF,EAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { muxVideoPlugin } from './plugin';
|
|
2
|
+
export { MuxVideo } from './collections/MuxVideo';
|
|
3
|
+
export { ingestMuxVideo } from './lib/ingest';
|
|
4
|
+
export type { IngestMuxVideoOptions } from './lib/ingest';
|
|
5
|
+
export type { MuxVideoInitSettings, MuxVideoNewAssetSettings, MuxVideoPluginOptions, MuxVideoSignedUrlOptions, MuxVideoUploadSettings, } from './types';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAGzC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAGjD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAGzD,YAAY,EACV,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The plugin
|
|
2
|
+
export { muxVideoPlugin } from "./plugin.js";
|
|
3
|
+
// The collection factory, for advanced consumers assembling their own config
|
|
4
|
+
export { MuxVideo } from "./collections/MuxVideo.js";
|
|
5
|
+
// Server-side ingest — create a mux-video from a local file / URL (seeding, imports, migrations)
|
|
6
|
+
export { ingestMuxVideo } from "./lib/ingest.js";
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// The plugin\nexport { muxVideoPlugin } from './plugin'\n\n// The collection factory, for advanced consumers assembling their own config\nexport { MuxVideo } from './collections/MuxVideo'\n\n// Server-side ingest — create a mux-video from a local file / URL (seeding, imports, migrations)\nexport { ingestMuxVideo } from './lib/ingest'\nexport type { IngestMuxVideoOptions } from './lib/ingest'\n\n// Types\nexport type {\n MuxVideoInitSettings,\n MuxVideoNewAssetSettings,\n MuxVideoPluginOptions,\n MuxVideoSignedUrlOptions,\n MuxVideoUploadSettings,\n} from './types'\n"],"names":["muxVideoPlugin","MuxVideo","ingestMuxVideo"],"mappings":"AAAA,aAAa;AACb,SAASA,cAAc,QAAQ,cAAU;AAEzC,6EAA6E;AAC7E,SAASC,QAAQ,QAAQ,4BAAwB;AAEjD,iGAAiG;AACjG,SAASC,cAAc,QAAQ,kBAAc"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
/** Default upload/read gate: allow only a logged-in user from the configured admin
|
|
3
|
+
* collection. Override per-plugin via the `access` option. */
|
|
4
|
+
export declare const defaultAccess: (req: PayloadRequest) => boolean;
|
|
5
|
+
//# sourceMappingURL=defaultAccess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultAccess.d.ts","sourceRoot":"","sources":["../../src/lib/defaultAccess.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;+DAC+D;AAC/D,eAAO,MAAM,aAAa,GAAI,KAAK,cAAc,KAAG,OAAqF,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Default upload/read gate: allow only a logged-in user from the configured admin
|
|
2
|
+
* collection. Override per-plugin via the `access` option. */ export const defaultAccess = (req)=>Boolean(req.user && req.user.collection === req.payload.config.admin.user);
|
|
3
|
+
|
|
4
|
+
//# sourceMappingURL=defaultAccess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/defaultAccess.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\n/** Default upload/read gate: allow only a logged-in user from the configured admin\n * collection. Override per-plugin via the `access` option. */\nexport const defaultAccess = (req: PayloadRequest): boolean => Boolean(req.user && req.user.collection === req.payload.config.admin.user)\n"],"names":["defaultAccess","req","Boolean","user","collection","payload","config","admin"],"mappings":"AAEA;6DAC6D,GAC7D,OAAO,MAAMA,gBAAgB,CAACC,MAAiCC,QAAQD,IAAIE,IAAI,IAAIF,IAAIE,IAAI,CAACC,UAAU,KAAKH,IAAII,OAAO,CAACC,MAAM,CAACC,KAAK,CAACJ,IAAI,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delay.d.ts","sourceRoot":"","sources":["../../src/lib/delay.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAAsD,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/delay.ts"],"sourcesContent":["/** Resolve after `ms` milliseconds. */\nexport const delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n"],"names":["delay","ms","Promise","resolve","setTimeout"],"mappings":"AAAA,qCAAqC,GACrC,OAAO,MAAMA,QAAQ,CAACC,KAA8B,IAAIC,QAAQ,CAACC,UAAYC,WAAWD,SAASF,KAAI"}
|