@openclaw/crabline 0.1.9 → 0.1.11
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 +47 -22
- package/assets/crabline-banner.svg +20 -0
- package/dist/src/bin/crabline.js.map +1 -1
- package/dist/src/cli/program.d.ts +25 -1
- package/dist/src/cli/program.js +579 -82
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/load.js +59 -6
- package/dist/src/config/load.js.map +1 -1
- package/dist/src/config/schema.d.ts +116 -92
- package/dist/src/config/schema.js +179 -73
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/errors.js +9 -2
- package/dist/src/core/errors.js.map +1 -1
- package/dist/src/core/exit-codes.js.map +1 -1
- package/dist/src/core/matcher.js +7 -6
- package/dist/src/core/matcher.js.map +1 -1
- package/dist/src/core/message-template.js.map +1 -1
- package/dist/src/core/nonces.d.ts +1 -0
- package/dist/src/core/nonces.js +4 -2
- package/dist/src/core/nonces.js.map +1 -1
- package/dist/src/core/reporters.d.ts +1 -0
- package/dist/src/core/reporters.js +42 -4
- package/dist/src/core/reporters.js.map +1 -1
- package/dist/src/core/run.d.ts +3 -0
- package/dist/src/core/run.js +415 -107
- package/dist/src/core/run.js.map +1 -1
- package/dist/src/core/safe-regex.d.ts +3 -0
- package/dist/src/core/safe-regex.js +21 -0
- package/dist/src/core/safe-regex.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/openclaw/artifact-generation.d.ts +43 -0
- package/dist/src/openclaw/artifact-generation.js +363 -0
- package/dist/src/openclaw/artifact-generation.js.map +1 -0
- package/dist/src/openclaw/bridges/matrix.js +97 -12
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +32 -10
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/signal.js +55 -18
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +85 -7
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +124 -30
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +78 -17
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +14 -5
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/outbound-contract.d.ts +5 -0
- package/dist/src/openclaw/outbound-contract.js +40 -0
- package/dist/src/openclaw/outbound-contract.js.map +1 -0
- package/dist/src/openclaw/private-file.d.ts +28 -0
- package/dist/src/openclaw/private-file.js +465 -0
- package/dist/src/openclaw/private-file.js.map +1 -0
- package/dist/src/openclaw/shared.d.ts +21 -2
- package/dist/src/openclaw/shared.js +115 -18
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw/smoke-lock.d.ts +63 -0
- package/dist/src/openclaw/smoke-lock.js +1004 -0
- package/dist/src/openclaw/smoke-lock.js.map +1 -0
- package/dist/src/openclaw.d.ts +16 -8
- package/dist/src/openclaw.js +334 -52
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/providers/builtin/discord.d.ts +10 -1
- package/dist/src/providers/builtin/discord.js +144 -18
- package/dist/src/providers/builtin/discord.js.map +1 -1
- package/dist/src/providers/builtin/external-webhook-auth.d.ts +11 -0
- package/dist/src/providers/builtin/external-webhook-auth.js +10 -0
- package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -0
- package/dist/src/providers/builtin/feishu.d.ts +19 -2
- package/dist/src/providers/builtin/feishu.js +307 -25
- package/dist/src/providers/builtin/feishu.js.map +1 -1
- package/dist/src/providers/builtin/googlechat.d.ts +20 -1
- package/dist/src/providers/builtin/googlechat.js +178 -19
- package/dist/src/providers/builtin/googlechat.js.map +1 -1
- package/dist/src/providers/builtin/imessage.d.ts +4 -0
- package/dist/src/providers/builtin/imessage.js +26 -11
- package/dist/src/providers/builtin/imessage.js.map +1 -1
- package/dist/src/providers/builtin/loopback.js +92 -30
- package/dist/src/providers/builtin/loopback.js.map +1 -1
- package/dist/src/providers/builtin/matrix.d.ts +11 -1
- package/dist/src/providers/builtin/matrix.js +53 -29
- package/dist/src/providers/builtin/matrix.js.map +1 -1
- package/dist/src/providers/builtin/mattermost.d.ts +10 -0
- package/dist/src/providers/builtin/mattermost.js +38 -15
- package/dist/src/providers/builtin/mattermost.js.map +1 -1
- package/dist/src/providers/builtin/msteams.d.ts +17 -1
- package/dist/src/providers/builtin/msteams.js +113 -14
- package/dist/src/providers/builtin/msteams.js.map +1 -1
- package/dist/src/providers/builtin/native-local-mock.d.ts +2 -9
- package/dist/src/providers/builtin/native-local-mock.js +12 -35
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
- package/dist/src/providers/builtin/script.d.ts +11 -15
- package/dist/src/providers/builtin/script.js +566 -104
- package/dist/src/providers/builtin/script.js.map +1 -1
- package/dist/src/providers/builtin/slack.d.ts +6 -0
- package/dist/src/providers/builtin/slack.js +99 -40
- package/dist/src/providers/builtin/slack.js.map +1 -1
- package/dist/src/providers/builtin/telegram.d.ts +6 -0
- package/dist/src/providers/builtin/telegram.js +64 -43
- package/dist/src/providers/builtin/telegram.js.map +1 -1
- package/dist/src/providers/builtin/whatsapp.d.ts +28 -8
- package/dist/src/providers/builtin/whatsapp.js +567 -30
- package/dist/src/providers/builtin/whatsapp.js.map +1 -1
- package/dist/src/providers/builtin/zalo.js +30 -19
- package/dist/src/providers/builtin/zalo.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +3 -3
- package/dist/src/providers/catalog.js +3 -3
- package/dist/src/providers/catalog.js.map +1 -1
- package/dist/src/providers/local-mock.d.ts +11 -2
- package/dist/src/providers/local-mock.js +322 -96
- package/dist/src/providers/local-mock.js.map +1 -1
- package/dist/src/providers/native-ids.d.ts +1 -0
- package/dist/src/providers/native-ids.js +6 -1
- package/dist/src/providers/native-ids.js.map +1 -1
- package/dist/src/providers/recorder.d.ts +30 -2
- package/dist/src/providers/recorder.js +549 -70
- package/dist/src/providers/recorder.js.map +1 -1
- package/dist/src/providers/registry.d.ts +25 -1
- package/dist/src/providers/registry.js +280 -40
- package/dist/src/providers/registry.js.map +1 -1
- package/dist/src/providers/signed-jwt.d.ts +29 -0
- package/dist/src/providers/signed-jwt.js +202 -0
- package/dist/src/providers/signed-jwt.js.map +1 -0
- package/dist/src/providers/slack-ids.js.map +1 -1
- package/dist/src/providers/target-normalizers.d.ts +26 -0
- package/dist/src/providers/target-normalizers.js +276 -0
- package/dist/src/providers/target-normalizers.js.map +1 -0
- package/dist/src/providers/types.d.ts +5 -0
- package/dist/src/providers/types.js.map +1 -1
- package/dist/src/providers/webhook-server.d.ts +2 -0
- package/dist/src/providers/webhook-server.js +72 -25
- package/dist/src/providers/webhook-server.js.map +1 -1
- package/dist/src/servers/http.d.ts +23 -3
- package/dist/src/servers/http.js +213 -20
- package/dist/src/servers/http.js.map +1 -1
- package/dist/src/servers/index.js +20 -21
- package/dist/src/servers/index.js.map +1 -1
- package/dist/src/servers/matrix.js +465 -51
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.d.ts +5 -0
- package/dist/src/servers/mattermost.js +306 -58
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/pending-events.d.ts +2 -0
- package/dist/src/servers/pending-events.js +11 -0
- package/dist/src/servers/pending-events.js.map +1 -0
- package/dist/src/servers/recorder.d.ts +5 -0
- package/dist/src/servers/recorder.js +262 -3
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.d.ts +2 -0
- package/dist/src/servers/signal.js +444 -56
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.d.ts +5 -0
- package/dist/src/servers/slack.js +515 -49
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram.d.ts +111 -0
- package/dist/src/servers/telegram.js +830 -158
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/webhook-target.d.ts +40 -0
- package/dist/src/servers/webhook-target.js +349 -0
- package/dist/src/servers/webhook-target.js.map +1 -0
- package/dist/src/servers/websocket.d.ts +2 -0
- package/dist/src/servers/websocket.js +29 -0
- package/dist/src/servers/websocket.js.map +1 -0
- package/dist/src/servers/whatsapp-baileys-websocket.d.ts +65 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +723 -99
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp-jid.d.ts +5 -0
- package/dist/src/servers/whatsapp-jid.js +32 -0
- package/dist/src/servers/whatsapp-jid.js.map +1 -0
- package/dist/src/servers/whatsapp-wire/binary-node.d.ts +5 -0
- package/dist/src/servers/whatsapp-wire/binary-node.js +87 -20
- package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/crypto.d.ts +6 -1
- package/dist/src/servers/whatsapp-wire/crypto.js +99 -45
- package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/handshake.js +39 -4
- package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +12 -7
- package/dist/src/servers/whatsapp.js +269 -107
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.d.ts +14 -0
- package/dist/src/servers/zalo.js +521 -88
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +197 -19
- package/fixtures/examples/crabline.example.yaml +10 -0
- package/package.json +20 -15
|
@@ -1,49 +1,172 @@
|
|
|
1
|
+
import { createDecipheriv, createHash, timingSafeEqual } from "node:crypto";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { CrablineError } from "../../core/errors.js";
|
|
3
4
|
import { LocalMockProviderAdapter } from "../local-mock.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const FEISHU_MESSAGE_ID_RULE = {
|
|
11
|
-
example: "om_abc123",
|
|
12
|
-
name: "Feishu message_id",
|
|
13
|
-
pattern: /^om_[A-Za-z0-9_-]+$/u,
|
|
14
|
-
};
|
|
15
|
-
export function resolveFeishuAdapterConfig(config, _env = process.env) {
|
|
5
|
+
import { FEISHU_CHAT_ID_RULE, FEISHU_MESSAGE_ID_RULE, getBuiltinTargetCodec, } from "../target-normalizers.js";
|
|
6
|
+
import { authorFromBotFlag, createSecretVerifier, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
|
|
7
|
+
import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
|
|
8
|
+
const FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;
|
|
9
|
+
const FEISHU_REPLAY_CACHE_LIMIT = 2_048;
|
|
10
|
+
export function resolveFeishuAdapterConfig(config, env = process.env) {
|
|
16
11
|
return {
|
|
17
12
|
appId: config.feishu?.appId ?? "local-mock-feishu-app",
|
|
13
|
+
encryptKey: config.feishu?.encryptKey ?? env.FEISHU_ENCRYPT_KEY,
|
|
18
14
|
userName: config.feishu?.userName,
|
|
15
|
+
verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function handleFeishuWebhookPayload(payload) {
|
|
19
|
+
if (isRecord(payload) &&
|
|
20
|
+
payload.type === "url_verification" &&
|
|
21
|
+
typeof payload.challenge === "string") {
|
|
22
|
+
return Response.json({ challenge: payload.challenge });
|
|
23
|
+
}
|
|
24
|
+
if (isRecord(payload) && isRecord(payload.event)) {
|
|
25
|
+
const message = optionalRecord(payload.event, "message");
|
|
26
|
+
if (!message) {
|
|
27
|
+
return new Response(null, { status: 200 });
|
|
28
|
+
}
|
|
29
|
+
const messageType = optionalString(message, "message_type");
|
|
30
|
+
const text = parseFeishuText(optionalString(message, "content"));
|
|
31
|
+
if (messageType !== "text" || !text) {
|
|
32
|
+
return new Response(null, { status: 200 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
export function createFeishuWebhookAuthenticator(config, env = process.env, runtime = {}) {
|
|
38
|
+
return createFeishuWebhookAuthenticatorWithReplay(config, env, runtime);
|
|
39
|
+
}
|
|
40
|
+
function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replayState) {
|
|
41
|
+
const resolved = resolveFeishuAdapterConfig(config, env);
|
|
42
|
+
const validateCallback = resolved.verificationToken
|
|
43
|
+
? createSecretVerifier(resolved.verificationToken)
|
|
44
|
+
: undefined;
|
|
45
|
+
if (!(resolved.encryptKey || validateCallback)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return async (request, rawBody) => {
|
|
49
|
+
let payload;
|
|
50
|
+
try {
|
|
51
|
+
payload = JSON.parse(rawBody);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (!isRecord(payload)) {
|
|
57
|
+
return unauthorizedFeishuWebhook();
|
|
58
|
+
}
|
|
59
|
+
const encryptedEnvelope = payload;
|
|
60
|
+
const encrypted = typeof payload.encrypt === "string";
|
|
61
|
+
let signedRequest = false;
|
|
62
|
+
if (encrypted) {
|
|
63
|
+
if (!resolved.encryptKey) {
|
|
64
|
+
return unauthorizedFeishuWebhook();
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
payload = decryptFeishuWebhookPayload(payload, resolved.encryptKey);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return unauthorizedFeishuWebhook();
|
|
71
|
+
}
|
|
72
|
+
if (!verifyFeishuSignature(request, rawBody, resolved.encryptKey) &&
|
|
73
|
+
!isFeishuUrlVerification(payload)) {
|
|
74
|
+
return unauthorizedFeishuWebhook();
|
|
75
|
+
}
|
|
76
|
+
signedRequest = !isFeishuUrlVerification(payload);
|
|
77
|
+
}
|
|
78
|
+
else if (resolved.encryptKey) {
|
|
79
|
+
return unauthorizedFeishuWebhook();
|
|
80
|
+
}
|
|
81
|
+
if (validateCallback && !validateCallback(readFeishuVerificationToken(payload))) {
|
|
82
|
+
return unauthorizedFeishuWebhook();
|
|
83
|
+
}
|
|
84
|
+
const now = runtime.now?.() ?? Date.now();
|
|
85
|
+
const callbackTimestamp = readFeishuCallbackTimestamp(request, payload, signedRequest);
|
|
86
|
+
if (callbackTimestamp === null) {
|
|
87
|
+
return unauthorizedFeishuWebhook();
|
|
88
|
+
}
|
|
89
|
+
if (callbackTimestamp !== undefined &&
|
|
90
|
+
Math.abs(now - callbackTimestamp) > FEISHU_MAX_CALLBACK_AGE_MS) {
|
|
91
|
+
return unauthorizedFeishuWebhook();
|
|
92
|
+
}
|
|
93
|
+
const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
|
|
94
|
+
if (!replayState) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
pruneFeishuReplayCache(replayState.accepted, now);
|
|
98
|
+
while (callbackKeys.length > 0) {
|
|
99
|
+
if (callbackKeys.some((key) => replayState.accepted.has(key))) {
|
|
100
|
+
return new Response(null, { status: 200 });
|
|
101
|
+
}
|
|
102
|
+
const reservation = callbackKeys
|
|
103
|
+
.map((key) => replayState.inFlight.get(key))
|
|
104
|
+
.find((candidate) => candidate !== undefined);
|
|
105
|
+
if (!reservation) {
|
|
106
|
+
reserveFeishuCallback(replayState, callbackKeys);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
if (await reservation.promise) {
|
|
110
|
+
return new Response(null, { status: 200 });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
19
114
|
};
|
|
20
115
|
}
|
|
21
116
|
export class FeishuProviderAdapter extends LocalMockProviderAdapter {
|
|
22
|
-
constructor(id, config, _userName,
|
|
117
|
+
constructor(id, config, _userName, runtime) {
|
|
118
|
+
const authRuntime = runtime ?? {};
|
|
119
|
+
const env = authRuntime.env ?? process.env;
|
|
120
|
+
const resolved = resolveFeishuAdapterConfig(config, env);
|
|
121
|
+
requireExternalWebhookAuthentication({
|
|
122
|
+
authenticated: Boolean(resolved.encryptKey),
|
|
123
|
+
provider: "Feishu",
|
|
124
|
+
requirement: "feishu.encryptKey or FEISHU_ENCRYPT_KEY for X-Lark-Signature verification",
|
|
125
|
+
webhook: config.feishu?.webhook,
|
|
126
|
+
});
|
|
127
|
+
const replayState = {
|
|
128
|
+
accepted: new Map(),
|
|
129
|
+
inFlight: new Map(),
|
|
130
|
+
};
|
|
131
|
+
const authenticateWebhookRequest = createFeishuWebhookAuthenticatorWithReplay(config, env, authRuntime, replayState);
|
|
132
|
+
const decodePayload = (payload) => resolved.encryptKey ? decryptFeishuWebhookPayload(payload, resolved.encryptKey) : payload;
|
|
23
133
|
super({
|
|
24
|
-
codec:
|
|
25
|
-
channel: FEISHU_CHAT_ID_RULE,
|
|
26
|
-
channelLabel: "Feishu chat_id",
|
|
27
|
-
thread: FEISHU_MESSAGE_ID_RULE,
|
|
28
|
-
threadLabel: "Feishu message_id",
|
|
29
|
-
}),
|
|
134
|
+
codec: getBuiltinTargetCodec("feishu"),
|
|
30
135
|
config,
|
|
31
136
|
id,
|
|
32
137
|
options: {
|
|
138
|
+
...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),
|
|
139
|
+
createWebhookSuccessResponse(payload, responseId) {
|
|
140
|
+
return new Response(JSON.stringify({ id: responseId, ok: true }), {
|
|
141
|
+
headers: { "content-type": "application/json" },
|
|
142
|
+
status: 200,
|
|
143
|
+
});
|
|
144
|
+
},
|
|
33
145
|
defaultWebhook: { host: "127.0.0.1", path: "/feishu/webhook", port: 8795 },
|
|
34
146
|
endpointLabel: "webhook endpoint",
|
|
35
|
-
|
|
147
|
+
handleWebhookPayload: (payload) => handleFeishuWebhookPayload(decodePayload(payload)),
|
|
148
|
+
normalizeWebhookPayload: (payload) => normalizeFeishuWebhookPayload(decodePayload(payload)),
|
|
36
149
|
platform: "feishu",
|
|
37
150
|
publicUrl: config.feishu?.webhook.publicUrl,
|
|
38
151
|
recorderPath: config.feishu?.recorder.path
|
|
39
152
|
? path.resolve(config.feishu.recorder.path)
|
|
40
153
|
: undefined,
|
|
154
|
+
settleWebhookRequest({ accepted, payload, rawBody }) {
|
|
155
|
+
const encryptedEnvelope = readFeishuSettledEnvelope(payload, rawBody);
|
|
156
|
+
const decodedPayload = decodePayload(encryptedEnvelope);
|
|
157
|
+
if (accepted) {
|
|
158
|
+
acceptFeishuCallback(replayState, decodedPayload, encryptedEnvelope, authRuntime.now?.() ?? Date.now());
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
rejectFeishuCallback(replayState, decodedPayload, encryptedEnvelope);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
41
164
|
webhook: config.feishu?.webhook,
|
|
42
165
|
},
|
|
43
166
|
});
|
|
44
167
|
}
|
|
45
168
|
}
|
|
46
|
-
function normalizeFeishuWebhookPayload(payload) {
|
|
169
|
+
export function normalizeFeishuWebhookPayload(payload) {
|
|
47
170
|
if (!isRecord(payload)) {
|
|
48
171
|
throw new CrablineError("Feishu webhook payload must be an object", { kind: "inbound" });
|
|
49
172
|
}
|
|
@@ -57,23 +180,32 @@ function normalizeFeishuWebhookPayload(payload) {
|
|
|
57
180
|
});
|
|
58
181
|
}
|
|
59
182
|
const chatId = optionalString(message, "chat_id");
|
|
183
|
+
const messageType = optionalString(message, "message_type");
|
|
60
184
|
const messageId = optionalString(message, "message_id");
|
|
185
|
+
const rootId = optionalString(message, "root_id");
|
|
61
186
|
const rawContent = optionalString(message, "content");
|
|
62
187
|
const text = parseFeishuText(rawContent);
|
|
188
|
+
if (messageType !== "text") {
|
|
189
|
+
throw new CrablineError("Feishu event payload requires message.message_type=text", {
|
|
190
|
+
kind: "inbound",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
63
193
|
if (!chatId || !text) {
|
|
64
194
|
throw new CrablineError("Feishu event payload requires message.chat_id and message.content", {
|
|
65
195
|
kind: "inbound",
|
|
66
196
|
});
|
|
67
197
|
}
|
|
68
198
|
return {
|
|
69
|
-
author: authorFromBotFlag(
|
|
199
|
+
author: authorFromBotFlag(event
|
|
200
|
+
? ["app", "bot"].includes(optionalString(optionalRecord(event, "sender") ?? {}, "sender_type") ?? "")
|
|
201
|
+
: false),
|
|
70
202
|
...(messageId
|
|
71
203
|
? { id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, "Feishu message_id") }
|
|
72
204
|
: {}),
|
|
73
205
|
raw: payload,
|
|
74
206
|
text,
|
|
75
|
-
threadId:
|
|
76
|
-
? requireNativeInboundId(
|
|
207
|
+
threadId: rootId
|
|
208
|
+
? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, "Feishu root_id")
|
|
77
209
|
: requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, "Feishu chat_id"),
|
|
78
210
|
};
|
|
79
211
|
}
|
|
@@ -86,10 +218,160 @@ function parseFeishuText(content) {
|
|
|
86
218
|
if (isRecord(parsed) && typeof parsed.text === "string") {
|
|
87
219
|
return parsed.text;
|
|
88
220
|
}
|
|
221
|
+
return undefined;
|
|
89
222
|
}
|
|
90
223
|
catch {
|
|
91
224
|
return content;
|
|
92
225
|
}
|
|
93
|
-
|
|
226
|
+
}
|
|
227
|
+
function readFeishuSettledEnvelope(payload, rawBody) {
|
|
228
|
+
if (isRecord(payload)) {
|
|
229
|
+
return payload;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
return JSON.parse(rawBody);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return payload;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export function decryptFeishuWebhookPayload(payload, encryptKey) {
|
|
239
|
+
if (!isRecord(payload) || typeof payload.encrypt !== "string") {
|
|
240
|
+
return payload;
|
|
241
|
+
}
|
|
242
|
+
const encrypted = Buffer.from(payload.encrypt, "base64");
|
|
243
|
+
if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {
|
|
244
|
+
throw new Error("Feishu encrypted payload is truncated.");
|
|
245
|
+
}
|
|
246
|
+
const key = createHash("sha256").update(encryptKey).digest();
|
|
247
|
+
const decipher = createDecipheriv("aes-256-cbc", key, encrypted.subarray(0, 16));
|
|
248
|
+
const decrypted = Buffer.concat([
|
|
249
|
+
decipher.update(encrypted.subarray(16)),
|
|
250
|
+
decipher.final(),
|
|
251
|
+
]).toString("utf8");
|
|
252
|
+
return JSON.parse(decrypted);
|
|
253
|
+
}
|
|
254
|
+
function readFeishuVerificationToken(payload) {
|
|
255
|
+
if (!isRecord(payload)) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const header = optionalRecord(payload, "header");
|
|
259
|
+
return (optionalString(payload, "token") ?? (header ? optionalString(header, "token") : null) ?? null);
|
|
260
|
+
}
|
|
261
|
+
function isFeishuUrlVerification(payload) {
|
|
262
|
+
return (isRecord(payload) &&
|
|
263
|
+
payload.type === "url_verification" &&
|
|
264
|
+
typeof payload.challenge === "string" &&
|
|
265
|
+
payload.challenge.length > 0);
|
|
266
|
+
}
|
|
267
|
+
function verifyFeishuSignature(request, rawBody, encryptKey) {
|
|
268
|
+
const timestamp = request.headers.get("x-lark-request-timestamp");
|
|
269
|
+
const nonce = request.headers.get("x-lark-request-nonce");
|
|
270
|
+
const signature = request.headers.get("x-lark-signature");
|
|
271
|
+
if (!timestamp || !nonce || !signature || !/^[0-9a-f]{64}$/iu.test(signature)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const expected = createHash("sha256")
|
|
275
|
+
.update(timestamp + nonce + encryptKey + rawBody)
|
|
276
|
+
.digest();
|
|
277
|
+
return timingSafeEqual(expected, Buffer.from(signature, "hex"));
|
|
278
|
+
}
|
|
279
|
+
function readFeishuCallbackTimestamp(request, payload, signedRequest) {
|
|
280
|
+
const requestTimestamp = signedRequest
|
|
281
|
+
? request.headers.get("x-lark-request-timestamp")
|
|
282
|
+
: undefined;
|
|
283
|
+
const header = isRecord(payload) ? optionalRecord(payload, "header") : undefined;
|
|
284
|
+
const headerTimestamp = header ? optionalString(header, "create_time") : undefined;
|
|
285
|
+
const value = requestTimestamp ?? headerTimestamp;
|
|
286
|
+
if (!value) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
if (!/^\d+$/u.test(value)) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
const timestamp = Number(value);
|
|
293
|
+
if (!Number.isSafeInteger(timestamp)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
if (timestamp >= 100_000_000_000_000) {
|
|
297
|
+
return Math.floor(timestamp / 1_000);
|
|
298
|
+
}
|
|
299
|
+
if (timestamp >= 100_000_000_000) {
|
|
300
|
+
return timestamp;
|
|
301
|
+
}
|
|
302
|
+
return timestamp * 1_000;
|
|
303
|
+
}
|
|
304
|
+
function readFeishuCallbackKeys(payload, encryptedEnvelope) {
|
|
305
|
+
if (!isRecord(payload) || isFeishuUrlVerification(payload)) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
const event = optionalRecord(payload, "event");
|
|
309
|
+
const message = event ? optionalRecord(event, "message") : undefined;
|
|
310
|
+
const header = optionalRecord(payload, "header");
|
|
311
|
+
const messageId = message ? optionalString(message, "message_id") : undefined;
|
|
312
|
+
const eventId = header ? optionalString(header, "event_id") : undefined;
|
|
313
|
+
const encrypted = isRecord(encryptedEnvelope)
|
|
314
|
+
? optionalString(encryptedEnvelope, "encrypt")
|
|
315
|
+
: undefined;
|
|
316
|
+
return [
|
|
317
|
+
...(messageId ? [`message:${messageId}`] : []),
|
|
318
|
+
...(eventId ? [`event:${eventId}`] : []),
|
|
319
|
+
...(encrypted ? [`encrypted:${createHash("sha256").update(encrypted).digest("hex")}`] : []),
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
function reserveFeishuCallback(replayState, callbackKeys) {
|
|
323
|
+
let resolveReservation;
|
|
324
|
+
const promise = new Promise((resolve) => {
|
|
325
|
+
resolveReservation = resolve;
|
|
326
|
+
});
|
|
327
|
+
const reservation = {
|
|
328
|
+
keys: new Set(callbackKeys),
|
|
329
|
+
promise,
|
|
330
|
+
resolve: resolveReservation,
|
|
331
|
+
};
|
|
332
|
+
for (const key of callbackKeys) {
|
|
333
|
+
replayState.inFlight.set(key, reservation);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function acceptFeishuCallback(replayState, payload, encryptedEnvelope, now) {
|
|
337
|
+
const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
|
|
338
|
+
pruneFeishuReplayCache(replayState.accepted, now);
|
|
339
|
+
for (const key of callbackKeys) {
|
|
340
|
+
replayState.accepted.set(key, now);
|
|
341
|
+
}
|
|
342
|
+
const reservations = new Set(callbackKeys
|
|
343
|
+
.map((key) => replayState.inFlight.get(key))
|
|
344
|
+
.filter((reservation) => reservation !== undefined));
|
|
345
|
+
for (const reservation of reservations) {
|
|
346
|
+
releaseFeishuReservation(replayState, reservation, true);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function rejectFeishuCallback(replayState, payload, encryptedEnvelope) {
|
|
350
|
+
const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
|
|
351
|
+
const reservations = new Set(callbackKeys
|
|
352
|
+
.map((key) => replayState.inFlight.get(key))
|
|
353
|
+
.filter((reservation) => reservation !== undefined));
|
|
354
|
+
for (const reservation of reservations) {
|
|
355
|
+
releaseFeishuReservation(replayState, reservation, false);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function releaseFeishuReservation(replayState, reservation, accepted) {
|
|
359
|
+
for (const key of reservation.keys) {
|
|
360
|
+
if (replayState.inFlight.get(key) === reservation) {
|
|
361
|
+
replayState.inFlight.delete(key);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
reservation.resolve(accepted);
|
|
365
|
+
}
|
|
366
|
+
function pruneFeishuReplayCache(recentCallbacks, now) {
|
|
367
|
+
for (const [key, acceptedAt] of recentCallbacks) {
|
|
368
|
+
if (now - acceptedAt > FEISHU_MAX_CALLBACK_AGE_MS ||
|
|
369
|
+
recentCallbacks.size > FEISHU_REPLAY_CACHE_LIMIT) {
|
|
370
|
+
recentCallbacks.delete(key);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function unauthorizedFeishuWebhook() {
|
|
375
|
+
return new Response("unauthorized", { status: 401 });
|
|
94
376
|
}
|
|
95
377
|
//# sourceMappingURL=feishu.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../../src/providers/builtin/feishu.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,kCAAkC,EAClC,QAAQ,EACR,cAAc,EACd,cAAc,EACd,sBAAsB,GAEvB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,mBAAmB,GAAiB;IACxC,OAAO,EAAE,WAAW;IACpB,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,sBAAsB;CAChC,CAAC;AAEF,MAAM,sBAAsB,GAAiB;IAC3C,OAAO,EAAE,WAAW;IACpB,IAAI,EAAE,mBAAmB;IACzB,OAAO,EAAE,sBAAsB;CAChC,CAAC;AAEF,MAAM,UAAU,0BAA0B,CACxC,MAAsB,EACtB,IAAI,GAAsB,OAAO,CAAC,GAAG;IAErC,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,uBAAuB;QACtD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ;KAClC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,qBAAsB,SAAQ,wBAAwB;IACjE,YAAY,EAAU,EAAE,MAAsB,EAAE,SAAiB,EAAE,QAAkB;QACnF,KAAK,CAAC;YACJ,KAAK,EAAE,uBAAuB,CAAC;gBAC7B,OAAO,EAAE,mBAAmB;gBAC5B,YAAY,EAAE,gBAAgB;gBAC9B,MAAM,EAAE,sBAAsB;gBAC9B,WAAW,EAAE,mBAAmB;aACjC,CAAC;YACF,MAAM;YACN,EAAE;YACF,OAAO,EAAE;gBACP,cAAc,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,IAAI,EAAE;gBAC1E,aAAa,EAAE,kBAAkB;gBACjC,uBAAuB,EAAE,6BAA6B;gBACtD,QAAQ,EAAE,QAAQ;gBAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS;gBAC3C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI;oBACxC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAC3C,CAAC,CAAC,SAAS;gBACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO;aAChC;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAED,SAAS,6BAA6B,CAAC,OAAgB;IACrD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,aAAa,CAAC,0CAA0C,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,kCAAkC,CAAC;YACxC,WAAW,EAAE,mBAAmB;YAChC,OAAO;YACP,UAAU,EAAE,sBAAsB;SACnC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,aAAa,CAAC,mEAAmE,EAAE;YAC3F,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC;QAChC,GAAG,CAAC,SAAS;YACX,CAAC,CAAC,EAAE,EAAE,EAAE,sBAAsB,CAAC,SAAS,EAAE,sBAAsB,EAAE,mBAAmB,CAAC,EAAE;YACxF,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,EAAE,OAAO;QACZ,IAAI;QACJ,QAAQ,EAAE,SAAS;YACjB,CAAC,CAAC,sBAAsB,CAAC,SAAS,EAAE,sBAAsB,EAAE,mBAAmB,CAAC;YAChF,CAAC,CAAC,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,EAAE,gBAAgB,CAAC;KAC1E,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAA2B;IAClD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACxD,OAAO,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
1
|
+
{"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../../src/providers/builtin/feishu.ts"],"names":[],"mappings":"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","sourcesContent":["import { createDecipheriv, createHash, 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 type { ProviderAdapter } from \"../types.js\";\nimport {\n FEISHU_CHAT_ID_RULE,\n FEISHU_MESSAGE_ID_RULE,\n getBuiltinTargetCodec,\n} from \"../target-normalizers.js\";\nimport {\n authorFromBotFlag,\n createSecretVerifier,\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\ntype FeishuEnvironment = Partial<\n Pick<NodeJS.ProcessEnv, \"FEISHU_ENCRYPT_KEY\" | \"FEISHU_VERIFICATION_TOKEN\">\n>;\n\ntype FeishuAuthRuntime = {\n now?: (() => number) | undefined;\n};\n\ntype FeishuReplayReservation = {\n keys: Set<string>;\n promise: Promise<boolean>;\n resolve: (accepted: boolean) => void;\n};\n\ntype FeishuReplayState = {\n accepted: Map<string, number>;\n inFlight: Map<string, FeishuReplayReservation>;\n};\n\nconst FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;\nconst FEISHU_REPLAY_CACHE_LIMIT = 2_048;\n\nexport function resolveFeishuAdapterConfig(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n) {\n return {\n appId: config.feishu?.appId ?? \"local-mock-feishu-app\",\n encryptKey: config.feishu?.encryptKey ?? env.FEISHU_ENCRYPT_KEY,\n userName: config.feishu?.userName,\n verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,\n };\n}\n\nexport function handleFeishuWebhookPayload(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 if (isRecord(payload) && isRecord(payload.event)) {\n const message = optionalRecord(payload.event, \"message\");\n if (!message) {\n return new Response(null, { status: 200 });\n }\n const messageType = optionalString(message, \"message_type\");\n const text = parseFeishuText(optionalString(message, \"content\"));\n if (messageType !== \"text\" || !text) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n}\n\nexport function createFeishuWebhookAuthenticator(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n runtime: FeishuAuthRuntime = {},\n) {\n return createFeishuWebhookAuthenticatorWithReplay(config, env, runtime);\n}\n\nfunction createFeishuWebhookAuthenticatorWithReplay(\n config: ProviderConfig,\n env: FeishuEnvironment,\n runtime: FeishuAuthRuntime,\n replayState?: FeishuReplayState,\n) {\n const resolved = resolveFeishuAdapterConfig(config, env);\n const validateCallback = resolved.verificationToken\n ? createSecretVerifier(resolved.verificationToken)\n : undefined;\n if (!(resolved.encryptKey || validateCallback)) {\n return undefined;\n }\n return async (request: Request, rawBody: string): Promise<Response | undefined> => {\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody) as unknown;\n } catch {\n return undefined;\n }\n if (!isRecord(payload)) {\n return unauthorizedFeishuWebhook();\n }\n const encryptedEnvelope = payload;\n\n const encrypted = typeof payload.encrypt === \"string\";\n let signedRequest = false;\n if (encrypted) {\n if (!resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n try {\n payload = decryptFeishuWebhookPayload(payload, resolved.encryptKey);\n } catch {\n return unauthorizedFeishuWebhook();\n }\n if (\n !verifyFeishuSignature(request, rawBody, resolved.encryptKey) &&\n !isFeishuUrlVerification(payload)\n ) {\n return unauthorizedFeishuWebhook();\n }\n signedRequest = !isFeishuUrlVerification(payload);\n } else if (resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n\n if (validateCallback && !validateCallback(readFeishuVerificationToken(payload))) {\n return unauthorizedFeishuWebhook();\n }\n const now = runtime.now?.() ?? Date.now();\n const callbackTimestamp = readFeishuCallbackTimestamp(request, payload, signedRequest);\n if (callbackTimestamp === null) {\n return unauthorizedFeishuWebhook();\n }\n if (\n callbackTimestamp !== undefined &&\n Math.abs(now - callbackTimestamp) > FEISHU_MAX_CALLBACK_AGE_MS\n ) {\n return unauthorizedFeishuWebhook();\n }\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n if (!replayState) {\n return undefined;\n }\n pruneFeishuReplayCache(replayState.accepted, now);\n while (callbackKeys.length > 0) {\n if (callbackKeys.some((key) => replayState.accepted.has(key))) {\n return new Response(null, { status: 200 });\n }\n const reservation = callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .find((candidate) => candidate !== undefined);\n if (!reservation) {\n reserveFeishuCallback(replayState, callbackKeys);\n break;\n }\n if (await reservation.promise) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n };\n}\n\nexport class FeishuProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown) {\n const authRuntime =\n (runtime as (FeishuAuthRuntime & { env?: FeishuEnvironment }) | undefined) ?? {};\n const env = authRuntime.env ?? process.env;\n const resolved = resolveFeishuAdapterConfig(config, env);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolved.encryptKey),\n provider: \"Feishu\",\n requirement: \"feishu.encryptKey or FEISHU_ENCRYPT_KEY for X-Lark-Signature verification\",\n webhook: config.feishu?.webhook,\n });\n const replayState: FeishuReplayState = {\n accepted: new Map(),\n inFlight: new Map(),\n };\n const authenticateWebhookRequest = createFeishuWebhookAuthenticatorWithReplay(\n config,\n env,\n authRuntime,\n replayState,\n );\n const decodePayload = (payload: unknown) =>\n resolved.encryptKey ? decryptFeishuWebhookPayload(payload, resolved.encryptKey) : payload;\n super({\n codec: getBuiltinTargetCodec(\"feishu\"),\n config,\n id,\n options: {\n ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),\n createWebhookSuccessResponse(payload, responseId) {\n return new Response(JSON.stringify({ id: responseId, ok: true }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n });\n },\n defaultWebhook: { host: \"127.0.0.1\", path: \"/feishu/webhook\", port: 8795 },\n endpointLabel: \"webhook endpoint\",\n handleWebhookPayload: (payload) => handleFeishuWebhookPayload(decodePayload(payload)),\n normalizeWebhookPayload: (payload) => normalizeFeishuWebhookPayload(decodePayload(payload)),\n platform: \"feishu\",\n publicUrl: config.feishu?.webhook.publicUrl,\n recorderPath: config.feishu?.recorder.path\n ? path.resolve(config.feishu.recorder.path)\n : undefined,\n settleWebhookRequest({ accepted, payload, rawBody }) {\n const encryptedEnvelope = readFeishuSettledEnvelope(payload, rawBody);\n const decodedPayload = decodePayload(encryptedEnvelope);\n if (accepted) {\n acceptFeishuCallback(\n replayState,\n decodedPayload,\n encryptedEnvelope,\n authRuntime.now?.() ?? Date.now(),\n );\n } else {\n rejectFeishuCallback(replayState, decodedPayload, encryptedEnvelope);\n }\n },\n webhook: config.feishu?.webhook,\n },\n });\n }\n}\n\nexport function normalizeFeishuWebhookPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Feishu webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n if (!message) {\n return genericMockPayloadWithNativeThread({\n channelRule: FEISHU_CHAT_ID_RULE,\n payload,\n threadRule: FEISHU_MESSAGE_ID_RULE,\n });\n }\n\n const chatId = optionalString(message, \"chat_id\");\n const messageType = optionalString(message, \"message_type\");\n const messageId = optionalString(message, \"message_id\");\n const rootId = optionalString(message, \"root_id\");\n const rawContent = optionalString(message, \"content\");\n const text = parseFeishuText(rawContent);\n if (messageType !== \"text\") {\n throw new CrablineError(\"Feishu event payload requires message.message_type=text\", {\n kind: \"inbound\",\n });\n }\n if (!chatId || !text) {\n throw new CrablineError(\"Feishu event payload requires message.chat_id and message.content\", {\n kind: \"inbound\",\n });\n }\n\n return {\n author: authorFromBotFlag(\n event\n ? [\"app\", \"bot\"].includes(\n optionalString(optionalRecord(event, \"sender\") ?? {}, \"sender_type\") ?? \"\",\n )\n : false,\n ),\n ...(messageId\n ? { id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, \"Feishu message_id\") }\n : {}),\n raw: payload,\n text,\n threadId: rootId\n ? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, \"Feishu root_id\")\n : requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, \"Feishu chat_id\"),\n };\n}\n\nfunction parseFeishuText(content: string | undefined): string | undefined {\n if (!content) {\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(content);\n if (isRecord(parsed) && typeof parsed.text === \"string\") {\n return parsed.text;\n }\n return undefined;\n } catch {\n return content;\n }\n}\n\nfunction readFeishuSettledEnvelope(payload: unknown, rawBody: string): unknown {\n if (isRecord(payload)) {\n return payload;\n }\n try {\n return JSON.parse(rawBody) as unknown;\n } catch {\n return payload;\n }\n}\n\nexport function decryptFeishuWebhookPayload(payload: unknown, encryptKey: string): unknown {\n if (!isRecord(payload) || typeof payload.encrypt !== \"string\") {\n return payload;\n }\n const encrypted = Buffer.from(payload.encrypt, \"base64\");\n if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {\n throw new Error(\"Feishu encrypted payload is truncated.\");\n }\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const decipher = createDecipheriv(\"aes-256-cbc\", key, encrypted.subarray(0, 16));\n const decrypted = Buffer.concat([\n decipher.update(encrypted.subarray(16)),\n decipher.final(),\n ]).toString(\"utf8\");\n return JSON.parse(decrypted) as unknown;\n}\n\nfunction readFeishuVerificationToken(payload: unknown): string | null {\n if (!isRecord(payload)) {\n return null;\n }\n const header = optionalRecord(payload, \"header\");\n return (\n optionalString(payload, \"token\") ?? (header ? optionalString(header, \"token\") : null) ?? null\n );\n}\n\nfunction isFeishuUrlVerification(payload: unknown): boolean {\n return (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\" &&\n payload.challenge.length > 0\n );\n}\n\nfunction verifyFeishuSignature(request: Request, rawBody: string, encryptKey: string): boolean {\n const timestamp = request.headers.get(\"x-lark-request-timestamp\");\n const nonce = request.headers.get(\"x-lark-request-nonce\");\n const signature = request.headers.get(\"x-lark-signature\");\n if (!timestamp || !nonce || !signature || !/^[0-9a-f]{64}$/iu.test(signature)) {\n return false;\n }\n const expected = createHash(\"sha256\")\n .update(timestamp + nonce + encryptKey + rawBody)\n .digest();\n return timingSafeEqual(expected, Buffer.from(signature, \"hex\"));\n}\n\nfunction readFeishuCallbackTimestamp(\n request: Request,\n payload: unknown,\n signedRequest: boolean,\n): number | null | undefined {\n const requestTimestamp = signedRequest\n ? request.headers.get(\"x-lark-request-timestamp\")\n : undefined;\n const header = isRecord(payload) ? optionalRecord(payload, \"header\") : undefined;\n const headerTimestamp = header ? optionalString(header, \"create_time\") : undefined;\n const value = requestTimestamp ?? headerTimestamp;\n if (!value) {\n return undefined;\n }\n if (!/^\\d+$/u.test(value)) {\n return null;\n }\n const timestamp = Number(value);\n if (!Number.isSafeInteger(timestamp)) {\n return null;\n }\n if (timestamp >= 100_000_000_000_000) {\n return Math.floor(timestamp / 1_000);\n }\n if (timestamp >= 100_000_000_000) {\n return timestamp;\n }\n return timestamp * 1_000;\n}\n\nfunction readFeishuCallbackKeys(payload: unknown, encryptedEnvelope: unknown): string[] {\n if (!isRecord(payload) || isFeishuUrlVerification(payload)) {\n return [];\n }\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n const header = optionalRecord(payload, \"header\");\n const messageId = message ? optionalString(message, \"message_id\") : undefined;\n const eventId = header ? optionalString(header, \"event_id\") : undefined;\n const encrypted = isRecord(encryptedEnvelope)\n ? optionalString(encryptedEnvelope, \"encrypt\")\n : undefined;\n return [\n ...(messageId ? [`message:${messageId}`] : []),\n ...(eventId ? [`event:${eventId}`] : []),\n ...(encrypted ? [`encrypted:${createHash(\"sha256\").update(encrypted).digest(\"hex\")}`] : []),\n ];\n}\n\nfunction reserveFeishuCallback(replayState: FeishuReplayState, callbackKeys: string[]): void {\n let resolveReservation!: (accepted: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveReservation = resolve;\n });\n const reservation: FeishuReplayReservation = {\n keys: new Set(callbackKeys),\n promise,\n resolve: resolveReservation,\n };\n for (const key of callbackKeys) {\n replayState.inFlight.set(key, reservation);\n }\n}\n\nfunction acceptFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n now: number,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n pruneFeishuReplayCache(replayState.accepted, now);\n for (const key of callbackKeys) {\n replayState.accepted.set(key, now);\n }\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, true);\n }\n}\n\nfunction rejectFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, false);\n }\n}\n\nfunction releaseFeishuReservation(\n replayState: FeishuReplayState,\n reservation: FeishuReplayReservation,\n accepted: boolean,\n): void {\n for (const key of reservation.keys) {\n if (replayState.inFlight.get(key) === reservation) {\n replayState.inFlight.delete(key);\n }\n }\n reservation.resolve(accepted);\n}\n\nfunction pruneFeishuReplayCache(recentCallbacks: Map<string, number>, now: number): void {\n for (const [key, acceptedAt] of recentCallbacks) {\n if (\n now - acceptedAt > FEISHU_MAX_CALLBACK_AGE_MS ||\n recentCallbacks.size > FEISHU_REPLAY_CACHE_LIMIT\n ) {\n recentCallbacks.delete(key);\n }\n }\n}\n\nfunction unauthorizedFeishuWebhook(): Response {\n return new Response(\"unauthorized\", { status: 401 });\n}\n"]}
|
|
@@ -4,8 +4,27 @@ import type { ProviderAdapter } from "../types.js";
|
|
|
4
4
|
export declare function resolveGoogleChatAdapterConfig(config: ProviderConfig, _env?: NodeJS.ProcessEnv): {
|
|
5
5
|
endpointUrl: string | undefined;
|
|
6
6
|
projectNumber: string;
|
|
7
|
+
pubsubServiceAccountEmail: string | undefined;
|
|
7
8
|
userName: string | undefined;
|
|
8
9
|
};
|
|
10
|
+
type GoogleChatAuthRuntime = {
|
|
11
|
+
fetch?: typeof fetch | undefined;
|
|
12
|
+
keyFetchTimeoutMs?: number | undefined;
|
|
13
|
+
now?: (() => number) | undefined;
|
|
14
|
+
unknownKeyCooldownMs?: number | undefined;
|
|
15
|
+
};
|
|
16
|
+
export declare function createGoogleChatWebhookAuthenticator(config: ProviderConfig, runtime?: GoogleChatAuthRuntime): ((request: Request, rawBody: string) => Promise<Response | undefined>) | undefined;
|
|
17
|
+
export declare function matchesGoogleChatThread(candidateThreadId: string, expectedThreadId: string | undefined, target?: {
|
|
18
|
+
channelId?: string | undefined;
|
|
19
|
+
}): boolean;
|
|
9
20
|
export declare class GoogleChatProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {
|
|
10
|
-
constructor(id: string, config: ProviderConfig, _userName: string,
|
|
21
|
+
constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown);
|
|
11
22
|
}
|
|
23
|
+
export declare function normalizeGoogleChatWebhookPayload(payload: unknown): {
|
|
24
|
+
author?: "assistant" | "system" | "user" | undefined;
|
|
25
|
+
authorIsBot?: boolean;
|
|
26
|
+
id?: string | undefined;
|
|
27
|
+
raw: {};
|
|
28
|
+
text?: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
export {};
|