@10x-media/webhooks 0.1.0-beta.0 → 0.1.0-beta.2
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/CHANGELOG.md +38 -0
- package/README.md +21 -149
- package/dist/collections/deliveries.js +99 -0
- package/dist/collections/deliveries.js.map +1 -0
- package/dist/collections/subscriptions.js +123 -0
- package/dist/collections/subscriptions.js.map +1 -0
- package/dist/constants.js +29 -0
- package/dist/constants.js.map +1 -0
- package/dist/delivery/DeliveryStatusCell.d.ts +10 -0
- package/dist/delivery/DeliveryStatusCell.js +42 -0
- package/dist/delivery/DeliveryStatusCell.js.map +1 -0
- package/dist/delivery/RedeliverButton.d.ts +6 -0
- package/dist/delivery/RedeliverButton.js +43 -0
- package/dist/delivery/RedeliverButton.js.map +1 -0
- package/dist/delivery/buildPayload.js +32 -0
- package/dist/delivery/buildPayload.js.map +1 -0
- package/dist/delivery/deliver.js +36 -0
- package/dist/delivery/deliver.js.map +1 -0
- package/dist/delivery/deliverTask.js +80 -0
- package/dist/delivery/deliverTask.js.map +1 -0
- package/dist/delivery/deriveDeliveryStatus.js +10 -0
- package/dist/delivery/deriveDeliveryStatus.js.map +1 -0
- package/dist/delivery/redeliver.js +83 -0
- package/dist/delivery/redeliver.js.map +1 -0
- package/dist/delivery/sendDelivery.js +32 -0
- package/dist/delivery/sendDelivery.js.map +1 -0
- package/dist/delivery/sign.js +10 -0
- package/dist/delivery/sign.js.map +1 -0
- package/dist/events/eventTypes.js +27 -0
- package/dist/events/eventTypes.js.map +1 -0
- package/dist/events/hooks.js +132 -0
- package/dist/events/hooks.js.map +1 -0
- package/dist/exports/client.d.ts +3 -14
- package/dist/exports/client.js +2 -81
- package/dist/exports/i18n.d.ts +3 -37
- package/dist/exports/i18n.js +2 -2
- package/dist/exports/types.d.ts +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +3 -817
- package/dist/index.js.map +1 -1
- package/dist/{index-CpsTtHO1.d.ts → options.d.ts} +17 -18
- package/dist/options.js +15 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin/registerTranslations.js +19 -0
- package/dist/plugin/registerTranslations.js.map +1 -0
- package/dist/plugin/registerWebhooks.js +109 -0
- package/dist/plugin/registerWebhooks.js.map +1 -0
- package/dist/plugin/resolveMode.js +15 -0
- package/dist/plugin/resolveMode.js.map +1 -0
- package/dist/plugin/resolveSubscriptions.js +53 -0
- package/dist/plugin/resolveSubscriptions.js.map +1 -0
- package/dist/{translations-CnkwrCJ3.js → translations/en.js} +3 -22
- package/dist/translations/en.js.map +1 -0
- package/dist/translations/index.d.ts +14 -0
- package/dist/translations/index.js +26 -0
- package/dist/translations/index.js.map +1 -0
- package/dist/translations/keys.d.ts +31 -0
- package/dist/{keys-BdNiD3rC.js → translations/keys.js} +2 -2
- package/dist/translations/keys.js.map +1 -0
- package/dist/translations/server.js +13 -0
- package/dist/translations/server.js.map +1 -0
- package/dist/translations/useTranslation.js +12 -0
- package/dist/translations/useTranslation.js.map +1 -0
- package/package.json +4 -3
- package/dist/exports/client.js.map +0 -1
- package/dist/keys-BdNiD3rC.js.map +0 -1
- package/dist/translations-CnkwrCJ3.js.map +0 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { eventId } from "../events/eventTypes.js";
|
|
2
|
+
//#region src/delivery/buildPayload.ts
|
|
3
|
+
/** Build the outbound body, applying any per-collection transform/redaction. */
|
|
4
|
+
const buildPayload = (args) => {
|
|
5
|
+
const { deliveryId, collection, operation, doc, previousDoc, occurredAt, config, req } = args;
|
|
6
|
+
const data = config?.transform ? config.transform({
|
|
7
|
+
doc,
|
|
8
|
+
previousDoc,
|
|
9
|
+
operation,
|
|
10
|
+
req,
|
|
11
|
+
target: "data"
|
|
12
|
+
}) : doc;
|
|
13
|
+
const body = {
|
|
14
|
+
id: deliveryId,
|
|
15
|
+
event: eventId(collection, operation),
|
|
16
|
+
collection,
|
|
17
|
+
operation,
|
|
18
|
+
occurredAt,
|
|
19
|
+
data
|
|
20
|
+
};
|
|
21
|
+
if (operation === "update" && config?.includePreviousData && previousDoc) body.previousData = config?.transform ? config.transform({
|
|
22
|
+
doc: previousDoc,
|
|
23
|
+
operation,
|
|
24
|
+
req,
|
|
25
|
+
target: "previousData"
|
|
26
|
+
}) : previousDoc;
|
|
27
|
+
return body;
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { buildPayload };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=buildPayload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildPayload.js","names":[],"sources":["../../src/delivery/buildPayload.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { eventId } from '../events/eventTypes'\nimport type { CollectionWebhookConfig, WebhookOperation } from '../options'\n\n/** The JSON body POSTed to a subscriber. */\nexport type WebhookBody = {\n\tid: string\n\tevent: string\n\tcollection: string\n\toperation: WebhookOperation\n\toccurredAt: string\n\tdata: unknown\n\tpreviousData?: unknown\n}\n\n/** Build the outbound body, applying any per-collection transform/redaction. */\nexport const buildPayload = (args: {\n\tdeliveryId: string\n\tcollection: string\n\toperation: WebhookOperation\n\tdoc: Record<string, unknown>\n\tpreviousDoc?: Record<string, unknown>\n\toccurredAt: string\n\tconfig?: CollectionWebhookConfig\n\treq: PayloadRequest\n}): WebhookBody => {\n\tconst { deliveryId, collection, operation, doc, previousDoc, occurredAt, config, req } = args\n\tconst data = config?.transform\n\t\t? config.transform({ doc, previousDoc, operation, req, target: 'data' })\n\t\t: doc\n\tconst body: WebhookBody = {\n\t\tid: deliveryId,\n\t\tevent: eventId(collection, operation),\n\t\tcollection,\n\t\toperation,\n\t\toccurredAt,\n\t\tdata,\n\t}\n\tif (operation === 'update' && config?.includePreviousData && previousDoc) {\n\t\tbody.previousData = config?.transform\n\t\t\t? config.transform({ doc: previousDoc, operation, req, target: 'previousData' })\n\t\t\t: previousDoc\n\t}\n\treturn body\n}\n"],"mappings":";;;AAiBA,MAAa,gBAAgB,SASV;CAClB,MAAM,EAAE,YAAY,YAAY,WAAW,KAAK,aAAa,YAAY,QAAQ,QAAQ;CACzF,MAAM,OAAO,QAAQ,YAClB,OAAO,UAAU;EAAE;EAAK;EAAa;EAAW;EAAK,QAAQ;CAAO,CAAC,IACrE;CACH,MAAM,OAAoB;EACzB,IAAI;EACJ,OAAO,QAAQ,YAAY,SAAS;EACpC;EACA;EACA;EACA;CACD;CACA,IAAI,cAAc,YAAY,QAAQ,uBAAuB,aAC5D,KAAK,eAAe,QAAQ,YACzB,OAAO,UAAU;EAAE,KAAK;EAAa;EAAW;EAAK,QAAQ;CAAe,CAAC,IAC7E;CAEJ,OAAO;AACR"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/delivery/deliver.ts
|
|
2
|
+
/** Max response-body characters retained for the delivery log. */
|
|
3
|
+
const MAX_RESPONSE_BODY = 2e3;
|
|
4
|
+
/** POST `body` to `url` with a hard timeout; never throws. */
|
|
5
|
+
const deliver = async (args) => {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const timer = setTimeout(() => controller.abort(), args.timeoutMs);
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
try {
|
|
10
|
+
const res = await fetch(args.url, {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: args.headers,
|
|
13
|
+
body: args.body,
|
|
14
|
+
signal: controller.signal
|
|
15
|
+
});
|
|
16
|
+
const text = await res.text();
|
|
17
|
+
return {
|
|
18
|
+
ok: res.ok,
|
|
19
|
+
responseStatus: res.status,
|
|
20
|
+
responseBody: text.slice(0, MAX_RESPONSE_BODY),
|
|
21
|
+
durationMs: Date.now() - start
|
|
22
|
+
};
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
error: err instanceof Error ? err.message : String(err),
|
|
27
|
+
durationMs: Date.now() - start
|
|
28
|
+
};
|
|
29
|
+
} finally {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { deliver };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=deliver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.js","names":[],"sources":["../../src/delivery/deliver.ts"],"sourcesContent":["/** Max response-body characters retained for the delivery log. */\nconst MAX_RESPONSE_BODY = 2_000\n\nexport type DeliverArgs = {\n\turl: string\n\tbody: string\n\theaders: Record<string, string>\n\ttimeoutMs: number\n}\n\nexport type DeliverResult = {\n\tok: boolean\n\tresponseStatus?: number\n\tresponseBody?: string\n\terror?: string\n\tdurationMs: number\n}\n\n/** POST `body` to `url` with a hard timeout; never throws. */\nexport const deliver = async (args: DeliverArgs): Promise<DeliverResult> => {\n\tconst controller = new AbortController()\n\tconst timer = setTimeout(() => controller.abort(), args.timeoutMs)\n\tconst start = Date.now()\n\ttry {\n\t\tconst res = await fetch(args.url, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: args.headers,\n\t\t\tbody: args.body,\n\t\t\tsignal: controller.signal,\n\t\t})\n\t\tconst text = await res.text()\n\t\treturn {\n\t\t\tok: res.ok,\n\t\t\tresponseStatus: res.status,\n\t\t\tresponseBody: text.slice(0, MAX_RESPONSE_BODY),\n\t\t\tdurationMs: Date.now() - start,\n\t\t}\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\tdurationMs: Date.now() - start,\n\t\t}\n\t} finally {\n\t\tclearTimeout(timer)\n\t}\n}\n"],"mappings":";;AACA,MAAM,oBAAoB;;AAkB1B,MAAa,UAAU,OAAO,SAA8C;CAC3E,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,KAAK,SAAS;CACjE,MAAM,QAAQ,KAAK,IAAI;CACvB,IAAI;EACH,MAAM,MAAM,MAAM,MAAM,KAAK,KAAK;GACjC,QAAQ;GACR,SAAS,KAAK;GACd,MAAM,KAAK;GACX,QAAQ,WAAW;EACpB,CAAC;EACD,MAAM,OAAO,MAAM,IAAI,KAAK;EAC5B,OAAO;GACN,IAAI,IAAI;GACR,gBAAgB,IAAI;GACpB,cAAc,KAAK,MAAM,GAAG,iBAAiB;GAC7C,YAAY,KAAK,IAAI,IAAI;EAC1B;CACD,SAAS,KAAK;EACb,OAAO;GACN,IAAI;GACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACtD,YAAY,KAAK,IAAI,IAAI;EAC1B;CACD,UAAU;EACT,aAAa,KAAK;CACnB;AACD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { WEBHOOK_DELIVER_TASK } from "../constants.js";
|
|
2
|
+
import { resolveSubscriptionById } from "../plugin/resolveSubscriptions.js";
|
|
3
|
+
import { deriveDeliveryStatus } from "./deriveDeliveryStatus.js";
|
|
4
|
+
import { sendDelivery } from "./sendDelivery.js";
|
|
5
|
+
//#region src/delivery/deliverTask.ts
|
|
6
|
+
/** Native Payload jobs task that performs one queued delivery attempt. */
|
|
7
|
+
const buildDeliverTask = (deps) => ({
|
|
8
|
+
slug: WEBHOOK_DELIVER_TASK,
|
|
9
|
+
retries: deps.retries,
|
|
10
|
+
inputSchema: [{
|
|
11
|
+
name: "deliveryId",
|
|
12
|
+
type: "text",
|
|
13
|
+
required: true
|
|
14
|
+
}],
|
|
15
|
+
handler: async ({ input, job, req }) => {
|
|
16
|
+
const { payload } = req;
|
|
17
|
+
const deliveryId = input.deliveryId;
|
|
18
|
+
const delivery = await payload.findByID({
|
|
19
|
+
collection: deps.deliveriesSlug,
|
|
20
|
+
id: deliveryId,
|
|
21
|
+
overrideAccess: true,
|
|
22
|
+
req
|
|
23
|
+
});
|
|
24
|
+
const subscription = await resolveSubscriptionById({
|
|
25
|
+
id: String(delivery.subscriptionId),
|
|
26
|
+
codeSubscriptions: deps.codeSubscriptions,
|
|
27
|
+
subscriptionsSlug: deps.subscriptionsSlug,
|
|
28
|
+
payload,
|
|
29
|
+
req
|
|
30
|
+
});
|
|
31
|
+
if (!subscription?.enabled) {
|
|
32
|
+
await payload.update({
|
|
33
|
+
collection: deps.deliveriesSlug,
|
|
34
|
+
id: deliveryId,
|
|
35
|
+
data: {
|
|
36
|
+
status: "dead",
|
|
37
|
+
error: subscription ? "subscription disabled" : "subscription not found"
|
|
38
|
+
},
|
|
39
|
+
overrideAccess: true,
|
|
40
|
+
req
|
|
41
|
+
});
|
|
42
|
+
return { output: {} };
|
|
43
|
+
}
|
|
44
|
+
const attempt = Number(job.totalTried ?? 0) + 1;
|
|
45
|
+
const result = await sendDelivery({
|
|
46
|
+
subscription,
|
|
47
|
+
deliveryId,
|
|
48
|
+
event: String(delivery.event),
|
|
49
|
+
body: JSON.stringify(delivery.payload),
|
|
50
|
+
timeoutMs: deps.timeoutMs,
|
|
51
|
+
now: Date.now()
|
|
52
|
+
});
|
|
53
|
+
const status = deriveDeliveryStatus({
|
|
54
|
+
ok: result.ok,
|
|
55
|
+
attempt,
|
|
56
|
+
maxRetries: deps.retries
|
|
57
|
+
});
|
|
58
|
+
await payload.update({
|
|
59
|
+
collection: deps.deliveriesSlug,
|
|
60
|
+
id: deliveryId,
|
|
61
|
+
data: {
|
|
62
|
+
status,
|
|
63
|
+
attempt,
|
|
64
|
+
responseStatus: result.responseStatus,
|
|
65
|
+
responseBody: result.responseBody,
|
|
66
|
+
error: result.error,
|
|
67
|
+
durationMs: result.durationMs,
|
|
68
|
+
jobId: String(job.id)
|
|
69
|
+
},
|
|
70
|
+
overrideAccess: true,
|
|
71
|
+
req
|
|
72
|
+
});
|
|
73
|
+
if (!result.ok) throw new Error(`Webhook delivery failed: ${result.error ?? result.responseStatus}`);
|
|
74
|
+
return { output: {} };
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
//#endregion
|
|
78
|
+
export { buildDeliverTask };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=deliverTask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliverTask.js","names":[],"sources":["../../src/delivery/deliverTask.ts"],"sourcesContent":["import type { TaskConfig } from 'payload'\n\nimport { WEBHOOK_DELIVER_TASK } from '../constants'\nimport type { CodeSubscription } from '../options'\nimport { resolveSubscriptionById } from '../plugin/resolveSubscriptions'\nimport { deriveDeliveryStatus } from './deriveDeliveryStatus'\nimport { sendDelivery } from './sendDelivery'\n\n/** Shared dependencies the delivery task closes over. */\nexport type DeliverTaskDeps = {\n\tdeliveriesSlug: string\n\tsubscriptionsSlug: string\n\tcodeSubscriptions: CodeSubscription[]\n\ttimeoutMs: number\n\tretries: number\n}\n\n/** Native Payload jobs task that performs one queued delivery attempt. */\nexport const buildDeliverTask = (deps: DeliverTaskDeps): TaskConfig =>\n\t({\n\t\tslug: WEBHOOK_DELIVER_TASK,\n\t\tretries: deps.retries,\n\t\tinputSchema: [{ name: 'deliveryId', type: 'text', required: true }],\n\t\thandler: async ({ input, job, req }) => {\n\t\t\tconst { payload } = req\n\t\t\tconst deliveryId = (input as { deliveryId: string }).deliveryId\n\t\t\tconst delivery = await payload.findByID({\n\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\tid: deliveryId,\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tconst subscription = await resolveSubscriptionById({\n\t\t\t\tid: String(delivery.subscriptionId),\n\t\t\t\tcodeSubscriptions: deps.codeSubscriptions,\n\t\t\t\tsubscriptionsSlug: deps.subscriptionsSlug,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!subscription?.enabled) {\n\t\t\t\tawait payload.update({\n\t\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\t\tid: deliveryId,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstatus: 'dead',\n\t\t\t\t\t\terror: subscription ? 'subscription disabled' : 'subscription not found',\n\t\t\t\t\t},\n\t\t\t\t\toverrideAccess: true,\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn { output: {} }\n\t\t\t}\n\n\t\t\tconst attempt = Number(job.totalTried ?? 0) + 1\n\t\t\tconst result = await sendDelivery({\n\t\t\t\tsubscription,\n\t\t\t\tdeliveryId,\n\t\t\t\tevent: String(delivery.event),\n\t\t\t\tbody: JSON.stringify(delivery.payload),\n\t\t\t\ttimeoutMs: deps.timeoutMs,\n\t\t\t\tnow: Date.now(),\n\t\t\t})\n\t\t\tconst status = deriveDeliveryStatus({ ok: result.ok, attempt, maxRetries: deps.retries })\n\t\t\tawait payload.update({\n\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\tid: deliveryId,\n\t\t\t\tdata: {\n\t\t\t\t\tstatus,\n\t\t\t\t\tattempt,\n\t\t\t\t\tresponseStatus: result.responseStatus,\n\t\t\t\t\tresponseBody: result.responseBody,\n\t\t\t\t\terror: result.error,\n\t\t\t\t\tdurationMs: result.durationMs,\n\t\t\t\t\tjobId: String(job.id),\n\t\t\t\t},\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!result.ok) {\n\t\t\t\tthrow new Error(`Webhook delivery failed: ${result.error ?? result.responseStatus}`)\n\t\t\t}\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n"],"mappings":";;;;;;AAkBA,MAAa,oBAAoB,UAC/B;CACA,MAAM;CACN,SAAS,KAAK;CACd,aAAa,CAAC;EAAE,MAAM;EAAc,MAAM;EAAQ,UAAU;CAAK,CAAC;CAClE,SAAS,OAAO,EAAE,OAAO,KAAK,UAAU;EACvC,MAAM,EAAE,YAAY;EACpB,MAAM,aAAc,MAAiC;EACrD,MAAM,WAAW,MAAM,QAAQ,SAAS;GACvC,YAAY,KAAK;GACjB,IAAI;GACJ,gBAAgB;GAChB;EACD,CAAC;EACD,MAAM,eAAe,MAAM,wBAAwB;GAClD,IAAI,OAAO,SAAS,cAAc;GAClC,mBAAmB,KAAK;GACxB,mBAAmB,KAAK;GACxB;GACA;EACD,CAAC;EACD,IAAI,CAAC,cAAc,SAAS;GAC3B,MAAM,QAAQ,OAAO;IACpB,YAAY,KAAK;IACjB,IAAI;IACJ,MAAM;KACL,QAAQ;KACR,OAAO,eAAe,0BAA0B;IACjD;IACA,gBAAgB;IAChB;GACD,CAAC;GACD,OAAO,EAAE,QAAQ,CAAC,EAAE;EACrB;EAEA,MAAM,UAAU,OAAO,IAAI,cAAc,CAAC,IAAI;EAC9C,MAAM,SAAS,MAAM,aAAa;GACjC;GACA;GACA,OAAO,OAAO,SAAS,KAAK;GAC5B,MAAM,KAAK,UAAU,SAAS,OAAO;GACrC,WAAW,KAAK;GAChB,KAAK,KAAK,IAAI;EACf,CAAC;EACD,MAAM,SAAS,qBAAqB;GAAE,IAAI,OAAO;GAAI;GAAS,YAAY,KAAK;EAAQ,CAAC;EACxF,MAAM,QAAQ,OAAO;GACpB,YAAY,KAAK;GACjB,IAAI;GACJ,MAAM;IACL;IACA;IACA,gBAAgB,OAAO;IACvB,cAAc,OAAO;IACrB,OAAO,OAAO;IACd,YAAY,OAAO;IACnB,OAAO,OAAO,IAAI,EAAE;GACrB;GACA,gBAAgB;GAChB;EACD,CAAC;EACD,IAAI,CAAC,OAAO,IACX,MAAM,IAAI,MAAM,4BAA4B,OAAO,SAAS,OAAO,gBAAgB;EAEpF,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/delivery/deriveDeliveryStatus.ts
|
|
2
|
+
/** Delivery status from the attempt outcome. `attempt` is 1-based; total tries = maxRetries + 1. */
|
|
3
|
+
const deriveDeliveryStatus = (args) => {
|
|
4
|
+
if (args.ok) return "success";
|
|
5
|
+
return args.attempt > args.maxRetries ? "dead" : "failed";
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { deriveDeliveryStatus };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=deriveDeliveryStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deriveDeliveryStatus.js","names":[],"sources":["../../src/delivery/deriveDeliveryStatus.ts"],"sourcesContent":["/** Delivery status from the attempt outcome. `attempt` is 1-based; total tries = maxRetries + 1. */\nexport const deriveDeliveryStatus = (args: {\n\tok: boolean\n\tattempt: number\n\tmaxRetries: number\n}): 'success' | 'failed' | 'dead' => {\n\tif (args.ok) {\n\t\treturn 'success'\n\t}\n\treturn args.attempt > args.maxRetries ? 'dead' : 'failed'\n}\n"],"mappings":";;AACA,MAAa,wBAAwB,SAIA;CACpC,IAAI,KAAK,IACR,OAAO;CAER,OAAO,KAAK,UAAU,KAAK,aAAa,SAAS;AAClD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { WEBHOOK_DELIVER_TASK } from "../constants.js";
|
|
2
|
+
import { resolveSubscriptionById } from "../plugin/resolveSubscriptions.js";
|
|
3
|
+
import { sendDelivery } from "./sendDelivery.js";
|
|
4
|
+
//#region src/delivery/redeliver.ts
|
|
5
|
+
/** Re-dispatch a past delivery from its stored payload, creating a new linked row. */
|
|
6
|
+
const redeliverDelivery = async (args) => {
|
|
7
|
+
const { deps, deliveryId, payload, req } = args;
|
|
8
|
+
const original = await payload.findByID({
|
|
9
|
+
collection: deps.deliveriesSlug,
|
|
10
|
+
id: deliveryId,
|
|
11
|
+
overrideAccess: true,
|
|
12
|
+
req
|
|
13
|
+
});
|
|
14
|
+
const subscription = await resolveSubscriptionById({
|
|
15
|
+
id: String(original.subscriptionId),
|
|
16
|
+
codeSubscriptions: deps.codeSubscriptions,
|
|
17
|
+
subscriptionsSlug: deps.subscriptionsSlug,
|
|
18
|
+
payload,
|
|
19
|
+
req
|
|
20
|
+
});
|
|
21
|
+
const created = await payload.create({
|
|
22
|
+
collection: deps.deliveriesSlug,
|
|
23
|
+
data: {
|
|
24
|
+
subscriptionId: original.subscriptionId,
|
|
25
|
+
endpoint: subscription?.url ?? original.endpoint,
|
|
26
|
+
event: original.event,
|
|
27
|
+
payload: original.payload,
|
|
28
|
+
status: "pending",
|
|
29
|
+
attempt: 0
|
|
30
|
+
},
|
|
31
|
+
overrideAccess: true,
|
|
32
|
+
req
|
|
33
|
+
});
|
|
34
|
+
const newId = String(created.id);
|
|
35
|
+
if (deps.mode === "queue") {
|
|
36
|
+
await payload.jobs.queue({
|
|
37
|
+
task: WEBHOOK_DELIVER_TASK,
|
|
38
|
+
input: { deliveryId: newId },
|
|
39
|
+
queue: deps.queue
|
|
40
|
+
});
|
|
41
|
+
return { id: newId };
|
|
42
|
+
}
|
|
43
|
+
if (!subscription?.enabled) {
|
|
44
|
+
await payload.update({
|
|
45
|
+
collection: deps.deliveriesSlug,
|
|
46
|
+
id: newId,
|
|
47
|
+
data: {
|
|
48
|
+
status: "dead",
|
|
49
|
+
error: subscription ? "subscription disabled" : "subscription not found"
|
|
50
|
+
},
|
|
51
|
+
overrideAccess: true,
|
|
52
|
+
req
|
|
53
|
+
});
|
|
54
|
+
return { id: newId };
|
|
55
|
+
}
|
|
56
|
+
const result = await sendDelivery({
|
|
57
|
+
subscription,
|
|
58
|
+
deliveryId: newId,
|
|
59
|
+
event: String(original.event),
|
|
60
|
+
body: JSON.stringify(original.payload),
|
|
61
|
+
timeoutMs: deps.timeoutMs,
|
|
62
|
+
now: Date.now()
|
|
63
|
+
});
|
|
64
|
+
await payload.update({
|
|
65
|
+
collection: deps.deliveriesSlug,
|
|
66
|
+
id: newId,
|
|
67
|
+
data: {
|
|
68
|
+
status: result.ok ? "success" : "dead",
|
|
69
|
+
attempt: 1,
|
|
70
|
+
responseStatus: result.responseStatus,
|
|
71
|
+
responseBody: result.responseBody,
|
|
72
|
+
error: result.error,
|
|
73
|
+
durationMs: result.durationMs
|
|
74
|
+
},
|
|
75
|
+
overrideAccess: true,
|
|
76
|
+
req
|
|
77
|
+
});
|
|
78
|
+
return { id: newId };
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { redeliverDelivery };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=redeliver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redeliver.js","names":[],"sources":["../../src/delivery/redeliver.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport { WEBHOOK_DELIVER_TASK } from '../constants'\nimport type { CodeSubscription } from '../options'\nimport { resolveSubscriptionById } from '../plugin/resolveSubscriptions'\nimport { sendDelivery } from './sendDelivery'\n\n/** Dependencies for re-dispatching a stored delivery. */\nexport type RedeliverDeps = {\n\tdeliveriesSlug: string\n\tsubscriptionsSlug: string\n\tcodeSubscriptions: CodeSubscription[]\n\tmode: 'queue' | 'inline'\n\ttimeoutMs: number\n\tqueue: string\n}\n\n/** Re-dispatch a past delivery from its stored payload, creating a new linked row. */\nexport const redeliverDelivery = async (args: {\n\tdeps: RedeliverDeps\n\tdeliveryId: string\n\tpayload: Payload\n\treq: PayloadRequest\n}): Promise<{ id: string }> => {\n\tconst { deps, deliveryId, payload, req } = args\n\tconst original = await payload.findByID({\n\t\tcollection: deps.deliveriesSlug,\n\t\tid: deliveryId,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\t// Resolved up front (cheap) so the new row's endpoint reflects the subscription's current URL\n\t// rather than whatever was stored at the time of the original delivery.\n\tconst subscription = await resolveSubscriptionById({\n\t\tid: String(original.subscriptionId),\n\t\tcodeSubscriptions: deps.codeSubscriptions,\n\t\tsubscriptionsSlug: deps.subscriptionsSlug,\n\t\tpayload,\n\t\treq,\n\t})\n\tconst created = await payload.create({\n\t\tcollection: deps.deliveriesSlug,\n\t\tdata: {\n\t\t\tsubscriptionId: original.subscriptionId,\n\t\t\tendpoint: subscription?.url ?? original.endpoint,\n\t\t\tevent: original.event,\n\t\t\tpayload: original.payload,\n\t\t\tstatus: 'pending',\n\t\t\tattempt: 0,\n\t\t},\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\tconst newId = String(created.id)\n\n\tif (deps.mode === 'queue') {\n\t\t// deliverTask re-resolves the subscription (and re-checks enabled) when it runs, so no\n\t\t// missing/disabled gate is needed here.\n\t\tawait payload.jobs.queue({\n\t\t\ttask: WEBHOOK_DELIVER_TASK,\n\t\t\tinput: { deliveryId: newId },\n\t\t\tqueue: deps.queue,\n\t\t})\n\t\treturn { id: newId }\n\t}\n\n\tif (!subscription?.enabled) {\n\t\tawait payload.update({\n\t\t\tcollection: deps.deliveriesSlug,\n\t\t\tid: newId,\n\t\t\tdata: {\n\t\t\t\tstatus: 'dead',\n\t\t\t\terror: subscription ? 'subscription disabled' : 'subscription not found',\n\t\t\t},\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\treturn { id: newId }\n\t}\n\tconst result = await sendDelivery({\n\t\tsubscription,\n\t\tdeliveryId: newId,\n\t\tevent: String(original.event),\n\t\tbody: JSON.stringify(original.payload),\n\t\ttimeoutMs: deps.timeoutMs,\n\t\tnow: Date.now(),\n\t})\n\tawait payload.update({\n\t\tcollection: deps.deliveriesSlug,\n\t\tid: newId,\n\t\tdata: {\n\t\t\tstatus: result.ok ? 'success' : 'dead',\n\t\t\tattempt: 1,\n\t\t\tresponseStatus: result.responseStatus,\n\t\t\tresponseBody: result.responseBody,\n\t\t\terror: result.error,\n\t\t\tdurationMs: result.durationMs,\n\t\t},\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\treturn { id: newId }\n}\n"],"mappings":";;;;;AAkBA,MAAa,oBAAoB,OAAO,SAKT;CAC9B,MAAM,EAAE,MAAM,YAAY,SAAS,QAAQ;CAC3C,MAAM,WAAW,MAAM,QAAQ,SAAS;EACvC,YAAY,KAAK;EACjB,IAAI;EACJ,gBAAgB;EAChB;CACD,CAAC;CAGD,MAAM,eAAe,MAAM,wBAAwB;EAClD,IAAI,OAAO,SAAS,cAAc;EAClC,mBAAmB,KAAK;EACxB,mBAAmB,KAAK;EACxB;EACA;CACD,CAAC;CACD,MAAM,UAAU,MAAM,QAAQ,OAAO;EACpC,YAAY,KAAK;EACjB,MAAM;GACL,gBAAgB,SAAS;GACzB,UAAU,cAAc,OAAO,SAAS;GACxC,OAAO,SAAS;GAChB,SAAS,SAAS;GAClB,QAAQ;GACR,SAAS;EACV;EACA,gBAAgB;EAChB;CACD,CAAC;CACD,MAAM,QAAQ,OAAO,QAAQ,EAAE;CAE/B,IAAI,KAAK,SAAS,SAAS;EAG1B,MAAM,QAAQ,KAAK,MAAM;GACxB,MAAM;GACN,OAAO,EAAE,YAAY,MAAM;GAC3B,OAAO,KAAK;EACb,CAAC;EACD,OAAO,EAAE,IAAI,MAAM;CACpB;CAEA,IAAI,CAAC,cAAc,SAAS;EAC3B,MAAM,QAAQ,OAAO;GACpB,YAAY,KAAK;GACjB,IAAI;GACJ,MAAM;IACL,QAAQ;IACR,OAAO,eAAe,0BAA0B;GACjD;GACA,gBAAgB;GAChB;EACD,CAAC;EACD,OAAO,EAAE,IAAI,MAAM;CACpB;CACA,MAAM,SAAS,MAAM,aAAa;EACjC;EACA,YAAY;EACZ,OAAO,OAAO,SAAS,KAAK;EAC5B,MAAM,KAAK,UAAU,SAAS,OAAO;EACrC,WAAW,KAAK;EAChB,KAAK,KAAK,IAAI;CACf,CAAC;CACD,MAAM,QAAQ,OAAO;EACpB,YAAY,KAAK;EACjB,IAAI;EACJ,MAAM;GACL,QAAQ,OAAO,KAAK,YAAY;GAChC,SAAS;GACT,gBAAgB,OAAO;GACvB,cAAc,OAAO;GACrB,OAAO,OAAO;GACd,YAAY,OAAO;EACpB;EACA,gBAAgB;EAChB;CACD,CAAC;CACD,OAAO,EAAE,IAAI,MAAM;AACpB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { deliver } from "./deliver.js";
|
|
2
|
+
import { signPayload, signatureHeader } from "./sign.js";
|
|
3
|
+
//#region src/delivery/sendDelivery.ts
|
|
4
|
+
const USER_AGENT = "10x-media-webhooks";
|
|
5
|
+
/** Assemble headers (+ signature) and POST the body to the subscription's URL. */
|
|
6
|
+
const sendDelivery = (args) => {
|
|
7
|
+
const { subscription, deliveryId, event, body, timeoutMs, now } = args;
|
|
8
|
+
const timestamp = Math.floor(now / 1e3);
|
|
9
|
+
const headers = {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
"User-Agent": USER_AGENT,
|
|
12
|
+
"X-Webhook-Id": deliveryId,
|
|
13
|
+
"X-Webhook-Event": event,
|
|
14
|
+
"X-Webhook-Timestamp": String(timestamp),
|
|
15
|
+
...subscription.headers
|
|
16
|
+
};
|
|
17
|
+
if (subscription.secret) headers["X-Webhook-Signature"] = signatureHeader(signPayload({
|
|
18
|
+
secret: subscription.secret,
|
|
19
|
+
timestamp,
|
|
20
|
+
body
|
|
21
|
+
}));
|
|
22
|
+
return deliver({
|
|
23
|
+
url: subscription.url,
|
|
24
|
+
body,
|
|
25
|
+
headers,
|
|
26
|
+
timeoutMs
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { sendDelivery };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=sendDelivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sendDelivery.js","names":[],"sources":["../../src/delivery/sendDelivery.ts"],"sourcesContent":["import type { ResolvedSubscription } from '../plugin/resolveSubscriptions'\nimport { type DeliverResult, deliver } from './deliver'\nimport { signatureHeader, signPayload } from './sign'\n\nconst USER_AGENT = '10x-media-webhooks'\n\n/** Assemble headers (+ signature) and POST the body to the subscription's URL. */\nexport const sendDelivery = (args: {\n\tsubscription: ResolvedSubscription\n\tdeliveryId: string\n\tevent: string\n\tbody: string\n\ttimeoutMs: number\n\tnow: number\n}): Promise<DeliverResult> => {\n\tconst { subscription, deliveryId, event, body, timeoutMs, now } = args\n\tconst timestamp = Math.floor(now / 1000)\n\tconst headers: Record<string, string> = {\n\t\t'Content-Type': 'application/json',\n\t\t'User-Agent': USER_AGENT,\n\t\t'X-Webhook-Id': deliveryId,\n\t\t'X-Webhook-Event': event,\n\t\t'X-Webhook-Timestamp': String(timestamp),\n\t\t...subscription.headers,\n\t}\n\tif (subscription.secret) {\n\t\theaders['X-Webhook-Signature'] = signatureHeader(\n\t\t\tsignPayload({ secret: subscription.secret, timestamp, body })\n\t\t)\n\t}\n\treturn deliver({ url: subscription.url, body, headers, timeoutMs })\n}\n"],"mappings":";;;AAIA,MAAM,aAAa;;AAGnB,MAAa,gBAAgB,SAOC;CAC7B,MAAM,EAAE,cAAc,YAAY,OAAO,MAAM,WAAW,QAAQ;CAClE,MAAM,YAAY,KAAK,MAAM,MAAM,GAAI;CACvC,MAAM,UAAkC;EACvC,gBAAgB;EAChB,cAAc;EACd,gBAAgB;EAChB,mBAAmB;EACnB,uBAAuB,OAAO,SAAS;EACvC,GAAG,aAAa;CACjB;CACA,IAAI,aAAa,QAChB,QAAQ,yBAAyB,gBAChC,YAAY;EAAE,QAAQ,aAAa;EAAQ;EAAW;CAAK,CAAC,CAC7D;CAED,OAAO,QAAQ;EAAE,KAAK,aAAa;EAAK;EAAM;EAAS;CAAU,CAAC;AACnE"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
//#region src/delivery/sign.ts
|
|
3
|
+
/** HMAC-SHA256 over `${timestamp}.${body}`, hex-encoded. */
|
|
4
|
+
const signPayload = (args) => createHmac("sha256", args.secret).update(`${args.timestamp}.${args.body}`).digest("hex");
|
|
5
|
+
/** Wrap a signature hex in the versioned header value. */
|
|
6
|
+
const signatureHeader = (hex) => `v1=${hex}`;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { signPayload, signatureHeader };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=sign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.js","names":[],"sources":["../../src/delivery/sign.ts"],"sourcesContent":["import { createHmac } from 'node:crypto'\n\n/** HMAC-SHA256 over `${timestamp}.${body}`, hex-encoded. */\nexport const signPayload = (args: { secret: string; timestamp: number; body: string }): string =>\n\tcreateHmac('sha256', args.secret).update(`${args.timestamp}.${args.body}`).digest('hex')\n\n/** Wrap a signature hex in the versioned header value. */\nexport const signatureHeader = (hex: string): string => `v1=${hex}`\n"],"mappings":";;;AAGA,MAAa,eAAe,SAC3B,WAAW,UAAU,KAAK,MAAM,EAAE,OAAO,GAAG,KAAK,UAAU,GAAG,KAAK,MAAM,EAAE,OAAO,KAAK;;AAGxF,MAAa,mBAAmB,QAAwB,MAAM"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/events/eventTypes.ts
|
|
2
|
+
const OP_TO_EVENT = {
|
|
3
|
+
create: "created",
|
|
4
|
+
update: "updated",
|
|
5
|
+
delete: "deleted"
|
|
6
|
+
};
|
|
7
|
+
/** Past-tense public event suffix for a Payload operation. */
|
|
8
|
+
const operationToEvent = (op) => OP_TO_EVENT[op];
|
|
9
|
+
/** Public event id, e.g. `posts.created`. */
|
|
10
|
+
const eventId = (collectionSlug, op) => `${collectionSlug}.${operationToEvent(op)}`;
|
|
11
|
+
/** Every event id the given opt-in collections can emit, in declaration order. */
|
|
12
|
+
const eventCatalog = (collections) => {
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const [slug, cfg] of Object.entries(collections)) {
|
|
15
|
+
const ops = (cfg === true ? void 0 : cfg.operations) ?? [
|
|
16
|
+
"create",
|
|
17
|
+
"update",
|
|
18
|
+
"delete"
|
|
19
|
+
];
|
|
20
|
+
for (const op of ops) out.push(eventId(slug, op));
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { eventCatalog, eventId };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=eventTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventTypes.js","names":[],"sources":["../../src/events/eventTypes.ts"],"sourcesContent":["import type { WebhookOperation } from '../options'\n\nconst OP_TO_EVENT: Record<WebhookOperation, string> = {\n\tcreate: 'created',\n\tupdate: 'updated',\n\tdelete: 'deleted',\n}\n\n/** Past-tense public event suffix for a Payload operation. */\nexport const operationToEvent = (op: WebhookOperation): string => OP_TO_EVENT[op]\n\n/** Public event id, e.g. `posts.created`. */\nexport const eventId = (collectionSlug: string, op: WebhookOperation): string =>\n\t`${collectionSlug}.${operationToEvent(op)}`\n\n/** Every event id the given opt-in collections can emit, in declaration order. */\nexport const eventCatalog = (\n\tcollections: Record<string, true | { operations?: WebhookOperation[] }>\n): string[] => {\n\tconst out: string[] = []\n\tfor (const [slug, cfg] of Object.entries(collections)) {\n\t\tconst ops = (cfg === true ? undefined : cfg.operations) ?? ['create', 'update', 'delete']\n\t\tfor (const op of ops) {\n\t\t\tout.push(eventId(slug, op))\n\t\t}\n\t}\n\treturn out\n}\n"],"mappings":";AAEA,MAAM,cAAgD;CACrD,QAAQ;CACR,QAAQ;CACR,QAAQ;AACT;;AAGA,MAAa,oBAAoB,OAAiC,YAAY;;AAG9E,MAAa,WAAW,gBAAwB,OAC/C,GAAG,eAAe,GAAG,iBAAiB,EAAE;;AAGzC,MAAa,gBACZ,gBACc;CACd,MAAM,MAAgB,CAAC;CACvB,KAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,WAAW,GAAG;EACtD,MAAM,OAAO,QAAQ,OAAO,KAAA,IAAY,IAAI,eAAe;GAAC;GAAU;GAAU;EAAQ;EACxF,KAAK,MAAM,MAAM,KAChB,IAAI,KAAK,QAAQ,MAAM,EAAE,CAAC;CAE5B;CACA,OAAO;AACR"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { SECRET_REVEAL_CONTEXT, WEBHOOK_DELIVER_TASK } from "../constants.js";
|
|
2
|
+
import { fromCodeSubscription, fromCollectionRow, matchSubscriptions } from "../plugin/resolveSubscriptions.js";
|
|
3
|
+
import { sendDelivery } from "../delivery/sendDelivery.js";
|
|
4
|
+
import { eventId } from "./eventTypes.js";
|
|
5
|
+
import { buildPayload } from "../delivery/buildPayload.js";
|
|
6
|
+
//#region src/events/hooks.ts
|
|
7
|
+
/** Max collection subscriptions scanned per event (pagination is a future enhancement). */
|
|
8
|
+
const SUBSCRIPTION_SCAN_LIMIT = 1e3;
|
|
9
|
+
const resolveListening = async (args) => {
|
|
10
|
+
const code = args.deps.codeSubscriptions.map(fromCodeSubscription);
|
|
11
|
+
args.req.context[SECRET_REVEAL_CONTEXT.forSigning] = true;
|
|
12
|
+
let res;
|
|
13
|
+
try {
|
|
14
|
+
res = await args.req.payload.find({
|
|
15
|
+
collection: args.deps.subscriptionsSlug,
|
|
16
|
+
where: { enabled: { not_equals: false } },
|
|
17
|
+
limit: SUBSCRIPTION_SCAN_LIMIT,
|
|
18
|
+
depth: 0,
|
|
19
|
+
overrideAccess: true,
|
|
20
|
+
req: args.req
|
|
21
|
+
});
|
|
22
|
+
} finally {
|
|
23
|
+
args.req.context[SECRET_REVEAL_CONTEXT.forSigning] = false;
|
|
24
|
+
}
|
|
25
|
+
if (res.docs.length >= SUBSCRIPTION_SCAN_LIMIT) args.req.payload.logger.warn(`@10x-media/webhooks: subscription scan hit the ${SUBSCRIPTION_SCAN_LIMIT} cap; some subscriptions may be skipped for ${args.event}.`);
|
|
26
|
+
const collection = res.docs.map((d) => fromCollectionRow(d));
|
|
27
|
+
return matchSubscriptions([...code, ...collection], args.event);
|
|
28
|
+
};
|
|
29
|
+
const dispatch = async (args) => {
|
|
30
|
+
const { deps, operation, doc, previousDoc, req } = args;
|
|
31
|
+
if (!deps.operations.includes(operation)) return;
|
|
32
|
+
const { payload } = req;
|
|
33
|
+
const event = eventId(deps.collectionSlug, operation);
|
|
34
|
+
const subscriptions = await resolveListening({
|
|
35
|
+
deps,
|
|
36
|
+
event,
|
|
37
|
+
req
|
|
38
|
+
});
|
|
39
|
+
if (!subscriptions.length) return;
|
|
40
|
+
const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
41
|
+
for (const subscription of subscriptions) {
|
|
42
|
+
const created = await payload.create({
|
|
43
|
+
collection: deps.deliveriesSlug,
|
|
44
|
+
data: {
|
|
45
|
+
subscriptionId: subscription.id,
|
|
46
|
+
endpoint: subscription.url,
|
|
47
|
+
event,
|
|
48
|
+
status: "pending",
|
|
49
|
+
attempt: 0
|
|
50
|
+
},
|
|
51
|
+
overrideAccess: true,
|
|
52
|
+
req
|
|
53
|
+
});
|
|
54
|
+
const deliveryId = String(created.id);
|
|
55
|
+
const body = buildPayload({
|
|
56
|
+
deliveryId,
|
|
57
|
+
collection: deps.collectionSlug,
|
|
58
|
+
operation,
|
|
59
|
+
doc,
|
|
60
|
+
previousDoc,
|
|
61
|
+
occurredAt,
|
|
62
|
+
config: deps.config,
|
|
63
|
+
req
|
|
64
|
+
});
|
|
65
|
+
await payload.update({
|
|
66
|
+
collection: deps.deliveriesSlug,
|
|
67
|
+
id: deliveryId,
|
|
68
|
+
data: { payload: body },
|
|
69
|
+
overrideAccess: true,
|
|
70
|
+
req
|
|
71
|
+
});
|
|
72
|
+
if (deps.mode === "queue") {
|
|
73
|
+
await payload.jobs.queue({
|
|
74
|
+
task: WEBHOOK_DELIVER_TASK,
|
|
75
|
+
input: { deliveryId },
|
|
76
|
+
queue: deps.queue
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const result = await sendDelivery({
|
|
82
|
+
subscription,
|
|
83
|
+
deliveryId,
|
|
84
|
+
event,
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
timeoutMs: deps.timeoutMs,
|
|
87
|
+
now: Date.now()
|
|
88
|
+
});
|
|
89
|
+
await payload.update({
|
|
90
|
+
collection: deps.deliveriesSlug,
|
|
91
|
+
id: deliveryId,
|
|
92
|
+
data: {
|
|
93
|
+
status: result.ok ? "success" : "dead",
|
|
94
|
+
attempt: 1,
|
|
95
|
+
responseStatus: result.responseStatus,
|
|
96
|
+
responseBody: result.responseBody,
|
|
97
|
+
error: result.error,
|
|
98
|
+
durationMs: result.durationMs
|
|
99
|
+
},
|
|
100
|
+
overrideAccess: true,
|
|
101
|
+
req
|
|
102
|
+
});
|
|
103
|
+
} catch (err) {
|
|
104
|
+
payload.logger.error(`@10x-media/webhooks: inline delivery ${deliveryId} threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/** afterChange hook factory for an opt-in source collection. */
|
|
109
|
+
const makeAfterChange = (deps) => async ({ doc, previousDoc, operation, req }) => {
|
|
110
|
+
await dispatch({
|
|
111
|
+
deps,
|
|
112
|
+
operation: operation === "create" ? "create" : "update",
|
|
113
|
+
doc,
|
|
114
|
+
previousDoc,
|
|
115
|
+
req
|
|
116
|
+
});
|
|
117
|
+
return doc;
|
|
118
|
+
};
|
|
119
|
+
/** afterDelete hook factory for an opt-in source collection. */
|
|
120
|
+
const makeAfterDelete = (deps) => async ({ doc, req }) => {
|
|
121
|
+
await dispatch({
|
|
122
|
+
deps,
|
|
123
|
+
operation: "delete",
|
|
124
|
+
doc,
|
|
125
|
+
req
|
|
126
|
+
});
|
|
127
|
+
return doc;
|
|
128
|
+
};
|
|
129
|
+
//#endregion
|
|
130
|
+
export { makeAfterChange, makeAfterDelete };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../../src/events/hooks.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, PayloadRequest } from 'payload'\n\nimport { SECRET_REVEAL_CONTEXT, WEBHOOK_DELIVER_TASK } from '../constants'\nimport { buildPayload } from '../delivery/buildPayload'\nimport { sendDelivery } from '../delivery/sendDelivery'\nimport type { CodeSubscription, CollectionWebhookConfig, WebhookOperation } from '../options'\nimport {\n\tfromCodeSubscription,\n\tfromCollectionRow,\n\tmatchSubscriptions,\n\ttype ResolvedSubscription,\n} from '../plugin/resolveSubscriptions'\nimport { eventId } from './eventTypes'\n\n/** Per-collection dispatch dependencies. */\nexport type WebhookDispatchDeps = {\n\tcollectionSlug: string\n\tconfig: CollectionWebhookConfig\n\toperations: WebhookOperation[]\n\tdeliveriesSlug: string\n\tsubscriptionsSlug: string\n\tcodeSubscriptions: CodeSubscription[]\n\tmode: 'queue' | 'inline'\n\ttimeoutMs: number\n\tqueue: string\n}\n\n/** Max collection subscriptions scanned per event (pagination is a future enhancement). */\nconst SUBSCRIPTION_SCAN_LIMIT = 1_000\n\nconst resolveListening = async (args: {\n\tdeps: WebhookDispatchDeps\n\tevent: string\n\treq: PayloadRequest\n}): Promise<ResolvedSubscription[]> => {\n\tconst code = args.deps.codeSubscriptions.map(fromCodeSubscription)\n\targs.req.context[SECRET_REVEAL_CONTEXT.forSigning] = true\n\tlet res: Awaited<ReturnType<typeof args.req.payload.find>>\n\ttry {\n\t\tres = await args.req.payload.find({\n\t\t\tcollection: args.deps.subscriptionsSlug,\n\t\t\twhere: { enabled: { not_equals: false } },\n\t\t\tlimit: SUBSCRIPTION_SCAN_LIMIT,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq: args.req,\n\t\t})\n\t} finally {\n\t\targs.req.context[SECRET_REVEAL_CONTEXT.forSigning] = false\n\t}\n\tif (res.docs.length >= SUBSCRIPTION_SCAN_LIMIT) {\n\t\targs.req.payload.logger.warn(\n\t\t\t`@10x-media/webhooks: subscription scan hit the ${SUBSCRIPTION_SCAN_LIMIT} cap; some subscriptions may be skipped for ${args.event}.`\n\t\t)\n\t}\n\tconst collection = res.docs.map((d) =>\n\t\tfromCollectionRow(d as Parameters<typeof fromCollectionRow>[0])\n\t)\n\treturn matchSubscriptions([...code, ...collection], args.event)\n}\n\nconst dispatch = async (args: {\n\tdeps: WebhookDispatchDeps\n\toperation: WebhookOperation\n\tdoc: Record<string, unknown>\n\tpreviousDoc?: Record<string, unknown>\n\treq: PayloadRequest\n}): Promise<void> => {\n\tconst { deps, operation, doc, previousDoc, req } = args\n\tif (!deps.operations.includes(operation)) {\n\t\treturn\n\t}\n\tconst { payload } = req\n\tconst event = eventId(deps.collectionSlug, operation)\n\tconst subscriptions = await resolveListening({ deps, event, req })\n\tif (!subscriptions.length) {\n\t\treturn\n\t}\n\n\tconst occurredAt = new Date().toISOString()\n\tfor (const subscription of subscriptions) {\n\t\tconst created = await payload.create({\n\t\t\tcollection: deps.deliveriesSlug,\n\t\t\tdata: {\n\t\t\t\tsubscriptionId: subscription.id,\n\t\t\t\tendpoint: subscription.url,\n\t\t\t\tevent,\n\t\t\t\tstatus: 'pending',\n\t\t\t\tattempt: 0,\n\t\t\t},\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\tconst deliveryId = String(created.id)\n\t\tconst body = buildPayload({\n\t\t\tdeliveryId,\n\t\t\tcollection: deps.collectionSlug,\n\t\t\toperation,\n\t\t\tdoc,\n\t\t\tpreviousDoc,\n\t\t\toccurredAt,\n\t\t\tconfig: deps.config,\n\t\t\treq,\n\t\t})\n\t\tawait payload.update({\n\t\t\tcollection: deps.deliveriesSlug,\n\t\t\tid: deliveryId,\n\t\t\tdata: { payload: body },\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\n\t\tif (deps.mode === 'queue') {\n\t\t\tawait payload.jobs.queue({\n\t\t\t\ttask: WEBHOOK_DELIVER_TASK,\n\t\t\t\tinput: { deliveryId },\n\t\t\t\tqueue: deps.queue,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\t// best-effort: a delivery failure must never abort the caller's write\n\t\ttry {\n\t\t\tconst result = await sendDelivery({\n\t\t\t\tsubscription,\n\t\t\t\tdeliveryId,\n\t\t\t\tevent,\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t\ttimeoutMs: deps.timeoutMs,\n\t\t\t\tnow: Date.now(),\n\t\t\t})\n\t\t\tawait payload.update({\n\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\tid: deliveryId,\n\t\t\t\tdata: {\n\t\t\t\t\tstatus: result.ok ? 'success' : 'dead',\n\t\t\t\t\tattempt: 1,\n\t\t\t\t\tresponseStatus: result.responseStatus,\n\t\t\t\t\tresponseBody: result.responseBody,\n\t\t\t\t\terror: result.error,\n\t\t\t\t\tdurationMs: result.durationMs,\n\t\t\t\t},\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tpayload.logger.error(\n\t\t\t\t`@10x-media/webhooks: inline delivery ${deliveryId} threw: ${err instanceof Error ? err.message : String(err)}`\n\t\t\t)\n\t\t}\n\t}\n}\n\n/** afterChange hook factory for an opt-in source collection. */\nexport const makeAfterChange =\n\t(deps: WebhookDispatchDeps): CollectionAfterChangeHook =>\n\tasync ({ doc, previousDoc, operation, req }) => {\n\t\tconst op: WebhookOperation = operation === 'create' ? 'create' : 'update'\n\t\tawait dispatch({ deps, operation: op, doc, previousDoc, req })\n\t\treturn doc\n\t}\n\n/** afterDelete hook factory for an opt-in source collection. */\nexport const makeAfterDelete =\n\t(deps: WebhookDispatchDeps): CollectionAfterDeleteHook =>\n\tasync ({ doc, req }) => {\n\t\tawait dispatch({ deps, operation: 'delete', doc: doc as Record<string, unknown>, req })\n\t\treturn doc\n\t}\n"],"mappings":"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"}
|
package/dist/exports/client.d.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/** List cell rendering a delivery's status as a native Payload Pill. */
|
|
5
|
-
declare const DeliveryStatusCell: ({
|
|
6
|
-
cellData
|
|
7
|
-
}: DefaultCellComponentProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/delivery/RedeliverButton.d.ts
|
|
10
|
-
/** Doc-view action that POSTs to the deliveries redeliver endpoint. */
|
|
11
|
-
declare const RedeliverButton: () => import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
-
//#endregion
|
|
13
|
-
export { DeliveryStatusCell, RedeliverButton };
|
|
14
|
-
//# sourceMappingURL=client.d.ts.map
|
|
1
|
+
import { DeliveryStatusCell } from "../delivery/DeliveryStatusCell.js";
|
|
2
|
+
import { RedeliverButton } from "../delivery/RedeliverButton.js";
|
|
3
|
+
export { DeliveryStatusCell, RedeliverButton };
|
package/dist/exports/client.js
CHANGED
|
@@ -1,83 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
|
-
import { useState } from "react";
|
|
6
|
-
//#region src/translations/useTranslation.ts
|
|
7
|
-
/**
|
|
8
|
-
* `useTranslation` bound to this plugin's keys, so `t(keys.X)` typechecks without
|
|
9
|
-
* a per-call `@ts-expect-error`. Returns Payload's `{ t, i18n }` unchanged.
|
|
10
|
-
*/
|
|
11
|
-
const useTranslation$1 = () => useTranslation();
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region src/delivery/DeliveryStatusCell.tsx
|
|
14
|
-
const META = {
|
|
15
|
-
pending: {
|
|
16
|
-
labelKey: keys.statusPending,
|
|
17
|
-
pillStyle: "light"
|
|
18
|
-
},
|
|
19
|
-
success: {
|
|
20
|
-
labelKey: keys.statusSuccess,
|
|
21
|
-
pillStyle: "success"
|
|
22
|
-
},
|
|
23
|
-
failed: {
|
|
24
|
-
labelKey: keys.statusFailed,
|
|
25
|
-
pillStyle: "warning"
|
|
26
|
-
},
|
|
27
|
-
dead: {
|
|
28
|
-
labelKey: keys.statusDead,
|
|
29
|
-
pillStyle: "error"
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
const FALLBACK = {
|
|
33
|
-
labelKey: keys.statusPending,
|
|
34
|
-
pillStyle: "light"
|
|
35
|
-
};
|
|
36
|
-
/** List cell rendering a delivery's status as a native Payload Pill. */
|
|
37
|
-
const DeliveryStatusCell = ({ cellData }) => {
|
|
38
|
-
const { t } = useTranslation$1();
|
|
39
|
-
const meta = META[String(cellData)] ?? FALLBACK;
|
|
40
|
-
return /* @__PURE__ */ jsx(Pill, {
|
|
41
|
-
pillStyle: meta.pillStyle,
|
|
42
|
-
size: "small",
|
|
43
|
-
children: t(meta.labelKey)
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/delivery/RedeliverButton.tsx
|
|
48
|
-
/** Doc-view action that POSTs to the deliveries redeliver endpoint. */
|
|
49
|
-
const RedeliverButton = () => {
|
|
50
|
-
const { id, collectionSlug } = useDocumentInfo();
|
|
51
|
-
const { config } = useConfig();
|
|
52
|
-
const { t } = useTranslation$1();
|
|
53
|
-
const [busy, setBusy] = useState(false);
|
|
54
|
-
if (!id || !collectionSlug) return null;
|
|
55
|
-
const apiRoute = config.routes?.api ?? "/api";
|
|
56
|
-
const serverURL = config.serverURL ?? "";
|
|
57
|
-
const onClick = async () => {
|
|
58
|
-
setBusy(true);
|
|
59
|
-
try {
|
|
60
|
-
const res = await fetch(`${serverURL}${apiRoute}/${collectionSlug}/${encodeURIComponent(String(id))}/redeliver`, {
|
|
61
|
-
method: "POST",
|
|
62
|
-
credentials: "include"
|
|
63
|
-
});
|
|
64
|
-
if (!res.ok) throw new Error(String(res.status));
|
|
65
|
-
toast.success(t(keys.redeliverDone));
|
|
66
|
-
} catch {
|
|
67
|
-
toast.error(t(keys.redeliver));
|
|
68
|
-
} finally {
|
|
69
|
-
setBusy(false);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
return /* @__PURE__ */ jsx(Button, {
|
|
73
|
-
buttonStyle: "secondary",
|
|
74
|
-
disabled: busy,
|
|
75
|
-
onClick,
|
|
76
|
-
size: "small",
|
|
77
|
-
children: t(keys.redeliver)
|
|
78
|
-
});
|
|
79
|
-
};
|
|
80
|
-
//#endregion
|
|
2
|
+
import { DeliveryStatusCell } from "../delivery/DeliveryStatusCell.js";
|
|
3
|
+
import { RedeliverButton } from "../delivery/RedeliverButton.js";
|
|
81
4
|
export { DeliveryStatusCell, RedeliverButton };
|
|
82
|
-
|
|
83
|
-
//# sourceMappingURL=client.js.map
|