@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.
- package/README.md +47 -22
- package/assets/crabline-banner.svg +20 -0
- package/dist/src/bin/crabline.js.map +1 -1
- package/dist/src/cli/program.d.ts +25 -1
- package/dist/src/cli/program.js +579 -82
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/load.js +59 -6
- package/dist/src/config/load.js.map +1 -1
- package/dist/src/config/schema.d.ts +116 -92
- package/dist/src/config/schema.js +179 -73
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/errors.js +9 -2
- package/dist/src/core/errors.js.map +1 -1
- package/dist/src/core/exit-codes.js.map +1 -1
- package/dist/src/core/matcher.js +7 -6
- package/dist/src/core/matcher.js.map +1 -1
- package/dist/src/core/message-template.js.map +1 -1
- package/dist/src/core/nonces.d.ts +1 -0
- package/dist/src/core/nonces.js +4 -2
- package/dist/src/core/nonces.js.map +1 -1
- package/dist/src/core/reporters.d.ts +1 -0
- package/dist/src/core/reporters.js +42 -4
- package/dist/src/core/reporters.js.map +1 -1
- package/dist/src/core/run.d.ts +3 -0
- package/dist/src/core/run.js +415 -107
- package/dist/src/core/run.js.map +1 -1
- package/dist/src/core/safe-regex.d.ts +3 -0
- package/dist/src/core/safe-regex.js +21 -0
- package/dist/src/core/safe-regex.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/openclaw/artifact-generation.d.ts +38 -0
- package/dist/src/openclaw/artifact-generation.js +314 -0
- package/dist/src/openclaw/artifact-generation.js.map +1 -0
- package/dist/src/openclaw/bridges/matrix.js +97 -12
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +32 -10
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/signal.js +55 -18
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +85 -7
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +124 -30
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +78 -17
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +14 -5
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/outbound-contract.d.ts +5 -0
- package/dist/src/openclaw/outbound-contract.js +40 -0
- package/dist/src/openclaw/outbound-contract.js.map +1 -0
- package/dist/src/openclaw/private-file.d.ts +26 -0
- package/dist/src/openclaw/private-file.js +411 -0
- package/dist/src/openclaw/private-file.js.map +1 -0
- package/dist/src/openclaw/shared.d.ts +21 -2
- package/dist/src/openclaw/shared.js +115 -18
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw/smoke-lock.d.ts +63 -0
- package/dist/src/openclaw/smoke-lock.js +949 -0
- package/dist/src/openclaw/smoke-lock.js.map +1 -0
- package/dist/src/openclaw.d.ts +16 -8
- package/dist/src/openclaw.js +194 -52
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/providers/builtin/discord.d.ts +10 -1
- package/dist/src/providers/builtin/discord.js +144 -18
- package/dist/src/providers/builtin/discord.js.map +1 -1
- package/dist/src/providers/builtin/external-webhook-auth.d.ts +11 -0
- package/dist/src/providers/builtin/external-webhook-auth.js +10 -0
- package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -0
- package/dist/src/providers/builtin/feishu.d.ts +19 -2
- package/dist/src/providers/builtin/feishu.js +307 -25
- package/dist/src/providers/builtin/feishu.js.map +1 -1
- package/dist/src/providers/builtin/googlechat.d.ts +20 -1
- package/dist/src/providers/builtin/googlechat.js +178 -19
- package/dist/src/providers/builtin/googlechat.js.map +1 -1
- package/dist/src/providers/builtin/imessage.d.ts +4 -0
- package/dist/src/providers/builtin/imessage.js +26 -11
- package/dist/src/providers/builtin/imessage.js.map +1 -1
- package/dist/src/providers/builtin/loopback.js +92 -30
- package/dist/src/providers/builtin/loopback.js.map +1 -1
- package/dist/src/providers/builtin/matrix.d.ts +11 -1
- package/dist/src/providers/builtin/matrix.js +53 -29
- package/dist/src/providers/builtin/matrix.js.map +1 -1
- package/dist/src/providers/builtin/mattermost.d.ts +10 -0
- package/dist/src/providers/builtin/mattermost.js +38 -15
- package/dist/src/providers/builtin/mattermost.js.map +1 -1
- package/dist/src/providers/builtin/msteams.d.ts +17 -1
- package/dist/src/providers/builtin/msteams.js +113 -14
- package/dist/src/providers/builtin/msteams.js.map +1 -1
- package/dist/src/providers/builtin/native-local-mock.d.ts +2 -9
- package/dist/src/providers/builtin/native-local-mock.js +12 -35
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
- package/dist/src/providers/builtin/script.d.ts +11 -15
- package/dist/src/providers/builtin/script.js +566 -104
- package/dist/src/providers/builtin/script.js.map +1 -1
- package/dist/src/providers/builtin/slack.d.ts +6 -0
- package/dist/src/providers/builtin/slack.js +99 -40
- package/dist/src/providers/builtin/slack.js.map +1 -1
- package/dist/src/providers/builtin/telegram.d.ts +6 -0
- package/dist/src/providers/builtin/telegram.js +64 -43
- package/dist/src/providers/builtin/telegram.js.map +1 -1
- package/dist/src/providers/builtin/whatsapp.d.ts +28 -8
- package/dist/src/providers/builtin/whatsapp.js +567 -30
- package/dist/src/providers/builtin/whatsapp.js.map +1 -1
- package/dist/src/providers/builtin/zalo.js +30 -19
- package/dist/src/providers/builtin/zalo.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +3 -3
- package/dist/src/providers/catalog.js +3 -3
- package/dist/src/providers/catalog.js.map +1 -1
- package/dist/src/providers/local-mock.d.ts +11 -2
- package/dist/src/providers/local-mock.js +322 -96
- package/dist/src/providers/local-mock.js.map +1 -1
- package/dist/src/providers/native-ids.d.ts +1 -0
- package/dist/src/providers/native-ids.js +6 -1
- package/dist/src/providers/native-ids.js.map +1 -1
- package/dist/src/providers/recorder.d.ts +30 -2
- package/dist/src/providers/recorder.js +539 -70
- package/dist/src/providers/recorder.js.map +1 -1
- package/dist/src/providers/registry.d.ts +25 -1
- package/dist/src/providers/registry.js +280 -40
- package/dist/src/providers/registry.js.map +1 -1
- package/dist/src/providers/signed-jwt.d.ts +29 -0
- package/dist/src/providers/signed-jwt.js +202 -0
- package/dist/src/providers/signed-jwt.js.map +1 -0
- package/dist/src/providers/slack-ids.js.map +1 -1
- package/dist/src/providers/target-normalizers.d.ts +26 -0
- package/dist/src/providers/target-normalizers.js +276 -0
- package/dist/src/providers/target-normalizers.js.map +1 -0
- package/dist/src/providers/types.d.ts +5 -0
- package/dist/src/providers/types.js.map +1 -1
- package/dist/src/providers/webhook-server.d.ts +2 -0
- package/dist/src/providers/webhook-server.js +72 -25
- package/dist/src/providers/webhook-server.js.map +1 -1
- package/dist/src/servers/http.d.ts +23 -3
- package/dist/src/servers/http.js +213 -20
- package/dist/src/servers/http.js.map +1 -1
- package/dist/src/servers/index.js +20 -21
- package/dist/src/servers/index.js.map +1 -1
- package/dist/src/servers/matrix.js +465 -51
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.d.ts +5 -0
- package/dist/src/servers/mattermost.js +306 -58
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/pending-events.d.ts +2 -0
- package/dist/src/servers/pending-events.js +11 -0
- package/dist/src/servers/pending-events.js.map +1 -0
- package/dist/src/servers/recorder.d.ts +5 -0
- package/dist/src/servers/recorder.js +46 -3
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.d.ts +2 -0
- package/dist/src/servers/signal.js +444 -56
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.d.ts +5 -0
- package/dist/src/servers/slack.js +515 -49
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram.d.ts +111 -0
- package/dist/src/servers/telegram.js +811 -152
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/webhook-target.d.ts +40 -0
- package/dist/src/servers/webhook-target.js +349 -0
- package/dist/src/servers/webhook-target.js.map +1 -0
- package/dist/src/servers/websocket.d.ts +2 -0
- package/dist/src/servers/websocket.js +29 -0
- package/dist/src/servers/websocket.js.map +1 -0
- package/dist/src/servers/whatsapp-baileys-websocket.d.ts +65 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +723 -99
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp-jid.d.ts +5 -0
- package/dist/src/servers/whatsapp-jid.js +32 -0
- package/dist/src/servers/whatsapp-jid.js.map +1 -0
- package/dist/src/servers/whatsapp-wire/binary-node.d.ts +5 -0
- package/dist/src/servers/whatsapp-wire/binary-node.js +87 -20
- package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/crypto.d.ts +6 -1
- package/dist/src/servers/whatsapp-wire/crypto.js +99 -45
- package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/handshake.js +39 -4
- package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +12 -7
- package/dist/src/servers/whatsapp.js +269 -107
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.d.ts +14 -0
- package/dist/src/servers/zalo.js +521 -88
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +197 -19
- package/fixtures/examples/crabline.example.yaml +10 -0
- package/package.json +20 -15
|
@@ -1,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 {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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,
|
|
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:
|
|
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 {
|
|
5
|
-
|
|
6
|
-
|
|
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:
|
|
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":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EACL,iBAAiB,EACjB,
|
|
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 {
|
|
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 [
|
|
31
|
-
|
|
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 [,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
decoded
|
|
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
|
|
53
|
-
if (!
|
|
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
|
|
66
|
-
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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:
|
|
134
|
+
messages: page.map((entry) => cloneMessage(entry.message)),
|
|
79
135
|
};
|
|
80
|
-
if (
|
|
81
|
-
result.nextCursor = String(
|
|
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) ?? [])
|
|
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
|
-
|
|
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:
|
|
224
|
+
codec: getBuiltinTargetCodec("loopback"),
|
|
164
225
|
config,
|
|
165
226
|
id,
|
|
166
227
|
options: {
|
|
167
|
-
defaultWebhook: { host: "127.0.0.1", path: "/loopback/webhook", port:
|
|
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
|
}
|