@openclaw/crabline 0.1.10 → 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.
Files changed (197) hide show
  1. package/README.md +99 -12
  2. package/dist/src/bin/crabline.js +3 -1
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +5 -1
  5. package/dist/src/cli/program.js +264 -50
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +39 -3
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +20 -0
  10. package/dist/src/config/schema.js +163 -35
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.d.ts +1 -0
  13. package/dist/src/core/errors.js +6 -3
  14. package/dist/src/core/errors.js.map +1 -1
  15. package/dist/src/core/http-path.d.ts +1 -0
  16. package/dist/src/core/http-path.js +14 -0
  17. package/dist/src/core/http-path.js.map +1 -0
  18. package/dist/src/core/matcher.d.ts +3 -1
  19. package/dist/src/core/matcher.js +58 -2
  20. package/dist/src/core/matcher.js.map +1 -1
  21. package/dist/src/core/nonces.d.ts +2 -0
  22. package/dist/src/core/nonces.js +11 -1
  23. package/dist/src/core/nonces.js.map +1 -1
  24. package/dist/src/core/reporters.d.ts +5 -0
  25. package/dist/src/core/reporters.js +26 -2
  26. package/dist/src/core/reporters.js.map +1 -1
  27. package/dist/src/core/run.d.ts +3 -1
  28. package/dist/src/core/run.js +178 -66
  29. package/dist/src/core/run.js.map +1 -1
  30. package/dist/src/matrix-ids.d.ts +5 -0
  31. package/dist/src/matrix-ids.js +83 -0
  32. package/dist/src/matrix-ids.js.map +1 -0
  33. package/dist/src/openclaw/artifact-generation.d.ts +12 -2
  34. package/dist/src/openclaw/artifact-generation.js +436 -15
  35. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  36. package/dist/src/openclaw/bridges/matrix.js +21 -4
  37. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  38. package/dist/src/openclaw/bridges/mattermost.js +13 -8
  39. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  40. package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
  41. package/dist/src/openclaw/bridges/probe-response.js +5 -0
  42. package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
  43. package/dist/src/openclaw/bridges/signal.js +15 -6
  44. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  45. package/dist/src/openclaw/bridges/slack.js +116 -23
  46. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  47. package/dist/src/openclaw/bridges/telegram.js +45 -19
  48. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  49. package/dist/src/openclaw/bridges/whatsapp.js +32 -16
  50. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  51. package/dist/src/openclaw/bridges/zalo.js +8 -3
  52. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  53. package/dist/src/openclaw/private-file.d.ts +40 -3
  54. package/dist/src/openclaw/private-file.js +2070 -95
  55. package/dist/src/openclaw/private-file.js.map +1 -1
  56. package/dist/src/openclaw/shared.d.ts +1 -0
  57. package/dist/src/openclaw/shared.js +48 -13
  58. package/dist/src/openclaw/shared.js.map +1 -1
  59. package/dist/src/openclaw/smoke-lock.d.ts +9 -0
  60. package/dist/src/openclaw/smoke-lock.js +696 -187
  61. package/dist/src/openclaw/smoke-lock.js.map +1 -1
  62. package/dist/src/openclaw.js +352 -29
  63. package/dist/src/openclaw.js.map +1 -1
  64. package/dist/src/platform/process-owned-lock.d.ts +11 -0
  65. package/dist/src/platform/process-owned-lock.js +1698 -0
  66. package/dist/src/platform/process-owned-lock.js.map +1 -0
  67. package/dist/src/platform/recorder-directory.d.ts +1 -0
  68. package/dist/src/platform/recorder-directory.js +14 -0
  69. package/dist/src/platform/recorder-directory.js.map +1 -0
  70. package/dist/src/platform/windows-acl.d.ts +18 -0
  71. package/dist/src/platform/windows-acl.js +1367 -0
  72. package/dist/src/platform/windows-acl.js.map +1 -0
  73. package/dist/src/platform/windows-lock-root.d.ts +13 -0
  74. package/dist/src/platform/windows-lock-root.js +98 -0
  75. package/dist/src/platform/windows-lock-root.js.map +1 -0
  76. package/dist/src/providers/builtin/discord.d.ts +4 -0
  77. package/dist/src/providers/builtin/discord.js +52 -9
  78. package/dist/src/providers/builtin/discord.js.map +1 -1
  79. package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
  80. package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
  81. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
  82. package/dist/src/providers/builtin/feishu.d.ts +1 -0
  83. package/dist/src/providers/builtin/feishu.js +96 -26
  84. package/dist/src/providers/builtin/feishu.js.map +1 -1
  85. package/dist/src/providers/builtin/googlechat.d.ts +1 -0
  86. package/dist/src/providers/builtin/googlechat.js +95 -26
  87. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  88. package/dist/src/providers/builtin/imessage.js +60 -17
  89. package/dist/src/providers/builtin/imessage.js.map +1 -1
  90. package/dist/src/providers/builtin/loopback.js +88 -35
  91. package/dist/src/providers/builtin/loopback.js.map +1 -1
  92. package/dist/src/providers/builtin/matrix.d.ts +2 -2
  93. package/dist/src/providers/builtin/matrix.js +78 -21
  94. package/dist/src/providers/builtin/matrix.js.map +1 -1
  95. package/dist/src/providers/builtin/mattermost.d.ts +11 -7
  96. package/dist/src/providers/builtin/mattermost.js +138 -24
  97. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  98. package/dist/src/providers/builtin/msteams.d.ts +1 -0
  99. package/dist/src/providers/builtin/msteams.js +118 -38
  100. package/dist/src/providers/builtin/msteams.js.map +1 -1
  101. package/dist/src/providers/builtin/native-local-mock.js +4 -4
  102. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  103. package/dist/src/providers/builtin/script.js +1057 -146
  104. package/dist/src/providers/builtin/script.js.map +1 -1
  105. package/dist/src/providers/builtin/slack.d.ts +8 -1
  106. package/dist/src/providers/builtin/slack.js +327 -24
  107. package/dist/src/providers/builtin/slack.js.map +1 -1
  108. package/dist/src/providers/builtin/telegram.d.ts +1 -1
  109. package/dist/src/providers/builtin/telegram.js +55 -14
  110. package/dist/src/providers/builtin/telegram.js.map +1 -1
  111. package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
  112. package/dist/src/providers/builtin/whatsapp.js +157 -29
  113. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  114. package/dist/src/providers/builtin/zalo.d.ts +5 -2
  115. package/dist/src/providers/builtin/zalo.js +31 -7
  116. package/dist/src/providers/builtin/zalo.js.map +1 -1
  117. package/dist/src/providers/catalog.d.ts +1 -1
  118. package/dist/src/providers/catalog.js +1 -1
  119. package/dist/src/providers/catalog.js.map +1 -1
  120. package/dist/src/providers/local-mock.d.ts +4 -0
  121. package/dist/src/providers/local-mock.js +162 -35
  122. package/dist/src/providers/local-mock.js.map +1 -1
  123. package/dist/src/providers/native-ids.d.ts +2 -0
  124. package/dist/src/providers/native-ids.js +3 -0
  125. package/dist/src/providers/native-ids.js.map +1 -1
  126. package/dist/src/providers/recorder.d.ts +14 -3
  127. package/dist/src/providers/recorder.js +532 -42
  128. package/dist/src/providers/recorder.js.map +1 -1
  129. package/dist/src/providers/registry.d.ts +1 -0
  130. package/dist/src/providers/registry.js +65 -20
  131. package/dist/src/providers/registry.js.map +1 -1
  132. package/dist/src/providers/signed-jwt.d.ts +6 -1
  133. package/dist/src/providers/signed-jwt.js +241 -50
  134. package/dist/src/providers/signed-jwt.js.map +1 -1
  135. package/dist/src/providers/slack-ids.d.ts +1 -0
  136. package/dist/src/providers/slack-ids.js +5 -0
  137. package/dist/src/providers/slack-ids.js.map +1 -1
  138. package/dist/src/providers/target-normalizers.d.ts +9 -1
  139. package/dist/src/providers/target-normalizers.js +177 -58
  140. package/dist/src/providers/target-normalizers.js.map +1 -1
  141. package/dist/src/providers/webhook-server.d.ts +3 -0
  142. package/dist/src/providers/webhook-server.js +268 -47
  143. package/dist/src/providers/webhook-server.js.map +1 -1
  144. package/dist/src/servers/http.d.ts +14 -2
  145. package/dist/src/servers/http.js +229 -58
  146. package/dist/src/servers/http.js.map +1 -1
  147. package/dist/src/servers/matrix.d.ts +3 -0
  148. package/dist/src/servers/matrix.js +268 -110
  149. package/dist/src/servers/matrix.js.map +1 -1
  150. package/dist/src/servers/mattermost.d.ts +5 -0
  151. package/dist/src/servers/mattermost.js +389 -91
  152. package/dist/src/servers/mattermost.js.map +1 -1
  153. package/dist/src/servers/recorder.d.ts +11 -0
  154. package/dist/src/servers/recorder.js +1241 -22
  155. package/dist/src/servers/recorder.js.map +1 -1
  156. package/dist/src/servers/signal.d.ts +1 -0
  157. package/dist/src/servers/signal.js +279 -44
  158. package/dist/src/servers/signal.js.map +1 -1
  159. package/dist/src/servers/slack.d.ts +15 -0
  160. package/dist/src/servers/slack.js +259 -51
  161. package/dist/src/servers/slack.js.map +1 -1
  162. package/dist/src/servers/telegram-identity.d.ts +6 -0
  163. package/dist/src/servers/telegram-identity.js +27 -0
  164. package/dist/src/servers/telegram-identity.js.map +1 -0
  165. package/dist/src/servers/telegram.d.ts +17 -6
  166. package/dist/src/servers/telegram.js +1193 -118
  167. package/dist/src/servers/telegram.js.map +1 -1
  168. package/dist/src/servers/webhook-target.d.ts +12 -4
  169. package/dist/src/servers/webhook-target.js +226 -28
  170. package/dist/src/servers/webhook-target.js.map +1 -1
  171. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
  172. package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
  173. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  174. package/dist/src/servers/whatsapp-jid.d.ts +1 -0
  175. package/dist/src/servers/whatsapp-jid.js +9 -5
  176. package/dist/src/servers/whatsapp-jid.js.map +1 -1
  177. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
  178. package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
  179. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  180. package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
  181. package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
  182. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  183. package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
  184. package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
  185. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  186. package/dist/src/servers/whatsapp-wire/signal.js +3 -3
  187. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  188. package/dist/src/servers/whatsapp.d.ts +7 -0
  189. package/dist/src/servers/whatsapp.js +149 -24
  190. package/dist/src/servers/whatsapp.js.map +1 -1
  191. package/dist/src/servers/zalo.d.ts +1 -0
  192. package/dist/src/servers/zalo.js +193 -82
  193. package/dist/src/servers/zalo.js.map +1 -1
  194. package/docs/channel-setup.md +185 -43
  195. package/fixtures/examples/crabline.example.yaml +3 -2
  196. package/package.json +18 -10
  197. package/fixtures/examples/openclaw-bridge.yaml +0 -125
@@ -6,11 +6,21 @@ import { FEISHU_CHAT_ID_RULE, FEISHU_MESSAGE_ID_RULE, getBuiltinTargetCodec, } f
6
6
  import { authorFromBotFlag, createSecretVerifier, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
7
7
  import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
8
8
  const FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;
9
+ const FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES = 1024 * 1024;
10
+ const FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH = Math.ceil(FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES / 3) * 4;
9
11
  const FEISHU_REPLAY_CACHE_LIMIT = 2_048;
12
+ const FEISHU_MAX_WEBHOOK_BODY_BYTES = 1024 * 1024;
10
13
  export function resolveFeishuAdapterConfig(config, env = process.env) {
14
+ const configuredEncryptKey = config.feishu?.encryptKey;
15
+ const encryptKey = configuredEncryptKey ?? env.FEISHU_ENCRYPT_KEY;
16
+ if (encryptKey !== undefined && !encryptKey.trim()) {
17
+ throw new CrablineError(configuredEncryptKey === undefined
18
+ ? "FEISHU_ENCRYPT_KEY must not be empty or whitespace-only."
19
+ : "Feishu encryptKey must not be empty or whitespace-only.", { kind: "config" });
20
+ }
11
21
  return {
12
22
  appId: config.feishu?.appId ?? "local-mock-feishu-app",
13
- encryptKey: config.feishu?.encryptKey ?? env.FEISHU_ENCRYPT_KEY,
23
+ encryptKey,
14
24
  userName: config.feishu?.userName,
15
25
  verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,
16
26
  };
@@ -27,8 +37,14 @@ export function handleFeishuWebhookPayload(payload) {
27
37
  return new Response(null, { status: 200 });
28
38
  }
29
39
  const messageType = optionalString(message, "message_type");
40
+ if (messageType !== "text") {
41
+ return new Response(null, { status: 200 });
42
+ }
30
43
  const text = parseFeishuText(optionalString(message, "content"));
31
- if (messageType !== "text" || !text) {
44
+ if (typeof text !== "string") {
45
+ return undefined;
46
+ }
47
+ if (!text) {
32
48
  return new Response(null, { status: 200 });
33
49
  }
34
50
  }
@@ -46,6 +62,17 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
46
62
  return undefined;
47
63
  }
48
64
  return async (request, rawBody) => {
65
+ if (!isFeishuWebhookBodyWithinLimit(rawBody)) {
66
+ return unauthorizedFeishuWebhook();
67
+ }
68
+ const signaturePresent = request.headers.has("x-lark-signature");
69
+ let signedRequest = false;
70
+ if (signaturePresent) {
71
+ if (!resolved.encryptKey || !verifyFeishuSignature(request, rawBody, resolved.encryptKey)) {
72
+ return unauthorizedFeishuWebhook();
73
+ }
74
+ signedRequest = true;
75
+ }
49
76
  let payload;
50
77
  try {
51
78
  payload = JSON.parse(rawBody);
@@ -58,7 +85,6 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
58
85
  }
59
86
  const encryptedEnvelope = payload;
60
87
  const encrypted = typeof payload.encrypt === "string";
61
- let signedRequest = false;
62
88
  if (encrypted) {
63
89
  if (!resolved.encryptKey) {
64
90
  return unauthorizedFeishuWebhook();
@@ -69,11 +95,9 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
69
95
  catch {
70
96
  return unauthorizedFeishuWebhook();
71
97
  }
72
- if (!verifyFeishuSignature(request, rawBody, resolved.encryptKey) &&
73
- !isFeishuUrlVerification(payload)) {
98
+ if (!signedRequest && !(isFeishuUrlVerification(payload) && validateCallback)) {
74
99
  return unauthorizedFeishuWebhook();
75
100
  }
76
- signedRequest = !isFeishuUrlVerification(payload);
77
101
  }
78
102
  else if (resolved.encryptKey) {
79
103
  return unauthorizedFeishuWebhook();
@@ -94,6 +118,7 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
94
118
  if (!replayState) {
95
119
  return undefined;
96
120
  }
121
+ const callbackExpiresAt = (callbackTimestamp ?? now) + FEISHU_MAX_CALLBACK_AGE_MS;
97
122
  pruneFeishuReplayCache(replayState.accepted, now);
98
123
  while (callbackKeys.length > 0) {
99
124
  if (callbackKeys.some((key) => replayState.accepted.has(key))) {
@@ -103,7 +128,9 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
103
128
  .map((key) => replayState.inFlight.get(key))
104
129
  .find((candidate) => candidate !== undefined);
105
130
  if (!reservation) {
106
- reserveFeishuCallback(replayState, callbackKeys);
131
+ if (!reserveFeishuCallback(replayState, callbackKeys, callbackExpiresAt, runtime.replayCacheLimit ?? FEISHU_REPLAY_CACHE_LIMIT)) {
132
+ return feishuReplayCapacityResponse();
133
+ }
107
134
  break;
108
135
  }
109
136
  if (await reservation.promise) {
@@ -170,12 +197,13 @@ export function normalizeFeishuWebhookPayload(payload) {
170
197
  if (!isRecord(payload)) {
171
198
  throw new CrablineError("Feishu webhook payload must be an object", { kind: "inbound" });
172
199
  }
200
+ const recordedPayload = redactFeishuVerificationTokens(payload);
173
201
  const event = optionalRecord(payload, "event");
174
202
  const message = event ? optionalRecord(event, "message") : undefined;
175
203
  if (!message) {
176
204
  return genericMockPayloadWithNativeThread({
177
205
  channelRule: FEISHU_CHAT_ID_RULE,
178
- payload,
206
+ payload: recordedPayload,
179
207
  threadRule: FEISHU_MESSAGE_ID_RULE,
180
208
  });
181
209
  }
@@ -185,13 +213,18 @@ export function normalizeFeishuWebhookPayload(payload) {
185
213
  const rootId = optionalString(message, "root_id");
186
214
  const rawContent = optionalString(message, "content");
187
215
  const text = parseFeishuText(rawContent);
216
+ if (text === null) {
217
+ throw new CrablineError("Feishu message.content must be valid JSON", {
218
+ kind: "inbound",
219
+ });
220
+ }
188
221
  if (messageType !== "text") {
189
222
  throw new CrablineError("Feishu event payload requires message.message_type=text", {
190
223
  kind: "inbound",
191
224
  });
192
225
  }
193
- if (!chatId || !text) {
194
- throw new CrablineError("Feishu event payload requires message.chat_id and message.content", {
226
+ if (!chatId || !messageId || !text) {
227
+ throw new CrablineError("Feishu event payload requires message.chat_id, message.message_id, and message.content", {
195
228
  kind: "inbound",
196
229
  });
197
230
  }
@@ -199,16 +232,25 @@ export function normalizeFeishuWebhookPayload(payload) {
199
232
  author: authorFromBotFlag(event
200
233
  ? ["app", "bot"].includes(optionalString(optionalRecord(event, "sender") ?? {}, "sender_type") ?? "")
201
234
  : false),
202
- ...(messageId
203
- ? { id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, "Feishu message_id") }
204
- : {}),
205
- raw: payload,
235
+ id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, "Feishu message_id"),
236
+ raw: recordedPayload,
206
237
  text,
207
238
  threadId: rootId
208
239
  ? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, "Feishu root_id")
209
240
  : requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, "Feishu chat_id"),
210
241
  };
211
242
  }
243
+ function redactFeishuVerificationTokens(payload) {
244
+ const redacted = { ...payload };
245
+ delete redacted.token;
246
+ const header = optionalRecord(payload, "header");
247
+ if (header) {
248
+ const redactedHeader = { ...header };
249
+ delete redactedHeader.token;
250
+ redacted.header = redactedHeader;
251
+ }
252
+ return redacted;
253
+ }
212
254
  function parseFeishuText(content) {
213
255
  if (!content) {
214
256
  return undefined;
@@ -221,7 +263,7 @@ function parseFeishuText(content) {
221
263
  return undefined;
222
264
  }
223
265
  catch {
224
- return content;
266
+ return null;
225
267
  }
226
268
  }
227
269
  function readFeishuSettledEnvelope(payload, rawBody) {
@@ -239,7 +281,13 @@ export function decryptFeishuWebhookPayload(payload, encryptKey) {
239
281
  if (!isRecord(payload) || typeof payload.encrypt !== "string") {
240
282
  return payload;
241
283
  }
284
+ if (payload.encrypt.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH) {
285
+ throw new Error("Feishu encrypted payload is too large.");
286
+ }
242
287
  const encrypted = Buffer.from(payload.encrypt, "base64");
288
+ if (encrypted.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES) {
289
+ throw new Error("Feishu encrypted payload is too large.");
290
+ }
243
291
  if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {
244
292
  throw new Error("Feishu encrypted payload is truncated.");
245
293
  }
@@ -264,6 +312,10 @@ function isFeishuUrlVerification(payload) {
264
312
  typeof payload.challenge === "string" &&
265
313
  payload.challenge.length > 0);
266
314
  }
315
+ function isFeishuWebhookBodyWithinLimit(rawBody) {
316
+ return (rawBody.length <= FEISHU_MAX_WEBHOOK_BODY_BYTES &&
317
+ Buffer.byteLength(rawBody, "utf8") <= FEISHU_MAX_WEBHOOK_BODY_BYTES);
318
+ }
267
319
  function verifyFeishuSignature(request, rawBody, encryptKey) {
268
320
  const timestamp = request.headers.get("x-lark-request-timestamp");
269
321
  const nonce = request.headers.get("x-lark-request-nonce");
@@ -272,7 +324,10 @@ function verifyFeishuSignature(request, rawBody, encryptKey) {
272
324
  return false;
273
325
  }
274
326
  const expected = createHash("sha256")
275
- .update(timestamp + nonce + encryptKey + rawBody)
327
+ .update(timestamp)
328
+ .update(nonce)
329
+ .update(encryptKey)
330
+ .update(rawBody)
276
331
  .digest();
277
332
  return timingSafeEqual(expected, Buffer.from(signature, "hex"));
278
333
  }
@@ -313,18 +368,26 @@ function readFeishuCallbackKeys(payload, encryptedEnvelope) {
313
368
  const encrypted = isRecord(encryptedEnvelope)
314
369
  ? optionalString(encryptedEnvelope, "encrypt")
315
370
  : undefined;
316
- return [
317
- ...(messageId ? [`message:${messageId}`] : []),
318
- ...(eventId ? [`event:${eventId}`] : []),
319
- ...(encrypted ? [`encrypted:${createHash("sha256").update(encrypted).digest("hex")}`] : []),
320
- ];
371
+ if (eventId) {
372
+ return [`event:${eventId}`];
373
+ }
374
+ if (messageId) {
375
+ return [`message:${messageId}`];
376
+ }
377
+ return encrypted ? [`encrypted:${createHash("sha256").update(encrypted).digest("hex")}`] : [];
321
378
  }
322
- function reserveFeishuCallback(replayState, callbackKeys) {
379
+ function reserveFeishuCallback(replayState, callbackKeys, expiresAt, cacheLimit) {
380
+ const occupiedKeys = new Set([...replayState.accepted.keys(), ...replayState.inFlight.keys()]);
381
+ const newKeys = new Set(callbackKeys.filter((key) => !occupiedKeys.has(key)));
382
+ if (occupiedKeys.size + newKeys.size > cacheLimit) {
383
+ return false;
384
+ }
323
385
  let resolveReservation;
324
386
  const promise = new Promise((resolve) => {
325
387
  resolveReservation = resolve;
326
388
  });
327
389
  const reservation = {
390
+ expiresAt,
328
391
  keys: new Set(callbackKeys),
329
392
  promise,
330
393
  resolve: resolveReservation,
@@ -332,12 +395,14 @@ function reserveFeishuCallback(replayState, callbackKeys) {
332
395
  for (const key of callbackKeys) {
333
396
  replayState.inFlight.set(key, reservation);
334
397
  }
398
+ return true;
335
399
  }
336
400
  function acceptFeishuCallback(replayState, payload, encryptedEnvelope, now) {
337
401
  const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
338
402
  pruneFeishuReplayCache(replayState.accepted, now);
339
403
  for (const key of callbackKeys) {
340
- replayState.accepted.set(key, now);
404
+ const reservation = replayState.inFlight.get(key);
405
+ replayState.accepted.set(key, Math.max(replayState.accepted.get(key) ?? 0, reservation?.expiresAt ?? now + FEISHU_MAX_CALLBACK_AGE_MS));
341
406
  }
342
407
  const reservations = new Set(callbackKeys
343
408
  .map((key) => replayState.inFlight.get(key))
@@ -364,13 +429,18 @@ function releaseFeishuReservation(replayState, reservation, accepted) {
364
429
  reservation.resolve(accepted);
365
430
  }
366
431
  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) {
432
+ for (const [key, expiresAt] of recentCallbacks) {
433
+ if (now > expiresAt) {
370
434
  recentCallbacks.delete(key);
371
435
  }
372
436
  }
373
437
  }
438
+ function feishuReplayCapacityResponse() {
439
+ return new Response("service unavailable", {
440
+ headers: { "cache-control": "no-store" },
441
+ status: 503,
442
+ });
443
+ }
374
444
  function unauthorizedFeishuWebhook() {
375
445
  return new Response("unauthorized", { status: 401 });
376
446
  }
@@ -1 +1 @@
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"]}
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 replayCacheLimit?: number | undefined;\n};\n\ntype FeishuReplayReservation = {\n expiresAt: number;\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_MAX_ENCRYPTED_PAYLOAD_BYTES = 1024 * 1024;\nconst FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH =\n Math.ceil(FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES / 3) * 4;\nconst FEISHU_REPLAY_CACHE_LIMIT = 2_048;\nconst FEISHU_MAX_WEBHOOK_BODY_BYTES = 1024 * 1024;\n\nexport function resolveFeishuAdapterConfig(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n) {\n const configuredEncryptKey = config.feishu?.encryptKey;\n const encryptKey = configuredEncryptKey ?? env.FEISHU_ENCRYPT_KEY;\n if (encryptKey !== undefined && !encryptKey.trim()) {\n throw new CrablineError(\n configuredEncryptKey === undefined\n ? \"FEISHU_ENCRYPT_KEY must not be empty or whitespace-only.\"\n : \"Feishu encryptKey must not be empty or whitespace-only.\",\n { kind: \"config\" },\n );\n }\n return {\n appId: config.feishu?.appId ?? \"local-mock-feishu-app\",\n encryptKey,\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 if (messageType !== \"text\") {\n return new Response(null, { status: 200 });\n }\n const text = parseFeishuText(optionalString(message, \"content\"));\n if (typeof text !== \"string\") {\n return undefined;\n }\n if (!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 if (!isFeishuWebhookBodyWithinLimit(rawBody)) {\n return unauthorizedFeishuWebhook();\n }\n const signaturePresent = request.headers.has(\"x-lark-signature\");\n let signedRequest = false;\n if (signaturePresent) {\n if (!resolved.encryptKey || !verifyFeishuSignature(request, rawBody, resolved.encryptKey)) {\n return unauthorizedFeishuWebhook();\n }\n signedRequest = true;\n }\n\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 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 (!signedRequest && !(isFeishuUrlVerification(payload) && validateCallback)) {\n return unauthorizedFeishuWebhook();\n }\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 const callbackExpiresAt = (callbackTimestamp ?? now) + FEISHU_MAX_CALLBACK_AGE_MS;\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 if (\n !reserveFeishuCallback(\n replayState,\n callbackKeys,\n callbackExpiresAt,\n runtime.replayCacheLimit ?? FEISHU_REPLAY_CACHE_LIMIT,\n )\n ) {\n return feishuReplayCapacityResponse();\n }\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 recordedPayload = redactFeishuVerificationTokens(payload);\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: recordedPayload,\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 (text === null) {\n throw new CrablineError(\"Feishu message.content must be valid JSON\", {\n kind: \"inbound\",\n });\n }\n if (messageType !== \"text\") {\n throw new CrablineError(\"Feishu event payload requires message.message_type=text\", {\n kind: \"inbound\",\n });\n }\n if (!chatId || !messageId || !text) {\n throw new CrablineError(\n \"Feishu event payload requires message.chat_id, message.message_id, and message.content\",\n {\n kind: \"inbound\",\n },\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 id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, \"Feishu message_id\"),\n raw: recordedPayload,\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 redactFeishuVerificationTokens(payload: Record<string, unknown>): Record<string, unknown> {\n const redacted = { ...payload };\n delete redacted.token;\n const header = optionalRecord(payload, \"header\");\n if (header) {\n const redactedHeader = { ...header };\n delete redactedHeader.token;\n redacted.header = redactedHeader;\n }\n return redacted;\n}\n\nfunction parseFeishuText(content: string | undefined): string | null | 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 null;\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 if (payload.encrypt.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH) {\n throw new Error(\"Feishu encrypted payload is too large.\");\n }\n const encrypted = Buffer.from(payload.encrypt, \"base64\");\n if (encrypted.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES) {\n throw new Error(\"Feishu encrypted payload is too large.\");\n }\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 isFeishuWebhookBodyWithinLimit(rawBody: string): boolean {\n return (\n rawBody.length <= FEISHU_MAX_WEBHOOK_BODY_BYTES &&\n Buffer.byteLength(rawBody, \"utf8\") <= FEISHU_MAX_WEBHOOK_BODY_BYTES\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)\n .update(nonce)\n .update(encryptKey)\n .update(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 if (eventId) {\n return [`event:${eventId}`];\n }\n if (messageId) {\n return [`message:${messageId}`];\n }\n return encrypted ? [`encrypted:${createHash(\"sha256\").update(encrypted).digest(\"hex\")}`] : [];\n}\n\nfunction reserveFeishuCallback(\n replayState: FeishuReplayState,\n callbackKeys: string[],\n expiresAt: number,\n cacheLimit: number,\n): boolean {\n const occupiedKeys = new Set([...replayState.accepted.keys(), ...replayState.inFlight.keys()]);\n const newKeys = new Set(callbackKeys.filter((key) => !occupiedKeys.has(key)));\n if (occupiedKeys.size + newKeys.size > cacheLimit) {\n return false;\n }\n let resolveReservation!: (accepted: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveReservation = resolve;\n });\n const reservation: FeishuReplayReservation = {\n expiresAt,\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 return true;\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 const reservation = replayState.inFlight.get(key);\n replayState.accepted.set(\n key,\n Math.max(\n replayState.accepted.get(key) ?? 0,\n reservation?.expiresAt ?? now + FEISHU_MAX_CALLBACK_AGE_MS,\n ),\n );\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, expiresAt] of recentCallbacks) {\n if (now > expiresAt) {\n recentCallbacks.delete(key);\n }\n }\n}\n\nfunction feishuReplayCapacityResponse(): Response {\n return new Response(\"service unavailable\", {\n headers: { \"cache-control\": \"no-store\" },\n status: 503,\n });\n}\n\nfunction unauthorizedFeishuWebhook(): Response {\n return new Response(\"unauthorized\", { status: 401 });\n}\n"]}
@@ -27,4 +27,5 @@ export declare function normalizeGoogleChatWebhookPayload(payload: unknown): {
27
27
  raw: {};
28
28
  text?: string | undefined;
29
29
  };
30
+ export declare function handleGoogleChatWebhookPayload(payload: unknown): Response | undefined;
30
31
  export {};