@premium-cms/plugin-webhook-notifier 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +44 -3
- package/dist/manifest.json +44 -4
- package/dist/plugin.d.mts +0 -9
- package/dist/plugin.mjs +1 -1
- package/dist/webhook-notifier-0.2.0.tar.gz +0 -0
- package/emdash-plugin.jsonc +21 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/** @type {import("@premium-cms/emdash").PluginDescriptor} */
|
|
8
8
|
const descriptor = Object.freeze({
|
|
9
9
|
"id": "webhook-notifier",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.3.0",
|
|
11
11
|
"format": "standard",
|
|
12
12
|
"entrypoint": "@premium-cms/plugin-webhook-notifier/sandbox",
|
|
13
13
|
"capabilities": [
|
|
@@ -23,10 +23,51 @@ const descriptor = Object.freeze({
|
|
|
23
23
|
]
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
+
"settingsSchema": {
|
|
27
|
+
"webhookUrl": {
|
|
28
|
+
"label": "Webhook URL",
|
|
29
|
+
"type": "url",
|
|
30
|
+
"placeholder": "https://example.com/hooks/emdash"
|
|
31
|
+
},
|
|
32
|
+
"secretToken": {
|
|
33
|
+
"label": "Secret token",
|
|
34
|
+
"description": "Sent as the X-Webhook-Secret header so the receiver can verify deliveries.",
|
|
35
|
+
"type": "secret"
|
|
36
|
+
},
|
|
37
|
+
"enabled": {
|
|
38
|
+
"label": "Enable webhooks",
|
|
39
|
+
"type": "boolean",
|
|
40
|
+
"default": true
|
|
41
|
+
},
|
|
42
|
+
"events": {
|
|
43
|
+
"label": "Events to send",
|
|
44
|
+
"type": "select",
|
|
45
|
+
"options": [
|
|
46
|
+
{
|
|
47
|
+
"value": "all",
|
|
48
|
+
"label": "All events"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"value": "content",
|
|
52
|
+
"label": "Content changes only"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"value": "media",
|
|
56
|
+
"label": "Media uploads only"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"default": "all"
|
|
60
|
+
},
|
|
61
|
+
"includeData": {
|
|
62
|
+
"label": "Include content data",
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"default": false
|
|
65
|
+
}
|
|
66
|
+
},
|
|
26
67
|
"adminPages": [
|
|
27
68
|
{
|
|
28
|
-
"path": "/
|
|
29
|
-
"label": "
|
|
69
|
+
"path": "/status",
|
|
70
|
+
"label": "Webhooks",
|
|
30
71
|
"icon": "send"
|
|
31
72
|
}
|
|
32
73
|
],
|
package/dist/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "webhook-notifier",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"declaredAccess": {
|
|
5
5
|
"network": {
|
|
6
6
|
"request": {}
|
|
@@ -40,14 +40,54 @@
|
|
|
40
40
|
"admin",
|
|
41
41
|
"status",
|
|
42
42
|
"settings",
|
|
43
|
-
"settings/save",
|
|
44
43
|
"test"
|
|
45
44
|
],
|
|
46
45
|
"admin": {
|
|
46
|
+
"settingsSchema": {
|
|
47
|
+
"webhookUrl": {
|
|
48
|
+
"label": "Webhook URL",
|
|
49
|
+
"type": "url",
|
|
50
|
+
"placeholder": "https://example.com/hooks/emdash"
|
|
51
|
+
},
|
|
52
|
+
"secretToken": {
|
|
53
|
+
"label": "Secret token",
|
|
54
|
+
"description": "Sent as the X-Webhook-Secret header so the receiver can verify deliveries.",
|
|
55
|
+
"type": "secret"
|
|
56
|
+
},
|
|
57
|
+
"enabled": {
|
|
58
|
+
"label": "Enable webhooks",
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"default": true
|
|
61
|
+
},
|
|
62
|
+
"events": {
|
|
63
|
+
"label": "Events to send",
|
|
64
|
+
"type": "select",
|
|
65
|
+
"options": [
|
|
66
|
+
{
|
|
67
|
+
"value": "all",
|
|
68
|
+
"label": "All events"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"value": "content",
|
|
72
|
+
"label": "Content changes only"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"value": "media",
|
|
76
|
+
"label": "Media uploads only"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"default": "all"
|
|
80
|
+
},
|
|
81
|
+
"includeData": {
|
|
82
|
+
"label": "Include content data",
|
|
83
|
+
"type": "boolean",
|
|
84
|
+
"default": false
|
|
85
|
+
}
|
|
86
|
+
},
|
|
47
87
|
"pages": [
|
|
48
88
|
{
|
|
49
|
-
"path": "/
|
|
50
|
-
"label": "
|
|
89
|
+
"path": "/status",
|
|
90
|
+
"label": "Webhooks",
|
|
51
91
|
"icon": "send"
|
|
52
92
|
}
|
|
53
93
|
],
|
package/dist/plugin.d.mts
CHANGED
|
@@ -60,15 +60,6 @@ declare const _default: {
|
|
|
60
60
|
events: string;
|
|
61
61
|
}>;
|
|
62
62
|
};
|
|
63
|
-
"settings/save": {
|
|
64
|
-
handler: (routeCtx: _premium_cms_emdash_plugin0.SandboxedRouteContext, ctx: PluginContext<_premium_cms_plugin_types0.PluginStorageConfig>) => Promise<{
|
|
65
|
-
success: boolean;
|
|
66
|
-
error?: undefined;
|
|
67
|
-
} | {
|
|
68
|
-
success: boolean;
|
|
69
|
-
error: string;
|
|
70
|
-
}>;
|
|
71
|
-
};
|
|
72
63
|
test: {
|
|
73
64
|
handler: (routeCtx: _premium_cms_emdash_plugin0.SandboxedRouteContext, ctx: PluginContext<_premium_cms_plugin_types0.PluginStorageConfig>) => Promise<{
|
|
74
65
|
success: boolean;
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=/^\[|\]$/g,t=new Set([`localhost`,`metadata.google.internal`,`[::1]`]),n=[{start:127<<24>>>0,end:2147483647},{start:10<<24>>>0,end:184549375},{start:2886729728,end:2887778303},{start:3232235520,end:3232301055},{start:2851995648,end:2852061183},{start:0,end:16777215}];function r(r){let i;try{i=new URL(r)}catch{throw Error(`Invalid webhook URL`)}if(i.protocol!==`http:`&&i.protocol!==`https:`)throw Error(`Webhook URL scheme '${i.protocol}' is not allowed`);let a=i.hostname.replace(e,``);if(t.has(a.toLowerCase()))throw Error(`Webhook URLs targeting internal hosts are not allowed`);let o=a.split(`.`);if(o.length===4){let e=o.map(Number);if(e.every(e=>!isNaN(e)&&e>=0&&e<=255)){let t=(e[0]<<24|e[1]<<16|e[2]<<8|e[3])>>>0;if(n.some(e=>t>=e.start&&t<=e.end))throw Error(`Webhook URLs targeting private IP addresses are not allowed`)}}if(a===`::1`||a.startsWith(`fe80:`)||a.startsWith(`fc`)||a.startsWith(`fd`))throw Error(`Webhook URLs targeting internal addresses are not allowed`)}async function i(e,t,n,i,a,o){r(n);let s,c;for(let r=1;r<=o;r++){try{let l={"Content-Type":`application/json`,"X-EmDash-Event":i.event};a&&(l.Authorization=`Bearer ${a}`);let u=await e(n,{method:`POST`,headers:l,body:JSON.stringify(i)});if(c=u.status,u.ok)return t.info(`Delivered ${i.event} to ${n} (${u.status})`),{success:!0,status:u.status};s=`HTTP ${u.status}: ${u.statusText}`,t.warn(`Attempt ${r}/${o} failed: ${s}`)}catch(e){s=e instanceof Error?e.message:`Unknown error`,t.warn(`Attempt ${r}/${o} failed: ${s}`)}r<o&&await new Promise(e=>setTimeout(e,100*2**(r-1)))}return t.error(`Failed to deliver ${i.event} after ${o} attempts`),{success:!1,status:c,error:s}}function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e,t){if(!a(e))return;let n=e[t];return typeof n==`string`?n:void 0}async function s(e){return{url:await e.kv.get(`settings:webhookUrl`),token:await e.kv.get(`settings:secretToken`),enabled:await e.kv.get(`settings:enabled`)}}function c(e){if(!e.http)throw Error(`Webhook notifier requires network:request capability`);return e.http.fetch}var l={hooks:{"content:afterSave":{priority:210,timeout:1e4,dependencies:[`audit-log`],errorPolicy:`continue`,handler:async(e,t)=>{let{url:n,token:r,enabled:a}=await s(t);if(a===!1||!n)return;let o=typeof e.content.id==`string`?e.content.id:String(e.content.id),l={event:e.isNew?`content:create`:`content:update`,timestamp:new Date().toISOString(),collection:e.collection,resourceId:o,resourceType:`content`,metadata:{slug:e.content.slug,status:e.content.status}};await i(c(t),t.log,n,l,r??void 0,3)}},"content:afterDelete":{priority:210,timeout:1e4,dependencies:[`audit-log`],errorPolicy:`continue`,handler:async(e,t)=>{let{url:n,token:r,enabled:a}=await s(t);if(a===!1||!n)return;let o={event:`content:delete`,timestamp:new Date().toISOString(),collection:e.collection,resourceId:e.id,resourceType:`content`};await i(c(t),t.log,n,o,r??void 0,3)}},"media:afterUpload":{priority:210,timeout:1e4,errorPolicy:`continue`,handler:async(e,t)=>{let{url:n,token:r,enabled:a}=await s(t);if(a===!1||!n)return;let o={event:`media:upload`,timestamp:new Date().toISOString(),resourceId:e.media.id,resourceType:`media`};await i(c(t),t.log,n,o,r??void 0,3)}}},routes:{admin:{handler:async(e,t)=>{let n=e.input;return n.type===`page_load`&&n.page===`widget:webhook-status`?u(t):n.type===`page_load`&&n.page===`/
|
|
1
|
+
const e=/^\[|\]$/g,t=new Set([`localhost`,`metadata.google.internal`,`[::1]`]),n=[{start:127<<24>>>0,end:2147483647},{start:10<<24>>>0,end:184549375},{start:2886729728,end:2887778303},{start:3232235520,end:3232301055},{start:2851995648,end:2852061183},{start:0,end:16777215}];function r(r){let i;try{i=new URL(r)}catch{throw Error(`Invalid webhook URL`)}if(i.protocol!==`http:`&&i.protocol!==`https:`)throw Error(`Webhook URL scheme '${i.protocol}' is not allowed`);let a=i.hostname.replace(e,``);if(t.has(a.toLowerCase()))throw Error(`Webhook URLs targeting internal hosts are not allowed`);let o=a.split(`.`);if(o.length===4){let e=o.map(Number);if(e.every(e=>!isNaN(e)&&e>=0&&e<=255)){let t=(e[0]<<24|e[1]<<16|e[2]<<8|e[3])>>>0;if(n.some(e=>t>=e.start&&t<=e.end))throw Error(`Webhook URLs targeting private IP addresses are not allowed`)}}if(a===`::1`||a.startsWith(`fe80:`)||a.startsWith(`fc`)||a.startsWith(`fd`))throw Error(`Webhook URLs targeting internal addresses are not allowed`)}async function i(e,t,n,i,a,o){r(n);let s,c;for(let r=1;r<=o;r++){try{let l={"Content-Type":`application/json`,"X-EmDash-Event":i.event};a&&(l.Authorization=`Bearer ${a}`);let u=await e(n,{method:`POST`,headers:l,body:JSON.stringify(i)});if(c=u.status,u.ok)return t.info(`Delivered ${i.event} to ${n} (${u.status})`),{success:!0,status:u.status};s=`HTTP ${u.status}: ${u.statusText}`,t.warn(`Attempt ${r}/${o} failed: ${s}`)}catch(e){s=e instanceof Error?e.message:`Unknown error`,t.warn(`Attempt ${r}/${o} failed: ${s}`)}r<o&&await new Promise(e=>setTimeout(e,100*2**(r-1)))}return t.error(`Failed to deliver ${i.event} after ${o} attempts`),{success:!1,status:c,error:s}}function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e,t){if(!a(e))return;let n=e[t];return typeof n==`string`?n:void 0}async function s(e){return{url:await e.kv.get(`settings:webhookUrl`),token:await e.kv.get(`settings:secretToken`),enabled:await e.kv.get(`settings:enabled`)}}function c(e){if(!e.http)throw Error(`Webhook notifier requires network:request capability`);return e.http.fetch}var l={hooks:{"content:afterSave":{priority:210,timeout:1e4,dependencies:[`audit-log`],errorPolicy:`continue`,handler:async(e,t)=>{let{url:n,token:r,enabled:a}=await s(t);if(a===!1||!n)return;let o=typeof e.content.id==`string`?e.content.id:String(e.content.id),l={event:e.isNew?`content:create`:`content:update`,timestamp:new Date().toISOString(),collection:e.collection,resourceId:o,resourceType:`content`,metadata:{slug:e.content.slug,status:e.content.status}};await i(c(t),t.log,n,l,r??void 0,3)}},"content:afterDelete":{priority:210,timeout:1e4,dependencies:[`audit-log`],errorPolicy:`continue`,handler:async(e,t)=>{let{url:n,token:r,enabled:a}=await s(t);if(a===!1||!n)return;let o={event:`content:delete`,timestamp:new Date().toISOString(),collection:e.collection,resourceId:e.id,resourceType:`content`};await i(c(t),t.log,n,o,r??void 0,3)}},"media:afterUpload":{priority:210,timeout:1e4,errorPolicy:`continue`,handler:async(e,t)=>{let{url:n,token:r,enabled:a}=await s(t);if(a===!1||!n)return;let o={event:`media:upload`,timestamp:new Date().toISOString(),resourceId:e.media.id,resourceType:`media`};await i(c(t),t.log,n,o,r??void 0,3)}}},routes:{admin:{handler:async(e,t)=>{let n=e.input;return n.type===`page_load`&&n.page===`widget:webhook-status`?u(t):n.type===`page_load`&&n.page===`/status`?d(t):n.type===`block_action`&&n.action_id===`test_webhook`?f(t):{blocks:[]}}},status:{handler:async(e,t)=>{try{let e=await t.kv.get(`settings:webhookUrl`),n=await t.kv.get(`settings:enabled`),r=t.storage.deliveries,i=await r.count({status:`success`}),a=await r.count({status:`failed`}),o=await r.count({status:`pending`});return{configured:!!e,enabled:n??!0,stats:{successful:i,failed:a,pending:o}}}catch(e){return t.log.error(`Failed to get status`,e),{configured:!1,enabled:!0,stats:{successful:0,failed:0,pending:0}}}}},settings:{handler:async(e,t)=>{try{let e=await t.kv.list(`settings:`),n={};for(let t of e)n[t.key.replace(`settings:`,``)]=t.value;return{webhookUrl:typeof n.webhookUrl==`string`?n.webhookUrl:``,enabled:typeof n.enabled==`boolean`?n.enabled:!0,includeData:typeof n.includeData==`boolean`?n.includeData:!1,events:typeof n.events==`string`?n.events:`all`}}catch(e){return t.log.error(`Failed to get settings`,e),{webhookUrl:``,enabled:!0,includeData:!1,events:`all`}}}},test:{handler:async(e,t)=>{let n=o(e.input,`url`);if(!n)return{success:!1,error:`No webhook URL provided`};let r=await t.kv.get(`settings:secretToken`),a={event:`content:create`,timestamp:new Date().toISOString(),resourceId:`test-`+Date.now(),resourceType:`content`,metadata:{test:!0,message:`Webhook test from EmDash CMS`}},s=await i(c(t),t.log,n,a,r??void 0,1);return{success:s.success,status:s.status,error:s.error,payload:a}}}}};async function u(e){try{let t=await e.kv.get(`settings:webhookUrl`),n=await e.kv.get(`settings:enabled`),r=!!t&&n!==!1,i=0,a=0,o=0;try{let t=e.storage.deliveries;i=await t.count({status:`success`}),a=await t.count({status:`failed`}),o=await t.count({status:`pending`})}catch{}let s=[{type:`fields`,fields:[{label:`Status`,value:r?`Active`:`Not Configured`},{label:`Endpoint`,value:t||`None`}]}];return r?s.push({type:`stats`,stats:[{label:`Delivered`,value:String(i)},{label:`Failed`,value:String(a)},{label:`Pending`,value:String(o)}]}):s.push({type:`context`,text:`Configure a webhook URL in settings to start sending events.`}),{blocks:s}}catch(t){return e.log.error(`Failed to build status widget`,t),{blocks:[{type:`context`,text:`Failed to load webhook status`}]}}}async function d(e){try{let t=await e.kv.get(`settings:webhookUrl`)??``,n=await e.kv.get(`settings:enabled`)??!0,r=await e.kv.get(`settings:includeData`)??!1,i=await e.kv.get(`settings:events`)??`all`,a=JSON.stringify({event:`content:create`,timestamp:new Date().toISOString(),collection:`posts`,resourceId:`abc123`,resourceType:`content`,...r&&{data:{title:`Example Post`,slug:`example-post`}},metadata:{slug:`example-post`,status:`published`}},null,2);return{blocks:[{type:`header`,text:`Webhooks`},{type:`context`,text:`Send notifications to external services when content changes.`},{type:`fields`,fields:[{label:`Status`,value:n?`Enabled`:`Disabled`},{label:`Endpoint`,value:t||`Not configured`},{label:`Events`,value:i},{label:`Content data`,value:r?`Included`:`Not included`}]},{type:`context`,text:`The endpoint, secret and event filter are set under Plugins → Webhook Notifier → Settings.`},{type:`divider`},{type:`section`,text:`**Payload Preview**`},{type:`section`,text:"```json\n"+a+"\n```"},{type:`actions`,elements:[{type:`button`,text:`Test Webhook`,action_id:`test_webhook`,style:`primary`}]}]}}catch(t){return e.log.error(`Failed to build status page`,t),{blocks:[{type:`context`,text:`Failed to load webhook status`}]}}}async function f(e){let t=await e.kv.get(`settings:webhookUrl`);if(!t)return{blocks:[{type:`banner`,style:`warning`,text:`Enter a webhook URL first.`}],toast:{message:`No webhook URL configured`,type:`error`}};let n=await e.kv.get(`settings:secretToken`),r={event:`content:create`,timestamp:new Date().toISOString(),resourceId:`test-`+Date.now(),resourceType:`content`,metadata:{test:!0,message:`Webhook test from EmDash CMS`}};try{let a=await i(c(e),e.log,t,r,n??void 0,1);return a.success?{...await d(e),toast:{message:`Test sent -- HTTP ${a.status}`,type:`success`}}:{...await d(e),toast:{message:`Test failed: ${a.error??`Unknown error`}`,type:`error`}}}catch(t){let n=t instanceof Error?t.message:String(t);return{...await d(e),toast:{message:`Test failed: ${n}`,type:`error`}}}}export{l as default};
|
|
2
2
|
//# sourceMappingURL=plugin.mjs.map
|
|
Binary file
|
package/emdash-plugin.jsonc
CHANGED
|
@@ -20,7 +20,27 @@
|
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
"admin": {
|
|
23
|
-
"
|
|
23
|
+
"settingsSchema": {
|
|
24
|
+
"webhookUrl": { "type": "url", "label": "Webhook URL", "placeholder": "https://example.com/hooks/emdash" },
|
|
25
|
+
"secretToken": {
|
|
26
|
+
"type": "secret",
|
|
27
|
+
"label": "Secret token",
|
|
28
|
+
"description": "Sent as the X-Webhook-Secret header so the receiver can verify deliveries."
|
|
29
|
+
},
|
|
30
|
+
"enabled": { "type": "boolean", "label": "Enable webhooks", "default": true },
|
|
31
|
+
"events": {
|
|
32
|
+
"type": "select",
|
|
33
|
+
"label": "Events to send",
|
|
34
|
+
"options": [
|
|
35
|
+
{ "value": "all", "label": "All events" },
|
|
36
|
+
{ "value": "content", "label": "Content changes only" },
|
|
37
|
+
{ "value": "media", "label": "Media uploads only" }
|
|
38
|
+
],
|
|
39
|
+
"default": "all"
|
|
40
|
+
},
|
|
41
|
+
"includeData": { "type": "boolean", "label": "Include content data", "default": false }
|
|
42
|
+
},
|
|
43
|
+
"pages": [{ "path": "/status", "label": "Webhooks", "icon": "send" }],
|
|
24
44
|
"widgets": [{ "id": "status", "title": "Webhooks", "size": "third" }],
|
|
25
45
|
},
|
|
26
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@premium-cms/plugin-webhook-notifier",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Webhook notification plugin for EmDash CMS - posts to external URLs on content changes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"tsdown": "0.20.3",
|
|
33
33
|
"typescript": "^6.0.3",
|
|
34
|
-
"@premium-cms/plugin-cli": "0.8.
|
|
34
|
+
"@premium-cms/plugin-cli": "0.8.5"
|
|
35
35
|
},
|
|
36
36
|
"repository": {
|
|
37
37
|
"type": "git",
|