@ampless/plugin-webhook 0.2.0-alpha.2 → 0.2.0-alpha.21
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/README.ja.md +106 -0
- package/README.md +3 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +2 -8
- package/package.json +2 -2
package/README.ja.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
> English: [README.md](./README.md)
|
|
2
|
+
>
|
|
3
|
+
|
|
4
|
+
# @ampless/plugin-webhook
|
|
5
|
+
|
|
6
|
+
ampless イベントを 1 つ以上の外部 URL に POST で通知します。
|
|
7
|
+
|
|
8
|
+
> **プレリリース / アルファ版。** v1.0 まではマイナーバージョンでも破壊的変更が入る可能性があります。
|
|
9
|
+
|
|
10
|
+
**untrusted** Lambda で実行されます — アウトバウンド HTTPS 呼び出しのみ行い、AWS データには一切触れないため、Webhook 受信先が侵害されても CMS への侵入口にはなりません。
|
|
11
|
+
|
|
12
|
+
## インストール
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @ampless/plugin-webhook@alpha
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 設定
|
|
19
|
+
|
|
20
|
+
`cms.config.ts` に記述します:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import webhookPlugin from '@ampless/plugin-webhook'
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
// ...
|
|
27
|
+
plugins: [
|
|
28
|
+
webhookPlugin({
|
|
29
|
+
endpoints: [
|
|
30
|
+
{
|
|
31
|
+
url: 'https://example.com/hooks/ampless',
|
|
32
|
+
secret: process.env.WEBHOOK_SECRET,
|
|
33
|
+
events: ['content.published', 'content.unpublished', 'content.deleted'],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
url: 'https://discord.com/api/webhooks/.../...',
|
|
37
|
+
// secret なし — Discord は署名を検証しない
|
|
38
|
+
events: ['content.published'],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| オプション | デフォルト | 備考 |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `endpoints[].url` | 必須 | POST 先の HTTPS エンドポイント |
|
|
49
|
+
| `endpoints[].secret` | なし | 設定時、本文を HMAC-SHA-256 で署名して `X-Ampless-Signature` ヘッダーで送信 |
|
|
50
|
+
| `endpoints[].events` | すべての `content.*` | このエンドポイントを発火するイベント種別を絞り込む |
|
|
51
|
+
| `endpoints[].headers` | `{}` | 全リクエストにマージされる追加ヘッダー |
|
|
52
|
+
| `endpoints[].timeoutMs` | `5000` | リクエストごとのタイムアウト |
|
|
53
|
+
| `url`(トップレベル) | なし | 単一エンドポイントのショートカット。`endpoints: [{ url, secret }]` と同等 |
|
|
54
|
+
| `secret`(トップレベル) | なし | トップレベルの `url` と対で使用 |
|
|
55
|
+
|
|
56
|
+
## リクエスト形式
|
|
57
|
+
|
|
58
|
+
```http
|
|
59
|
+
POST /hooks/ampless HTTP/1.1
|
|
60
|
+
Host: example.com
|
|
61
|
+
Content-Type: application/json
|
|
62
|
+
X-Ampless-Event: content.published
|
|
63
|
+
X-Ampless-Signature: sha256=<hex>
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
"type": "content.published",
|
|
67
|
+
"payload": {
|
|
68
|
+
"siteId": "default",
|
|
69
|
+
"postId": "post-001",
|
|
70
|
+
"slug": "hello",
|
|
71
|
+
"title": "Hello",
|
|
72
|
+
"status": "published",
|
|
73
|
+
"publishedAt": "2026-04-30T00:00:00.000Z",
|
|
74
|
+
"tags": ["intro"]
|
|
75
|
+
},
|
|
76
|
+
"timestamp": "2026-04-30T00:00:01.000Z"
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 署名の検証(Node.js)
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
84
|
+
|
|
85
|
+
function verify(rawBody: string, signatureHeader: string, secret: string): boolean {
|
|
86
|
+
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
|
|
87
|
+
const a = Buffer.from(expected)
|
|
88
|
+
const b = Buffer.from(signatureHeader)
|
|
89
|
+
if (a.length !== b.length) return false
|
|
90
|
+
return timingSafeEqual(a, b)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
HMAC は必ず JSON パース前の**生のリクエストボディ**に対して計算し、定数時間比較を使用してください。
|
|
95
|
+
|
|
96
|
+
## リトライ動作
|
|
97
|
+
|
|
98
|
+
いずれかのエンドポイントが 2xx 以外のレスポンスを返した場合(またはタイムアウトした場合)、プラグインは例外を投げます。トラストレベルプロセッサーの Lambda が SQS に再スローし、デッドレターキューに移動するまで最大 3 回リトライされます。同じイベントが複数回配信される可能性があるため、冪等な受信側を推奨します。
|
|
99
|
+
|
|
100
|
+
## 発火するイベント
|
|
101
|
+
|
|
102
|
+
- `content.created` — 新規投稿(任意のステータス)
|
|
103
|
+
- `content.published` — ステータスが `draft` → `published` に変化、または公開済み投稿が挿入された
|
|
104
|
+
- `content.unpublished` — ステータスが `published` → `draft` に変化、または公開済み投稿が削除された
|
|
105
|
+
- `content.updated` — 任意の MODIFY(公開 / 非公開への遷移時にも `published` / `unpublished` と同時に発火)
|
|
106
|
+
- `content.deleted` — 投稿が削除された
|
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -29,16 +29,13 @@ interface WebhookEndpoint {
|
|
|
29
29
|
timeoutMs?: number;
|
|
30
30
|
}
|
|
31
31
|
interface WebhookPluginOptions {
|
|
32
|
-
endpoints
|
|
33
|
-
/** Backwards-compatible single-endpoint shortcut. */
|
|
34
|
-
url?: string;
|
|
35
|
-
secret?: string;
|
|
32
|
+
endpoints: WebhookEndpoint[];
|
|
36
33
|
}
|
|
37
34
|
/**
|
|
38
35
|
* Webhook plugin. Posts a JSON envelope to one or more external URLs
|
|
39
36
|
* whenever a subscribed event fires. Runs in the untrusted Lambda — it
|
|
40
37
|
* needs no AWS data access, only outbound HTTPS.
|
|
41
38
|
*/
|
|
42
|
-
declare function webhookPlugin(options
|
|
39
|
+
declare function webhookPlugin(options: WebhookPluginOptions): AmplessPlugin;
|
|
43
40
|
|
|
44
41
|
export { type WebhookEndpoint, type WebhookPluginOptions, webhookPlugin as default, signPayload };
|
package/dist/index.js
CHANGED
|
@@ -15,8 +15,8 @@ var ALL_CONTENT_EVENTS = [
|
|
|
15
15
|
"content.unpublished",
|
|
16
16
|
"content.deleted"
|
|
17
17
|
];
|
|
18
|
-
function webhookPlugin(options
|
|
19
|
-
const endpoints =
|
|
18
|
+
function webhookPlugin(options) {
|
|
19
|
+
const endpoints = options.endpoints;
|
|
20
20
|
const subscribed = /* @__PURE__ */ new Set();
|
|
21
21
|
for (const ep of endpoints) {
|
|
22
22
|
const events = ep.events ?? ALL_CONTENT_EVENTS;
|
|
@@ -35,12 +35,6 @@ function webhookPlugin(options = {}) {
|
|
|
35
35
|
hooks
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
function normalizeEndpoints(options) {
|
|
39
|
-
const list = [];
|
|
40
|
-
if (options.url) list.push({ url: options.url, secret: options.secret });
|
|
41
|
-
if (options.endpoints) list.push(...options.endpoints);
|
|
42
|
-
return list;
|
|
43
|
-
}
|
|
44
38
|
async function dispatch(endpoints, event) {
|
|
45
39
|
const body = JSON.stringify({
|
|
46
40
|
type: event.type,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/plugin-webhook",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.21",
|
|
4
4
|
"description": "Webhook plugin for ampless — POST events to external URLs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"homepage": "https://github.com/heavymoons/ampless/tree/main/packages/plugin-webhook#readme",
|
|
26
26
|
"bugs": "https://github.com/heavymoons/ampless/issues",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"ampless": "0.
|
|
28
|
+
"ampless": "1.0.0-alpha.21"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
31
|
"ampless",
|