@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
@@ -1,8 +1,8 @@
1
- import { createPublicKey } from "node:crypto";
1
+ import { createPublicKey, X509Certificate } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { CrablineError } from "../../core/errors.js";
4
4
  import { LocalMockProviderAdapter } from "../local-mock.js";
5
- import { createCachedJwtKeyResolver, readBearerToken, resolveHttpCacheExpiry, verifySignedJwt, } from "../signed-jwt.js";
5
+ import { createCachedJwtKeyResolver, JwtKeyInfrastructureError, readBearerToken, resolveHttpCacheExpiry, verifySignedJwt, } from "../signed-jwt.js";
6
6
  import { getBuiltinTargetCodec, GOOGLE_CHAT_SPACE_RULE, GOOGLE_CHAT_THREAD_RULE, } from "../target-normalizers.js";
7
7
  import { authorFromBotFlag, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
8
8
  import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
@@ -17,6 +17,7 @@ export function resolveGoogleChatAdapterConfig(config, _env = process.env) {
17
17
  const GOOGLE_CHAT_SERVICE_ACCOUNT = "chat@system.gserviceaccount.com";
18
18
  const GOOGLE_OAUTH_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs";
19
19
  const GOOGLE_CHAT_CERTS_URL = "https://www.googleapis.com/service_accounts/v1/metadata/x509/chat@system.gserviceaccount.com";
20
+ const GOOGLE_CHAT_MESSAGE_NAME_PATTERN = /^spaces\/[A-Za-z0-9_-]+\/messages\/[^/\s]+$/u;
20
21
  export function createGoogleChatWebhookAuthenticator(config, runtime = {}) {
21
22
  if (config.googlechat?.disableSignatureVerification) {
22
23
  return undefined;
@@ -32,20 +33,42 @@ export function createGoogleChatWebhookAuthenticator(config, runtime = {}) {
32
33
  const createCertificateResolver = (certificateUrl) => {
33
34
  return createCachedJwtKeyResolver({
34
35
  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}.`);
36
+ try {
37
+ const requestedAt = runtime.now?.() ?? Date.now();
38
+ const response = await fetchImpl(certificateUrl, { signal });
39
+ if (!response.ok) {
40
+ throw new Error(`Google certificate fetch failed with HTTP ${response.status}.`);
41
+ }
42
+ const body = await response.json();
43
+ if (!isRecord(body)) {
44
+ throw new Error("Google certificate response must be an object.");
45
+ }
46
+ const values = Object.entries(body).map(([kid, certificate]) => {
47
+ if (typeof certificate !== "string") {
48
+ throw new Error("Google certificate response values must be strings.");
49
+ }
50
+ const certificateLabel = ["-----BEGIN", "CERTIFICATE-----"].join(" ");
51
+ const key = certificate.includes(certificateLabel)
52
+ ? new X509Certificate(certificate).publicKey
53
+ : createPublicKey(certificate);
54
+ if (key.asymmetricKeyType !== "rsa") {
55
+ throw new Error("Google certificate response keys must use RSA.");
56
+ }
57
+ return { kid, key };
58
+ });
59
+ if (values.length === 0) {
60
+ throw new Error("Google certificate response must include signing keys.");
61
+ }
62
+ return {
63
+ expiresAt: resolveHttpCacheExpiry(response, requestedAt),
64
+ values,
65
+ };
39
66
  }
40
- const body = await response.json();
41
- if (!isRecord(body)) {
42
- throw new Error("Google certificate response must be an object.");
67
+ catch (error) {
68
+ throw error instanceof JwtKeyInfrastructureError
69
+ ? error
70
+ : new JwtKeyInfrastructureError(error instanceof Error ? error.message : "Google certificate fetch failed.", { cause: error });
43
71
  }
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
72
  },
50
73
  keyId: (value) => value.kid,
51
74
  now: runtime.now,
@@ -70,7 +93,10 @@ export function createGoogleChatWebhookAuthenticator(config, runtime = {}) {
70
93
  payload = JSON.parse(rawBody);
71
94
  }
72
95
  catch {
73
- return undefined;
96
+ return new Response("unauthorized", {
97
+ headers: { "www-authenticate": "Bearer" },
98
+ status: 401,
99
+ });
74
100
  }
75
101
  const isPubsub = isRecord(payload) &&
76
102
  isRecord(payload.message) &&
@@ -94,7 +120,7 @@ export function createGoogleChatWebhookAuthenticator(config, runtime = {}) {
94
120
  const certificate = await (certificateUrl === GOOGLE_OAUTH_CERTS_URL
95
121
  ? resolveGoogleIdentityCertificate(header)
96
122
  : resolveGoogleChatCertificate(header));
97
- return createPublicKey(certificate.certificate);
123
+ return certificate.key;
98
124
  },
99
125
  token,
100
126
  });
@@ -111,14 +137,20 @@ export function createGoogleChatWebhookAuthenticator(config, runtime = {}) {
111
137
  }
112
138
  return undefined;
113
139
  }
114
- catch {
115
- return new Response("unauthorized", {
116
- headers: { "www-authenticate": "Bearer" },
117
- status: 401,
118
- });
140
+ catch (error) {
141
+ return googleAuthenticationResponse(error instanceof JwtKeyInfrastructureError ? 503 : 401);
119
142
  }
120
143
  };
121
144
  }
145
+ function googleAuthenticationResponse(status) {
146
+ return new Response(status === 401 ? "unauthorized" : "service unavailable", {
147
+ headers: {
148
+ "cache-control": "no-store",
149
+ ...(status === 401 ? { "www-authenticate": "Bearer" } : {}),
150
+ },
151
+ status,
152
+ });
153
+ }
122
154
  export function matchesGoogleChatThread(candidateThreadId, expectedThreadId, target = {}) {
123
155
  const candidateSpace = GOOGLE_CHAT_THREAD_RULE.pattern.test(candidateThreadId)
124
156
  ? candidateThreadId.slice(0, candidateThreadId.indexOf("/threads/"))
@@ -137,12 +169,18 @@ export class GoogleChatProviderAdapter extends LocalMockProviderAdapter {
137
169
  constructor(id, config, _userName, runtime) {
138
170
  const endpointAudience = config.googlechat?.endpointUrl ?? config.googlechat?.webhook.publicUrl;
139
171
  const pubsubServiceAccount = config.googlechat?.pubsubServiceAccountEmail ?? config.googlechat?.credentials?.client_email;
172
+ if (config.googlechat?.pubsubAudience && !pubsubServiceAccount) {
173
+ throw new CrablineError("Google Chat pubsubAudience requires a Pub/Sub service-account identity via pubsubServiceAccountEmail or credentials.client_email.", { kind: "config" });
174
+ }
140
175
  const authenticationConfigured = !config.googlechat?.disableSignatureVerification &&
141
176
  Boolean(endpointAudience ||
142
177
  config.googlechat?.googleChatProjectNumber ||
143
178
  (config.googlechat?.pubsubAudience && pubsubServiceAccount));
144
179
  requireExternalWebhookAuthentication({
145
180
  authenticated: authenticationConfigured,
181
+ authenticatedIngressUrls: config.googlechat?.endpointUrl
182
+ ? [config.googlechat.endpointUrl]
183
+ : undefined,
146
184
  provider: "Google Chat",
147
185
  requirement: "googlechat.endpointUrl, googlechat.googleChatProjectNumber, or googlechat.pubsubAudience with a Pub/Sub service-account identity and signature verification enabled",
148
186
  webhook: config.googlechat?.webhook,
@@ -154,8 +192,10 @@ export class GoogleChatProviderAdapter extends LocalMockProviderAdapter {
154
192
  id,
155
193
  options: {
156
194
  ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),
195
+ createWebhookSuccessResponse: () => new Response(null, { status: 200 }),
157
196
  defaultWebhook: { host: "127.0.0.1", path: "/googlechat/webhook", port: 8792 },
158
197
  endpointLabel: "webhook endpoint",
198
+ handleWebhookPayload: handleGoogleChatWebhookPayload,
159
199
  matchesThread: matchesGoogleChatThread,
160
200
  normalizeWebhookPayload: normalizeGoogleChatWebhookPayload,
161
201
  platform: "googlechat",
@@ -164,6 +204,7 @@ export class GoogleChatProviderAdapter extends LocalMockProviderAdapter {
164
204
  ? path.resolve(config.googlechat.recorder.path)
165
205
  : undefined,
166
206
  webhook: config.googlechat?.webhook,
207
+ webhookMethods: ["POST"],
167
208
  },
168
209
  });
169
210
  }
@@ -193,27 +234,55 @@ export function normalizeGoogleChatWebhookPayload(payload) {
193
234
  const sender = optionalRecord(message, "sender");
194
235
  const spaceName = space ? optionalString(space, "name") : undefined;
195
236
  const threadName = thread ? optionalString(thread, "name") : undefined;
237
+ const messageName = optionalString(message, "name");
196
238
  const text = optionalString(message, "text") ?? optionalString(message, "argumentText");
197
239
  if (!spaceName || !text) {
198
240
  throw new CrablineError("Google Chat message payload requires space.name and text", {
199
241
  kind: "inbound",
200
242
  });
201
243
  }
202
- if (threadName && !threadName.startsWith(`${spaceName}/threads/`)) {
244
+ const validatedSpaceName = requireNativeInboundId(spaceName, GOOGLE_CHAT_SPACE_RULE, "Google Chat space.name");
245
+ if (messageName) {
246
+ if (!GOOGLE_CHAT_MESSAGE_NAME_PATTERN.test(messageName)) {
247
+ throw new CrablineError("Google Chat message.name must be a valid message resource name", {
248
+ kind: "inbound",
249
+ });
250
+ }
251
+ if (!messageName.startsWith(`${validatedSpaceName}/messages/`)) {
252
+ throw new CrablineError("Google Chat message.name must belong to message.space.name", {
253
+ kind: "inbound",
254
+ });
255
+ }
256
+ }
257
+ const validatedThreadName = threadName
258
+ ? requireNativeInboundId(threadName, GOOGLE_CHAT_THREAD_RULE, "Google Chat thread.name")
259
+ : undefined;
260
+ if (validatedThreadName && !validatedThreadName.startsWith(`${validatedSpaceName}/threads/`)) {
203
261
  throw new CrablineError("Google Chat thread.name must belong to message.space.name", {
204
262
  kind: "inbound",
205
263
  });
206
264
  }
207
265
  return {
208
266
  author: authorFromBotFlag(optionalString(sender ?? {}, "type") === "BOT"),
209
- ...(optionalString(message, "name") ? { id: optionalString(message, "name") } : {}),
267
+ ...(messageName ? { id: messageName } : {}),
210
268
  raw: payload,
211
269
  text,
212
- threadId: threadName
213
- ? requireNativeInboundId(threadName, GOOGLE_CHAT_THREAD_RULE, "Google Chat thread.name")
214
- : requireNativeInboundId(spaceName, GOOGLE_CHAT_SPACE_RULE, "Google Chat space.name"),
270
+ threadId: validatedThreadName ?? validatedSpaceName,
215
271
  };
216
272
  }
273
+ export function handleGoogleChatWebhookPayload(payload) {
274
+ let event;
275
+ try {
276
+ event = unwrapGoogleChatPubsubPayload(payload);
277
+ }
278
+ catch {
279
+ return undefined;
280
+ }
281
+ if (isRecord(event) && typeof event.type === "string" && event.type !== "MESSAGE") {
282
+ return new Response(null, { status: 200 });
283
+ }
284
+ return undefined;
285
+ }
217
286
  function unwrapGoogleChatPubsubPayload(payload) {
218
287
  if (!isRecord(payload)) {
219
288
  return payload;
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"googlechat.js","sourceRoot":"","sources":["../../../../src/providers/builtin/googlechat.ts"],"names":[],"mappings":"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","sourcesContent":["import { createPublicKey, type KeyObject, X509Certificate } 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 JwtKeyInfrastructureError,\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\";\nconst GOOGLE_CHAT_MESSAGE_NAME_PATTERN = /^spaces\\/[A-Za-z0-9_-]+\\/messages\\/[^/\\s]+$/u;\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 kid: string;\n key: KeyObject;\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 try {\n const requestedAt = 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).map(([kid, certificate]) => {\n if (typeof certificate !== \"string\") {\n throw new Error(\"Google certificate response values must be strings.\");\n }\n const certificateLabel = [\"-----BEGIN\", \"CERTIFICATE-----\"].join(\" \");\n const key = certificate.includes(certificateLabel)\n ? new X509Certificate(certificate).publicKey\n : createPublicKey(certificate);\n if (key.asymmetricKeyType !== \"rsa\") {\n throw new Error(\"Google certificate response keys must use RSA.\");\n }\n return { kid, key };\n });\n if (values.length === 0) {\n throw new Error(\"Google certificate response must include signing keys.\");\n }\n return {\n expiresAt: resolveHttpCacheExpiry(response, requestedAt),\n values,\n };\n } catch (error) {\n throw error instanceof JwtKeyInfrastructureError\n ? error\n : new JwtKeyInfrastructureError(\n error instanceof Error ? error.message : \"Google certificate fetch failed.\",\n { cause: error },\n );\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 new Response(\"unauthorized\", {\n headers: { \"www-authenticate\": \"Bearer\" },\n status: 401,\n });\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 certificate.key;\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 (error) {\n return googleAuthenticationResponse(error instanceof JwtKeyInfrastructureError ? 503 : 401);\n }\n };\n}\n\nfunction googleAuthenticationResponse(status: 401 | 503): Response {\n return new Response(status === 401 ? \"unauthorized\" : \"service unavailable\", {\n headers: {\n \"cache-control\": \"no-store\",\n ...(status === 401 ? { \"www-authenticate\": \"Bearer\" } : {}),\n },\n status,\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 if (config.googlechat?.pubsubAudience && !pubsubServiceAccount) {\n throw new CrablineError(\n \"Google Chat pubsubAudience requires a Pub/Sub service-account identity via pubsubServiceAccountEmail or credentials.client_email.\",\n { kind: \"config\" },\n );\n }\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 authenticatedIngressUrls: config.googlechat?.endpointUrl\n ? [config.googlechat.endpointUrl]\n : undefined,\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 createWebhookSuccessResponse: () => new Response(null, { status: 200 }),\n defaultWebhook: { host: \"127.0.0.1\", path: \"/googlechat/webhook\", port: 8792 },\n endpointLabel: \"webhook endpoint\",\n handleWebhookPayload: handleGoogleChatWebhookPayload,\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 webhookMethods: [\"POST\"],\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 messageName = optionalString(message, \"name\");\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 const validatedSpaceName = requireNativeInboundId(\n spaceName,\n GOOGLE_CHAT_SPACE_RULE,\n \"Google Chat space.name\",\n );\n if (messageName) {\n if (!GOOGLE_CHAT_MESSAGE_NAME_PATTERN.test(messageName)) {\n throw new CrablineError(\"Google Chat message.name must be a valid message resource name\", {\n kind: \"inbound\",\n });\n }\n if (!messageName.startsWith(`${validatedSpaceName}/messages/`)) {\n throw new CrablineError(\"Google Chat message.name must belong to message.space.name\", {\n kind: \"inbound\",\n });\n }\n }\n const validatedThreadName = threadName\n ? requireNativeInboundId(threadName, GOOGLE_CHAT_THREAD_RULE, \"Google Chat thread.name\")\n : undefined;\n if (validatedThreadName && !validatedThreadName.startsWith(`${validatedSpaceName}/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 ...(messageName ? { id: messageName } : {}),\n raw: payload,\n text,\n threadId: validatedThreadName ?? validatedSpaceName,\n };\n}\n\nexport function handleGoogleChatWebhookPayload(payload: unknown): Response | undefined {\n let event: unknown;\n try {\n event = unwrapGoogleChatPubsubPayload(payload);\n } catch {\n return undefined;\n }\n if (isRecord(event) && typeof event.type === \"string\" && event.type !== \"MESSAGE\") {\n return new Response(null, { status: 200 });\n }\n return undefined;\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"]}
@@ -1,16 +1,60 @@
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 { matchesNativeId } from "../native-ids.js";
4
5
  import { getBuiltinTargetCodec, IMESSAGE_THREAD_RULE } from "../target-normalizers.js";
5
6
  import { authorFromBotFlag, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
6
7
  import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
7
- export function resolveIMessageAdapterConfig(config, env = process.env) {
8
+ export function resolveIMessageAdapterConfig(config, env) {
9
+ const local = config.imessage?.local ?? true;
10
+ env ??= local ? {} : process.env;
11
+ const configuredApiKey = config.imessage?.apiKey;
12
+ const environmentApiKey = env.IMESSAGE_API_KEY;
13
+ let apiKey = configuredApiKey?.trim()
14
+ ? configuredApiKey
15
+ : environmentApiKey?.trim()
16
+ ? environmentApiKey
17
+ : undefined;
18
+ if (!apiKey) {
19
+ if (!local) {
20
+ throw new CrablineError("Remote iMessage mode requires imessage.apiKey or IMESSAGE_API_KEY to be non-empty.", { kind: "config" });
21
+ }
22
+ apiKey = "local-mock-imessage-api-key";
23
+ }
8
24
  return {
9
- apiKey: config.imessage?.apiKey ?? env.IMESSAGE_API_KEY ?? "local-mock-imessage-api-key",
10
- local: config.imessage?.local ?? true,
25
+ apiKey,
26
+ local,
11
27
  serverUrl: config.imessage?.serverUrl ?? env.IMESSAGE_SERVER_URL,
12
28
  };
13
29
  }
30
+ function isNativeIMessageData(data) {
31
+ return [
32
+ "chat_guid",
33
+ "chat_identifier",
34
+ "chatGuid",
35
+ "chatIdentifier",
36
+ "guid",
37
+ "is_from_me",
38
+ "isFromMe",
39
+ ].some((key) => key in data);
40
+ }
41
+ function iMessageNativeData(payload) {
42
+ const data = optionalRecord(payload, "data") ?? payload;
43
+ const params = optionalRecord(data, "params");
44
+ const message = optionalRecord(params ?? {}, "message") ?? optionalRecord(data, "message");
45
+ if (message && isNativeIMessageData(message)) {
46
+ return message;
47
+ }
48
+ return data;
49
+ }
50
+ function iMessageThreadIdentifiers(data) {
51
+ return [
52
+ optionalString(data, "chatGuid"),
53
+ optionalString(data, "chatIdentifier"),
54
+ optionalString(data, "chat_guid"),
55
+ optionalString(data, "chat_identifier"),
56
+ ].filter((value) => value !== undefined);
57
+ }
14
58
  export class IMessageProviderAdapter extends LocalMockProviderAdapter {
15
59
  constructor(id, config, _userName, _runtime) {
16
60
  requireExternalWebhookAuthentication({
@@ -40,39 +84,38 @@ export class IMessageProviderAdapter extends LocalMockProviderAdapter {
40
84
  }
41
85
  export function matchesIMessageThread(candidateThreadId, expectedThreadId, target, raw) {
42
86
  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);
87
+ const data = rawPayload ? iMessageNativeData(rawPayload) : undefined;
88
+ const aliases = data ? iMessageThreadIdentifiers(data) : [];
89
+ const expectedIdentifiers = new Set([target.channelId ?? target.id]);
90
+ if (expectedThreadId !== undefined) {
91
+ expectedIdentifiers.add(expectedThreadId);
51
92
  }
52
93
  return (expectedIdentifiers.has(candidateThreadId) ||
53
- (recipientAlias !== undefined && expectedIdentifiers.has(recipientAlias)));
94
+ aliases.some((alias) => expectedIdentifiers.has(alias)));
54
95
  }
55
96
  function normalizeIMessageWebhookPayload(payload) {
56
97
  if (!isRecord(payload)) {
57
98
  throw new CrablineError("iMessage webhook payload must be an object", { kind: "inbound" });
58
99
  }
59
- if (optionalRecord(payload, "message")) {
100
+ const message = optionalRecord(payload, "message");
101
+ if ((message && !isNativeIMessageData(message)) ||
102
+ (!message && !isNativeIMessageData(payload) && optionalString(payload, "threadId"))) {
60
103
  return genericMockPayloadWithNativeThread({
61
104
  channelRule: IMESSAGE_THREAD_RULE,
62
105
  payload,
63
106
  threadRule: IMESSAGE_THREAD_RULE,
64
107
  });
65
108
  }
66
- const data = optionalRecord(payload, "data") ?? payload;
67
- const threadId = optionalString(data, "chatGuid") ?? optionalString(data, "chatIdentifier");
109
+ const data = iMessageNativeData(payload);
110
+ const threadId = iMessageThreadIdentifiers(data).find((candidate) => matchesNativeId(candidate, IMESSAGE_THREAD_RULE));
68
111
  const text = optionalString(data, "text") ?? optionalString(data, "message");
69
112
  if (!threadId || !text) {
70
- throw new CrablineError("iMessage webhook payload requires chatGuid or chatIdentifier and text", {
113
+ throw new CrablineError("iMessage webhook payload requires chatGuid or chatIdentifier (including native snake_case aliases) and text", {
71
114
  kind: "inbound",
72
115
  });
73
116
  }
74
117
  return {
75
- author: authorFromBotFlag(data.isFromMe === true),
118
+ author: authorFromBotFlag(data.isFromMe === true || data.is_from_me === true),
76
119
  ...(optionalString(data, "guid") ? { id: optionalString(data, "guid") } : {}),
77
120
  raw: payload,
78
121
  text,
@@ -1 +1 @@
1
- {"version":3,"file":"imessage.js","sourceRoot":"","sources":["../../../../src/providers/builtin/imessage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,EACL,iBAAiB,EACjB,kCAAkC,EAClC,QAAQ,EACR,cAAc,EACd,cAAc,EACd,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oCAAoC,EAAE,MAAM,4BAA4B,CAAC;AAElF,MAAM,UAAU,4BAA4B,CAC1C,MAAsB,EACtB,GAAG,GAAsB,OAAO,CAAC,GAAG;IAEpC,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,CAAC,gBAAgB,IAAI,6BAA6B;QACxF,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,IAAI,IAAI;QACrC,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,IAAI,GAAG,CAAC,mBAAmB;KACjE,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,uBAAwB,SAAQ,wBAAwB;IACnE,YAAY,EAAU,EAAE,MAAsB,EAAE,SAAiB,EAAE,QAAkB;QACnF,oCAAoC,CAAC;YACnC,aAAa,EAAE,KAAK;YACpB,QAAQ,EAAE,UAAU;YACpB,WAAW,EACT,mFAAmF;YACrF,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO;SAClC,CAAC,CAAC;QACH,KAAK,CAAC;YACJ,KAAK,EAAE,qBAAqB,CAAC,UAAU,CAAC;YACxC,MAAM;YACN,EAAE;YACF,OAAO,EAAE;gBACP,cAAc,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,IAAI,EAAE;gBAC5E,aAAa,EAAE,kBAAkB;gBACjC,aAAa,EAAE,qBAAqB;gBACpC,uBAAuB,EAAE,+BAA+B;gBACxD,QAAQ,EAAE,UAAU;gBACpB,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS;gBAC7C,YAAY,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI;oBAC1C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAC7C,CAAC,CAAC,SAAS;gBACb,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO;aAClC;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,UAAU,qBAAqB,CACnC,iBAAyB,EACzB,gBAAoC,EACpC,MAAsD,EACtD,GAAa;IAEb,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACnD,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACjF,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,gBAAgB,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,CACL,mBAAmB,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAC1C,CAAC,cAAc,KAAK,SAAS,IAAI,mBAAmB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAC1E,CAAC;AACJ,CAAC;AAED,SAAS,+BAA+B,CAAC,OAAgB;IACvD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,aAAa,CAAC,4CAA4C,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;QACvC,OAAO,kCAAkC,CAAC;YACxC,WAAW,EAAE,oBAAoB;YACjC,OAAO;YACP,UAAU,EAAE,oBAAoB;SACjC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,OAAO,CAAC;IACxD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC5F,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC7E,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,aAAa,CACrB,uEAAuE,EACvE;YACE,IAAI,EAAE,SAAS;SAChB,CACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC;QACjD,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,GAAG,EAAE,OAAO;QACZ,IAAI;QACJ,QAAQ,EAAE,sBAAsB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,mBAAmB,CAAC;KACtF,CAAC;AACJ,CAAC","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
+ {"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 { matchesNativeId } from \"../native-ids.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(config: ProviderConfig, env?: NodeJS.ProcessEnv) {\n const local = config.imessage?.local ?? true;\n env ??= local ? {} : process.env;\n const configuredApiKey = config.imessage?.apiKey;\n const environmentApiKey = env.IMESSAGE_API_KEY;\n let apiKey = configuredApiKey?.trim()\n ? configuredApiKey\n : environmentApiKey?.trim()\n ? environmentApiKey\n : undefined;\n if (!apiKey) {\n if (!local) {\n throw new CrablineError(\n \"Remote iMessage mode requires imessage.apiKey or IMESSAGE_API_KEY to be non-empty.\",\n { kind: \"config\" },\n );\n }\n apiKey = \"local-mock-imessage-api-key\";\n }\n return {\n apiKey,\n local,\n serverUrl: config.imessage?.serverUrl ?? env.IMESSAGE_SERVER_URL,\n };\n}\n\nfunction isNativeIMessageData(data: Record<string, unknown>): boolean {\n return [\n \"chat_guid\",\n \"chat_identifier\",\n \"chatGuid\",\n \"chatIdentifier\",\n \"guid\",\n \"is_from_me\",\n \"isFromMe\",\n ].some((key) => key in data);\n}\n\nfunction iMessageNativeData(payload: Record<string, unknown>): Record<string, unknown> {\n const data = optionalRecord(payload, \"data\") ?? payload;\n const params = optionalRecord(data, \"params\");\n const message = optionalRecord(params ?? {}, \"message\") ?? optionalRecord(data, \"message\");\n if (message && isNativeIMessageData(message)) {\n return message;\n }\n return data;\n}\n\nfunction iMessageThreadIdentifiers(data: Record<string, unknown>): string[] {\n return [\n optionalString(data, \"chatGuid\"),\n optionalString(data, \"chatIdentifier\"),\n optionalString(data, \"chat_guid\"),\n optionalString(data, \"chat_identifier\"),\n ].filter((value): value is string => value !== undefined);\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 ? iMessageNativeData(rawPayload) : undefined;\n const aliases = data ? iMessageThreadIdentifiers(data) : [];\n const expectedIdentifiers = new Set([target.channelId ?? target.id]);\n if (expectedThreadId !== undefined) {\n expectedIdentifiers.add(expectedThreadId);\n }\n return (\n expectedIdentifiers.has(candidateThreadId) ||\n aliases.some((alias) => expectedIdentifiers.has(alias))\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 const message = optionalRecord(payload, \"message\");\n if (\n (message && !isNativeIMessageData(message)) ||\n (!message && !isNativeIMessageData(payload) && optionalString(payload, \"threadId\"))\n ) {\n return genericMockPayloadWithNativeThread({\n channelRule: IMESSAGE_THREAD_RULE,\n payload,\n threadRule: IMESSAGE_THREAD_RULE,\n });\n }\n\n const data = iMessageNativeData(payload);\n const threadId = iMessageThreadIdentifiers(data).find((candidate) =>\n matchesNativeId(candidate, IMESSAGE_THREAD_RULE),\n );\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 (including native snake_case aliases) and text\",\n {\n kind: \"inbound\",\n },\n );\n }\n\n return {\n author: authorFromBotFlag(data.isFromMe === true || data.is_from_me === 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"]}