rogerthat 1.24.6 → 1.25.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/README.md +6 -5
- package/dist/admin.js +2 -2
- package/dist/agentcard.js +2 -2
- package/dist/app.js +9 -527
- package/dist/channel.js +1 -2
- package/dist/cli.js +2 -8
- package/dist/connect.js +1 -1
- package/dist/discovery.js +15 -226
- package/dist/landing.js +8 -290
- package/dist/mcp.js +36 -363
- package/dist/policy.js +5 -7
- package/dist/presets.js +6 -41
- package/dist/store.js +2 -93
- package/package.json +1 -1
- package/dist/account-ui.js +0 -895
- package/dist/accounts.js +0 -253
- package/dist/email.js +0 -67
- package/dist/remote-control.js +0 -174
- package/dist/remote-ui.js +0 -906
- package/dist/webhooks.js +0 -154
package/dist/webhooks.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
const WEBHOOKS_PATH = process.env.ROGERRAT_WEBHOOKS ?? "./data/webhooks.json";
|
|
5
|
-
let hooks = [];
|
|
6
|
-
let loaded = false;
|
|
7
|
-
function load() {
|
|
8
|
-
if (loaded)
|
|
9
|
-
return;
|
|
10
|
-
loaded = true;
|
|
11
|
-
try {
|
|
12
|
-
if (existsSync(WEBHOOKS_PATH)) {
|
|
13
|
-
hooks = JSON.parse(readFileSync(WEBHOOKS_PATH, "utf8"));
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
catch (err) {
|
|
17
|
-
console.error("[webhooks] failed to load:", err);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function persist() {
|
|
21
|
-
const dir = dirname(WEBHOOKS_PATH);
|
|
22
|
-
if (!existsSync(dir))
|
|
23
|
-
mkdirSync(dir, { recursive: true });
|
|
24
|
-
const tmp = `${WEBHOOKS_PATH}.tmp`;
|
|
25
|
-
writeFileSync(tmp, JSON.stringify(hooks, null, 2), { mode: 0o600 });
|
|
26
|
-
renameSync(tmp, WEBHOOKS_PATH);
|
|
27
|
-
}
|
|
28
|
-
const VALID_EVENTS = new Set(["message.received"]);
|
|
29
|
-
const MAX_PER_ACCOUNT = 10;
|
|
30
|
-
const MAX_PER_CHANNEL = 10;
|
|
31
|
-
export function createWebhook(accountId, url, events) {
|
|
32
|
-
load();
|
|
33
|
-
try {
|
|
34
|
-
const u = new URL(url);
|
|
35
|
-
if (u.protocol !== "https:" && u.protocol !== "http:")
|
|
36
|
-
return { error: "url must be http(s)" };
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return { error: "invalid url" };
|
|
40
|
-
}
|
|
41
|
-
if (!events.length || events.some((e) => !VALID_EVENTS.has(e))) {
|
|
42
|
-
return { error: `events must be a non-empty subset of: ${[...VALID_EVENTS].join(", ")}` };
|
|
43
|
-
}
|
|
44
|
-
if (hooks.filter((h) => h.accountId === accountId).length >= MAX_PER_ACCOUNT) {
|
|
45
|
-
return { error: `max ${MAX_PER_ACCOUNT} webhooks per account` };
|
|
46
|
-
}
|
|
47
|
-
const id = "wh_" + randomBytes(6).toString("base64url");
|
|
48
|
-
const secret = "whsec_" + randomBytes(24).toString("base64url");
|
|
49
|
-
hooks.push({ id, accountId, url, secret, events, createdAt: Date.now(), active: true });
|
|
50
|
-
persist();
|
|
51
|
-
return { id, secret };
|
|
52
|
-
}
|
|
53
|
-
export function listWebhooks(accountId) {
|
|
54
|
-
load();
|
|
55
|
-
return hooks
|
|
56
|
-
.filter((h) => h.accountId === accountId)
|
|
57
|
-
.map((h) => ({ id: h.id, url: h.url, events: h.events, created_at: h.createdAt, active: h.active }))
|
|
58
|
-
.sort((a, b) => b.created_at - a.created_at);
|
|
59
|
-
}
|
|
60
|
-
export function deleteWebhook(accountId, id) {
|
|
61
|
-
load();
|
|
62
|
-
const idx = hooks.findIndex((h) => h.id === id && h.accountId === accountId);
|
|
63
|
-
if (idx === -1)
|
|
64
|
-
return false;
|
|
65
|
-
hooks.splice(idx, 1);
|
|
66
|
-
persist();
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
export function getActiveWebhooksForAccount(accountId, event) {
|
|
70
|
-
load();
|
|
71
|
-
return hooks.filter((h) => h.accountId === accountId && h.active && h.events.includes(event));
|
|
72
|
-
}
|
|
73
|
-
export function createChannelWebhook(channelId, url, events) {
|
|
74
|
-
load();
|
|
75
|
-
try {
|
|
76
|
-
const u = new URL(url);
|
|
77
|
-
if (u.protocol !== "https:" && u.protocol !== "http:")
|
|
78
|
-
return { error: "url must be http(s)" };
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return { error: "invalid url" };
|
|
82
|
-
}
|
|
83
|
-
if (!events.length || events.some((e) => !VALID_EVENTS.has(e))) {
|
|
84
|
-
return { error: `events must be a non-empty subset of: ${[...VALID_EVENTS].join(", ")}` };
|
|
85
|
-
}
|
|
86
|
-
if (hooks.filter((h) => h.channelId === channelId).length >= MAX_PER_CHANNEL) {
|
|
87
|
-
return { error: `max ${MAX_PER_CHANNEL} webhooks per channel` };
|
|
88
|
-
}
|
|
89
|
-
const id = "wh_" + randomBytes(6).toString("base64url");
|
|
90
|
-
const secret = "whsec_" + randomBytes(24).toString("base64url");
|
|
91
|
-
hooks.push({ id, channelId, url, secret, events, createdAt: Date.now(), active: true });
|
|
92
|
-
persist();
|
|
93
|
-
return { id, secret };
|
|
94
|
-
}
|
|
95
|
-
export function listChannelWebhooks(channelId) {
|
|
96
|
-
load();
|
|
97
|
-
return hooks
|
|
98
|
-
.filter((h) => h.channelId === channelId)
|
|
99
|
-
.map((h) => ({ id: h.id, url: h.url, events: h.events, created_at: h.createdAt, active: h.active }))
|
|
100
|
-
.sort((a, b) => b.created_at - a.created_at);
|
|
101
|
-
}
|
|
102
|
-
export function deleteChannelWebhook(channelId, id) {
|
|
103
|
-
load();
|
|
104
|
-
const idx = hooks.findIndex((h) => h.id === id && h.channelId === channelId);
|
|
105
|
-
if (idx === -1)
|
|
106
|
-
return false;
|
|
107
|
-
hooks.splice(idx, 1);
|
|
108
|
-
persist();
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
export function getActiveWebhooksForChannel(channelId, event) {
|
|
112
|
-
load();
|
|
113
|
-
return hooks.filter((h) => h.channelId === channelId && h.active && h.events.includes(event));
|
|
114
|
-
}
|
|
115
|
-
function sign(secret, body) {
|
|
116
|
-
return "sha256=" + createHmac("sha256", secret).update(body).digest("hex");
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Fire-and-forget delivery with 3 attempts + exponential backoff.
|
|
120
|
-
* Does not block the caller.
|
|
121
|
-
*/
|
|
122
|
-
export function deliver(hook, event, payload) {
|
|
123
|
-
const body = JSON.stringify({ event, ...payload, hook_id: hook.id, delivered_at: Date.now() });
|
|
124
|
-
const signature = sign(hook.secret, body);
|
|
125
|
-
const attempt = async (n) => {
|
|
126
|
-
try {
|
|
127
|
-
const r = await fetch(hook.url, {
|
|
128
|
-
method: "POST",
|
|
129
|
-
headers: {
|
|
130
|
-
"Content-Type": "application/json",
|
|
131
|
-
"X-RogerThat-Event": event,
|
|
132
|
-
"X-RogerThat-Signature": signature,
|
|
133
|
-
"X-RogerThat-Delivery": hook.id + "-" + Date.now(),
|
|
134
|
-
"User-Agent": "RogerThat-Webhooks/1.0",
|
|
135
|
-
},
|
|
136
|
-
body,
|
|
137
|
-
signal: AbortSignal.timeout(10_000),
|
|
138
|
-
});
|
|
139
|
-
if (r.status >= 200 && r.status < 300)
|
|
140
|
-
return;
|
|
141
|
-
if (n < 2 && r.status >= 500)
|
|
142
|
-
throw new Error(`upstream ${r.status}`);
|
|
143
|
-
}
|
|
144
|
-
catch (err) {
|
|
145
|
-
if (n < 2) {
|
|
146
|
-
const wait = 1000 * Math.pow(3, n); // 1s, 3s
|
|
147
|
-
setTimeout(() => attempt(n + 1), wait);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
console.error(`[webhook ${hook.id}] failed after retries:`, err.message);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
void attempt(0);
|
|
154
|
-
}
|