@openclaw/crabline 0.1.9 → 0.1.10

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 (189) hide show
  1. package/README.md +47 -22
  2. package/assets/crabline-banner.svg +20 -0
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +25 -1
  5. package/dist/src/cli/program.js +579 -82
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +59 -6
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +116 -92
  10. package/dist/src/config/schema.js +179 -73
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.js +9 -2
  13. package/dist/src/core/errors.js.map +1 -1
  14. package/dist/src/core/exit-codes.js.map +1 -1
  15. package/dist/src/core/matcher.js +7 -6
  16. package/dist/src/core/matcher.js.map +1 -1
  17. package/dist/src/core/message-template.js.map +1 -1
  18. package/dist/src/core/nonces.d.ts +1 -0
  19. package/dist/src/core/nonces.js +4 -2
  20. package/dist/src/core/nonces.js.map +1 -1
  21. package/dist/src/core/reporters.d.ts +1 -0
  22. package/dist/src/core/reporters.js +42 -4
  23. package/dist/src/core/reporters.js.map +1 -1
  24. package/dist/src/core/run.d.ts +3 -0
  25. package/dist/src/core/run.js +415 -107
  26. package/dist/src/core/run.js.map +1 -1
  27. package/dist/src/core/safe-regex.d.ts +3 -0
  28. package/dist/src/core/safe-regex.js +21 -0
  29. package/dist/src/core/safe-regex.js.map +1 -0
  30. package/dist/src/index.d.ts +2 -2
  31. package/dist/src/index.js +1 -1
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/openclaw/artifact-generation.d.ts +38 -0
  34. package/dist/src/openclaw/artifact-generation.js +314 -0
  35. package/dist/src/openclaw/artifact-generation.js.map +1 -0
  36. package/dist/src/openclaw/bridges/matrix.js +97 -12
  37. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  38. package/dist/src/openclaw/bridges/mattermost.js +32 -10
  39. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  40. package/dist/src/openclaw/bridges/signal.js +55 -18
  41. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  42. package/dist/src/openclaw/bridges/slack.js +85 -7
  43. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  44. package/dist/src/openclaw/bridges/telegram.js +124 -30
  45. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  46. package/dist/src/openclaw/bridges/whatsapp.js +78 -17
  47. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  48. package/dist/src/openclaw/bridges/zalo.js +14 -5
  49. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  50. package/dist/src/openclaw/outbound-contract.d.ts +5 -0
  51. package/dist/src/openclaw/outbound-contract.js +40 -0
  52. package/dist/src/openclaw/outbound-contract.js.map +1 -0
  53. package/dist/src/openclaw/private-file.d.ts +26 -0
  54. package/dist/src/openclaw/private-file.js +411 -0
  55. package/dist/src/openclaw/private-file.js.map +1 -0
  56. package/dist/src/openclaw/shared.d.ts +21 -2
  57. package/dist/src/openclaw/shared.js +115 -18
  58. package/dist/src/openclaw/shared.js.map +1 -1
  59. package/dist/src/openclaw/smoke-lock.d.ts +63 -0
  60. package/dist/src/openclaw/smoke-lock.js +949 -0
  61. package/dist/src/openclaw/smoke-lock.js.map +1 -0
  62. package/dist/src/openclaw.d.ts +16 -8
  63. package/dist/src/openclaw.js +194 -52
  64. package/dist/src/openclaw.js.map +1 -1
  65. package/dist/src/providers/builtin/discord.d.ts +10 -1
  66. package/dist/src/providers/builtin/discord.js +144 -18
  67. package/dist/src/providers/builtin/discord.js.map +1 -1
  68. package/dist/src/providers/builtin/external-webhook-auth.d.ts +11 -0
  69. package/dist/src/providers/builtin/external-webhook-auth.js +10 -0
  70. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -0
  71. package/dist/src/providers/builtin/feishu.d.ts +19 -2
  72. package/dist/src/providers/builtin/feishu.js +307 -25
  73. package/dist/src/providers/builtin/feishu.js.map +1 -1
  74. package/dist/src/providers/builtin/googlechat.d.ts +20 -1
  75. package/dist/src/providers/builtin/googlechat.js +178 -19
  76. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  77. package/dist/src/providers/builtin/imessage.d.ts +4 -0
  78. package/dist/src/providers/builtin/imessage.js +26 -11
  79. package/dist/src/providers/builtin/imessage.js.map +1 -1
  80. package/dist/src/providers/builtin/loopback.js +92 -30
  81. package/dist/src/providers/builtin/loopback.js.map +1 -1
  82. package/dist/src/providers/builtin/matrix.d.ts +11 -1
  83. package/dist/src/providers/builtin/matrix.js +53 -29
  84. package/dist/src/providers/builtin/matrix.js.map +1 -1
  85. package/dist/src/providers/builtin/mattermost.d.ts +10 -0
  86. package/dist/src/providers/builtin/mattermost.js +38 -15
  87. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  88. package/dist/src/providers/builtin/msteams.d.ts +17 -1
  89. package/dist/src/providers/builtin/msteams.js +113 -14
  90. package/dist/src/providers/builtin/msteams.js.map +1 -1
  91. package/dist/src/providers/builtin/native-local-mock.d.ts +2 -9
  92. package/dist/src/providers/builtin/native-local-mock.js +12 -35
  93. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  94. package/dist/src/providers/builtin/script.d.ts +11 -15
  95. package/dist/src/providers/builtin/script.js +566 -104
  96. package/dist/src/providers/builtin/script.js.map +1 -1
  97. package/dist/src/providers/builtin/slack.d.ts +6 -0
  98. package/dist/src/providers/builtin/slack.js +99 -40
  99. package/dist/src/providers/builtin/slack.js.map +1 -1
  100. package/dist/src/providers/builtin/telegram.d.ts +6 -0
  101. package/dist/src/providers/builtin/telegram.js +64 -43
  102. package/dist/src/providers/builtin/telegram.js.map +1 -1
  103. package/dist/src/providers/builtin/whatsapp.d.ts +28 -8
  104. package/dist/src/providers/builtin/whatsapp.js +567 -30
  105. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  106. package/dist/src/providers/builtin/zalo.js +30 -19
  107. package/dist/src/providers/builtin/zalo.js.map +1 -1
  108. package/dist/src/providers/catalog.d.ts +3 -3
  109. package/dist/src/providers/catalog.js +3 -3
  110. package/dist/src/providers/catalog.js.map +1 -1
  111. package/dist/src/providers/local-mock.d.ts +11 -2
  112. package/dist/src/providers/local-mock.js +322 -96
  113. package/dist/src/providers/local-mock.js.map +1 -1
  114. package/dist/src/providers/native-ids.d.ts +1 -0
  115. package/dist/src/providers/native-ids.js +6 -1
  116. package/dist/src/providers/native-ids.js.map +1 -1
  117. package/dist/src/providers/recorder.d.ts +30 -2
  118. package/dist/src/providers/recorder.js +539 -70
  119. package/dist/src/providers/recorder.js.map +1 -1
  120. package/dist/src/providers/registry.d.ts +25 -1
  121. package/dist/src/providers/registry.js +280 -40
  122. package/dist/src/providers/registry.js.map +1 -1
  123. package/dist/src/providers/signed-jwt.d.ts +29 -0
  124. package/dist/src/providers/signed-jwt.js +202 -0
  125. package/dist/src/providers/signed-jwt.js.map +1 -0
  126. package/dist/src/providers/slack-ids.js.map +1 -1
  127. package/dist/src/providers/target-normalizers.d.ts +26 -0
  128. package/dist/src/providers/target-normalizers.js +276 -0
  129. package/dist/src/providers/target-normalizers.js.map +1 -0
  130. package/dist/src/providers/types.d.ts +5 -0
  131. package/dist/src/providers/types.js.map +1 -1
  132. package/dist/src/providers/webhook-server.d.ts +2 -0
  133. package/dist/src/providers/webhook-server.js +72 -25
  134. package/dist/src/providers/webhook-server.js.map +1 -1
  135. package/dist/src/servers/http.d.ts +23 -3
  136. package/dist/src/servers/http.js +213 -20
  137. package/dist/src/servers/http.js.map +1 -1
  138. package/dist/src/servers/index.js +20 -21
  139. package/dist/src/servers/index.js.map +1 -1
  140. package/dist/src/servers/matrix.js +465 -51
  141. package/dist/src/servers/matrix.js.map +1 -1
  142. package/dist/src/servers/mattermost.d.ts +5 -0
  143. package/dist/src/servers/mattermost.js +306 -58
  144. package/dist/src/servers/mattermost.js.map +1 -1
  145. package/dist/src/servers/pending-events.d.ts +2 -0
  146. package/dist/src/servers/pending-events.js +11 -0
  147. package/dist/src/servers/pending-events.js.map +1 -0
  148. package/dist/src/servers/recorder.d.ts +5 -0
  149. package/dist/src/servers/recorder.js +46 -3
  150. package/dist/src/servers/recorder.js.map +1 -1
  151. package/dist/src/servers/signal.d.ts +2 -0
  152. package/dist/src/servers/signal.js +444 -56
  153. package/dist/src/servers/signal.js.map +1 -1
  154. package/dist/src/servers/slack.d.ts +5 -0
  155. package/dist/src/servers/slack.js +515 -49
  156. package/dist/src/servers/slack.js.map +1 -1
  157. package/dist/src/servers/telegram.d.ts +111 -0
  158. package/dist/src/servers/telegram.js +811 -152
  159. package/dist/src/servers/telegram.js.map +1 -1
  160. package/dist/src/servers/webhook-target.d.ts +40 -0
  161. package/dist/src/servers/webhook-target.js +349 -0
  162. package/dist/src/servers/webhook-target.js.map +1 -0
  163. package/dist/src/servers/websocket.d.ts +2 -0
  164. package/dist/src/servers/websocket.js +29 -0
  165. package/dist/src/servers/websocket.js.map +1 -0
  166. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +65 -1
  167. package/dist/src/servers/whatsapp-baileys-websocket.js +723 -99
  168. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  169. package/dist/src/servers/whatsapp-jid.d.ts +5 -0
  170. package/dist/src/servers/whatsapp-jid.js +32 -0
  171. package/dist/src/servers/whatsapp-jid.js.map +1 -0
  172. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +5 -0
  173. package/dist/src/servers/whatsapp-wire/binary-node.js +87 -20
  174. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  175. package/dist/src/servers/whatsapp-wire/crypto.d.ts +6 -1
  176. package/dist/src/servers/whatsapp-wire/crypto.js +99 -45
  177. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  178. package/dist/src/servers/whatsapp-wire/handshake.js +39 -4
  179. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  180. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  181. package/dist/src/servers/whatsapp.d.ts +12 -7
  182. package/dist/src/servers/whatsapp.js +269 -107
  183. package/dist/src/servers/whatsapp.js.map +1 -1
  184. package/dist/src/servers/zalo.d.ts +14 -0
  185. package/dist/src/servers/zalo.js +521 -88
  186. package/dist/src/servers/zalo.js.map +1 -1
  187. package/docs/channel-setup.md +197 -19
  188. package/fixtures/examples/crabline.example.yaml +10 -0
  189. package/package.json +20 -15
@@ -1,38 +1,162 @@
1
+ import { createPublicKey } 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 { authorFromBotFlag, createNativeTargetCodec, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
5
- const GOOGLE_CHAT_SPACE_RULE = {
6
- example: "spaces/AAAABbbbCCC",
7
- name: "Google Chat space name",
8
- pattern: /^spaces\/[A-Za-z0-9_-]+$/u,
9
- };
10
- const GOOGLE_CHAT_THREAD_RULE = {
11
- example: "spaces/AAAABbbbCCC/threads/BBBBccccDDD",
12
- name: "Google Chat thread name",
13
- pattern: /^spaces\/[A-Za-z0-9_-]+\/threads\/[A-Za-z0-9_-]+$/u,
14
- };
5
+ import { createCachedJwtKeyResolver, readBearerToken, resolveHttpCacheExpiry, verifySignedJwt, } from "../signed-jwt.js";
6
+ import { getBuiltinTargetCodec, GOOGLE_CHAT_SPACE_RULE, GOOGLE_CHAT_THREAD_RULE, } from "../target-normalizers.js";
7
+ import { authorFromBotFlag, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
8
+ import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
15
9
  export function resolveGoogleChatAdapterConfig(config, _env = process.env) {
16
10
  return {
17
11
  endpointUrl: config.googlechat?.endpointUrl,
18
12
  projectNumber: config.googlechat?.googleChatProjectNumber ?? "local-mock-googlechat",
13
+ pubsubServiceAccountEmail: config.googlechat?.pubsubServiceAccountEmail ?? config.googlechat?.credentials?.client_email,
19
14
  userName: config.googlechat?.userName,
20
15
  };
21
16
  }
17
+ const GOOGLE_CHAT_SERVICE_ACCOUNT = "chat@system.gserviceaccount.com";
18
+ const GOOGLE_OAUTH_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs";
19
+ const GOOGLE_CHAT_CERTS_URL = "https://www.googleapis.com/service_accounts/v1/metadata/x509/chat@system.gserviceaccount.com";
20
+ export function createGoogleChatWebhookAuthenticator(config, runtime = {}) {
21
+ if (config.googlechat?.disableSignatureVerification) {
22
+ return undefined;
23
+ }
24
+ const endpointAudience = config.googlechat?.endpointUrl ?? config.googlechat?.webhook.publicUrl;
25
+ const projectAudience = config.googlechat?.googleChatProjectNumber;
26
+ const pubsubAudience = config.googlechat?.pubsubAudience ?? endpointAudience;
27
+ const pubsubServiceAccount = config.googlechat?.pubsubServiceAccountEmail ?? config.googlechat?.credentials?.client_email;
28
+ if (!(endpointAudience || projectAudience || pubsubAudience)) {
29
+ return undefined;
30
+ }
31
+ const fetchImpl = runtime.fetch ?? fetch;
32
+ const createCertificateResolver = (certificateUrl) => {
33
+ return createCachedJwtKeyResolver({
34
+ async fetchKeys(signal) {
35
+ const fetchedAt = runtime.now?.() ?? Date.now();
36
+ const response = await fetchImpl(certificateUrl, { signal });
37
+ if (!response.ok) {
38
+ throw new Error(`Google certificate fetch failed with HTTP ${response.status}.`);
39
+ }
40
+ const body = await response.json();
41
+ if (!isRecord(body)) {
42
+ throw new Error("Google certificate response must be an object.");
43
+ }
44
+ const values = Object.entries(body).flatMap(([kid, certificate]) => typeof certificate === "string" ? [{ certificate, kid }] : []);
45
+ return {
46
+ expiresAt: resolveHttpCacheExpiry(response, fetchedAt),
47
+ values,
48
+ };
49
+ },
50
+ keyId: (value) => value.kid,
51
+ now: runtime.now,
52
+ refreshCooldownMs: runtime.unknownKeyCooldownMs,
53
+ timeoutMs: runtime.keyFetchTimeoutMs,
54
+ unknownKeyMessage: "Google JWT signing key is unknown.",
55
+ });
56
+ };
57
+ const resolveGoogleIdentityCertificate = createCertificateResolver(GOOGLE_OAUTH_CERTS_URL);
58
+ const resolveGoogleChatCertificate = createCertificateResolver(GOOGLE_CHAT_CERTS_URL);
59
+ return async (request, rawBody) => {
60
+ const token = readBearerToken(request);
61
+ if (!token) {
62
+ return new Response("unauthorized", {
63
+ headers: { "www-authenticate": "Bearer" },
64
+ status: 401,
65
+ });
66
+ }
67
+ try {
68
+ let payload;
69
+ try {
70
+ payload = JSON.parse(rawBody);
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ const isPubsub = isRecord(payload) &&
76
+ isRecord(payload.message) &&
77
+ typeof payload.message.data === "string" &&
78
+ typeof payload.subscription === "string";
79
+ const audience = isPubsub ? pubsubAudience : (projectAudience ?? endpointAudience);
80
+ if (!audience) {
81
+ throw new Error("Google Chat verification is not configured for this transport.");
82
+ }
83
+ const usesGoogleIdentityToken = isPubsub || (projectAudience === undefined && endpointAudience !== undefined);
84
+ const certificateUrl = usesGoogleIdentityToken
85
+ ? GOOGLE_OAUTH_CERTS_URL
86
+ : GOOGLE_CHAT_CERTS_URL;
87
+ const claims = await verifySignedJwt({
88
+ audience,
89
+ issuers: usesGoogleIdentityToken
90
+ ? ["accounts.google.com", "https://accounts.google.com"]
91
+ : [GOOGLE_CHAT_SERVICE_ACCOUNT],
92
+ now: runtime.now,
93
+ async resolveKey(header) {
94
+ const certificate = await (certificateUrl === GOOGLE_OAUTH_CERTS_URL
95
+ ? resolveGoogleIdentityCertificate(header)
96
+ : resolveGoogleChatCertificate(header));
97
+ return createPublicKey(certificate.certificate);
98
+ },
99
+ token,
100
+ });
101
+ if (isPubsub &&
102
+ (!pubsubServiceAccount ||
103
+ claims.email !== pubsubServiceAccount ||
104
+ claims.email_verified !== true)) {
105
+ throw new Error("Google Pub/Sub token identity is invalid.");
106
+ }
107
+ if (!isPubsub &&
108
+ usesGoogleIdentityToken &&
109
+ (claims.email !== GOOGLE_CHAT_SERVICE_ACCOUNT || claims.email_verified !== true)) {
110
+ throw new Error("Google Chat ID token identity is invalid.");
111
+ }
112
+ return undefined;
113
+ }
114
+ catch {
115
+ return new Response("unauthorized", {
116
+ headers: { "www-authenticate": "Bearer" },
117
+ status: 401,
118
+ });
119
+ }
120
+ };
121
+ }
122
+ export function matchesGoogleChatThread(candidateThreadId, expectedThreadId, target = {}) {
123
+ const candidateSpace = GOOGLE_CHAT_THREAD_RULE.pattern.test(candidateThreadId)
124
+ ? candidateThreadId.slice(0, candidateThreadId.indexOf("/threads/"))
125
+ : candidateThreadId;
126
+ if (target.channelId && candidateSpace !== target.channelId) {
127
+ return false;
128
+ }
129
+ if (!expectedThreadId) {
130
+ return true;
131
+ }
132
+ return (candidateThreadId === expectedThreadId ||
133
+ (GOOGLE_CHAT_SPACE_RULE.pattern.test(expectedThreadId) &&
134
+ candidateThreadId.startsWith(`${expectedThreadId}/threads/`)));
135
+ }
22
136
  export class GoogleChatProviderAdapter extends LocalMockProviderAdapter {
23
- constructor(id, config, _userName, _runtime) {
137
+ constructor(id, config, _userName, runtime) {
138
+ const endpointAudience = config.googlechat?.endpointUrl ?? config.googlechat?.webhook.publicUrl;
139
+ const pubsubServiceAccount = config.googlechat?.pubsubServiceAccountEmail ?? config.googlechat?.credentials?.client_email;
140
+ const authenticationConfigured = !config.googlechat?.disableSignatureVerification &&
141
+ Boolean(endpointAudience ||
142
+ config.googlechat?.googleChatProjectNumber ||
143
+ (config.googlechat?.pubsubAudience && pubsubServiceAccount));
144
+ requireExternalWebhookAuthentication({
145
+ authenticated: authenticationConfigured,
146
+ provider: "Google Chat",
147
+ requirement: "googlechat.endpointUrl, googlechat.googleChatProjectNumber, or googlechat.pubsubAudience with a Pub/Sub service-account identity and signature verification enabled",
148
+ webhook: config.googlechat?.webhook,
149
+ });
150
+ const authenticateWebhookRequest = createGoogleChatWebhookAuthenticator(config, runtime ?? {});
24
151
  super({
25
- codec: createNativeTargetCodec({
26
- channel: GOOGLE_CHAT_SPACE_RULE,
27
- channelLabel: "Google Chat space.name",
28
- thread: GOOGLE_CHAT_THREAD_RULE,
29
- threadLabel: "Google Chat thread.name",
30
- }),
152
+ codec: getBuiltinTargetCodec("googlechat"),
31
153
  config,
32
154
  id,
33
155
  options: {
156
+ ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),
34
157
  defaultWebhook: { host: "127.0.0.1", path: "/googlechat/webhook", port: 8792 },
35
158
  endpointLabel: "webhook endpoint",
159
+ matchesThread: matchesGoogleChatThread,
36
160
  normalizeWebhookPayload: normalizeGoogleChatWebhookPayload,
37
161
  platform: "googlechat",
38
162
  publicUrl: config.googlechat?.webhook.publicUrl,
@@ -44,12 +168,17 @@ export class GoogleChatProviderAdapter extends LocalMockProviderAdapter {
44
168
  });
45
169
  }
46
170
  }
47
- function normalizeGoogleChatWebhookPayload(payload) {
171
+ export function normalizeGoogleChatWebhookPayload(payload) {
172
+ payload = unwrapGoogleChatPubsubPayload(payload);
48
173
  if (!isRecord(payload)) {
49
174
  throw new CrablineError("Google Chat webhook payload must be an object", {
50
175
  kind: "inbound",
51
176
  });
52
177
  }
178
+ const chat = optionalRecord(payload, "chat");
179
+ if (chat && "messagePayload" in chat) {
180
+ throw new CrablineError("Google Workspace add-on chat.messagePayload events are unsupported without a configured deployment identity", { kind: "inbound" });
181
+ }
53
182
  const payloadMessage = optionalRecord(payload, "message");
54
183
  const message = payloadMessage ?? payload;
55
184
  if (payloadMessage && optionalString(payloadMessage, "threadId")) {
@@ -70,6 +199,11 @@ function normalizeGoogleChatWebhookPayload(payload) {
70
199
  kind: "inbound",
71
200
  });
72
201
  }
202
+ if (threadName && !threadName.startsWith(`${spaceName}/threads/`)) {
203
+ throw new CrablineError("Google Chat thread.name must belong to message.space.name", {
204
+ kind: "inbound",
205
+ });
206
+ }
73
207
  return {
74
208
  author: authorFromBotFlag(optionalString(sender ?? {}, "type") === "BOT"),
75
209
  ...(optionalString(message, "name") ? { id: optionalString(message, "name") } : {}),
@@ -80,4 +214,29 @@ function normalizeGoogleChatWebhookPayload(payload) {
80
214
  : requireNativeInboundId(spaceName, GOOGLE_CHAT_SPACE_RULE, "Google Chat space.name"),
81
215
  };
82
216
  }
217
+ function unwrapGoogleChatPubsubPayload(payload) {
218
+ if (!isRecord(payload)) {
219
+ return payload;
220
+ }
221
+ const message = optionalRecord(payload, "message");
222
+ const data = message ? optionalString(message, "data") : undefined;
223
+ if (!data || typeof payload.subscription !== "string") {
224
+ return payload;
225
+ }
226
+ try {
227
+ if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(data)) {
228
+ throw new Error("invalid base64");
229
+ }
230
+ const decoded = Buffer.from(data, "base64");
231
+ if (decoded.toString("base64") !== data) {
232
+ throw new Error("non-canonical base64");
233
+ }
234
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(decoded));
235
+ }
236
+ catch {
237
+ throw new CrablineError("Google Pub/Sub message.data must contain base64-encoded JSON.", {
238
+ kind: "inbound",
239
+ });
240
+ }
241
+ }
83
242
  //# sourceMappingURL=googlechat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"googlechat.js","sourceRoot":"","sources":["../../../../src/providers/builtin/googlechat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"googlechat.js","sourceRoot":"","sources":["../../../../src/providers/builtin/googlechat.ts"],"names":[],"mappings":"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","sourcesContent":["import { createPublicKey } 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 createCachedJwtKeyResolver,\n readBearerToken,\n resolveHttpCacheExpiry,\n verifySignedJwt,\n} from \"../signed-jwt.js\";\nimport {\n getBuiltinTargetCodec,\n GOOGLE_CHAT_SPACE_RULE,\n GOOGLE_CHAT_THREAD_RULE,\n} from \"../target-normalizers.js\";\nimport {\n authorFromBotFlag,\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\nexport function resolveGoogleChatAdapterConfig(\n config: ProviderConfig,\n _env: NodeJS.ProcessEnv = process.env,\n) {\n return {\n endpointUrl: config.googlechat?.endpointUrl,\n projectNumber: config.googlechat?.googleChatProjectNumber ?? \"local-mock-googlechat\",\n pubsubServiceAccountEmail:\n config.googlechat?.pubsubServiceAccountEmail ?? config.googlechat?.credentials?.client_email,\n userName: config.googlechat?.userName,\n };\n}\n\nconst GOOGLE_CHAT_SERVICE_ACCOUNT = \"chat@system.gserviceaccount.com\";\nconst GOOGLE_OAUTH_CERTS_URL = \"https://www.googleapis.com/oauth2/v1/certs\";\nconst GOOGLE_CHAT_CERTS_URL =\n \"https://www.googleapis.com/service_accounts/v1/metadata/x509/chat@system.gserviceaccount.com\";\n\ntype GoogleChatAuthRuntime = {\n fetch?: typeof fetch | undefined;\n keyFetchTimeoutMs?: number | undefined;\n now?: (() => number) | undefined;\n unknownKeyCooldownMs?: number | undefined;\n};\n\ntype GoogleCertificate = {\n certificate: string;\n kid: string;\n};\n\nexport function createGoogleChatWebhookAuthenticator(\n config: ProviderConfig,\n runtime: GoogleChatAuthRuntime = {},\n) {\n if (config.googlechat?.disableSignatureVerification) {\n return undefined;\n }\n const endpointAudience = config.googlechat?.endpointUrl ?? config.googlechat?.webhook.publicUrl;\n const projectAudience = config.googlechat?.googleChatProjectNumber;\n const pubsubAudience = config.googlechat?.pubsubAudience ?? endpointAudience;\n const pubsubServiceAccount =\n config.googlechat?.pubsubServiceAccountEmail ?? config.googlechat?.credentials?.client_email;\n if (!(endpointAudience || projectAudience || pubsubAudience)) {\n return undefined;\n }\n\n const fetchImpl = runtime.fetch ?? fetch;\n const createCertificateResolver = (certificateUrl: string) => {\n return createCachedJwtKeyResolver<GoogleCertificate>({\n async fetchKeys(signal) {\n const fetchedAt = runtime.now?.() ?? Date.now();\n const response = await fetchImpl(certificateUrl, { signal });\n if (!response.ok) {\n throw new Error(`Google certificate fetch failed with HTTP ${response.status}.`);\n }\n const body: unknown = await response.json();\n if (!isRecord(body)) {\n throw new Error(\"Google certificate response must be an object.\");\n }\n const values = Object.entries(body).flatMap(([kid, certificate]) =>\n typeof certificate === \"string\" ? [{ certificate, kid }] : [],\n );\n return {\n expiresAt: resolveHttpCacheExpiry(response, fetchedAt),\n values,\n };\n },\n keyId: (value) => value.kid,\n now: runtime.now,\n refreshCooldownMs: runtime.unknownKeyCooldownMs,\n timeoutMs: runtime.keyFetchTimeoutMs,\n unknownKeyMessage: \"Google JWT signing key is unknown.\",\n });\n };\n const resolveGoogleIdentityCertificate = createCertificateResolver(GOOGLE_OAUTH_CERTS_URL);\n const resolveGoogleChatCertificate = createCertificateResolver(GOOGLE_CHAT_CERTS_URL);\n return async (request: Request, rawBody: string): Promise<Response | undefined> => {\n const token = readBearerToken(request);\n if (!token) {\n return new Response(\"unauthorized\", {\n headers: { \"www-authenticate\": \"Bearer\" },\n status: 401,\n });\n }\n try {\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody) as unknown;\n } catch {\n return undefined;\n }\n const isPubsub =\n isRecord(payload) &&\n isRecord(payload.message) &&\n typeof payload.message.data === \"string\" &&\n typeof payload.subscription === \"string\";\n const audience = isPubsub ? pubsubAudience : (projectAudience ?? endpointAudience);\n if (!audience) {\n throw new Error(\"Google Chat verification is not configured for this transport.\");\n }\n const usesGoogleIdentityToken =\n isPubsub || (projectAudience === undefined && endpointAudience !== undefined);\n const certificateUrl = usesGoogleIdentityToken\n ? GOOGLE_OAUTH_CERTS_URL\n : GOOGLE_CHAT_CERTS_URL;\n const claims = await verifySignedJwt({\n audience,\n issuers: usesGoogleIdentityToken\n ? [\"accounts.google.com\", \"https://accounts.google.com\"]\n : [GOOGLE_CHAT_SERVICE_ACCOUNT],\n now: runtime.now,\n async resolveKey(header) {\n const certificate = await (certificateUrl === GOOGLE_OAUTH_CERTS_URL\n ? resolveGoogleIdentityCertificate(header)\n : resolveGoogleChatCertificate(header));\n return createPublicKey(certificate.certificate);\n },\n token,\n });\n if (\n isPubsub &&\n (!pubsubServiceAccount ||\n claims.email !== pubsubServiceAccount ||\n claims.email_verified !== true)\n ) {\n throw new Error(\"Google Pub/Sub token identity is invalid.\");\n }\n if (\n !isPubsub &&\n usesGoogleIdentityToken &&\n (claims.email !== GOOGLE_CHAT_SERVICE_ACCOUNT || claims.email_verified !== true)\n ) {\n throw new Error(\"Google Chat ID token identity is invalid.\");\n }\n return undefined;\n } catch {\n return new Response(\"unauthorized\", {\n headers: { \"www-authenticate\": \"Bearer\" },\n status: 401,\n });\n }\n };\n}\n\nexport function matchesGoogleChatThread(\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: { channelId?: string | undefined } = {},\n): boolean {\n const candidateSpace = GOOGLE_CHAT_THREAD_RULE.pattern.test(candidateThreadId)\n ? candidateThreadId.slice(0, candidateThreadId.indexOf(\"/threads/\"))\n : candidateThreadId;\n if (target.channelId && candidateSpace !== target.channelId) {\n return false;\n }\n if (!expectedThreadId) {\n return true;\n }\n return (\n candidateThreadId === expectedThreadId ||\n (GOOGLE_CHAT_SPACE_RULE.pattern.test(expectedThreadId) &&\n candidateThreadId.startsWith(`${expectedThreadId}/threads/`))\n );\n}\n\nexport class GoogleChatProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown) {\n const endpointAudience = config.googlechat?.endpointUrl ?? config.googlechat?.webhook.publicUrl;\n const pubsubServiceAccount =\n config.googlechat?.pubsubServiceAccountEmail ?? config.googlechat?.credentials?.client_email;\n const authenticationConfigured =\n !config.googlechat?.disableSignatureVerification &&\n Boolean(\n endpointAudience ||\n config.googlechat?.googleChatProjectNumber ||\n (config.googlechat?.pubsubAudience && pubsubServiceAccount),\n );\n requireExternalWebhookAuthentication({\n authenticated: authenticationConfigured,\n provider: \"Google Chat\",\n requirement:\n \"googlechat.endpointUrl, googlechat.googleChatProjectNumber, or googlechat.pubsubAudience with a Pub/Sub service-account identity and signature verification enabled\",\n webhook: config.googlechat?.webhook,\n });\n const authenticateWebhookRequest = createGoogleChatWebhookAuthenticator(\n config,\n (runtime as GoogleChatAuthRuntime | undefined) ?? {},\n );\n super({\n codec: getBuiltinTargetCodec(\"googlechat\"),\n config,\n id,\n options: {\n ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),\n defaultWebhook: { host: \"127.0.0.1\", path: \"/googlechat/webhook\", port: 8792 },\n endpointLabel: \"webhook endpoint\",\n matchesThread: matchesGoogleChatThread,\n normalizeWebhookPayload: normalizeGoogleChatWebhookPayload,\n platform: \"googlechat\",\n publicUrl: config.googlechat?.webhook.publicUrl,\n recorderPath: config.googlechat?.recorder.path\n ? path.resolve(config.googlechat.recorder.path)\n : undefined,\n webhook: config.googlechat?.webhook,\n },\n });\n }\n}\n\nexport function normalizeGoogleChatWebhookPayload(payload: unknown) {\n payload = unwrapGoogleChatPubsubPayload(payload);\n if (!isRecord(payload)) {\n throw new CrablineError(\"Google Chat webhook payload must be an object\", {\n kind: \"inbound\",\n });\n }\n\n const chat = optionalRecord(payload, \"chat\");\n if (chat && \"messagePayload\" in chat) {\n throw new CrablineError(\n \"Google Workspace add-on chat.messagePayload events are unsupported without a configured deployment identity\",\n { kind: \"inbound\" },\n );\n }\n const payloadMessage = optionalRecord(payload, \"message\");\n const message = payloadMessage ?? payload;\n if (payloadMessage && optionalString(payloadMessage, \"threadId\")) {\n return genericMockPayloadWithNativeThread({\n channelRule: GOOGLE_CHAT_SPACE_RULE,\n payload,\n threadRule: GOOGLE_CHAT_THREAD_RULE,\n });\n }\n\n const space = optionalRecord(message, \"space\");\n const thread = optionalRecord(message, \"thread\");\n const sender = optionalRecord(message, \"sender\");\n const spaceName = space ? optionalString(space, \"name\") : undefined;\n const threadName = thread ? optionalString(thread, \"name\") : undefined;\n const text = optionalString(message, \"text\") ?? optionalString(message, \"argumentText\");\n if (!spaceName || !text) {\n throw new CrablineError(\"Google Chat message payload requires space.name and text\", {\n kind: \"inbound\",\n });\n }\n if (threadName && !threadName.startsWith(`${spaceName}/threads/`)) {\n throw new CrablineError(\"Google Chat thread.name must belong to message.space.name\", {\n kind: \"inbound\",\n });\n }\n\n return {\n author: authorFromBotFlag(optionalString(sender ?? {}, \"type\") === \"BOT\"),\n ...(optionalString(message, \"name\") ? { id: optionalString(message, \"name\") } : {}),\n raw: payload,\n text,\n threadId: threadName\n ? requireNativeInboundId(threadName, GOOGLE_CHAT_THREAD_RULE, \"Google Chat thread.name\")\n : requireNativeInboundId(spaceName, GOOGLE_CHAT_SPACE_RULE, \"Google Chat space.name\"),\n };\n}\n\nfunction unwrapGoogleChatPubsubPayload(payload: unknown): unknown {\n if (!isRecord(payload)) {\n return payload;\n }\n const message = optionalRecord(payload, \"message\");\n const data = message ? optionalString(message, \"data\") : undefined;\n if (!data || typeof payload.subscription !== \"string\") {\n return payload;\n }\n\n try {\n if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(data)) {\n throw new Error(\"invalid base64\");\n }\n const decoded = Buffer.from(data, \"base64\");\n if (decoded.toString(\"base64\") !== data) {\n throw new Error(\"non-canonical base64\");\n }\n return JSON.parse(new TextDecoder(\"utf-8\", { fatal: true }).decode(decoded)) as unknown;\n } catch {\n throw new CrablineError(\"Google Pub/Sub message.data must contain base64-encoded JSON.\", {\n kind: \"inbound\",\n });\n }\n}\n"]}
@@ -9,3 +9,7 @@ export declare function resolveIMessageAdapterConfig(config: ProviderConfig, env
9
9
  export declare class IMessageProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {
10
10
  constructor(id: string, config: ProviderConfig, _userName: string, _runtime?: unknown);
11
11
  }
12
+ export declare function matchesIMessageThread(candidateThreadId: string, expectedThreadId: string | undefined, target: {
13
+ channelId?: string | undefined;
14
+ id: string;
15
+ }, raw?: unknown): boolean;
@@ -1,12 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { CrablineError } from "../../core/errors.js";
3
3
  import { LocalMockProviderAdapter } from "../local-mock.js";
4
- import { authorFromBotFlag, createNativeTargetCodec, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
5
- const IMESSAGE_THREAD_RULE = {
6
- example: "+15551234567, user@example.com, or iMessage;-;chat-guid",
7
- name: "iMessage recipient or chat GUID",
8
- pattern: /^(?:\+[1-9]\d{6,14}|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}|(?:iMessage|SMS);[+-];.+)$/u,
9
- };
4
+ import { getBuiltinTargetCodec, IMESSAGE_THREAD_RULE } from "../target-normalizers.js";
5
+ import { authorFromBotFlag, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
6
+ import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
10
7
  export function resolveIMessageAdapterConfig(config, env = process.env) {
11
8
  return {
12
9
  apiKey: config.imessage?.apiKey ?? env.IMESSAGE_API_KEY ?? "local-mock-imessage-api-key",
@@ -16,16 +13,20 @@ export function resolveIMessageAdapterConfig(config, env = process.env) {
16
13
  }
17
14
  export class IMessageProviderAdapter extends LocalMockProviderAdapter {
18
15
  constructor(id, config, _userName, _runtime) {
16
+ requireExternalWebhookAuthentication({
17
+ authenticated: false,
18
+ provider: "iMessage",
19
+ requirement: "a provider-native authenticated ingress mode, which this adapter does not support",
20
+ webhook: config.imessage?.webhook,
21
+ });
19
22
  super({
20
- codec: createNativeTargetCodec({
21
- channel: IMESSAGE_THREAD_RULE,
22
- channelLabel: "iMessage recipient or chat GUID",
23
- }),
23
+ codec: getBuiltinTargetCodec("imessage"),
24
24
  config,
25
25
  id,
26
26
  options: {
27
27
  defaultWebhook: { host: "127.0.0.1", path: "/imessage/webhook", port: 8796 },
28
28
  endpointLabel: "webhook endpoint",
29
+ matchesThread: matchesIMessageThread,
29
30
  normalizeWebhookPayload: normalizeIMessageWebhookPayload,
30
31
  platform: "imessage",
31
32
  publicUrl: config.imessage?.webhook.publicUrl,
@@ -37,6 +38,20 @@ export class IMessageProviderAdapter extends LocalMockProviderAdapter {
37
38
  });
38
39
  }
39
40
  }
41
+ export function matchesIMessageThread(candidateThreadId, expectedThreadId, target, raw) {
42
+ const rawPayload = isRecord(raw) ? raw : undefined;
43
+ const data = rawPayload ? (optionalRecord(rawPayload, "data") ?? rawPayload) : undefined;
44
+ const recipientAlias = data ? optionalString(data, "chatIdentifier") : undefined;
45
+ if (expectedThreadId === undefined) {
46
+ return true;
47
+ }
48
+ const expectedIdentifiers = new Set([expectedThreadId, target.id]);
49
+ if (target.channelId) {
50
+ expectedIdentifiers.add(target.channelId);
51
+ }
52
+ return (expectedIdentifiers.has(candidateThreadId) ||
53
+ (recipientAlias !== undefined && expectedIdentifiers.has(recipientAlias)));
54
+ }
40
55
  function normalizeIMessageWebhookPayload(payload) {
41
56
  if (!isRecord(payload)) {
42
57
  throw new CrablineError("iMessage webhook payload must be an object", { kind: "inbound" });
@@ -52,7 +67,7 @@ function normalizeIMessageWebhookPayload(payload) {
52
67
  const threadId = optionalString(data, "chatGuid") ?? optionalString(data, "chatIdentifier");
53
68
  const text = optionalString(data, "text") ?? optionalString(data, "message");
54
69
  if (!threadId || !text) {
55
- throw new CrablineError("iMessage webhook payload requires chatGuid and text", {
70
+ throw new CrablineError("iMessage webhook payload requires chatGuid or chatIdentifier and text", {
56
71
  kind: "inbound",
57
72
  });
58
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"imessage.js","sourceRoot":"","sources":["../../../../src/providers/builtin/imessage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"imessage.js","sourceRoot":"","sources":["../../../../src/providers/builtin/imessage.ts"],"names":[],"mappings":"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","sourcesContent":["import 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 { getBuiltinTargetCodec, IMESSAGE_THREAD_RULE } from \"../target-normalizers.js\";\nimport {\n authorFromBotFlag,\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\nexport function resolveIMessageAdapterConfig(\n config: ProviderConfig,\n env: NodeJS.ProcessEnv = process.env,\n) {\n return {\n apiKey: config.imessage?.apiKey ?? env.IMESSAGE_API_KEY ?? \"local-mock-imessage-api-key\",\n local: config.imessage?.local ?? true,\n serverUrl: config.imessage?.serverUrl ?? env.IMESSAGE_SERVER_URL,\n };\n}\n\nexport class IMessageProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, _runtime?: unknown) {\n requireExternalWebhookAuthentication({\n authenticated: false,\n provider: \"iMessage\",\n requirement:\n \"a provider-native authenticated ingress mode, which this adapter does not support\",\n webhook: config.imessage?.webhook,\n });\n super({\n codec: getBuiltinTargetCodec(\"imessage\"),\n config,\n id,\n options: {\n defaultWebhook: { host: \"127.0.0.1\", path: \"/imessage/webhook\", port: 8796 },\n endpointLabel: \"webhook endpoint\",\n matchesThread: matchesIMessageThread,\n normalizeWebhookPayload: normalizeIMessageWebhookPayload,\n platform: \"imessage\",\n publicUrl: config.imessage?.webhook.publicUrl,\n recorderPath: config.imessage?.recorder.path\n ? path.resolve(config.imessage.recorder.path)\n : undefined,\n webhook: config.imessage?.webhook,\n },\n });\n }\n}\n\nexport function matchesIMessageThread(\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: { channelId?: string | undefined; id: string },\n raw?: unknown,\n): boolean {\n const rawPayload = isRecord(raw) ? raw : undefined;\n const data = rawPayload ? (optionalRecord(rawPayload, \"data\") ?? rawPayload) : undefined;\n const recipientAlias = data ? optionalString(data, \"chatIdentifier\") : undefined;\n if (expectedThreadId === undefined) {\n return true;\n }\n const expectedIdentifiers = new Set([expectedThreadId, target.id]);\n if (target.channelId) {\n expectedIdentifiers.add(target.channelId);\n }\n return (\n expectedIdentifiers.has(candidateThreadId) ||\n (recipientAlias !== undefined && expectedIdentifiers.has(recipientAlias))\n );\n}\n\nfunction normalizeIMessageWebhookPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"iMessage webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n if (optionalRecord(payload, \"message\")) {\n return genericMockPayloadWithNativeThread({\n channelRule: IMESSAGE_THREAD_RULE,\n payload,\n threadRule: IMESSAGE_THREAD_RULE,\n });\n }\n\n const data = optionalRecord(payload, \"data\") ?? payload;\n const threadId = optionalString(data, \"chatGuid\") ?? optionalString(data, \"chatIdentifier\");\n const text = optionalString(data, \"text\") ?? optionalString(data, \"message\");\n if (!threadId || !text) {\n throw new CrablineError(\n \"iMessage webhook payload requires chatGuid or chatIdentifier and text\",\n {\n kind: \"inbound\",\n },\n );\n }\n\n return {\n author: authorFromBotFlag(data.isFromMe === true),\n ...(optionalString(data, \"guid\") ? { id: optionalString(data, \"guid\") } : {}),\n raw: payload,\n text,\n threadId: requireNativeInboundId(threadId, IMESSAGE_THREAD_RULE, \"iMessage chatGuid\"),\n };\n}\n"]}
@@ -1,8 +1,26 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
1
3
  import { CrablineError } from "../../core/errors.js";
2
- import { createGenericLocalMockTargetCodec, LocalMockProviderAdapter } from "../local-mock.js";
4
+ import { LocalMockProviderAdapter } from "../local-mock.js";
5
+ import { getBuiltinTargetCodec } from "../target-normalizers.js";
3
6
  function createMessageId() {
4
7
  return `loopback-mock-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
5
8
  }
9
+ function cloneRawMessage(raw) {
10
+ return { ...raw };
11
+ }
12
+ function cloneMessage(message) {
13
+ return {
14
+ ...message,
15
+ author: { ...message.author },
16
+ metadata: {
17
+ dateSent: new Date(message.metadata.dateSent),
18
+ edited: message.metadata.edited,
19
+ ...(message.metadata.editedAt ? { editedAt: new Date(message.metadata.editedAt) } : {}),
20
+ },
21
+ raw: cloneRawMessage(message.raw),
22
+ };
23
+ }
6
24
  function toPostableText(message) {
7
25
  if (typeof message === "string") {
8
26
  return message;
@@ -20,6 +38,7 @@ export class LoopbackChatAdapter {
20
38
  persistMessageHistory = true;
21
39
  userName;
22
40
  #messages = new Map();
41
+ #nextSequence = new Map();
23
42
  constructor(userName) {
24
43
  this.userName = userName;
25
44
  }
@@ -27,58 +46,95 @@ export class LoopbackChatAdapter {
27
46
  return Promise.resolve();
28
47
  }
29
48
  channelIdFromThreadId(threadId) {
30
- const [channelId = threadId] = threadId.split("::");
31
- return channelId;
49
+ const [address = threadId] = threadId.split("::");
50
+ if (!address.startsWith("loopback+v2:")) {
51
+ return address;
52
+ }
53
+ return this.decodeThreadId(threadId).channelId ?? address;
32
54
  }
33
55
  decodeThreadId(threadId) {
34
56
  const [address = threadId, rawThreadId] = threadId.split("::");
35
- const [, channelId = address, id = address] = address.split(":");
36
- const decoded = { id };
37
- if (channelId) {
38
- decoded.channelId = channelId;
57
+ const [platform, rawChannelOrId, rawId] = address.split(":");
58
+ if (platform !== "loopback+v2" || !rawChannelOrId) {
59
+ const [, channelId = address, id = address] = address.split(":");
60
+ const decoded = { id };
61
+ if (channelId) {
62
+ decoded.channelId = channelId;
63
+ }
64
+ if (rawThreadId) {
65
+ decoded.threadId = rawThreadId;
66
+ }
67
+ return decoded;
68
+ }
69
+ const decoded = {
70
+ id: decodeURIComponent(rawId ?? rawChannelOrId),
71
+ };
72
+ if (rawId) {
73
+ decoded.channelId = decodeURIComponent(rawChannelOrId);
39
74
  }
40
75
  if (rawThreadId) {
41
- decoded.threadId = rawThreadId;
76
+ decoded.threadId = decodeURIComponent(rawThreadId);
42
77
  }
43
78
  return decoded;
44
79
  }
45
80
  deleteMessage(threadId, messageId) {
46
81
  const messages = this.#messages.get(threadId) ?? [];
47
- this.#messages.set(threadId, messages.filter((entry) => entry.id !== messageId));
82
+ this.#messages.set(threadId, messages.filter((entry) => entry.message.id !== messageId));
48
83
  return Promise.resolve();
49
84
  }
50
85
  editMessage(threadId, messageId, message) {
51
86
  const messages = this.#messages.get(threadId) ?? [];
52
- const existing = messages.find((entry) => entry.id === messageId);
53
- if (!existing) {
87
+ const stored = messages.find((entry) => entry.message.id === messageId);
88
+ if (!stored) {
54
89
  throw new CrablineError(`Loopback message not found: ${messageId}`, { kind: "inbound" });
55
90
  }
91
+ const existing = stored.message;
56
92
  const text = toPostableText(message);
57
93
  existing.text = text;
58
94
  existing.formatted = text;
59
95
  existing.metadata.edited = true;
60
96
  existing.metadata.editedAt = new Date();
61
97
  existing.raw.text = text;
62
- return Promise.resolve({ id: existing.id, raw: existing.raw, threadId });
98
+ return Promise.resolve({ id: existing.id, raw: cloneRawMessage(existing.raw), threadId });
63
99
  }
64
100
  encodeThreadId(platformData) {
65
- const channelId = platformData.channelId ?? `loopback:${platformData.id}`;
66
- return platformData.threadId ? `${channelId}::${platformData.threadId}` : channelId;
101
+ const address = platformData.channelId
102
+ ? `loopback+v2:${encodeURIComponent(platformData.channelId)}:${encodeURIComponent(platformData.id)}`
103
+ : `loopback+v2:${encodeURIComponent(platformData.id)}`;
104
+ return platformData.threadId
105
+ ? `${address}::${encodeURIComponent(platformData.threadId)}`
106
+ : address;
67
107
  }
68
108
  fetchMessages(threadId, options) {
69
- const messages = [...(this.#messages.get(threadId) ?? [])];
70
- const limit = options?.limit ?? messages.length;
71
- if (!options?.cursor) {
72
- return Promise.resolve({
73
- messages: messages.slice(-limit),
109
+ const storedMessages = [...(this.#messages.get(threadId) ?? [])];
110
+ if (options?.limit !== undefined &&
111
+ (!Number.isSafeInteger(options.limit) || options.limit <= 0)) {
112
+ throw new CrablineError("Loopback message limit must be a positive safe integer.", {
113
+ kind: "config",
74
114
  });
75
115
  }
76
- const offset = Number(options.cursor);
116
+ const limit = options?.limit ?? storedMessages.length;
117
+ let cursor;
118
+ if (options?.cursor) {
119
+ if (!/^[1-9]\d*$/u.test(options.cursor)) {
120
+ throw new CrablineError("Loopback message cursor must be a positive safe integer.", {
121
+ kind: "config",
122
+ });
123
+ }
124
+ cursor = Number(options.cursor);
125
+ if (!Number.isSafeInteger(cursor) || cursor > (this.#nextSequence.get(threadId) ?? 0)) {
126
+ throw new CrablineError("Loopback message cursor must be a positive safe integer within message history.", { kind: "config" });
127
+ }
128
+ }
129
+ const eligibleMessages = cursor === undefined
130
+ ? storedMessages
131
+ : storedMessages.filter((entry) => entry.sequence < cursor);
132
+ const page = eligibleMessages.slice(-limit);
77
133
  const result = {
78
- messages: messages.slice(Math.max(0, offset - limit), offset),
134
+ messages: page.map((entry) => cloneMessage(entry.message)),
79
135
  };
80
- if (offset - limit > 0) {
81
- result.nextCursor = String(offset - limit);
136
+ if (eligibleMessages.length > page.length && page[0]) {
137
+ result.nextCursor = String(page[0].sequence);
82
138
  }
83
139
  return Promise.resolve(result);
84
140
  }
@@ -108,7 +164,7 @@ export class LoopbackChatAdapter {
108
164
  dateSent: new Date(raw.timestamp),
109
165
  edited: false,
110
166
  },
111
- raw,
167
+ raw: cloneRawMessage(raw),
112
168
  text: raw.text,
113
169
  threadId: raw.threadId,
114
170
  };
@@ -124,7 +180,7 @@ export class LoopbackChatAdapter {
124
180
  };
125
181
  const parsed = this.parseMessage(raw);
126
182
  this.#append(threadId, parsed);
127
- return Promise.resolve({ id: raw.id, raw, threadId });
183
+ return Promise.resolve({ id: raw.id, raw: cloneRawMessage(raw), threadId });
128
184
  }
129
185
  removeReaction() {
130
186
  return Promise.resolve();
@@ -145,28 +201,34 @@ export class LoopbackChatAdapter {
145
201
  };
146
202
  const parsed = this.parseMessage(raw);
147
203
  this.#append(threadId, parsed);
148
- return parsed;
204
+ return cloneMessage(parsed);
149
205
  }
150
206
  listSince(threadId, since) {
151
207
  const sinceTime = new Date(since).getTime();
152
- return (this.#messages.get(threadId) ?? []).filter((entry) => entry.metadata.dateSent.getTime() >= sinceTime);
208
+ return (this.#messages.get(threadId) ?? [])
209
+ .map((entry) => entry.message)
210
+ .filter((message) => message.metadata.dateSent.getTime() >= sinceTime)
211
+ .map(cloneMessage);
153
212
  }
154
213
  #append(threadId, message) {
155
214
  const bucket = this.#messages.get(threadId) ?? [];
156
- bucket.push(message);
215
+ const sequence = (this.#nextSequence.get(threadId) ?? 0) + 1;
216
+ bucket.push({ message: cloneMessage(message), sequence });
157
217
  this.#messages.set(threadId, bucket);
218
+ this.#nextSequence.set(threadId, sequence);
158
219
  }
159
220
  }
160
221
  export class LoopbackProviderAdapter extends LocalMockProviderAdapter {
161
222
  constructor(id, config, _userName) {
162
223
  super({
163
- codec: createGenericLocalMockTargetCodec("loopback"),
224
+ codec: getBuiltinTargetCodec("loopback"),
164
225
  config,
165
226
  id,
166
227
  options: {
167
- defaultWebhook: { host: "127.0.0.1", path: "/loopback/webhook", port: 8786 },
228
+ defaultWebhook: { host: "127.0.0.1", path: "/loopback/webhook", port: 0 },
168
229
  endpointLabel: "webhook endpoint",
169
230
  platform: "loopback",
231
+ recorderPath: path.resolve(".crabline", "recorders", `${id}-${randomUUID()}.jsonl`),
170
232
  },
171
233
  });
172
234
  }