@10x-media/webhooks 0.1.0-beta.0 → 0.1.0-beta.1
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 +28 -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 +26 -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} +10 -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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @10x-media/webhooks
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add a typed `translations` option to every plugin factory and make translation keys a stable public API. Each plugin's `./i18n` subpath now exports the `keys` object, the `TranslationKey` union, and the `TranslationsOption` shape. Overrides are flat and per-locale: values win over the built-in locales key-by-key, locales a plugin does not ship are added whole, and app-level `i18n.translations` still wins over everything.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { analytics } from "@10x-media/analytics";
|
|
11
|
+
import { keys } from "@10x-media/analytics/i18n";
|
|
12
|
+
|
|
13
|
+
analytics({
|
|
14
|
+
adapters: [nativeAdapter()],
|
|
15
|
+
translations: {
|
|
16
|
+
de: { [keys.pluginName]: "Analytik" },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
A typo'd key inside `translations` is a compile error.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Restructure README: features, quick start, and links into the documentation site at https://docs.10xmedia.de. Long-form documentation moved out of the package README.
|
|
26
|
+
|
|
27
|
+
- Update README documentation links: the docs site now serves from the domain root, so `docs.10xmedia.de/docs/<plugin>` links became `docs.10xmedia.de/<plugin>`.
|
|
28
|
+
|
|
29
|
+
- Ship per-file dist output instead of bundled chunks. Bundling merged client components into shared chunks and dropped their 'use client' directives, so Next.js lost the RSC boundary and the admin panel crashed with "useRef only works in Client Components" when rendering components imported through such a chunk (for analytics: every chart-based dashboard widget). Dist now mirrors src one file at a time, directives stay exactly where they were authored, and file names are stable across releases. A repo-level `check:dist` verification (directive parity, no inlined dependencies, exports resolution, publint) now runs in CI so this class of regression cannot ship again.
|
|
30
|
+
|
|
3
31
|
## 0.1.0-beta.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
# @10x-media/webhooks
|
|
2
2
|
|
|
3
|
-
Outbound webhook subscriptions for Payload v3. Opt
|
|
3
|
+
Outbound webhook subscriptions for Payload v3. Opt collections in, let subscribers register URLs in the admin panel (or in code), and deliver signed HTTP POSTs on every create, update, and delete, inline or through Payload's jobs queue, with retries and an append-only delivery log.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@10x-media/webhooks)
|
|
6
6
|
|
|
7
7
|
Part of the [@10x-media Payload plugins](https://github.com/10x-media/payload-plugins) collection. In beta: published under the `beta` dist-tag until a stable 1.0.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
11
|
+
- **Opt-in collections** with per-collection operations, `previousData` on updates, and a `transform` to reshape or redact payloads.
|
|
12
|
+
- **Two subscription sources**: admin-managed records and hard-coded code subscriptions, merged at delivery time.
|
|
13
|
+
- **Delivery modes**: queued through Payload jobs (retries, timeouts) or inline; `auto` detects whether a runner exists.
|
|
14
|
+
- **HMAC-SHA256 signing** with reveal-once secrets and id/event/timestamp headers on every request.
|
|
15
|
+
- **Delivery log** with status, response code, duration, the exact body sent, and one-click redelivery.
|
|
16
|
+
- **Jobs family interop**: installing `@10x-media/jobs` switches delivery to the queue automatically.
|
|
17
|
+
- **Typed translations** with per-key overrides via `@10x-media/webhooks/i18n`.
|
|
14
18
|
|
|
15
|
-
##
|
|
19
|
+
## Quick start
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
22
|
pnpm add @10x-media/webhooks
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
## Usage
|
|
22
|
-
|
|
23
25
|
```ts
|
|
26
|
+
// payload.config.ts
|
|
24
27
|
import { buildConfig } from 'payload'
|
|
25
28
|
import { webhooks } from '@10x-media/webhooks'
|
|
26
29
|
|
|
@@ -36,150 +39,19 @@ export default buildConfig({
|
|
|
36
39
|
})
|
|
37
40
|
```
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- **Webhook Subscriptions** -- admins create endpoint records (URL, events, optional secret).
|
|
42
|
-
- **Webhook Deliveries** -- an append-only log with status, response code, and a redeliver button.
|
|
43
|
-
|
|
44
|
-
## Options
|
|
45
|
-
|
|
46
|
-
| Option | Type | Default | Description |
|
|
47
|
-
|---|---|---|---|
|
|
48
|
-
| `disabled` | `boolean` | `false` | When `true`, returns the config unchanged. |
|
|
49
|
-
| `collections` | `Record<string, true \| CollectionWebhookConfig>` | `{}` | Collections that emit webhook events. |
|
|
50
|
-
| `subscriptions` | `CodeSubscription[]` | `[]` | Hard-coded subscriptions (no admin record needed). |
|
|
51
|
-
| `delivery` | `DeliveryMode \| DeliveryOptions` | `'auto'` | How deliveries are executed. |
|
|
52
|
-
| `subscriptionsCollection` | `{ slug?: string; hidden?: boolean }` | -- | Override the subscriptions collection slug or hide it from the admin panel. |
|
|
53
|
-
| `deliveriesLog` | `{ slug?: string; hidden?: boolean }` | -- | Override the deliveries collection slug or hide it from the admin panel. |
|
|
54
|
-
|
|
55
|
-
### `CollectionWebhookConfig`
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
type CollectionWebhookConfig = {
|
|
59
|
-
operations?: ('create' | 'update' | 'delete')[]
|
|
60
|
-
includePreviousData?: boolean
|
|
61
|
-
transform?: (args: {
|
|
62
|
-
doc: Record<string, unknown>
|
|
63
|
-
previousDoc?: Record<string, unknown>
|
|
64
|
-
operation: 'create' | 'update' | 'delete'
|
|
65
|
-
req: PayloadRequest
|
|
66
|
-
}) => unknown
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
`true` is shorthand for all three operations with no transform.
|
|
71
|
-
|
|
72
|
-
`transform` lets you redact fields or reshape the payload before it is sent. Return `undefined` to suppress delivery for that document.
|
|
73
|
-
|
|
74
|
-
`includePreviousData` adds a `previousData` key to the body on `update` events.
|
|
75
|
-
|
|
76
|
-
### Delivery modes
|
|
77
|
-
|
|
78
|
-
| Mode | Behavior |
|
|
79
|
-
|---|---|
|
|
80
|
-
| `'auto'` (default) | Queued when `config.jobs.autoRun` is set or `@10x-media/jobs` is installed; inline otherwise. |
|
|
81
|
-
| `'queue'` | Always enqueued as a Payload job. A worker must run `payload.jobs.run()`. |
|
|
82
|
-
| `'inline'` | Awaited in the `afterChange`/`afterDelete` hook. Simple but adds latency to every write. |
|
|
83
|
-
|
|
84
|
-
Pass a full `DeliveryOptions` object to tune the queue and timeout:
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
webhooks({
|
|
88
|
-
collections: { posts: true },
|
|
89
|
-
delivery: {
|
|
90
|
-
mode: 'queue',
|
|
91
|
-
timeoutMs: 5_000,
|
|
92
|
-
retries: 3,
|
|
93
|
-
queue: 'webhooks',
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Defaults: `timeoutMs: 10000`, `retries: 4`, `queue: 'default'`.
|
|
99
|
-
|
|
100
|
-
### Code subscriptions
|
|
101
|
-
|
|
102
|
-
Register subscriptions in code when you do not want them managed through the admin panel:
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
webhooks({
|
|
106
|
-
collections: { orders: true },
|
|
107
|
-
subscriptions: [
|
|
108
|
-
{
|
|
109
|
-
id: 'my-crm',
|
|
110
|
-
url: 'https://crm.example.com/hooks/orders',
|
|
111
|
-
events: ['orders.created', 'orders.updated'],
|
|
112
|
-
secret: process.env.WEBHOOK_SECRET,
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Code subscriptions are merged with admin-managed ones at delivery time.
|
|
119
|
-
|
|
120
|
-
## Webhook payload
|
|
121
|
-
|
|
122
|
-
Every delivery POSTs a JSON body:
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
{
|
|
126
|
-
"id": "<delivery-id>",
|
|
127
|
-
"event": "posts.created",
|
|
128
|
-
"collection": "posts",
|
|
129
|
-
"operation": "create",
|
|
130
|
-
"occurredAt": "2025-01-01T00:00:00.000Z",
|
|
131
|
-
"data": { ... }
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
`previousData` is included on `update` events when `includePreviousData: true`.
|
|
136
|
-
|
|
137
|
-
## Signature verification
|
|
138
|
-
|
|
139
|
-
When a subscription has a `secret`, each request carries an `X-Webhook-Signature` header:
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
X-Webhook-Signature: v1=<hex>
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
The signature is HMAC-SHA256 over `${timestamp}.${rawBody}`, where `timestamp` is the Unix second from the `X-Webhook-Timestamp` header.
|
|
146
|
-
|
|
147
|
-
Verify in your receiver:
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
import { createHmac } from 'node:crypto'
|
|
151
|
-
|
|
152
|
-
function verify(secret: string, timestamp: string, rawBody: string, signature: string): boolean {
|
|
153
|
-
const expected = createHmac('sha256', secret)
|
|
154
|
-
.update(`${timestamp}.${rawBody}`)
|
|
155
|
-
.digest('hex')
|
|
156
|
-
return signature === `v1=${expected}`
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Additional headers sent on every request: `X-Webhook-Id`, `X-Webhook-Event`, `X-Webhook-Timestamp`, `User-Agent: 10x-media-webhooks`. Subscriptions can inject extra headers via the admin panel's **Headers** array field.
|
|
161
|
-
|
|
162
|
-
## Admin panel
|
|
163
|
-
|
|
164
|
-
The plugin adds a **Webhooks** group with two collections.
|
|
165
|
-
|
|
166
|
-
**Webhook Subscriptions**: create and manage endpoint records. A random 48-character hex secret is auto-generated on create and shown in full **exactly once** on that create response -- copy it to your receiver then. On every later read it is masked (`__redacted__`); the raw value is still used internally to sign deliveries but is never returned through the API or admin again. Rotate by deleting and recreating the record.
|
|
167
|
-
|
|
168
|
-
**Webhook Deliveries**: an append-only delivery log. Each row shows the event, subscription, status, HTTP response code, and a **Redeliver** button that replays the original payload to the original URL. Access requires a logged-in admin.
|
|
169
|
-
|
|
170
|
-
## Composing with `@10x-media/jobs`
|
|
171
|
-
|
|
172
|
-
When `@10x-media/jobs` is installed, `delivery` mode auto-resolves to `'queue'` and the delivery task runs under the jobs worker. No extra config is needed -- the plugin detects the sibling plugin automatically.
|
|
173
|
-
|
|
174
|
-
## Composing with `@10x-media/automations`
|
|
42
|
+
Then create a subscription in the admin (**Webhooks** group), copy its reveal-once secret to your receiver, and write to an opted-in collection.
|
|
175
43
|
|
|
176
|
-
|
|
44
|
+
## Documentation
|
|
177
45
|
|
|
178
|
-
|
|
46
|
+
Full documentation at [docs.10xmedia.de](https://docs.10xmedia.de/webhooks):
|
|
179
47
|
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
48
|
+
- [Overview](https://docs.10xmedia.de/webhooks)
|
|
49
|
+
- [Quick start](https://docs.10xmedia.de/webhooks/quick-start)
|
|
50
|
+
- [Subscriptions](https://docs.10xmedia.de/webhooks/subscriptions)
|
|
51
|
+
- [Deliveries](https://docs.10xmedia.de/webhooks/deliveries)
|
|
52
|
+
- [Signing and secrets](https://docs.10xmedia.de/webhooks/signing)
|
|
53
|
+
- [Security model](https://docs.10xmedia.de/webhooks/security)
|
|
54
|
+
- [i18n](https://docs.10xmedia.de/webhooks/i18n)
|
|
183
55
|
|
|
184
56
|
## License
|
|
185
57
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { ADMIN_GROUP } from "../constants.js";
|
|
3
|
+
import { labelForKey } from "../translations/server.js";
|
|
4
|
+
//#region src/collections/deliveries.ts
|
|
5
|
+
const STATUS_CELL = "@10x-media/webhooks/client#DeliveryStatusCell";
|
|
6
|
+
const loggedIn = ({ req }) => Boolean(req.user);
|
|
7
|
+
/** Runner-written delivery audit log; read-only in admin, server writes use overrideAccess. */
|
|
8
|
+
const buildDeliveriesCollection = (args) => ({
|
|
9
|
+
slug: args.slug,
|
|
10
|
+
labels: {
|
|
11
|
+
singular: labelForKey(keys.deliverySingular),
|
|
12
|
+
plural: labelForKey(keys.deliveryPlural)
|
|
13
|
+
},
|
|
14
|
+
admin: {
|
|
15
|
+
group: ADMIN_GROUP,
|
|
16
|
+
useAsTitle: "event",
|
|
17
|
+
defaultColumns: [
|
|
18
|
+
"event",
|
|
19
|
+
"endpoint",
|
|
20
|
+
"status",
|
|
21
|
+
"responseStatus",
|
|
22
|
+
"attempt",
|
|
23
|
+
"createdAt"
|
|
24
|
+
],
|
|
25
|
+
hidden: args.hidden
|
|
26
|
+
},
|
|
27
|
+
access: {
|
|
28
|
+
read: loggedIn,
|
|
29
|
+
create: () => false,
|
|
30
|
+
update: () => false,
|
|
31
|
+
delete: loggedIn
|
|
32
|
+
},
|
|
33
|
+
fields: [
|
|
34
|
+
{
|
|
35
|
+
name: "subscriptionId",
|
|
36
|
+
type: "text",
|
|
37
|
+
index: true
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "endpoint",
|
|
41
|
+
type: "text"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "event",
|
|
45
|
+
type: "text",
|
|
46
|
+
index: true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "status",
|
|
50
|
+
type: "select",
|
|
51
|
+
defaultValue: "pending",
|
|
52
|
+
options: [
|
|
53
|
+
"pending",
|
|
54
|
+
"success",
|
|
55
|
+
"failed",
|
|
56
|
+
"dead"
|
|
57
|
+
],
|
|
58
|
+
admin: { components: { Cell: STATUS_CELL } }
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "attempt",
|
|
62
|
+
type: "number",
|
|
63
|
+
defaultValue: 0
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "responseStatus",
|
|
67
|
+
type: "number"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "responseBody",
|
|
71
|
+
type: "textarea"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "error",
|
|
75
|
+
type: "textarea"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "durationMs",
|
|
79
|
+
type: "number"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "jobId",
|
|
83
|
+
type: "text"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "payload",
|
|
87
|
+
type: "json"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "redeliver",
|
|
91
|
+
type: "ui",
|
|
92
|
+
admin: { components: { Field: "@10x-media/webhooks/client#RedeliverButton" } }
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
//#endregion
|
|
97
|
+
export { buildDeliveriesCollection };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=deliveries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliveries.js","names":[],"sources":["../../src/collections/deliveries.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport { ADMIN_GROUP } from '../constants'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\n\nconst STATUS_CELL = '@10x-media/webhooks/client#DeliveryStatusCell'\n\nconst loggedIn = ({ req }: { req: { user?: unknown } }) => Boolean(req.user)\n\n/** Runner-written delivery audit log; read-only in admin, server writes use overrideAccess. */\nexport const buildDeliveriesCollection = (args: {\n\tslug: string\n\thidden: boolean\n}): CollectionConfig => ({\n\tslug: args.slug,\n\tlabels: {\n\t\tsingular: labelForKey(keys.deliverySingular),\n\t\tplural: labelForKey(keys.deliveryPlural),\n\t},\n\tadmin: {\n\t\tgroup: ADMIN_GROUP,\n\t\tuseAsTitle: 'event',\n\t\tdefaultColumns: ['event', 'endpoint', 'status', 'responseStatus', 'attempt', 'createdAt'],\n\t\thidden: args.hidden,\n\t},\n\taccess: { read: loggedIn, create: () => false, update: () => false, delete: loggedIn },\n\tfields: [\n\t\t{ name: 'subscriptionId', type: 'text', index: true },\n\t\t{ name: 'endpoint', type: 'text' },\n\t\t{ name: 'event', type: 'text', index: true },\n\t\t{\n\t\t\tname: 'status',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: 'pending',\n\t\t\toptions: ['pending', 'success', 'failed', 'dead'],\n\t\t\tadmin: { components: { Cell: STATUS_CELL } },\n\t\t},\n\t\t{ name: 'attempt', type: 'number', defaultValue: 0 },\n\t\t{ name: 'responseStatus', type: 'number' },\n\t\t{ name: 'responseBody', type: 'textarea' },\n\t\t{ name: 'error', type: 'textarea' },\n\t\t{ name: 'durationMs', type: 'number' },\n\t\t{ name: 'jobId', type: 'text' },\n\t\t{ name: 'payload', type: 'json' },\n\t\t{\n\t\t\tname: 'redeliver',\n\t\t\ttype: 'ui',\n\t\t\tadmin: { components: { Field: '@10x-media/webhooks/client#RedeliverButton' } },\n\t\t},\n\t],\n})\n"],"mappings":";;;;AAMA,MAAM,cAAc;AAEpB,MAAM,YAAY,EAAE,UAAuC,QAAQ,IAAI,IAAI;;AAG3E,MAAa,6BAA6B,UAGjB;CACxB,MAAM,KAAK;CACX,QAAQ;EACP,UAAU,YAAY,KAAK,gBAAgB;EAC3C,QAAQ,YAAY,KAAK,cAAc;CACxC;CACA,OAAO;EACN,OAAO;EACP,YAAY;EACZ,gBAAgB;GAAC;GAAS;GAAY;GAAU;GAAkB;GAAW;EAAW;EACxF,QAAQ,KAAK;CACd;CACA,QAAQ;EAAE,MAAM;EAAU,cAAc;EAAO,cAAc;EAAO,QAAQ;CAAS;CACrF,QAAQ;EACP;GAAE,MAAM;GAAkB,MAAM;GAAQ,OAAO;EAAK;EACpD;GAAE,MAAM;GAAY,MAAM;EAAO;EACjC;GAAE,MAAM;GAAS,MAAM;GAAQ,OAAO;EAAK;EAC3C;GACC,MAAM;GACN,MAAM;GACN,cAAc;GACd,SAAS;IAAC;IAAW;IAAW;IAAU;GAAM;GAChD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,EAAE;EAC5C;EACA;GAAE,MAAM;GAAW,MAAM;GAAU,cAAc;EAAE;EACnD;GAAE,MAAM;GAAkB,MAAM;EAAS;EACzC;GAAE,MAAM;GAAgB,MAAM;EAAW;EACzC;GAAE,MAAM;GAAS,MAAM;EAAW;EAClC;GAAE,MAAM;GAAc,MAAM;EAAS;EACrC;GAAE,MAAM;GAAS,MAAM;EAAO;EAC9B;GAAE,MAAM;GAAW,MAAM;EAAO;EAChC;GACC,MAAM;GACN,MAAM;GACN,OAAO,EAAE,YAAY,EAAE,OAAO,6CAA6C,EAAE;EAC9E;CACD;AACD"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { ADMIN_GROUP, SECRET_MASK, SECRET_REVEAL_CONTEXT } from "../constants.js";
|
|
3
|
+
import { labelForKey } from "../translations/server.js";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
//#region src/collections/subscriptions.ts
|
|
6
|
+
const generateSecret = ({ data, operation, req }) => {
|
|
7
|
+
if (operation === "create" && !data.secret) {
|
|
8
|
+
req.context[SECRET_REVEAL_CONTEXT.once] = true;
|
|
9
|
+
return {
|
|
10
|
+
...data,
|
|
11
|
+
secret: randomBytes(24).toString("hex")
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return data;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The DB always stores the raw secret; this mask only shapes read output. It runs even under
|
|
18
|
+
* `overrideAccess` (field hooks are not gated by access), so the raw value reaches a reader only
|
|
19
|
+
* when a reveal flag is set: `once` on the create response, `forSigning` on internal delivery reads.
|
|
20
|
+
*/
|
|
21
|
+
const maskSecret = ({ value, req }) => {
|
|
22
|
+
if (req.context[SECRET_REVEAL_CONTEXT.forSigning] || req.context[SECRET_REVEAL_CONTEXT.once]) return value;
|
|
23
|
+
return value == null ? value : SECRET_MASK;
|
|
24
|
+
};
|
|
25
|
+
const clearRevealOnce = ({ doc, req }) => {
|
|
26
|
+
req.context[SECRET_REVEAL_CONTEXT.once] = false;
|
|
27
|
+
return doc;
|
|
28
|
+
};
|
|
29
|
+
const loggedIn = ({ req }) => Boolean(req.user);
|
|
30
|
+
/** Admin-managed subscriptions collection; `events` options come from the catalog. */
|
|
31
|
+
const buildSubscriptionsCollection = (args) => ({
|
|
32
|
+
slug: args.slug,
|
|
33
|
+
labels: {
|
|
34
|
+
singular: labelForKey(keys.subscriptionSingular),
|
|
35
|
+
plural: labelForKey(keys.subscriptionPlural)
|
|
36
|
+
},
|
|
37
|
+
admin: {
|
|
38
|
+
group: ADMIN_GROUP,
|
|
39
|
+
useAsTitle: "name",
|
|
40
|
+
defaultColumns: [
|
|
41
|
+
"name",
|
|
42
|
+
"url",
|
|
43
|
+
"enabled"
|
|
44
|
+
],
|
|
45
|
+
hidden: args.hidden
|
|
46
|
+
},
|
|
47
|
+
access: {
|
|
48
|
+
read: loggedIn,
|
|
49
|
+
create: loggedIn,
|
|
50
|
+
update: loggedIn,
|
|
51
|
+
delete: loggedIn
|
|
52
|
+
},
|
|
53
|
+
hooks: {
|
|
54
|
+
beforeChange: [generateSecret],
|
|
55
|
+
afterChange: [clearRevealOnce]
|
|
56
|
+
},
|
|
57
|
+
fields: [
|
|
58
|
+
{
|
|
59
|
+
name: "name",
|
|
60
|
+
type: "text",
|
|
61
|
+
required: true,
|
|
62
|
+
label: labelForKey(keys.fieldName)
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "url",
|
|
66
|
+
type: "text",
|
|
67
|
+
required: true,
|
|
68
|
+
label: labelForKey(keys.fieldUrl)
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "enabled",
|
|
72
|
+
type: "checkbox",
|
|
73
|
+
defaultValue: true,
|
|
74
|
+
label: labelForKey(keys.fieldEnabled)
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "events",
|
|
78
|
+
type: "select",
|
|
79
|
+
hasMany: true,
|
|
80
|
+
label: labelForKey(keys.fieldEvents),
|
|
81
|
+
options: args.events.length ? args.events.map((e) => ({
|
|
82
|
+
label: e,
|
|
83
|
+
value: e
|
|
84
|
+
})) : [{
|
|
85
|
+
label: "(none)",
|
|
86
|
+
value: "__none__"
|
|
87
|
+
}]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "secret",
|
|
91
|
+
type: "text",
|
|
92
|
+
label: labelForKey(keys.fieldSecret),
|
|
93
|
+
admin: {
|
|
94
|
+
readOnly: true,
|
|
95
|
+
description: labelForKey(keys.fieldSecretHelp)
|
|
96
|
+
},
|
|
97
|
+
access: { update: () => false },
|
|
98
|
+
hooks: { afterRead: [maskSecret] }
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "headers",
|
|
102
|
+
type: "array",
|
|
103
|
+
label: labelForKey(keys.fieldHeaders),
|
|
104
|
+
fields: [{
|
|
105
|
+
name: "key",
|
|
106
|
+
type: "text",
|
|
107
|
+
required: true
|
|
108
|
+
}, {
|
|
109
|
+
name: "value",
|
|
110
|
+
type: "text"
|
|
111
|
+
}]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "description",
|
|
115
|
+
type: "textarea",
|
|
116
|
+
label: labelForKey(keys.fieldDescription)
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
});
|
|
120
|
+
//#endregion
|
|
121
|
+
export { buildSubscriptionsCollection };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=subscriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptions.js","names":[],"sources":["../../src/collections/subscriptions.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport type {\n\tCollectionAfterChangeHook,\n\tCollectionBeforeChangeHook,\n\tCollectionConfig,\n\tFieldHook,\n} from 'payload'\n\nimport { ADMIN_GROUP, SECRET_MASK, SECRET_REVEAL_CONTEXT } from '../constants'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\n\nconst generateSecret: CollectionBeforeChangeHook = ({ data, operation, req }) => {\n\tif (operation === 'create' && !data.secret) {\n\t\treq.context[SECRET_REVEAL_CONTEXT.once] = true\n\t\treturn { ...data, secret: randomBytes(24).toString('hex') }\n\t}\n\treturn data\n}\n\n/**\n * The DB always stores the raw secret; this mask only shapes read output. It runs even under\n * `overrideAccess` (field hooks are not gated by access), so the raw value reaches a reader only\n * when a reveal flag is set: `once` on the create response, `forSigning` on internal delivery reads.\n */\nconst maskSecret: FieldHook = ({ value, req }) => {\n\tif (req.context[SECRET_REVEAL_CONTEXT.forSigning] || req.context[SECRET_REVEAL_CONTEXT.once]) {\n\t\treturn value\n\t}\n\treturn value == null ? value : SECRET_MASK\n}\n\nconst clearRevealOnce: CollectionAfterChangeHook = ({ doc, req }) => {\n\treq.context[SECRET_REVEAL_CONTEXT.once] = false\n\treturn doc\n}\n\nconst loggedIn = ({ req }: { req: { user?: unknown } }) => Boolean(req.user)\n\n/** Admin-managed subscriptions collection; `events` options come from the catalog. */\nexport const buildSubscriptionsCollection = (args: {\n\tslug: string\n\tevents: string[]\n\thidden: boolean\n}): CollectionConfig => ({\n\tslug: args.slug,\n\tlabels: {\n\t\tsingular: labelForKey(keys.subscriptionSingular),\n\t\tplural: labelForKey(keys.subscriptionPlural),\n\t},\n\tadmin: {\n\t\tgroup: ADMIN_GROUP,\n\t\tuseAsTitle: 'name',\n\t\tdefaultColumns: ['name', 'url', 'enabled'],\n\t\thidden: args.hidden,\n\t},\n\taccess: { read: loggedIn, create: loggedIn, update: loggedIn, delete: loggedIn },\n\thooks: { beforeChange: [generateSecret], afterChange: [clearRevealOnce] },\n\tfields: [\n\t\t{ name: 'name', type: 'text', required: true, label: labelForKey(keys.fieldName) },\n\t\t{ name: 'url', type: 'text', required: true, label: labelForKey(keys.fieldUrl) },\n\t\t{\n\t\t\tname: 'enabled',\n\t\t\ttype: 'checkbox',\n\t\t\tdefaultValue: true,\n\t\t\tlabel: labelForKey(keys.fieldEnabled),\n\t\t},\n\t\t{\n\t\t\tname: 'events',\n\t\t\ttype: 'select',\n\t\t\thasMany: true,\n\t\t\tlabel: labelForKey(keys.fieldEvents),\n\t\t\toptions: args.events.length\n\t\t\t\t? args.events.map((e) => ({ label: e, value: e }))\n\t\t\t\t: [{ label: '(none)', value: '__none__' }],\n\t\t},\n\t\t{\n\t\t\tname: 'secret',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.fieldSecret),\n\t\t\tadmin: { readOnly: true, description: labelForKey(keys.fieldSecretHelp) },\n\t\t\taccess: { update: () => false },\n\t\t\thooks: { afterRead: [maskSecret] },\n\t\t},\n\t\t{\n\t\t\tname: 'headers',\n\t\t\ttype: 'array',\n\t\t\tlabel: labelForKey(keys.fieldHeaders),\n\t\t\tfields: [\n\t\t\t\t{ name: 'key', type: 'text', required: true },\n\t\t\t\t{ name: 'value', type: 'text' },\n\t\t\t],\n\t\t},\n\t\t{ name: 'description', type: 'textarea', label: labelForKey(keys.fieldDescription) },\n\t],\n})\n"],"mappings":";;;;;AAYA,MAAM,kBAA8C,EAAE,MAAM,WAAW,UAAU;CAChF,IAAI,cAAc,YAAY,CAAC,KAAK,QAAQ;EAC3C,IAAI,QAAQ,sBAAsB,QAAQ;EAC1C,OAAO;GAAE,GAAG;GAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,KAAK;EAAE;CAC3D;CACA,OAAO;AACR;;;;;;AAOA,MAAM,cAAyB,EAAE,OAAO,UAAU;CACjD,IAAI,IAAI,QAAQ,sBAAsB,eAAe,IAAI,QAAQ,sBAAsB,OACtF,OAAO;CAER,OAAO,SAAS,OAAO,QAAQ;AAChC;AAEA,MAAM,mBAA8C,EAAE,KAAK,UAAU;CACpE,IAAI,QAAQ,sBAAsB,QAAQ;CAC1C,OAAO;AACR;AAEA,MAAM,YAAY,EAAE,UAAuC,QAAQ,IAAI,IAAI;;AAG3E,MAAa,gCAAgC,UAIpB;CACxB,MAAM,KAAK;CACX,QAAQ;EACP,UAAU,YAAY,KAAK,oBAAoB;EAC/C,QAAQ,YAAY,KAAK,kBAAkB;CAC5C;CACA,OAAO;EACN,OAAO;EACP,YAAY;EACZ,gBAAgB;GAAC;GAAQ;GAAO;EAAS;EACzC,QAAQ,KAAK;CACd;CACA,QAAQ;EAAE,MAAM;EAAU,QAAQ;EAAU,QAAQ;EAAU,QAAQ;CAAS;CAC/E,OAAO;EAAE,cAAc,CAAC,cAAc;EAAG,aAAa,CAAC,eAAe;CAAE;CACxE,QAAQ;EACP;GAAE,MAAM;GAAQ,MAAM;GAAQ,UAAU;GAAM,OAAO,YAAY,KAAK,SAAS;EAAE;EACjF;GAAE,MAAM;GAAO,MAAM;GAAQ,UAAU;GAAM,OAAO,YAAY,KAAK,QAAQ;EAAE;EAC/E;GACC,MAAM;GACN,MAAM;GACN,cAAc;GACd,OAAO,YAAY,KAAK,YAAY;EACrC;EACA;GACC,MAAM;GACN,MAAM;GACN,SAAS;GACT,OAAO,YAAY,KAAK,WAAW;GACnC,SAAS,KAAK,OAAO,SAClB,KAAK,OAAO,KAAK,OAAO;IAAE,OAAO;IAAG,OAAO;GAAE,EAAE,IAC/C,CAAC;IAAE,OAAO;IAAU,OAAO;GAAW,CAAC;EAC3C;EACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,YAAY,KAAK,WAAW;GACnC,OAAO;IAAE,UAAU;IAAM,aAAa,YAAY,KAAK,eAAe;GAAE;GACxE,QAAQ,EAAE,cAAc,MAAM;GAC9B,OAAO,EAAE,WAAW,CAAC,UAAU,EAAE;EAClC;EACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,YAAY,KAAK,YAAY;GACpC,QAAQ,CACP;IAAE,MAAM;IAAO,MAAM;IAAQ,UAAU;GAAK,GAC5C;IAAE,MAAM;IAAS,MAAM;GAAO,CAC/B;EACD;EACA;GAAE,MAAM;GAAe,MAAM;GAAY,OAAO,YAAY,KAAK,gBAAgB;EAAE;CACpF;AACD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/constants.ts
|
|
2
|
+
const DEFAULT_SUBSCRIPTIONS_SLUG = "webhook-subscriptions";
|
|
3
|
+
const DEFAULT_DELIVERIES_SLUG = "webhook-deliveries";
|
|
4
|
+
const ADMIN_GROUP = "Webhooks";
|
|
5
|
+
const WEBHOOK_DELIVER_TASK = "webhooksDeliver";
|
|
6
|
+
const DEFAULT_DELIVERY_QUEUE = "default";
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
8
|
+
/** Placeholder returned for the signing secret on every read after its single create reveal. */
|
|
9
|
+
const SECRET_MASK = "__redacted__";
|
|
10
|
+
/**
|
|
11
|
+
* `req.context` flags that opt a subscription read into seeing the raw signing secret.
|
|
12
|
+
* The field `afterRead` mask runs even under `overrideAccess`, so internal signing reads
|
|
13
|
+
* must set `revealSecretForSigning` to recover the raw value; `revealSecretOnce` is set by
|
|
14
|
+
* the create `beforeChange` so the create response shows the secret exactly once.
|
|
15
|
+
*/
|
|
16
|
+
const SECRET_REVEAL_CONTEXT = {
|
|
17
|
+
forSigning: "webhooksRevealSecretForSigning",
|
|
18
|
+
once: "webhooksRevealSecretOnce"
|
|
19
|
+
};
|
|
20
|
+
const RESERVED_SLUGS = [
|
|
21
|
+
"payload-jobs",
|
|
22
|
+
"payload-locks",
|
|
23
|
+
"payload-preferences",
|
|
24
|
+
"payload-migrations"
|
|
25
|
+
];
|
|
26
|
+
//#endregion
|
|
27
|
+
export { ADMIN_GROUP, DEFAULT_DELIVERIES_SLUG, DEFAULT_DELIVERY_QUEUE, DEFAULT_SUBSCRIPTIONS_SLUG, DEFAULT_TIMEOUT_MS, RESERVED_SLUGS, SECRET_MASK, SECRET_REVEAL_CONTEXT, WEBHOOK_DELIVER_TASK };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const DEFAULT_SUBSCRIPTIONS_SLUG = 'webhook-subscriptions'\nexport const DEFAULT_DELIVERIES_SLUG = 'webhook-deliveries'\nexport const ADMIN_GROUP = 'Webhooks'\nexport const WEBHOOK_DELIVER_TASK = 'webhooksDeliver'\nexport const DEFAULT_DELIVERY_QUEUE = 'default'\nexport const DEFAULT_TIMEOUT_MS = 10_000\nexport const DEFAULT_RETRIES = 4\n\n/** Placeholder returned for the signing secret on every read after its single create reveal. */\nexport const SECRET_MASK = '__redacted__'\n\n/**\n * `req.context` flags that opt a subscription read into seeing the raw signing secret.\n * The field `afterRead` mask runs even under `overrideAccess`, so internal signing reads\n * must set `revealSecretForSigning` to recover the raw value; `revealSecretOnce` is set by\n * the create `beforeChange` so the create response shows the secret exactly once.\n */\nexport const SECRET_REVEAL_CONTEXT = {\n\tforSigning: 'webhooksRevealSecretForSigning',\n\tonce: 'webhooksRevealSecretOnce',\n} as const\nexport const RESERVED_SLUGS = [\n\t'payload-jobs',\n\t'payload-locks',\n\t'payload-preferences',\n\t'payload-migrations',\n] as const\n"],"mappings":";AAAA,MAAa,6BAA6B;AAC1C,MAAa,0BAA0B;AACvC,MAAa,cAAc;AAC3B,MAAa,uBAAuB;AACpC,MAAa,yBAAyB;AACtC,MAAa,qBAAqB;;AAIlC,MAAa,cAAc;;;;;;;AAQ3B,MAAa,wBAAwB;CACpC,YAAY;CACZ,MAAM;AACP;AACA,MAAa,iBAAiB;CAC7B;CACA;CACA;CACA;AACD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DefaultCellComponentProps } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/delivery/DeliveryStatusCell.d.ts
|
|
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
|
+
export { DeliveryStatusCell };
|
|
10
|
+
//# sourceMappingURL=DeliveryStatusCell.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { keys } from "../translations/keys.js";
|
|
3
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
4
|
+
import { Pill } from "@payloadcms/ui";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/delivery/DeliveryStatusCell.tsx
|
|
7
|
+
const META = {
|
|
8
|
+
pending: {
|
|
9
|
+
labelKey: keys.statusPending,
|
|
10
|
+
pillStyle: "light"
|
|
11
|
+
},
|
|
12
|
+
success: {
|
|
13
|
+
labelKey: keys.statusSuccess,
|
|
14
|
+
pillStyle: "success"
|
|
15
|
+
},
|
|
16
|
+
failed: {
|
|
17
|
+
labelKey: keys.statusFailed,
|
|
18
|
+
pillStyle: "warning"
|
|
19
|
+
},
|
|
20
|
+
dead: {
|
|
21
|
+
labelKey: keys.statusDead,
|
|
22
|
+
pillStyle: "error"
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const FALLBACK = {
|
|
26
|
+
labelKey: keys.statusPending,
|
|
27
|
+
pillStyle: "light"
|
|
28
|
+
};
|
|
29
|
+
/** List cell rendering a delivery's status as a native Payload Pill. */
|
|
30
|
+
const DeliveryStatusCell = ({ cellData }) => {
|
|
31
|
+
const { t } = useTranslation$1();
|
|
32
|
+
const meta = META[String(cellData)] ?? FALLBACK;
|
|
33
|
+
return /* @__PURE__ */ jsx(Pill, {
|
|
34
|
+
pillStyle: meta.pillStyle,
|
|
35
|
+
size: "small",
|
|
36
|
+
children: t(meta.labelKey)
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { DeliveryStatusCell };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=DeliveryStatusCell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeliveryStatusCell.js","names":["useTranslation"],"sources":["../../src/delivery/DeliveryStatusCell.tsx"],"sourcesContent":["'use client'\n\nimport { Pill } from '@payloadcms/ui'\nimport type { DefaultCellComponentProps } from 'payload'\n\nimport { keys, type TranslationKey } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\ntype MetaEntry = { labelKey: TranslationKey; pillStyle: 'error' | 'light' | 'success' | 'warning' }\n\nconst META: Record<string, MetaEntry> = {\n\tpending: { labelKey: keys.statusPending, pillStyle: 'light' },\n\tsuccess: { labelKey: keys.statusSuccess, pillStyle: 'success' },\n\tfailed: { labelKey: keys.statusFailed, pillStyle: 'warning' },\n\tdead: { labelKey: keys.statusDead, pillStyle: 'error' },\n}\n\nconst FALLBACK: MetaEntry = { labelKey: keys.statusPending, pillStyle: 'light' }\n\n/** List cell rendering a delivery's status as a native Payload Pill. */\nexport const DeliveryStatusCell = ({ cellData }: DefaultCellComponentProps) => {\n\tconst { t } = useTranslation()\n\tconst meta = META[String(cellData)] ?? FALLBACK\n\treturn (\n\t\t<Pill pillStyle={meta.pillStyle} size=\"small\">\n\t\t\t{t(meta.labelKey)}\n\t\t</Pill>\n\t)\n}\n"],"mappings":";;;;;;AAUA,MAAM,OAAkC;CACvC,SAAS;EAAE,UAAU,KAAK;EAAe,WAAW;CAAQ;CAC5D,SAAS;EAAE,UAAU,KAAK;EAAe,WAAW;CAAU;CAC9D,QAAQ;EAAE,UAAU,KAAK;EAAc,WAAW;CAAU;CAC5D,MAAM;EAAE,UAAU,KAAK;EAAY,WAAW;CAAQ;AACvD;AAEA,MAAM,WAAsB;CAAE,UAAU,KAAK;CAAe,WAAW;AAAQ;;AAG/E,MAAa,sBAAsB,EAAE,eAA0C;CAC9E,MAAM,EAAE,MAAMA,iBAAe;CAC7B,MAAM,OAAO,KAAK,OAAO,QAAQ,MAAM;CACvC,OACC,oBAAC,MAAD;EAAM,WAAW,KAAK;EAAW,MAAK;YACpC,EAAE,KAAK,QAAQ;CACX,CAAA;AAER"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/delivery/RedeliverButton.d.ts
|
|
2
|
+
/** Doc-view action that POSTs to the deliveries redeliver endpoint. */
|
|
3
|
+
declare const RedeliverButton: () => import("react/jsx-runtime").JSX.Element | null;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { RedeliverButton };
|
|
6
|
+
//# sourceMappingURL=RedeliverButton.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { keys } from "../translations/keys.js";
|
|
3
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
4
|
+
import { Button, toast, useConfig, useDocumentInfo } from "@payloadcms/ui";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
//#region src/delivery/RedeliverButton.tsx
|
|
8
|
+
/** Doc-view action that POSTs to the deliveries redeliver endpoint. */
|
|
9
|
+
const RedeliverButton = () => {
|
|
10
|
+
const { id, collectionSlug } = useDocumentInfo();
|
|
11
|
+
const { config } = useConfig();
|
|
12
|
+
const { t } = useTranslation$1();
|
|
13
|
+
const [busy, setBusy] = useState(false);
|
|
14
|
+
if (!id || !collectionSlug) return null;
|
|
15
|
+
const apiRoute = config.routes?.api ?? "/api";
|
|
16
|
+
const serverURL = config.serverURL ?? "";
|
|
17
|
+
const onClick = async () => {
|
|
18
|
+
setBusy(true);
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(`${serverURL}${apiRoute}/${collectionSlug}/${encodeURIComponent(String(id))}/redeliver`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
credentials: "include"
|
|
23
|
+
});
|
|
24
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
25
|
+
toast.success(t(keys.redeliverDone));
|
|
26
|
+
} catch {
|
|
27
|
+
toast.error(t(keys.redeliver));
|
|
28
|
+
} finally {
|
|
29
|
+
setBusy(false);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
33
|
+
buttonStyle: "secondary",
|
|
34
|
+
disabled: busy,
|
|
35
|
+
onClick,
|
|
36
|
+
size: "small",
|
|
37
|
+
children: t(keys.redeliver)
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { RedeliverButton };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=RedeliverButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedeliverButton.js","names":["useTranslation"],"sources":["../../src/delivery/RedeliverButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useConfig, useDocumentInfo } from '@payloadcms/ui'\nimport { useState } from 'react'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\n/** Doc-view action that POSTs to the deliveries redeliver endpoint. */\nexport const RedeliverButton = () => {\n\tconst { id, collectionSlug } = useDocumentInfo()\n\tconst { config } = useConfig()\n\tconst { t } = useTranslation()\n\tconst [busy, setBusy] = useState(false)\n\n\tif (!id || !collectionSlug) {\n\t\treturn null\n\t}\n\n\tconst apiRoute = config.routes?.api ?? '/api'\n\tconst serverURL = config.serverURL ?? ''\n\n\tconst onClick = async () => {\n\t\tsetBusy(true)\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${serverURL}${apiRoute}/${collectionSlug}/${encodeURIComponent(String(id))}/redeliver`,\n\t\t\t\t{ method: 'POST', credentials: 'include' }\n\t\t\t)\n\t\t\tif (!res.ok) {\n\t\t\t\tthrow new Error(String(res.status))\n\t\t\t}\n\t\t\ttoast.success(t(keys.redeliverDone))\n\t\t} catch {\n\t\t\ttoast.error(t(keys.redeliver))\n\t\t} finally {\n\t\t\tsetBusy(false)\n\t\t}\n\t}\n\n\treturn (\n\t\t<Button buttonStyle=\"secondary\" disabled={busy} onClick={onClick} size=\"small\">\n\t\t\t{t(keys.redeliver)}\n\t\t</Button>\n\t)\n}\n"],"mappings":";;;;;;;;AASA,MAAa,wBAAwB;CACpC,MAAM,EAAE,IAAI,mBAAmB,gBAAgB;CAC/C,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,EAAE,MAAMA,iBAAe;CAC7B,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,IAAI,CAAC,MAAM,CAAC,gBACX,OAAO;CAGR,MAAM,WAAW,OAAO,QAAQ,OAAO;CACvC,MAAM,YAAY,OAAO,aAAa;CAEtC,MAAM,UAAU,YAAY;EAC3B,QAAQ,IAAI;EACZ,IAAI;GACH,MAAM,MAAM,MAAM,MACjB,GAAG,YAAY,SAAS,GAAG,eAAe,GAAG,mBAAmB,OAAO,EAAE,CAAC,EAAE,aAC5E;IAAE,QAAQ;IAAQ,aAAa;GAAU,CAC1C;GACA,IAAI,CAAC,IAAI,IACR,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM,CAAC;GAEnC,MAAM,QAAQ,EAAE,KAAK,aAAa,CAAC;EACpC,QAAQ;GACP,MAAM,MAAM,EAAE,KAAK,SAAS,CAAC;EAC9B,UAAU;GACT,QAAQ,KAAK;EACd;CACD;CAEA,OACC,oBAAC,QAAD;EAAQ,aAAY;EAAY,UAAU;EAAe;EAAS,MAAK;YACrE,EAAE,KAAK,SAAS;CACV,CAAA;AAEV"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}) : doc;
|
|
12
|
+
const body = {
|
|
13
|
+
id: deliveryId,
|
|
14
|
+
event: eventId(collection, operation),
|
|
15
|
+
collection,
|
|
16
|
+
operation,
|
|
17
|
+
occurredAt,
|
|
18
|
+
data
|
|
19
|
+
};
|
|
20
|
+
if (operation === "update" && config?.includePreviousData && previousDoc) body.previousData = previousDoc;
|
|
21
|
+
return body;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { buildPayload };
|
|
25
|
+
|
|
26
|
+
//# 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 ? config.transform({ doc, previousDoc, operation, req }) : 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 = 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,YAAY,OAAO,UAAU;EAAE;EAAK;EAAa;EAAW;CAAI,CAAC,IAAI;CAC1F,MAAM,OAAoB;EACzB,IAAI;EACJ,OAAO,QAAQ,YAAY,SAAS;EACpC;EACA;EACA;EACA;CACD;CACA,IAAI,cAAc,YAAY,QAAQ,uBAAuB,aAC5D,KAAK,eAAe;CAErB,OAAO;AACR"}
|