@openclaw/crabline 0.1.11 → 0.1.12
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 +99 -12
- package/dist/src/bin/crabline.js +3 -1
- package/dist/src/bin/crabline.js.map +1 -1
- package/dist/src/cli/program.d.ts +5 -1
- package/dist/src/cli/program.js +264 -50
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/load.js +39 -3
- package/dist/src/config/load.js.map +1 -1
- package/dist/src/config/schema.d.ts +20 -0
- package/dist/src/config/schema.js +163 -35
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/errors.d.ts +1 -0
- package/dist/src/core/errors.js +6 -3
- package/dist/src/core/errors.js.map +1 -1
- package/dist/src/core/http-path.d.ts +1 -0
- package/dist/src/core/http-path.js +14 -0
- package/dist/src/core/http-path.js.map +1 -0
- package/dist/src/core/matcher.d.ts +3 -1
- package/dist/src/core/matcher.js +58 -2
- package/dist/src/core/matcher.js.map +1 -1
- package/dist/src/core/nonces.d.ts +2 -0
- package/dist/src/core/nonces.js +11 -1
- package/dist/src/core/nonces.js.map +1 -1
- package/dist/src/core/reporters.d.ts +5 -0
- package/dist/src/core/reporters.js +26 -2
- package/dist/src/core/reporters.js.map +1 -1
- package/dist/src/core/run.d.ts +3 -1
- package/dist/src/core/run.js +178 -66
- package/dist/src/core/run.js.map +1 -1
- package/dist/src/matrix-ids.d.ts +5 -0
- package/dist/src/matrix-ids.js +83 -0
- package/dist/src/matrix-ids.js.map +1 -0
- package/dist/src/openclaw/artifact-generation.d.ts +7 -2
- package/dist/src/openclaw/artifact-generation.js +399 -27
- package/dist/src/openclaw/artifact-generation.js.map +1 -1
- package/dist/src/openclaw/bridges/matrix.js +21 -4
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +13 -8
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
- package/dist/src/openclaw/bridges/probe-response.js +5 -0
- package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
- package/dist/src/openclaw/bridges/signal.js +15 -6
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +116 -23
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +45 -19
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +32 -16
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +8 -3
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/private-file.d.ts +38 -3
- package/dist/src/openclaw/private-file.js +2008 -87
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/openclaw/shared.d.ts +1 -0
- package/dist/src/openclaw/shared.js +48 -13
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw/smoke-lock.d.ts +9 -0
- package/dist/src/openclaw/smoke-lock.js +626 -172
- package/dist/src/openclaw/smoke-lock.js.map +1 -1
- package/dist/src/openclaw.js +225 -42
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/platform/process-owned-lock.d.ts +11 -0
- package/dist/src/platform/process-owned-lock.js +1698 -0
- package/dist/src/platform/process-owned-lock.js.map +1 -0
- package/dist/src/platform/recorder-directory.d.ts +1 -0
- package/dist/src/platform/recorder-directory.js +14 -0
- package/dist/src/platform/recorder-directory.js.map +1 -0
- package/dist/src/platform/windows-acl.d.ts +18 -0
- package/dist/src/platform/windows-acl.js +1367 -0
- package/dist/src/platform/windows-acl.js.map +1 -0
- package/dist/src/platform/windows-lock-root.d.ts +13 -0
- package/dist/src/platform/windows-lock-root.js +98 -0
- package/dist/src/platform/windows-lock-root.js.map +1 -0
- package/dist/src/providers/builtin/discord.d.ts +4 -0
- package/dist/src/providers/builtin/discord.js +52 -9
- package/dist/src/providers/builtin/discord.js.map +1 -1
- package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
- package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
- package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
- package/dist/src/providers/builtin/feishu.d.ts +1 -0
- package/dist/src/providers/builtin/feishu.js +96 -26
- package/dist/src/providers/builtin/feishu.js.map +1 -1
- package/dist/src/providers/builtin/googlechat.d.ts +1 -0
- package/dist/src/providers/builtin/googlechat.js +95 -26
- package/dist/src/providers/builtin/googlechat.js.map +1 -1
- package/dist/src/providers/builtin/imessage.js +60 -17
- package/dist/src/providers/builtin/imessage.js.map +1 -1
- package/dist/src/providers/builtin/loopback.js +88 -35
- package/dist/src/providers/builtin/loopback.js.map +1 -1
- package/dist/src/providers/builtin/matrix.d.ts +2 -2
- package/dist/src/providers/builtin/matrix.js +78 -21
- package/dist/src/providers/builtin/matrix.js.map +1 -1
- package/dist/src/providers/builtin/mattermost.d.ts +11 -7
- package/dist/src/providers/builtin/mattermost.js +138 -24
- package/dist/src/providers/builtin/mattermost.js.map +1 -1
- package/dist/src/providers/builtin/msteams.d.ts +1 -0
- package/dist/src/providers/builtin/msteams.js +118 -38
- package/dist/src/providers/builtin/msteams.js.map +1 -1
- package/dist/src/providers/builtin/native-local-mock.js +4 -4
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
- package/dist/src/providers/builtin/script.js +1057 -146
- package/dist/src/providers/builtin/script.js.map +1 -1
- package/dist/src/providers/builtin/slack.d.ts +8 -1
- package/dist/src/providers/builtin/slack.js +327 -24
- package/dist/src/providers/builtin/slack.js.map +1 -1
- package/dist/src/providers/builtin/telegram.d.ts +1 -1
- package/dist/src/providers/builtin/telegram.js +55 -14
- package/dist/src/providers/builtin/telegram.js.map +1 -1
- package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
- package/dist/src/providers/builtin/whatsapp.js +157 -29
- package/dist/src/providers/builtin/whatsapp.js.map +1 -1
- package/dist/src/providers/builtin/zalo.d.ts +5 -2
- package/dist/src/providers/builtin/zalo.js +31 -7
- package/dist/src/providers/builtin/zalo.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +1 -1
- package/dist/src/providers/catalog.js +1 -1
- package/dist/src/providers/catalog.js.map +1 -1
- package/dist/src/providers/local-mock.d.ts +4 -0
- package/dist/src/providers/local-mock.js +162 -35
- package/dist/src/providers/local-mock.js.map +1 -1
- package/dist/src/providers/native-ids.d.ts +2 -0
- package/dist/src/providers/native-ids.js +3 -0
- package/dist/src/providers/native-ids.js.map +1 -1
- package/dist/src/providers/recorder.d.ts +14 -3
- package/dist/src/providers/recorder.js +521 -41
- package/dist/src/providers/recorder.js.map +1 -1
- package/dist/src/providers/registry.d.ts +1 -0
- package/dist/src/providers/registry.js +65 -20
- package/dist/src/providers/registry.js.map +1 -1
- package/dist/src/providers/signed-jwt.d.ts +6 -1
- package/dist/src/providers/signed-jwt.js +241 -50
- package/dist/src/providers/signed-jwt.js.map +1 -1
- package/dist/src/providers/slack-ids.d.ts +1 -0
- package/dist/src/providers/slack-ids.js +5 -0
- package/dist/src/providers/slack-ids.js.map +1 -1
- package/dist/src/providers/target-normalizers.d.ts +9 -1
- package/dist/src/providers/target-normalizers.js +177 -58
- package/dist/src/providers/target-normalizers.js.map +1 -1
- package/dist/src/providers/webhook-server.d.ts +3 -0
- package/dist/src/providers/webhook-server.js +268 -47
- package/dist/src/providers/webhook-server.js.map +1 -1
- package/dist/src/servers/http.d.ts +14 -2
- package/dist/src/servers/http.js +229 -58
- package/dist/src/servers/http.js.map +1 -1
- package/dist/src/servers/matrix.d.ts +3 -0
- package/dist/src/servers/matrix.js +268 -110
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.d.ts +5 -0
- package/dist/src/servers/mattermost.js +389 -91
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/recorder.d.ts +11 -0
- package/dist/src/servers/recorder.js +1072 -69
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.d.ts +1 -0
- package/dist/src/servers/signal.js +279 -44
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.d.ts +15 -0
- package/dist/src/servers/slack.js +259 -51
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram-identity.d.ts +6 -0
- package/dist/src/servers/telegram-identity.js +27 -0
- package/dist/src/servers/telegram-identity.js.map +1 -0
- package/dist/src/servers/telegram.d.ts +17 -6
- package/dist/src/servers/telegram.js +1169 -107
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/webhook-target.d.ts +12 -4
- package/dist/src/servers/webhook-target.js +226 -28
- package/dist/src/servers/webhook-target.js.map +1 -1
- package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp-jid.d.ts +1 -0
- package/dist/src/servers/whatsapp-jid.js +9 -5
- package/dist/src/servers/whatsapp-jid.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
- package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
- package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
- package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
- package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
- package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
- package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/signal.js +3 -3
- package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +7 -0
- package/dist/src/servers/whatsapp.js +149 -24
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.d.ts +1 -0
- package/dist/src/servers/zalo.js +193 -82
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +185 -43
- package/fixtures/examples/crabline.example.yaml +3 -2
- package/package.json +18 -10
- package/fixtures/examples/openclaw-bridge.yaml +0 -125
|
@@ -2,24 +2,35 @@ import { createHmac, timingSafeEqual } from "node:crypto";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { CrablineError } from "../../core/errors.js";
|
|
4
4
|
import { LocalMockProviderAdapter } from "../local-mock.js";
|
|
5
|
-
import { slackTargetKey, SLACK_CHANNEL_ID_RULE, SLACK_TS_RULE } from "../slack-ids.js";
|
|
5
|
+
import { slackTargetKey, SLACK_CHANNEL_ID_RULE, SLACK_EVENT_ID_RULE, SLACK_TS_RULE, SLACK_USER_ID_RULE, } from "../slack-ids.js";
|
|
6
6
|
import { getBuiltinTargetCodec } from "../target-normalizers.js";
|
|
7
|
-
import { genericMockPayloadWithNativeThread, isRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
|
|
7
|
+
import { genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
|
|
8
8
|
import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
|
|
9
9
|
const SLACK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;
|
|
10
|
+
const SLACK_EVENT_RETRY_RETENTION_HOURS = 25;
|
|
11
|
+
const SLACK_EVENT_RATE_LIMIT_PER_HOUR = 30_000;
|
|
12
|
+
const SLACK_EVENT_RETRY_RETENTION_MS = SLACK_EVENT_RETRY_RETENTION_HOURS * 60 * 60_000;
|
|
13
|
+
const SLACK_EVENT_REPLAY_CACHE_LIMIT = SLACK_EVENT_RATE_LIMIT_PER_HOUR * SLACK_EVENT_RETRY_RETENTION_HOURS;
|
|
10
14
|
export function resolveSlackAdapterConfig(config, env = process.env) {
|
|
15
|
+
const configuredSigningSecret = config.slack?.signingSecret;
|
|
16
|
+
const signingSecret = configuredSigningSecret ?? env.SLACK_SIGNING_SECRET;
|
|
17
|
+
if (signingSecret !== undefined && !signingSecret.trim()) {
|
|
18
|
+
throw new CrablineError(configuredSigningSecret === undefined
|
|
19
|
+
? "SLACK_SIGNING_SECRET must not be empty or whitespace-only."
|
|
20
|
+
: "Slack signingSecret must not be empty or whitespace-only.", { kind: "config" });
|
|
21
|
+
}
|
|
11
22
|
return {
|
|
12
|
-
signingSecret
|
|
23
|
+
signingSecret,
|
|
13
24
|
};
|
|
14
25
|
}
|
|
15
|
-
function authenticateSlackWebhook(request, rawBody, signingSecret) {
|
|
26
|
+
function authenticateSlackWebhook(request, rawBody, signingSecret, now = Date.now) {
|
|
16
27
|
const timestamp = request.headers.get("x-slack-request-timestamp");
|
|
17
28
|
const signature = request.headers.get("x-slack-signature");
|
|
18
29
|
const timestampSeconds = timestamp ? Number(timestamp) : Number.NaN;
|
|
19
30
|
if (!timestamp ||
|
|
20
31
|
!signature ||
|
|
21
32
|
!Number.isSafeInteger(timestampSeconds) ||
|
|
22
|
-
Math.abs(
|
|
33
|
+
Math.abs(now() / 1000 - timestampSeconds) > SLACK_SIGNATURE_TOLERANCE_SECONDS) {
|
|
23
34
|
return new Response("unauthorized", { status: 401 });
|
|
24
35
|
}
|
|
25
36
|
const expected = `v0=${createHmac("sha256", signingSecret)
|
|
@@ -39,32 +50,179 @@ function slackAuthorFromEvent(event) {
|
|
|
39
50
|
}
|
|
40
51
|
return "user";
|
|
41
52
|
}
|
|
42
|
-
function
|
|
53
|
+
function pushSlackText(value, output) {
|
|
54
|
+
if (typeof value === "string" && value.trim()) {
|
|
55
|
+
output.push(value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function slackInlineText(value) {
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
return value.map(slackInlineText).join("");
|
|
61
|
+
}
|
|
62
|
+
if (!isRecord(value)) {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
if (value.type === "link" && typeof value.url === "string") {
|
|
66
|
+
return typeof value.text === "string" && value.text.length > 0 ? value.text : value.url;
|
|
67
|
+
}
|
|
68
|
+
if (value.type === "user" && typeof value.user_id === "string") {
|
|
69
|
+
return `<@${value.user_id}>`;
|
|
70
|
+
}
|
|
71
|
+
if (value.type === "channel" && typeof value.channel_id === "string") {
|
|
72
|
+
return `<#${value.channel_id}>`;
|
|
73
|
+
}
|
|
74
|
+
if (value.type === "usergroup" && typeof value.usergroup_id === "string") {
|
|
75
|
+
return `<!subteam^${value.usergroup_id}>`;
|
|
76
|
+
}
|
|
77
|
+
if (value.type === "emoji" && typeof value.name === "string") {
|
|
78
|
+
return `:${value.name}:`;
|
|
79
|
+
}
|
|
80
|
+
if (value.type === "broadcast" && typeof value.range === "string") {
|
|
81
|
+
return `<!${value.range}>`;
|
|
82
|
+
}
|
|
83
|
+
if (value.type === "date") {
|
|
84
|
+
if (typeof value.fallback === "string" && value.fallback.length > 0) {
|
|
85
|
+
return value.fallback;
|
|
86
|
+
}
|
|
87
|
+
if ((typeof value.timestamp === "number" || typeof value.timestamp === "string") &&
|
|
88
|
+
typeof value.format === "string") {
|
|
89
|
+
return `<!date^${value.timestamp}^${value.format}>`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (typeof value.text === "string") {
|
|
93
|
+
return value.text;
|
|
94
|
+
}
|
|
95
|
+
return slackInlineText(value.elements);
|
|
96
|
+
}
|
|
97
|
+
function collectSlackTextValue(value, output) {
|
|
98
|
+
if (typeof value === "string") {
|
|
99
|
+
pushSlackText(value, output);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (isRecord(value) && typeof value.text === "string") {
|
|
103
|
+
pushSlackText(value.text, output);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
collectSlackBlockText(value, output);
|
|
107
|
+
}
|
|
108
|
+
function collectSlackBlockText(value, output) {
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
for (const entry of value) {
|
|
111
|
+
collectSlackBlockText(entry, output);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!isRecord(value)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (value.type === "rich_text_section" ||
|
|
119
|
+
value.type === "rich_text_preformatted" ||
|
|
120
|
+
value.type === "rich_text_quote") {
|
|
121
|
+
pushSlackText(slackInlineText(value.elements), output);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
for (const key of [
|
|
125
|
+
"alt_text",
|
|
126
|
+
"title",
|
|
127
|
+
"body",
|
|
128
|
+
"subtitle",
|
|
129
|
+
"subtext",
|
|
130
|
+
"details",
|
|
131
|
+
"output",
|
|
132
|
+
]) {
|
|
133
|
+
collectSlackTextValue(value[key], output);
|
|
134
|
+
}
|
|
135
|
+
collectSlackTextValue(value.text, output);
|
|
136
|
+
for (const key of [
|
|
137
|
+
"blocks",
|
|
138
|
+
"elements",
|
|
139
|
+
"fields",
|
|
140
|
+
"rows",
|
|
141
|
+
"tasks",
|
|
142
|
+
"actions",
|
|
143
|
+
"hero_image",
|
|
144
|
+
"icon",
|
|
145
|
+
]) {
|
|
146
|
+
collectSlackBlockText(value[key], output);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function collectSlackAttachmentText(value, output) {
|
|
150
|
+
if (Array.isArray(value)) {
|
|
151
|
+
for (const entry of value) {
|
|
152
|
+
collectSlackAttachmentText(entry, output);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (!isRecord(value)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
for (const key of ["fallback", "pretext", "author_name", "title", "text", "footer"]) {
|
|
160
|
+
pushSlackText(value[key], output);
|
|
161
|
+
}
|
|
162
|
+
if (Array.isArray(value.fields)) {
|
|
163
|
+
for (const field of value.fields) {
|
|
164
|
+
if (!isRecord(field)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
pushSlackText(field.title, output);
|
|
168
|
+
pushSlackText(field.value, output);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
collectSlackBlockText(value.blocks, output);
|
|
172
|
+
}
|
|
173
|
+
function slackMessageText(message) {
|
|
174
|
+
if (typeof message.text === "string" && message.text.trim()) {
|
|
175
|
+
return message.text;
|
|
176
|
+
}
|
|
177
|
+
const fallback = [];
|
|
178
|
+
collectSlackBlockText(message.blocks, fallback);
|
|
179
|
+
collectSlackAttachmentText(message.attachments, fallback);
|
|
180
|
+
return fallback.length > 0 ? fallback.join("\n") : undefined;
|
|
181
|
+
}
|
|
182
|
+
function hasMalformedSlackMessageContent(message) {
|
|
183
|
+
return ((message.text !== undefined && typeof message.text !== "string") ||
|
|
184
|
+
(message.blocks !== undefined && !Array.isArray(message.blocks)) ||
|
|
185
|
+
(message.attachments !== undefined && !Array.isArray(message.attachments)));
|
|
186
|
+
}
|
|
187
|
+
export function normalizeSlackEventsPayload(payload) {
|
|
43
188
|
if (!isRecord(payload)) {
|
|
44
189
|
throw new CrablineError("Slack webhook payload must be an object", { kind: "inbound" });
|
|
45
190
|
}
|
|
191
|
+
const { token: _token, ...safePayload } = payload;
|
|
46
192
|
if (isRecord(payload.event)) {
|
|
47
193
|
const event = payload.event;
|
|
194
|
+
const isMessageChanged = event.subtype === "message_changed";
|
|
48
195
|
const changedMessage = isRecord(event.message) ? event.message : undefined;
|
|
49
|
-
|
|
196
|
+
if (isMessageChanged && !changedMessage) {
|
|
197
|
+
throw new CrablineError("Slack message_changed event requires event.message", {
|
|
198
|
+
kind: "inbound",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
50
201
|
const message = isMessageChanged ? changedMessage : event;
|
|
202
|
+
if (!message || hasMalformedSlackMessageContent(message)) {
|
|
203
|
+
throw new CrablineError("Slack event payload contains malformed message content", {
|
|
204
|
+
kind: "inbound",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
51
207
|
const channel = event.channel;
|
|
52
|
-
const text = message
|
|
53
|
-
if (typeof channel !== "string" ||
|
|
208
|
+
const text = slackMessageText(message);
|
|
209
|
+
if (typeof channel !== "string" || text === undefined) {
|
|
54
210
|
throw new CrablineError("Slack event payload requires event.channel and event.text", {
|
|
55
211
|
kind: "inbound",
|
|
56
212
|
});
|
|
57
213
|
}
|
|
58
214
|
const threadTs = message.thread_ts;
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
: optionalString(message, "ts");
|
|
215
|
+
const callbackEventId = optionalString(payload, "event_id");
|
|
216
|
+
const eventId = callbackEventId ??
|
|
217
|
+
(isMessageChanged ? optionalString(event, "event_ts") : optionalString(message, "ts"));
|
|
62
218
|
return {
|
|
63
219
|
author: slackAuthorFromEvent(message),
|
|
64
220
|
...(eventId
|
|
65
|
-
? {
|
|
221
|
+
? {
|
|
222
|
+
id: requireNativeInboundId(eventId, callbackEventId ? SLACK_EVENT_ID_RULE : SLACK_TS_RULE, callbackEventId ? "Slack event_id" : "Slack event timestamp"),
|
|
223
|
+
}
|
|
66
224
|
: {}),
|
|
67
|
-
raw:
|
|
225
|
+
raw: safePayload,
|
|
68
226
|
text,
|
|
69
227
|
threadId: typeof threadTs === "string"
|
|
70
228
|
? slackTargetKey(requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, "Slack event.channel"), requireNativeInboundId(threadTs, SLACK_TS_RULE, "Slack event.thread_ts"))
|
|
@@ -73,7 +231,7 @@ function normalizeSlackEventsPayload(payload) {
|
|
|
73
231
|
}
|
|
74
232
|
const normalized = genericMockPayloadWithNativeThread({
|
|
75
233
|
channelRule: SLACK_CHANNEL_ID_RULE,
|
|
76
|
-
payload,
|
|
234
|
+
payload: safePayload,
|
|
77
235
|
threadRule: SLACK_TS_RULE,
|
|
78
236
|
});
|
|
79
237
|
const message = isRecord(payload.message) ? payload.message : undefined;
|
|
@@ -99,26 +257,157 @@ function normalizeSlackEventsPayload(payload) {
|
|
|
99
257
|
threadId: scopedThreadId,
|
|
100
258
|
};
|
|
101
259
|
}
|
|
102
|
-
function matchesSlackThread(candidateThreadId, expectedThreadId, target) {
|
|
260
|
+
function matchesSlackThread(candidateThreadId, expectedThreadId, target, raw) {
|
|
103
261
|
if (!expectedThreadId) {
|
|
104
262
|
return true;
|
|
105
263
|
}
|
|
106
264
|
const scopedExpectedThreadId = target.channelId && SLACK_TS_RULE.pattern.test(expectedThreadId)
|
|
107
265
|
? slackTargetKey(target.channelId, expectedThreadId)
|
|
108
266
|
: expectedThreadId;
|
|
109
|
-
|
|
110
|
-
candidateThreadId.startsWith(`${scopedExpectedThreadId}:`))
|
|
267
|
+
if (candidateThreadId === scopedExpectedThreadId ||
|
|
268
|
+
candidateThreadId.startsWith(`${scopedExpectedThreadId}:`)) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
if (!target.channelId || !SLACK_USER_ID_RULE.pattern.test(target.channelId) || !isRecord(raw)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const event = optionalRecord(raw, "event");
|
|
275
|
+
const message = event?.subtype === "message_changed" ? optionalRecord(event, "message") : event;
|
|
276
|
+
const channel = event ? optionalString(event, "channel") : undefined;
|
|
277
|
+
const user = (event ? optionalString(event, "user") : undefined) ??
|
|
278
|
+
(message ? optionalString(message, "user") : undefined);
|
|
279
|
+
if (!channel?.startsWith("D") || user !== target.channelId) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
if (expectedThreadId === target.channelId) {
|
|
283
|
+
return candidateThreadId === channel || candidateThreadId.startsWith(`${channel}:thread:`);
|
|
284
|
+
}
|
|
285
|
+
const messageThreadTs = message ? optionalString(message, "thread_ts") : undefined;
|
|
286
|
+
return Boolean(SLACK_TS_RULE.pattern.test(expectedThreadId) &&
|
|
287
|
+
messageThreadTs === expectedThreadId &&
|
|
288
|
+
candidateThreadId === slackTargetKey(channel, expectedThreadId));
|
|
111
289
|
}
|
|
112
290
|
export function handleSlackWebhookPayload(payload) {
|
|
113
|
-
if (isRecord(payload)
|
|
114
|
-
|
|
115
|
-
|
|
291
|
+
if (!isRecord(payload)) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
if (payload.type === "url_verification" && typeof payload.challenge === "string") {
|
|
116
295
|
return Response.json({ challenge: payload.challenge });
|
|
117
296
|
}
|
|
297
|
+
if (payload.type === "event_callback") {
|
|
298
|
+
if (!isRecord(payload.event)) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
const eventType = optionalString(payload.event, "type");
|
|
302
|
+
if (!eventType) {
|
|
303
|
+
return new Response(null, { status: 200 });
|
|
304
|
+
}
|
|
305
|
+
if (eventType !== "message" && eventType !== "app_mention") {
|
|
306
|
+
return new Response(null, { status: 200 });
|
|
307
|
+
}
|
|
308
|
+
const isMessageChanged = payload.event.subtype === "message_changed";
|
|
309
|
+
if (isMessageChanged && !isRecord(payload.event.message)) {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
const message = isMessageChanged ? payload.event.message : payload.event;
|
|
313
|
+
if (!isRecord(message)) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
if (typeof payload.event.channel !== "string" || hasMalformedSlackMessageContent(message)) {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
if (slackMessageText(message) === undefined) {
|
|
320
|
+
return new Response(null, { status: 200 });
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
if (typeof payload.type === "string" && payload.type.trim()) {
|
|
325
|
+
return new Response(null, { status: 200 });
|
|
326
|
+
}
|
|
118
327
|
return undefined;
|
|
119
328
|
}
|
|
329
|
+
async function handleSlackEventReplay(payload, state) {
|
|
330
|
+
if (!isRecord(payload) || payload.type !== "event_callback") {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
const eventId = optionalString(payload, "event_id");
|
|
334
|
+
if (!eventId || !SLACK_EVENT_ID_RULE.pattern.test(eventId)) {
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
const now = state.now();
|
|
338
|
+
pruneSlackEventReplayState(state, now);
|
|
339
|
+
for (;;) {
|
|
340
|
+
if (state.accepted.has(eventId)) {
|
|
341
|
+
return new Response(null, { status: 200 });
|
|
342
|
+
}
|
|
343
|
+
const pending = state.inFlight.get(eventId);
|
|
344
|
+
if (!pending) {
|
|
345
|
+
if (state.accepted.size + state.inFlight.size >= state.cacheLimit) {
|
|
346
|
+
return slackEventReplayCapacityResponse();
|
|
347
|
+
}
|
|
348
|
+
reserveSlackEvent(state, eventId);
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
if (await pending.promise) {
|
|
352
|
+
return new Response(null, { status: 200 });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function reserveSlackEvent(state, eventId) {
|
|
357
|
+
let resolveReservation;
|
|
358
|
+
const promise = new Promise((resolve) => {
|
|
359
|
+
resolveReservation = resolve;
|
|
360
|
+
});
|
|
361
|
+
state.inFlight.set(eventId, {
|
|
362
|
+
promise,
|
|
363
|
+
resolve: resolveReservation,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function settleSlackEventReplay(state, payload, accepted) {
|
|
367
|
+
if (!isRecord(payload)) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const eventId = optionalString(payload, "event_id");
|
|
371
|
+
if (!eventId) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const reservation = state.inFlight.get(eventId);
|
|
375
|
+
if (!reservation) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
state.inFlight.delete(eventId);
|
|
379
|
+
if (accepted) {
|
|
380
|
+
const expiresAt = state.now() + SLACK_EVENT_RETRY_RETENTION_MS;
|
|
381
|
+
state.accepted.set(eventId, expiresAt);
|
|
382
|
+
state.expiryQueue.push({ eventId, expiresAt });
|
|
383
|
+
}
|
|
384
|
+
reservation.resolve(accepted);
|
|
385
|
+
}
|
|
386
|
+
function pruneSlackEventReplayState(state, now) {
|
|
387
|
+
while (state.expiryHead < state.expiryQueue.length) {
|
|
388
|
+
const { eventId, expiresAt } = state.expiryQueue[state.expiryHead];
|
|
389
|
+
if (now <= expiresAt) {
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
state.expiryHead += 1;
|
|
393
|
+
if (state.accepted.get(eventId) === expiresAt) {
|
|
394
|
+
state.accepted.delete(eventId);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (state.expiryHead >= 1_024 && state.expiryHead * 2 >= state.expiryQueue.length) {
|
|
398
|
+
state.expiryQueue = state.expiryQueue.slice(state.expiryHead);
|
|
399
|
+
state.expiryHead = 0;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function slackEventReplayCapacityResponse() {
|
|
403
|
+
return new Response("service unavailable", {
|
|
404
|
+
headers: { "cache-control": "no-store" },
|
|
405
|
+
status: 503,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
120
408
|
export class SlackProviderAdapter extends LocalMockProviderAdapter {
|
|
121
|
-
constructor(id, config, _userName,
|
|
409
|
+
constructor(id, config, _userName, runtime) {
|
|
410
|
+
const slackRuntime = runtime ?? {};
|
|
122
411
|
const resolvedConfig = resolveSlackAdapterConfig(config);
|
|
123
412
|
requireExternalWebhookAuthentication({
|
|
124
413
|
authenticated: Boolean(resolvedConfig.signingSecret),
|
|
@@ -126,6 +415,14 @@ export class SlackProviderAdapter extends LocalMockProviderAdapter {
|
|
|
126
415
|
requirement: "slack.signingSecret or SLACK_SIGNING_SECRET",
|
|
127
416
|
webhook: config.slack?.webhook,
|
|
128
417
|
});
|
|
418
|
+
const replayState = {
|
|
419
|
+
accepted: new Map(),
|
|
420
|
+
cacheLimit: slackRuntime.replayCacheLimit ?? SLACK_EVENT_REPLAY_CACHE_LIMIT,
|
|
421
|
+
expiryHead: 0,
|
|
422
|
+
expiryQueue: [],
|
|
423
|
+
inFlight: new Map(),
|
|
424
|
+
now: slackRuntime.now ?? (() => Date.now()),
|
|
425
|
+
};
|
|
129
426
|
super({
|
|
130
427
|
codec: getBuiltinTargetCodec("slack"),
|
|
131
428
|
config,
|
|
@@ -134,13 +431,16 @@ export class SlackProviderAdapter extends LocalMockProviderAdapter {
|
|
|
134
431
|
...(resolvedConfig.signingSecret
|
|
135
432
|
? {
|
|
136
433
|
authenticateWebhookRequest(request, rawBody) {
|
|
137
|
-
return authenticateSlackWebhook(request, rawBody, resolvedConfig.signingSecret);
|
|
434
|
+
return authenticateSlackWebhook(request, rawBody, resolvedConfig.signingSecret, replayState.now);
|
|
138
435
|
},
|
|
139
436
|
}
|
|
140
437
|
: {}),
|
|
141
438
|
defaultWebhook: { host: "127.0.0.1", path: "/slack/events", port: 8787 },
|
|
142
439
|
endpointLabel: "events endpoint",
|
|
143
|
-
handleWebhookPayload
|
|
440
|
+
async handleWebhookPayload(payload) {
|
|
441
|
+
return (handleSlackWebhookPayload(payload) ??
|
|
442
|
+
(await handleSlackEventReplay(payload, replayState)));
|
|
443
|
+
},
|
|
144
444
|
matchesThread: matchesSlackThread,
|
|
145
445
|
normalizeWebhookPayload: normalizeSlackEventsPayload,
|
|
146
446
|
platform: "slack",
|
|
@@ -148,6 +448,9 @@ export class SlackProviderAdapter extends LocalMockProviderAdapter {
|
|
|
148
448
|
recorderPath: config.slack?.recorder.path
|
|
149
449
|
? path.resolve(config.slack.recorder.path)
|
|
150
450
|
: undefined,
|
|
451
|
+
settleWebhookRequest({ accepted, payload }) {
|
|
452
|
+
settleSlackEventReplay(replayState, payload, accepted);
|
|
453
|
+
},
|
|
151
454
|
webhook: config.slack?.webhook,
|
|
152
455
|
},
|
|
153
456
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../../src/providers/builtin/slack.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport path from \"node:path\";\nimport { CrablineError } from \"../../core/errors.js\";\nimport type { ProviderConfig } from \"../../config/schema.js\";\nimport { LocalMockProviderAdapter } from \"../local-mock.js\";\nimport { slackTargetKey, SLACK_CHANNEL_ID_RULE, SLACK_TS_RULE } from \"../slack-ids.js\";\nimport { getBuiltinTargetCodec } from \"../target-normalizers.js\";\nimport type { InboundEnvelope, ProviderAdapter } from \"../types.js\";\nimport {\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\nconst SLACK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;\n\ntype SlackEnvironment = Partial<Pick<NodeJS.ProcessEnv, \"SLACK_SIGNING_SECRET\">>;\n\nexport function resolveSlackAdapterConfig(\n config: ProviderConfig,\n env: SlackEnvironment = process.env,\n) {\n return {\n signingSecret: config.slack?.signingSecret ?? env.SLACK_SIGNING_SECRET,\n };\n}\n\nfunction authenticateSlackWebhook(\n request: Request,\n rawBody: string,\n signingSecret: string,\n): Response | undefined {\n const timestamp = request.headers.get(\"x-slack-request-timestamp\");\n const signature = request.headers.get(\"x-slack-signature\");\n const timestampSeconds = timestamp ? Number(timestamp) : Number.NaN;\n if (\n !timestamp ||\n !signature ||\n !Number.isSafeInteger(timestampSeconds) ||\n Math.abs(Date.now() / 1000 - timestampSeconds) > SLACK_SIGNATURE_TOLERANCE_SECONDS\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n\n const expected = `v0=${createHmac(\"sha256\", signingSecret)\n .update(`v0:${timestamp}:${rawBody}`)\n .digest(\"hex\")}`;\n const actualBuffer = Buffer.from(signature);\n const expectedBuffer = Buffer.from(expected);\n if (\n actualBuffer.length !== expectedBuffer.length ||\n !timingSafeEqual(actualBuffer, expectedBuffer)\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n return undefined;\n}\n\nfunction slackAuthorFromEvent(event: Record<string, unknown>): InboundEnvelope[\"author\"] {\n if (typeof event.bot_id === \"string\" || event.subtype === \"bot_message\") {\n return \"assistant\";\n }\n return \"user\";\n}\n\nfunction normalizeSlackEventsPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Slack webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n if (isRecord(payload.event)) {\n const event = payload.event;\n const changedMessage = isRecord(event.message) ? event.message : undefined;\n const isMessageChanged = event.subtype === \"message_changed\" && changedMessage !== undefined;\n const message = isMessageChanged ? changedMessage : event;\n const channel = event.channel;\n const text = message.text;\n if (typeof channel !== \"string\" || typeof text !== \"string\") {\n throw new CrablineError(\"Slack event payload requires event.channel and event.text\", {\n kind: \"inbound\",\n });\n }\n const threadTs = message.thread_ts;\n const eventId = isMessageChanged\n ? optionalString(event, \"event_ts\")\n : optionalString(message, \"ts\");\n return {\n author: slackAuthorFromEvent(message),\n ...(eventId\n ? { id: requireNativeInboundId(eventId, SLACK_TS_RULE, \"Slack event timestamp\") }\n : {}),\n raw: payload,\n text,\n threadId:\n typeof threadTs === \"string\"\n ? slackTargetKey(\n requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n requireNativeInboundId(threadTs, SLACK_TS_RULE, \"Slack event.thread_ts\"),\n )\n : requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n };\n }\n\n const normalized = genericMockPayloadWithNativeThread({\n channelRule: SLACK_CHANNEL_ID_RULE,\n payload,\n threadRule: SLACK_TS_RULE,\n });\n const message = isRecord(payload.message) ? payload.message : undefined;\n const channelId =\n (message ? optionalString(message, \"channelId\") : undefined) ??\n optionalString(payload, \"channelId\") ??\n optionalString(payload, \"channel\");\n const threadId =\n (message ? optionalString(message, \"threadId\") : undefined) ??\n optionalString(payload, \"threadId\");\n if (threadId && SLACK_TS_RULE.pattern.test(threadId) && !channelId) {\n throw new CrablineError(\"Slack timestamp threadId requires a native channelId\", {\n kind: \"inbound\",\n });\n }\n if (!channelId || !threadId || !SLACK_TS_RULE.pattern.test(threadId)) {\n return normalized;\n }\n const scopedThreadId = slackTargetKey(\n requireNativeInboundId(channelId, SLACK_CHANNEL_ID_RULE, \"Slack channelId\"),\n threadId,\n );\n return {\n ...normalized,\n ...(\"message\" in normalized && isRecord(normalized.message)\n ? { message: { ...normalized.message, threadId: scopedThreadId } }\n : {}),\n threadId: scopedThreadId,\n };\n}\n\nfunction matchesSlackThread(\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: { channelId?: string | undefined },\n): boolean {\n if (!expectedThreadId) {\n return true;\n }\n const scopedExpectedThreadId =\n target.channelId && SLACK_TS_RULE.pattern.test(expectedThreadId)\n ? slackTargetKey(target.channelId, expectedThreadId)\n : expectedThreadId;\n return (\n candidateThreadId === scopedExpectedThreadId ||\n candidateThreadId.startsWith(`${scopedExpectedThreadId}:`)\n );\n}\n\nexport function handleSlackWebhookPayload(payload: unknown): Response | undefined {\n if (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\"\n ) {\n return Response.json({ challenge: payload.challenge });\n }\n return undefined;\n}\n\nexport class SlackProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, _runtime?: unknown) {\n const resolvedConfig = resolveSlackAdapterConfig(config);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolvedConfig.signingSecret),\n provider: \"Slack\",\n requirement: \"slack.signingSecret or SLACK_SIGNING_SECRET\",\n webhook: config.slack?.webhook,\n });\n super({\n codec: getBuiltinTargetCodec(\"slack\"),\n config,\n id,\n options: {\n ...(resolvedConfig.signingSecret\n ? {\n authenticateWebhookRequest(request: Request, rawBody: string) {\n return authenticateSlackWebhook(request, rawBody, resolvedConfig.signingSecret!);\n },\n }\n : {}),\n defaultWebhook: { host: \"127.0.0.1\", path: \"/slack/events\", port: 8787 },\n endpointLabel: \"events endpoint\",\n handleWebhookPayload: handleSlackWebhookPayload,\n matchesThread: matchesSlackThread,\n normalizeWebhookPayload: normalizeSlackEventsPayload,\n platform: \"slack\",\n publicUrl: config.slack?.webhook.publicUrl,\n recorderPath: config.slack?.recorder.path\n ? path.resolve(config.slack.recorder.path)\n : undefined,\n webhook: config.slack?.webhook,\n },\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../../src/providers/builtin/slack.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport path from \"node:path\";\nimport { CrablineError } from \"../../core/errors.js\";\nimport type { ProviderConfig } from \"../../config/schema.js\";\nimport { LocalMockProviderAdapter } from \"../local-mock.js\";\nimport {\n slackTargetKey,\n SLACK_CHANNEL_ID_RULE,\n SLACK_EVENT_ID_RULE,\n SLACK_TS_RULE,\n SLACK_USER_ID_RULE,\n} from \"../slack-ids.js\";\nimport { getBuiltinTargetCodec } from \"../target-normalizers.js\";\nimport type { InboundEnvelope, ProviderAdapter } from \"../types.js\";\nimport {\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\nconst SLACK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;\nconst SLACK_EVENT_RETRY_RETENTION_HOURS = 25;\nconst SLACK_EVENT_RATE_LIMIT_PER_HOUR = 30_000;\nconst SLACK_EVENT_RETRY_RETENTION_MS = SLACK_EVENT_RETRY_RETENTION_HOURS * 60 * 60_000;\nconst SLACK_EVENT_REPLAY_CACHE_LIMIT =\n SLACK_EVENT_RATE_LIMIT_PER_HOUR * SLACK_EVENT_RETRY_RETENTION_HOURS;\n\ntype SlackEnvironment = Partial<Pick<NodeJS.ProcessEnv, \"SLACK_SIGNING_SECRET\">>;\n\ntype SlackRuntime = {\n now?: (() => number) | undefined;\n replayCacheLimit?: number | undefined;\n};\n\ntype SlackEventReservation = {\n promise: Promise<boolean>;\n resolve: (accepted: boolean) => void;\n};\n\ntype SlackEventReplayState = {\n accepted: Map<string, number>;\n cacheLimit: number;\n expiryHead: number;\n expiryQueue: Array<{ eventId: string; expiresAt: number }>;\n inFlight: Map<string, SlackEventReservation>;\n now: () => number;\n};\n\nexport function resolveSlackAdapterConfig(\n config: ProviderConfig,\n env: SlackEnvironment = process.env,\n) {\n const configuredSigningSecret = config.slack?.signingSecret;\n const signingSecret = configuredSigningSecret ?? env.SLACK_SIGNING_SECRET;\n if (signingSecret !== undefined && !signingSecret.trim()) {\n throw new CrablineError(\n configuredSigningSecret === undefined\n ? \"SLACK_SIGNING_SECRET must not be empty or whitespace-only.\"\n : \"Slack signingSecret must not be empty or whitespace-only.\",\n { kind: \"config\" },\n );\n }\n return {\n signingSecret,\n };\n}\n\nfunction authenticateSlackWebhook(\n request: Request,\n rawBody: string,\n signingSecret: string,\n now: () => number = Date.now,\n): Response | undefined {\n const timestamp = request.headers.get(\"x-slack-request-timestamp\");\n const signature = request.headers.get(\"x-slack-signature\");\n const timestampSeconds = timestamp ? Number(timestamp) : Number.NaN;\n if (\n !timestamp ||\n !signature ||\n !Number.isSafeInteger(timestampSeconds) ||\n Math.abs(now() / 1000 - timestampSeconds) > SLACK_SIGNATURE_TOLERANCE_SECONDS\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n\n const expected = `v0=${createHmac(\"sha256\", signingSecret)\n .update(`v0:${timestamp}:${rawBody}`)\n .digest(\"hex\")}`;\n const actualBuffer = Buffer.from(signature);\n const expectedBuffer = Buffer.from(expected);\n if (\n actualBuffer.length !== expectedBuffer.length ||\n !timingSafeEqual(actualBuffer, expectedBuffer)\n ) {\n return new Response(\"unauthorized\", { status: 401 });\n }\n return undefined;\n}\n\nfunction slackAuthorFromEvent(event: Record<string, unknown>): InboundEnvelope[\"author\"] {\n if (typeof event.bot_id === \"string\" || event.subtype === \"bot_message\") {\n return \"assistant\";\n }\n return \"user\";\n}\n\nfunction pushSlackText(value: unknown, output: string[]): void {\n if (typeof value === \"string\" && value.trim()) {\n output.push(value);\n }\n}\n\nfunction slackInlineText(value: unknown): string {\n if (Array.isArray(value)) {\n return value.map(slackInlineText).join(\"\");\n }\n if (!isRecord(value)) {\n return \"\";\n }\n if (value.type === \"link\" && typeof value.url === \"string\") {\n return typeof value.text === \"string\" && value.text.length > 0 ? value.text : value.url;\n }\n if (value.type === \"user\" && typeof value.user_id === \"string\") {\n return `<@${value.user_id}>`;\n }\n if (value.type === \"channel\" && typeof value.channel_id === \"string\") {\n return `<#${value.channel_id}>`;\n }\n if (value.type === \"usergroup\" && typeof value.usergroup_id === \"string\") {\n return `<!subteam^${value.usergroup_id}>`;\n }\n if (value.type === \"emoji\" && typeof value.name === \"string\") {\n return `:${value.name}:`;\n }\n if (value.type === \"broadcast\" && typeof value.range === \"string\") {\n return `<!${value.range}>`;\n }\n if (value.type === \"date\") {\n if (typeof value.fallback === \"string\" && value.fallback.length > 0) {\n return value.fallback;\n }\n if (\n (typeof value.timestamp === \"number\" || typeof value.timestamp === \"string\") &&\n typeof value.format === \"string\"\n ) {\n return `<!date^${value.timestamp}^${value.format}>`;\n }\n }\n if (typeof value.text === \"string\") {\n return value.text;\n }\n return slackInlineText(value.elements);\n}\n\nfunction collectSlackTextValue(value: unknown, output: string[]): void {\n if (typeof value === \"string\") {\n pushSlackText(value, output);\n return;\n }\n if (isRecord(value) && typeof value.text === \"string\") {\n pushSlackText(value.text, output);\n return;\n }\n collectSlackBlockText(value, output);\n}\n\nfunction collectSlackBlockText(value: unknown, output: string[]): void {\n if (Array.isArray(value)) {\n for (const entry of value) {\n collectSlackBlockText(entry, output);\n }\n return;\n }\n if (!isRecord(value)) {\n return;\n }\n if (\n value.type === \"rich_text_section\" ||\n value.type === \"rich_text_preformatted\" ||\n value.type === \"rich_text_quote\"\n ) {\n pushSlackText(slackInlineText(value.elements), output);\n return;\n }\n for (const key of [\n \"alt_text\",\n \"title\",\n \"body\",\n \"subtitle\",\n \"subtext\",\n \"details\",\n \"output\",\n ] as const) {\n collectSlackTextValue(value[key], output);\n }\n collectSlackTextValue(value.text, output);\n for (const key of [\n \"blocks\",\n \"elements\",\n \"fields\",\n \"rows\",\n \"tasks\",\n \"actions\",\n \"hero_image\",\n \"icon\",\n ] as const) {\n collectSlackBlockText(value[key], output);\n }\n}\n\nfunction collectSlackAttachmentText(value: unknown, output: string[]): void {\n if (Array.isArray(value)) {\n for (const entry of value) {\n collectSlackAttachmentText(entry, output);\n }\n return;\n }\n if (!isRecord(value)) {\n return;\n }\n for (const key of [\"fallback\", \"pretext\", \"author_name\", \"title\", \"text\", \"footer\"] as const) {\n pushSlackText(value[key], output);\n }\n if (Array.isArray(value.fields)) {\n for (const field of value.fields) {\n if (!isRecord(field)) {\n continue;\n }\n pushSlackText(field.title, output);\n pushSlackText(field.value, output);\n }\n }\n collectSlackBlockText(value.blocks, output);\n}\n\nfunction slackMessageText(message: Record<string, unknown>): string | undefined {\n if (typeof message.text === \"string\" && message.text.trim()) {\n return message.text;\n }\n const fallback: string[] = [];\n collectSlackBlockText(message.blocks, fallback);\n collectSlackAttachmentText(message.attachments, fallback);\n return fallback.length > 0 ? fallback.join(\"\\n\") : undefined;\n}\n\nfunction hasMalformedSlackMessageContent(message: Record<string, unknown>): boolean {\n return (\n (message.text !== undefined && typeof message.text !== \"string\") ||\n (message.blocks !== undefined && !Array.isArray(message.blocks)) ||\n (message.attachments !== undefined && !Array.isArray(message.attachments))\n );\n}\n\nexport function normalizeSlackEventsPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Slack webhook payload must be an object\", { kind: \"inbound\" });\n }\n const { token: _token, ...safePayload } = payload;\n\n if (isRecord(payload.event)) {\n const event = payload.event;\n const isMessageChanged = event.subtype === \"message_changed\";\n const changedMessage = isRecord(event.message) ? event.message : undefined;\n if (isMessageChanged && !changedMessage) {\n throw new CrablineError(\"Slack message_changed event requires event.message\", {\n kind: \"inbound\",\n });\n }\n const message = isMessageChanged ? changedMessage : event;\n if (!message || hasMalformedSlackMessageContent(message)) {\n throw new CrablineError(\"Slack event payload contains malformed message content\", {\n kind: \"inbound\",\n });\n }\n const channel = event.channel;\n const text = slackMessageText(message);\n if (typeof channel !== \"string\" || text === undefined) {\n throw new CrablineError(\"Slack event payload requires event.channel and event.text\", {\n kind: \"inbound\",\n });\n }\n const threadTs = message.thread_ts;\n const callbackEventId = optionalString(payload, \"event_id\");\n const eventId =\n callbackEventId ??\n (isMessageChanged ? optionalString(event, \"event_ts\") : optionalString(message, \"ts\"));\n return {\n author: slackAuthorFromEvent(message),\n ...(eventId\n ? {\n id: requireNativeInboundId(\n eventId,\n callbackEventId ? SLACK_EVENT_ID_RULE : SLACK_TS_RULE,\n callbackEventId ? \"Slack event_id\" : \"Slack event timestamp\",\n ),\n }\n : {}),\n raw: safePayload,\n text,\n threadId:\n typeof threadTs === \"string\"\n ? slackTargetKey(\n requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n requireNativeInboundId(threadTs, SLACK_TS_RULE, \"Slack event.thread_ts\"),\n )\n : requireNativeInboundId(channel, SLACK_CHANNEL_ID_RULE, \"Slack event.channel\"),\n };\n }\n\n const normalized = genericMockPayloadWithNativeThread({\n channelRule: SLACK_CHANNEL_ID_RULE,\n payload: safePayload,\n threadRule: SLACK_TS_RULE,\n });\n const message = isRecord(payload.message) ? payload.message : undefined;\n const channelId =\n (message ? optionalString(message, \"channelId\") : undefined) ??\n optionalString(payload, \"channelId\") ??\n optionalString(payload, \"channel\");\n const threadId =\n (message ? optionalString(message, \"threadId\") : undefined) ??\n optionalString(payload, \"threadId\");\n if (threadId && SLACK_TS_RULE.pattern.test(threadId) && !channelId) {\n throw new CrablineError(\"Slack timestamp threadId requires a native channelId\", {\n kind: \"inbound\",\n });\n }\n if (!channelId || !threadId || !SLACK_TS_RULE.pattern.test(threadId)) {\n return normalized;\n }\n const scopedThreadId = slackTargetKey(\n requireNativeInboundId(channelId, SLACK_CHANNEL_ID_RULE, \"Slack channelId\"),\n threadId,\n );\n return {\n ...normalized,\n ...(\"message\" in normalized && isRecord(normalized.message)\n ? { message: { ...normalized.message, threadId: scopedThreadId } }\n : {}),\n threadId: scopedThreadId,\n };\n}\n\nfunction matchesSlackThread(\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: { channelId?: string | undefined },\n raw?: unknown,\n): boolean {\n if (!expectedThreadId) {\n return true;\n }\n const scopedExpectedThreadId =\n target.channelId && SLACK_TS_RULE.pattern.test(expectedThreadId)\n ? slackTargetKey(target.channelId, expectedThreadId)\n : expectedThreadId;\n if (\n candidateThreadId === scopedExpectedThreadId ||\n candidateThreadId.startsWith(`${scopedExpectedThreadId}:`)\n ) {\n return true;\n }\n if (!target.channelId || !SLACK_USER_ID_RULE.pattern.test(target.channelId) || !isRecord(raw)) {\n return false;\n }\n const event = optionalRecord(raw, \"event\");\n const message = event?.subtype === \"message_changed\" ? optionalRecord(event, \"message\") : event;\n const channel = event ? optionalString(event, \"channel\") : undefined;\n const user =\n (event ? optionalString(event, \"user\") : undefined) ??\n (message ? optionalString(message, \"user\") : undefined);\n if (!channel?.startsWith(\"D\") || user !== target.channelId) {\n return false;\n }\n if (expectedThreadId === target.channelId) {\n return candidateThreadId === channel || candidateThreadId.startsWith(`${channel}:thread:`);\n }\n const messageThreadTs = message ? optionalString(message, \"thread_ts\") : undefined;\n return Boolean(\n SLACK_TS_RULE.pattern.test(expectedThreadId) &&\n messageThreadTs === expectedThreadId &&\n candidateThreadId === slackTargetKey(channel, expectedThreadId),\n );\n}\n\nexport function handleSlackWebhookPayload(payload: unknown): Response | undefined {\n if (!isRecord(payload)) {\n return undefined;\n }\n if (payload.type === \"url_verification\" && typeof payload.challenge === \"string\") {\n return Response.json({ challenge: payload.challenge });\n }\n if (payload.type === \"event_callback\") {\n if (!isRecord(payload.event)) {\n return undefined;\n }\n const eventType = optionalString(payload.event, \"type\");\n if (!eventType) {\n return new Response(null, { status: 200 });\n }\n if (eventType !== \"message\" && eventType !== \"app_mention\") {\n return new Response(null, { status: 200 });\n }\n const isMessageChanged = payload.event.subtype === \"message_changed\";\n if (isMessageChanged && !isRecord(payload.event.message)) {\n return undefined;\n }\n const message = isMessageChanged ? payload.event.message : payload.event;\n if (!isRecord(message)) {\n return undefined;\n }\n if (typeof payload.event.channel !== \"string\" || hasMalformedSlackMessageContent(message)) {\n return undefined;\n }\n if (slackMessageText(message) === undefined) {\n return new Response(null, { status: 200 });\n }\n return undefined;\n }\n if (typeof payload.type === \"string\" && payload.type.trim()) {\n return new Response(null, { status: 200 });\n }\n return undefined;\n}\n\nasync function handleSlackEventReplay(\n payload: unknown,\n state: SlackEventReplayState,\n): Promise<Response | undefined> {\n if (!isRecord(payload) || payload.type !== \"event_callback\") {\n return undefined;\n }\n const eventId = optionalString(payload, \"event_id\");\n if (!eventId || !SLACK_EVENT_ID_RULE.pattern.test(eventId)) {\n return undefined;\n }\n const now = state.now();\n pruneSlackEventReplayState(state, now);\n for (;;) {\n if (state.accepted.has(eventId)) {\n return new Response(null, { status: 200 });\n }\n const pending = state.inFlight.get(eventId);\n if (!pending) {\n if (state.accepted.size + state.inFlight.size >= state.cacheLimit) {\n return slackEventReplayCapacityResponse();\n }\n reserveSlackEvent(state, eventId);\n return undefined;\n }\n if (await pending.promise) {\n return new Response(null, { status: 200 });\n }\n }\n}\n\nfunction reserveSlackEvent(state: SlackEventReplayState, eventId: string): void {\n let resolveReservation!: (accepted: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveReservation = resolve;\n });\n state.inFlight.set(eventId, {\n promise,\n resolve: resolveReservation,\n });\n}\n\nfunction settleSlackEventReplay(\n state: SlackEventReplayState,\n payload: unknown,\n accepted: boolean,\n): void {\n if (!isRecord(payload)) {\n return;\n }\n const eventId = optionalString(payload, \"event_id\");\n if (!eventId) {\n return;\n }\n const reservation = state.inFlight.get(eventId);\n if (!reservation) {\n return;\n }\n state.inFlight.delete(eventId);\n if (accepted) {\n const expiresAt = state.now() + SLACK_EVENT_RETRY_RETENTION_MS;\n state.accepted.set(eventId, expiresAt);\n state.expiryQueue.push({ eventId, expiresAt });\n }\n reservation.resolve(accepted);\n}\n\nfunction pruneSlackEventReplayState(state: SlackEventReplayState, now: number): void {\n while (state.expiryHead < state.expiryQueue.length) {\n const { eventId, expiresAt } = state.expiryQueue[state.expiryHead]!;\n if (now <= expiresAt) {\n break;\n }\n state.expiryHead += 1;\n if (state.accepted.get(eventId) === expiresAt) {\n state.accepted.delete(eventId);\n }\n }\n if (state.expiryHead >= 1_024 && state.expiryHead * 2 >= state.expiryQueue.length) {\n state.expiryQueue = state.expiryQueue.slice(state.expiryHead);\n state.expiryHead = 0;\n }\n}\n\nfunction slackEventReplayCapacityResponse(): Response {\n return new Response(\"service unavailable\", {\n headers: { \"cache-control\": \"no-store\" },\n status: 503,\n });\n}\n\nexport class SlackProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown) {\n const slackRuntime = (runtime as SlackRuntime | undefined) ?? {};\n const resolvedConfig = resolveSlackAdapterConfig(config);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolvedConfig.signingSecret),\n provider: \"Slack\",\n requirement: \"slack.signingSecret or SLACK_SIGNING_SECRET\",\n webhook: config.slack?.webhook,\n });\n const replayState: SlackEventReplayState = {\n accepted: new Map(),\n cacheLimit: slackRuntime.replayCacheLimit ?? SLACK_EVENT_REPLAY_CACHE_LIMIT,\n expiryHead: 0,\n expiryQueue: [],\n inFlight: new Map(),\n now: slackRuntime.now ?? (() => Date.now()),\n };\n super({\n codec: getBuiltinTargetCodec(\"slack\"),\n config,\n id,\n options: {\n ...(resolvedConfig.signingSecret\n ? {\n authenticateWebhookRequest(request: Request, rawBody: string) {\n return authenticateSlackWebhook(\n request,\n rawBody,\n resolvedConfig.signingSecret!,\n replayState.now,\n );\n },\n }\n : {}),\n defaultWebhook: { host: \"127.0.0.1\", path: \"/slack/events\", port: 8787 },\n endpointLabel: \"events endpoint\",\n async handleWebhookPayload(payload) {\n return (\n handleSlackWebhookPayload(payload) ??\n (await handleSlackEventReplay(payload, replayState))\n );\n },\n matchesThread: matchesSlackThread,\n normalizeWebhookPayload: normalizeSlackEventsPayload,\n platform: \"slack\",\n publicUrl: config.slack?.webhook.publicUrl,\n recorderPath: config.slack?.recorder.path\n ? path.resolve(config.slack.recorder.path)\n : undefined,\n settleWebhookRequest({ accepted, payload }) {\n settleSlackEventReplay(replayState, payload, accepted);\n },\n webhook: config.slack?.webhook,\n },\n });\n }\n}\n"]}
|
|
@@ -22,6 +22,6 @@ export declare function normalizeTelegramWebhookPayload(payload: unknown): {
|
|
|
22
22
|
threadId: string;
|
|
23
23
|
};
|
|
24
24
|
export declare class TelegramProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {
|
|
25
|
-
constructor(id: string, config: ProviderConfig, _userName: string);
|
|
25
|
+
constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown);
|
|
26
26
|
}
|
|
27
27
|
export {};
|