@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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type KeyObject } from "node:crypto";
|
|
2
|
+
type JwtHeader = {
|
|
3
|
+
alg: string;
|
|
4
|
+
kid: string;
|
|
5
|
+
};
|
|
6
|
+
export type JwtClaims = Record<string, unknown>;
|
|
7
|
+
export type RemoteJwtKeySet<T> = {
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
values: readonly T[];
|
|
10
|
+
};
|
|
11
|
+
export declare function readBearerToken(request: Request): string | undefined;
|
|
12
|
+
export declare function resolveHttpCacheExpiry(response: Response, now: number): number;
|
|
13
|
+
export declare function createCachedJwtKeyResolver<T>(params: {
|
|
14
|
+
fetchKeys(signal: AbortSignal): Promise<RemoteJwtKeySet<T>>;
|
|
15
|
+
keyId(value: T): string | undefined;
|
|
16
|
+
now?: (() => number) | undefined;
|
|
17
|
+
refreshCooldownMs?: number | undefined;
|
|
18
|
+
timeoutMs?: number | undefined;
|
|
19
|
+
unknownKeyMessage: string;
|
|
20
|
+
}): (header: JwtHeader) => Promise<T>;
|
|
21
|
+
export declare function verifySignedJwt(params: {
|
|
22
|
+
audience: string;
|
|
23
|
+
clockSkewSeconds?: number | undefined;
|
|
24
|
+
issuers: readonly string[];
|
|
25
|
+
now?: (() => number) | undefined;
|
|
26
|
+
resolveKey(header: JwtHeader): Promise<KeyObject>;
|
|
27
|
+
token: string;
|
|
28
|
+
}): Promise<JwtClaims>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { verify } from "node:crypto";
|
|
2
|
+
const DEFAULT_KEY_FETCH_TIMEOUT_MS = 5_000;
|
|
3
|
+
const DEFAULT_UNKNOWN_KEY_COOLDOWN_MS = 30_000;
|
|
4
|
+
const MAX_NEGATIVE_KEY_IDS = 128;
|
|
5
|
+
const UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
6
|
+
function decodeBase64UrlPart(value, label) {
|
|
7
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(value)) {
|
|
8
|
+
throw new Error(`${label} must use unpadded base64url encoding.`);
|
|
9
|
+
}
|
|
10
|
+
const decoded = Buffer.from(value, "base64url");
|
|
11
|
+
if (decoded.length === 0 || decoded.toString("base64url") !== value) {
|
|
12
|
+
throw new Error(`${label} must use canonical base64url encoding.`);
|
|
13
|
+
}
|
|
14
|
+
return decoded;
|
|
15
|
+
}
|
|
16
|
+
function decodeJsonPart(value) {
|
|
17
|
+
const decoded = JSON.parse(UTF8_DECODER.decode(decodeBase64UrlPart(value, "JWT part")));
|
|
18
|
+
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) {
|
|
19
|
+
throw new Error("JWT part must be a JSON object.");
|
|
20
|
+
}
|
|
21
|
+
return decoded;
|
|
22
|
+
}
|
|
23
|
+
function readHeader(value) {
|
|
24
|
+
if (value.alg !== "RS256" || typeof value.kid !== "string" || !value.kid) {
|
|
25
|
+
throw new Error("JWT must use RS256 and include a key id.");
|
|
26
|
+
}
|
|
27
|
+
return { alg: value.alg, kid: value.kid };
|
|
28
|
+
}
|
|
29
|
+
function hasAudience(claim, expected) {
|
|
30
|
+
return claim === expected || (Array.isArray(claim) && claim.includes(expected));
|
|
31
|
+
}
|
|
32
|
+
function numericClaim(claims, name, required = false) {
|
|
33
|
+
if (!(name in claims)) {
|
|
34
|
+
if (required) {
|
|
35
|
+
throw new Error(`JWT ${name} claim is required.`);
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const value = claims[name];
|
|
40
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
41
|
+
throw new Error(`JWT ${name} claim must be a finite number.`);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
export function readBearerToken(request) {
|
|
46
|
+
const authorization = request.headers.get("authorization");
|
|
47
|
+
const match = /^Bearer\s+(\S+)$/iu.exec(authorization ?? "");
|
|
48
|
+
return match?.[1];
|
|
49
|
+
}
|
|
50
|
+
export function resolveHttpCacheExpiry(response, now) {
|
|
51
|
+
const cacheControl = response.headers.get("cache-control");
|
|
52
|
+
if (/(?:^|,)\s*(?:no-cache|no-store)(?:\s*(?:=|,|$))/iu.test(cacheControl ?? "")) {
|
|
53
|
+
return now;
|
|
54
|
+
}
|
|
55
|
+
const ageHeader = response.headers.get("age");
|
|
56
|
+
const ageSeconds = ageHeader && /^\d+$/u.test(ageHeader) ? Number.parseInt(ageHeader, 10) : 0;
|
|
57
|
+
const maxAge = /(?:^|,)\s*max-age=(\d+)/iu.exec(cacheControl ?? "");
|
|
58
|
+
if (maxAge) {
|
|
59
|
+
return now + Math.max(0, Number(maxAge[1]) - ageSeconds) * 1_000;
|
|
60
|
+
}
|
|
61
|
+
const expires = Date.parse(response.headers.get("expires") ?? "");
|
|
62
|
+
return Number.isFinite(expires) && expires > now
|
|
63
|
+
? expires
|
|
64
|
+
: now + Math.max(0, 60 * 60 - ageSeconds) * 1_000;
|
|
65
|
+
}
|
|
66
|
+
export function createCachedJwtKeyResolver(params) {
|
|
67
|
+
const now = params.now ?? Date.now;
|
|
68
|
+
const refreshCooldownMs = params.refreshCooldownMs ?? DEFAULT_UNKNOWN_KEY_COOLDOWN_MS;
|
|
69
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_KEY_FETCH_TIMEOUT_MS;
|
|
70
|
+
let cached;
|
|
71
|
+
let fetchInFlight;
|
|
72
|
+
let refreshInFlight;
|
|
73
|
+
let refreshCooldownUntil = 0;
|
|
74
|
+
let fetchFailureCooldownUntil = 0;
|
|
75
|
+
let fetchFailureError;
|
|
76
|
+
const negativeKeyIds = new Map();
|
|
77
|
+
const rejectUnknownKey = (kid, expiresAt) => {
|
|
78
|
+
for (const [candidate, candidateExpiry] of negativeKeyIds) {
|
|
79
|
+
if (candidateExpiry <= now()) {
|
|
80
|
+
negativeKeyIds.delete(candidate);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (negativeKeyIds.size >= MAX_NEGATIVE_KEY_IDS) {
|
|
84
|
+
const oldest = negativeKeyIds.keys().next().value;
|
|
85
|
+
if (oldest) {
|
|
86
|
+
negativeKeyIds.delete(oldest);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
negativeKeyIds.set(kid, expiresAt);
|
|
90
|
+
throw new Error(params.unknownKeyMessage);
|
|
91
|
+
};
|
|
92
|
+
const fetchKeys = async () => {
|
|
93
|
+
if (fetchInFlight) {
|
|
94
|
+
return await fetchInFlight;
|
|
95
|
+
}
|
|
96
|
+
if (fetchFailureCooldownUntil > now()) {
|
|
97
|
+
throw fetchFailureError;
|
|
98
|
+
}
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
let timeout;
|
|
101
|
+
const timedOut = new Promise((_, reject) => {
|
|
102
|
+
timeout = setTimeout(() => {
|
|
103
|
+
controller.abort();
|
|
104
|
+
reject(new Error("JWT signing key fetch timed out."));
|
|
105
|
+
}, timeoutMs);
|
|
106
|
+
});
|
|
107
|
+
fetchInFlight = Promise.race([params.fetchKeys(controller.signal), timedOut])
|
|
108
|
+
.then((keySet) => {
|
|
109
|
+
cached = keySet.expiresAt > now() ? keySet : undefined;
|
|
110
|
+
fetchFailureCooldownUntil = 0;
|
|
111
|
+
fetchFailureError = undefined;
|
|
112
|
+
for (const value of keySet.values) {
|
|
113
|
+
const keyId = params.keyId(value);
|
|
114
|
+
if (keyId) {
|
|
115
|
+
negativeKeyIds.delete(keyId);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return keySet;
|
|
119
|
+
})
|
|
120
|
+
.catch((error) => {
|
|
121
|
+
fetchFailureCooldownUntil = now() + refreshCooldownMs;
|
|
122
|
+
fetchFailureError = error;
|
|
123
|
+
throw error;
|
|
124
|
+
})
|
|
125
|
+
.finally(() => {
|
|
126
|
+
if (timeout) {
|
|
127
|
+
clearTimeout(timeout);
|
|
128
|
+
}
|
|
129
|
+
fetchInFlight = undefined;
|
|
130
|
+
});
|
|
131
|
+
return await fetchInFlight;
|
|
132
|
+
};
|
|
133
|
+
return async (header) => {
|
|
134
|
+
const currentTime = now();
|
|
135
|
+
const negativeExpiry = negativeKeyIds.get(header.kid);
|
|
136
|
+
if (negativeExpiry && negativeExpiry > currentTime) {
|
|
137
|
+
throw new Error(params.unknownKeyMessage);
|
|
138
|
+
}
|
|
139
|
+
negativeKeyIds.delete(header.kid);
|
|
140
|
+
const freshCache = cached && cached.expiresAt > currentTime ? cached : undefined;
|
|
141
|
+
const keySet = freshCache ?? (await fetchKeys());
|
|
142
|
+
const key = keySet.values.find((candidate) => params.keyId(candidate) === header.kid);
|
|
143
|
+
if (key) {
|
|
144
|
+
return key;
|
|
145
|
+
}
|
|
146
|
+
if (!freshCache) {
|
|
147
|
+
return rejectUnknownKey(header.kid, now() + refreshCooldownMs);
|
|
148
|
+
}
|
|
149
|
+
let refreshed;
|
|
150
|
+
if (refreshInFlight) {
|
|
151
|
+
refreshed = await refreshInFlight;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const refreshTime = now();
|
|
155
|
+
if (refreshCooldownUntil > refreshTime) {
|
|
156
|
+
return rejectUnknownKey(header.kid, refreshCooldownUntil);
|
|
157
|
+
}
|
|
158
|
+
refreshCooldownUntil = refreshTime + refreshCooldownMs;
|
|
159
|
+
refreshInFlight = fetchKeys().finally(() => {
|
|
160
|
+
refreshInFlight = undefined;
|
|
161
|
+
});
|
|
162
|
+
refreshed = await refreshInFlight;
|
|
163
|
+
}
|
|
164
|
+
const rotatedKey = refreshed.values.find((candidate) => params.keyId(candidate) === header.kid);
|
|
165
|
+
if (!rotatedKey) {
|
|
166
|
+
return rejectUnknownKey(header.kid, refreshCooldownUntil);
|
|
167
|
+
}
|
|
168
|
+
return rotatedKey;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
export async function verifySignedJwt(params) {
|
|
172
|
+
const parts = params.token.split(".");
|
|
173
|
+
if (parts.length !== 3) {
|
|
174
|
+
throw new Error("JWT must contain three parts.");
|
|
175
|
+
}
|
|
176
|
+
const [encodedHeader, encodedClaims, encodedSignature] = parts;
|
|
177
|
+
const header = readHeader(decodeJsonPart(encodedHeader));
|
|
178
|
+
const claims = decodeJsonPart(encodedClaims);
|
|
179
|
+
const key = await params.resolveKey(header);
|
|
180
|
+
const signature = decodeBase64UrlPart(encodedSignature, "JWT signature");
|
|
181
|
+
if (!verify("RSA-SHA256", Buffer.from(`${encodedHeader}.${encodedClaims}`), key, signature)) {
|
|
182
|
+
throw new Error("JWT signature is invalid.");
|
|
183
|
+
}
|
|
184
|
+
if (!params.issuers.includes(String(claims.iss ?? ""))) {
|
|
185
|
+
throw new Error("JWT issuer is invalid.");
|
|
186
|
+
}
|
|
187
|
+
if (!hasAudience(claims.aud, params.audience)) {
|
|
188
|
+
throw new Error("JWT audience is invalid.");
|
|
189
|
+
}
|
|
190
|
+
const now = Math.floor((params.now?.() ?? Date.now()) / 1000);
|
|
191
|
+
const skew = params.clockSkewSeconds ?? 300;
|
|
192
|
+
const expiresAt = numericClaim(claims, "exp", true);
|
|
193
|
+
if (expiresAt <= now - skew) {
|
|
194
|
+
throw new Error("JWT is expired.");
|
|
195
|
+
}
|
|
196
|
+
const notBefore = numericClaim(claims, "nbf");
|
|
197
|
+
if (notBefore !== undefined && notBefore > now + skew) {
|
|
198
|
+
throw new Error("JWT is not active.");
|
|
199
|
+
}
|
|
200
|
+
return claims;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=signed-jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signed-jwt.js","sourceRoot":"","sources":["../../../src/providers/signed-jwt.ts"],"names":[],"mappings":"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","sourcesContent":["import { verify, type KeyObject } from \"node:crypto\";\n\ntype JwtHeader = {\n alg: string;\n kid: string;\n};\n\nexport type JwtClaims = Record<string, unknown>;\n\nexport type RemoteJwtKeySet<T> = {\n expiresAt: number;\n values: readonly T[];\n};\n\nconst DEFAULT_KEY_FETCH_TIMEOUT_MS = 5_000;\nconst DEFAULT_UNKNOWN_KEY_COOLDOWN_MS = 30_000;\nconst MAX_NEGATIVE_KEY_IDS = 128;\nconst UTF8_DECODER = new TextDecoder(\"utf-8\", { fatal: true });\n\nfunction decodeBase64UrlPart(value: string, label: string): Buffer {\n if (!/^[A-Za-z0-9_-]+$/u.test(value)) {\n throw new Error(`${label} must use unpadded base64url encoding.`);\n }\n const decoded = Buffer.from(value, \"base64url\");\n if (decoded.length === 0 || decoded.toString(\"base64url\") !== value) {\n throw new Error(`${label} must use canonical base64url encoding.`);\n }\n return decoded;\n}\n\nfunction decodeJsonPart(value: string): Record<string, unknown> {\n const decoded: unknown = JSON.parse(UTF8_DECODER.decode(decodeBase64UrlPart(value, \"JWT part\")));\n if (!decoded || typeof decoded !== \"object\" || Array.isArray(decoded)) {\n throw new Error(\"JWT part must be a JSON object.\");\n }\n return decoded as Record<string, unknown>;\n}\n\nfunction readHeader(value: Record<string, unknown>): JwtHeader {\n if (value.alg !== \"RS256\" || typeof value.kid !== \"string\" || !value.kid) {\n throw new Error(\"JWT must use RS256 and include a key id.\");\n }\n return { alg: value.alg, kid: value.kid };\n}\n\nfunction hasAudience(claim: unknown, expected: string): boolean {\n return claim === expected || (Array.isArray(claim) && claim.includes(expected));\n}\n\nfunction numericClaim(claims: JwtClaims, name: string, required = false): number | undefined {\n if (!(name in claims)) {\n if (required) {\n throw new Error(`JWT ${name} claim is required.`);\n }\n return undefined;\n }\n const value = claims[name];\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new Error(`JWT ${name} claim must be a finite number.`);\n }\n return value;\n}\n\nexport function readBearerToken(request: Request): string | undefined {\n const authorization = request.headers.get(\"authorization\");\n const match = /^Bearer\\s+(\\S+)$/iu.exec(authorization ?? \"\");\n return match?.[1];\n}\n\nexport function resolveHttpCacheExpiry(response: Response, now: number): number {\n const cacheControl = response.headers.get(\"cache-control\");\n if (/(?:^|,)\\s*(?:no-cache|no-store)(?:\\s*(?:=|,|$))/iu.test(cacheControl ?? \"\")) {\n return now;\n }\n const ageHeader = response.headers.get(\"age\");\n const ageSeconds = ageHeader && /^\\d+$/u.test(ageHeader) ? Number.parseInt(ageHeader, 10) : 0;\n const maxAge = /(?:^|,)\\s*max-age=(\\d+)/iu.exec(cacheControl ?? \"\");\n if (maxAge) {\n return now + Math.max(0, Number(maxAge[1]) - ageSeconds) * 1_000;\n }\n const expires = Date.parse(response.headers.get(\"expires\") ?? \"\");\n return Number.isFinite(expires) && expires > now\n ? expires\n : now + Math.max(0, 60 * 60 - ageSeconds) * 1_000;\n}\n\nexport function createCachedJwtKeyResolver<T>(params: {\n fetchKeys(signal: AbortSignal): Promise<RemoteJwtKeySet<T>>;\n keyId(value: T): string | undefined;\n now?: (() => number) | undefined;\n refreshCooldownMs?: number | undefined;\n timeoutMs?: number | undefined;\n unknownKeyMessage: string;\n}) {\n const now = params.now ?? Date.now;\n const refreshCooldownMs = params.refreshCooldownMs ?? DEFAULT_UNKNOWN_KEY_COOLDOWN_MS;\n const timeoutMs = params.timeoutMs ?? DEFAULT_KEY_FETCH_TIMEOUT_MS;\n let cached: RemoteJwtKeySet<T> | undefined;\n let fetchInFlight: Promise<RemoteJwtKeySet<T>> | undefined;\n let refreshInFlight: Promise<RemoteJwtKeySet<T>> | undefined;\n let refreshCooldownUntil = 0;\n let fetchFailureCooldownUntil = 0;\n let fetchFailureError: unknown;\n const negativeKeyIds = new Map<string, number>();\n\n const rejectUnknownKey = (kid: string, expiresAt: number): never => {\n for (const [candidate, candidateExpiry] of negativeKeyIds) {\n if (candidateExpiry <= now()) {\n negativeKeyIds.delete(candidate);\n }\n }\n if (negativeKeyIds.size >= MAX_NEGATIVE_KEY_IDS) {\n const oldest = negativeKeyIds.keys().next().value;\n if (oldest) {\n negativeKeyIds.delete(oldest);\n }\n }\n negativeKeyIds.set(kid, expiresAt);\n throw new Error(params.unknownKeyMessage);\n };\n\n const fetchKeys = async (): Promise<RemoteJwtKeySet<T>> => {\n if (fetchInFlight) {\n return await fetchInFlight;\n }\n if (fetchFailureCooldownUntil > now()) {\n throw fetchFailureError;\n }\n const controller = new AbortController();\n let timeout: NodeJS.Timeout | undefined;\n const timedOut = new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n controller.abort();\n reject(new Error(\"JWT signing key fetch timed out.\"));\n }, timeoutMs);\n });\n fetchInFlight = Promise.race([params.fetchKeys(controller.signal), timedOut])\n .then((keySet) => {\n cached = keySet.expiresAt > now() ? keySet : undefined;\n fetchFailureCooldownUntil = 0;\n fetchFailureError = undefined;\n for (const value of keySet.values) {\n const keyId = params.keyId(value);\n if (keyId) {\n negativeKeyIds.delete(keyId);\n }\n }\n return keySet;\n })\n .catch((error: unknown) => {\n fetchFailureCooldownUntil = now() + refreshCooldownMs;\n fetchFailureError = error;\n throw error;\n })\n .finally(() => {\n if (timeout) {\n clearTimeout(timeout);\n }\n fetchInFlight = undefined;\n });\n return await fetchInFlight;\n };\n\n return async (header: JwtHeader): Promise<T> => {\n const currentTime = now();\n const negativeExpiry = negativeKeyIds.get(header.kid);\n if (negativeExpiry && negativeExpiry > currentTime) {\n throw new Error(params.unknownKeyMessage);\n }\n negativeKeyIds.delete(header.kid);\n\n const freshCache = cached && cached.expiresAt > currentTime ? cached : undefined;\n const keySet = freshCache ?? (await fetchKeys());\n const key = keySet.values.find((candidate) => params.keyId(candidate) === header.kid);\n if (key) {\n return key;\n }\n\n if (!freshCache) {\n return rejectUnknownKey(header.kid, now() + refreshCooldownMs);\n }\n\n let refreshed: RemoteJwtKeySet<T>;\n if (refreshInFlight) {\n refreshed = await refreshInFlight;\n } else {\n const refreshTime = now();\n if (refreshCooldownUntil > refreshTime) {\n return rejectUnknownKey(header.kid, refreshCooldownUntil);\n }\n refreshCooldownUntil = refreshTime + refreshCooldownMs;\n refreshInFlight = fetchKeys().finally(() => {\n refreshInFlight = undefined;\n });\n refreshed = await refreshInFlight;\n }\n\n const rotatedKey = refreshed.values.find((candidate) => params.keyId(candidate) === header.kid);\n if (!rotatedKey) {\n return rejectUnknownKey(header.kid, refreshCooldownUntil);\n }\n return rotatedKey;\n };\n}\n\nexport async function verifySignedJwt(params: {\n audience: string;\n clockSkewSeconds?: number | undefined;\n issuers: readonly string[];\n now?: (() => number) | undefined;\n resolveKey(header: JwtHeader): Promise<KeyObject>;\n token: string;\n}): Promise<JwtClaims> {\n const parts = params.token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"JWT must contain three parts.\");\n }\n const [encodedHeader, encodedClaims, encodedSignature] = parts as [string, string, string];\n const header = readHeader(decodeJsonPart(encodedHeader));\n const claims = decodeJsonPart(encodedClaims);\n const key = await params.resolveKey(header);\n const signature = decodeBase64UrlPart(encodedSignature, \"JWT signature\");\n if (!verify(\"RSA-SHA256\", Buffer.from(`${encodedHeader}.${encodedClaims}`), key, signature)) {\n throw new Error(\"JWT signature is invalid.\");\n }\n\n if (!params.issuers.includes(String(claims.iss ?? \"\"))) {\n throw new Error(\"JWT issuer is invalid.\");\n }\n if (!hasAudience(claims.aud, params.audience)) {\n throw new Error(\"JWT audience is invalid.\");\n }\n\n const now = Math.floor((params.now?.() ?? Date.now()) / 1000);\n const skew = params.clockSkewSeconds ?? 300;\n const expiresAt = numericClaim(claims, \"exp\", true)!;\n if (expiresAt <= now - skew) {\n throw new Error(\"JWT is expired.\");\n }\n const notBefore = numericClaim(claims, \"nbf\");\n if (notBefore !== undefined && notBefore > now + skew) {\n throw new Error(\"JWT is not active.\");\n }\n\n return claims;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-ids.js","sourceRoot":"","sources":["../../../src/providers/slack-ids.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,OAAO,EAAE,aAAa;IACtB,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,sBAAsB;CAChC,CAAC;AAEF,MAAM,CAAC,MAAM,yBAAyB,GAAiB;IACrD,OAAO,EAAE,aAAa;IACtB,IAAI,EAAE,+BAA+B;IACrC,OAAO,EAAE,wBAAwB;CAClC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAiB;IAC9C,OAAO,EAAE,aAAa;IACtB,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,qBAAqB;CAC/B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,OAAO,EAAE,mBAAmB;IAC5B,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,kBAAkB;CAC5B,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,QAAiB;IAC/D,OAAO,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,WAAW,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9D,CAAC"}
|
|
1
|
+
{"version":3,"file":"slack-ids.js","sourceRoot":"","sources":["../../../src/providers/slack-ids.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,OAAO,EAAE,aAAa;IACtB,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,sBAAsB;CAChC,CAAC;AAEF,MAAM,CAAC,MAAM,yBAAyB,GAAiB;IACrD,OAAO,EAAE,aAAa;IACtB,IAAI,EAAE,+BAA+B;IACrC,OAAO,EAAE,wBAAwB;CAClC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAiB;IAC9C,OAAO,EAAE,aAAa;IACtB,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,qBAAqB;CAC/B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,OAAO,EAAE,mBAAmB;IAC5B,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,kBAAkB;CAC5B,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,QAAiB;IAC/D,OAAO,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,WAAW,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9D,CAAC","sourcesContent":["import type { NativeIdRule } from \"./native-ids.js\";\n\nexport const SLACK_CHANNEL_ID_RULE: NativeIdRule = {\n example: \"C1234567890\",\n name: \"Slack conversation id\",\n pattern: /^[CDG][A-Z0-9]{2,}$/u,\n};\n\nexport const SLACK_SEND_TARGET_ID_RULE: NativeIdRule = {\n example: \"C1234567890\",\n name: \"Slack conversation or user id\",\n pattern: /^[CDGUW][A-Z0-9]{2,}$/u,\n};\n\nexport const SLACK_USER_ID_RULE: NativeIdRule = {\n example: \"U1234567890\",\n name: \"Slack user id\",\n pattern: /^[UW][A-Z0-9]{2,}$/u,\n};\n\nexport const SLACK_TS_RULE: NativeIdRule = {\n example: \"1700000000.000100\",\n name: \"Slack timestamp\",\n pattern: /^\\d{10}\\.\\d{6}$/u,\n};\n\nexport function slackTargetKey(channel: string, threadTs?: string) {\n return threadTs ? `${channel}:thread:${threadTs}` : channel;\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BuiltinAdapterId, FixtureDefinition, ProviderPlatform } from "../config/schema.js";
|
|
2
|
+
import type { LocalMockTargetCodec } from "./local-mock.js";
|
|
3
|
+
import type { NativeIdRule } from "./native-ids.js";
|
|
4
|
+
import type { NormalizedTarget } from "./types.js";
|
|
5
|
+
export type BuiltinProviderAdapterId = Exclude<BuiltinAdapterId, "script">;
|
|
6
|
+
export declare const DISCORD_SNOWFLAKE_RULE: NativeIdRule;
|
|
7
|
+
export declare const FEISHU_CHAT_ID_RULE: NativeIdRule;
|
|
8
|
+
export declare const FEISHU_MESSAGE_ID_RULE: NativeIdRule;
|
|
9
|
+
export declare const GOOGLE_CHAT_SPACE_RULE: NativeIdRule;
|
|
10
|
+
export declare const GOOGLE_CHAT_THREAD_RULE: NativeIdRule;
|
|
11
|
+
export declare const IMESSAGE_THREAD_RULE: NativeIdRule;
|
|
12
|
+
export declare const MATRIX_ROOM_ID_RULE: NativeIdRule;
|
|
13
|
+
export declare const MATRIX_EVENT_ID_RULE: NativeIdRule;
|
|
14
|
+
export declare const MATTERMOST_ID_RULE: NativeIdRule;
|
|
15
|
+
export declare const MSTEAMS_CONVERSATION_ID_RULE: NativeIdRule;
|
|
16
|
+
export declare const TELEGRAM_CHAT_ID_RULE: NativeIdRule;
|
|
17
|
+
export declare const TELEGRAM_MESSAGE_THREAD_ID_RULE: NativeIdRule;
|
|
18
|
+
export declare const WHATSAPP_WA_ID_RULE: NativeIdRule;
|
|
19
|
+
export declare const ZALO_ID_RULE: NativeIdRule;
|
|
20
|
+
export declare function createGenericLocalMockTargetCodec(platform: ProviderPlatform): LocalMockTargetCodec;
|
|
21
|
+
export declare function parseCanonicalTelegramTopic(value: string): {
|
|
22
|
+
chatId: string;
|
|
23
|
+
topicId: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
export declare function getBuiltinTargetCodec(adapter: BuiltinProviderAdapterId): LocalMockTargetCodec;
|
|
26
|
+
export declare function normalizeBuiltinTarget(adapter: BuiltinProviderAdapterId, target: FixtureDefinition["target"]): NormalizedTarget;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { CrablineError } from "../core/errors.js";
|
|
2
|
+
import { slackTargetKey, SLACK_SEND_TARGET_ID_RULE, SLACK_TS_RULE } from "./slack-ids.js";
|
|
3
|
+
export const DISCORD_SNOWFLAKE_RULE = {
|
|
4
|
+
example: "123456789012345678",
|
|
5
|
+
name: "Discord snowflake id",
|
|
6
|
+
pattern: /^\d{17,20}$/u,
|
|
7
|
+
};
|
|
8
|
+
export const FEISHU_CHAT_ID_RULE = {
|
|
9
|
+
example: "oc_abc123",
|
|
10
|
+
name: "Feishu chat_id",
|
|
11
|
+
pattern: /^oc_[A-Za-z0-9_-]+$/u,
|
|
12
|
+
};
|
|
13
|
+
export const FEISHU_MESSAGE_ID_RULE = {
|
|
14
|
+
example: "om_abc123",
|
|
15
|
+
name: "Feishu message_id",
|
|
16
|
+
pattern: /^om_[A-Za-z0-9_-]+$/u,
|
|
17
|
+
};
|
|
18
|
+
export const GOOGLE_CHAT_SPACE_RULE = {
|
|
19
|
+
example: "spaces/AAAABbbbCCC",
|
|
20
|
+
name: "Google Chat space name",
|
|
21
|
+
pattern: /^spaces\/[A-Za-z0-9_-]+$/u,
|
|
22
|
+
};
|
|
23
|
+
export const GOOGLE_CHAT_THREAD_RULE = {
|
|
24
|
+
example: "spaces/AAAABbbbCCC/threads/BBBBccccDDD",
|
|
25
|
+
name: "Google Chat thread name",
|
|
26
|
+
pattern: /^spaces\/[A-Za-z0-9_-]+\/threads\/[A-Za-z0-9_-]+$/u,
|
|
27
|
+
};
|
|
28
|
+
export const IMESSAGE_THREAD_RULE = {
|
|
29
|
+
example: "+15551234567, user@example.com, or iMessage;-;chat-guid",
|
|
30
|
+
name: "iMessage recipient or chat GUID",
|
|
31
|
+
pattern: /^(?:\+[1-9]\d{6,14}|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}|(?:iMessage|SMS);[+-];.+)$/u,
|
|
32
|
+
};
|
|
33
|
+
export const MATRIX_ROOM_ID_RULE = {
|
|
34
|
+
example: "!abcdef:matrix.org",
|
|
35
|
+
name: "Matrix room id",
|
|
36
|
+
pattern: /^!(?:[^:\s]+:[^\s]+|[A-Za-z0-9_-]{43})$/u,
|
|
37
|
+
};
|
|
38
|
+
export const MATRIX_EVENT_ID_RULE = {
|
|
39
|
+
example: "$eventid:matrix.org",
|
|
40
|
+
name: "Matrix event id",
|
|
41
|
+
pattern: /^\$[^\s]+(?::[^\s]+)?$/u,
|
|
42
|
+
};
|
|
43
|
+
export const MATTERMOST_ID_RULE = {
|
|
44
|
+
example: "abcdefghijklmnopqrstuvwx12",
|
|
45
|
+
name: "Mattermost id",
|
|
46
|
+
pattern: /^[a-z0-9]{26}$/u,
|
|
47
|
+
};
|
|
48
|
+
export const MSTEAMS_CONVERSATION_ID_RULE = {
|
|
49
|
+
example: "a:opaque-conversation-id",
|
|
50
|
+
name: "Microsoft Teams conversation id",
|
|
51
|
+
pattern: /^.+$/su,
|
|
52
|
+
};
|
|
53
|
+
export const TELEGRAM_CHAT_ID_RULE = {
|
|
54
|
+
example: "-1001234567890 or @channelusername",
|
|
55
|
+
name: "Telegram chat id",
|
|
56
|
+
pattern: /^(?:-?[1-9]\d*|@[A-Za-z][A-Za-z0-9_]{3,31})$/u,
|
|
57
|
+
};
|
|
58
|
+
export const TELEGRAM_MESSAGE_THREAD_ID_RULE = {
|
|
59
|
+
example: "42",
|
|
60
|
+
name: "Telegram message_thread_id",
|
|
61
|
+
pattern: /^[1-9]\d*$/u,
|
|
62
|
+
};
|
|
63
|
+
export const WHATSAPP_WA_ID_RULE = {
|
|
64
|
+
example: "15551234567",
|
|
65
|
+
name: "WhatsApp wa_id",
|
|
66
|
+
pattern: /^\d{7,15}$/u,
|
|
67
|
+
};
|
|
68
|
+
export const ZALO_ID_RULE = {
|
|
69
|
+
example: "user-1",
|
|
70
|
+
name: "Zalo string id",
|
|
71
|
+
pattern: /^\S+$/u,
|
|
72
|
+
};
|
|
73
|
+
function requireNativeId(value, rule, label) {
|
|
74
|
+
if (!rule.pattern.test(value)) {
|
|
75
|
+
throw new CrablineError(`${label} must be a native ${rule.name} such as ${rule.example}.`, {
|
|
76
|
+
kind: "config",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
function createNativeTargetCodec(options) {
|
|
82
|
+
const channelLabel = options.channelLabel ?? "channelId";
|
|
83
|
+
const threadLabel = options.threadLabel ?? "threadId";
|
|
84
|
+
const threadRule = options.thread ?? options.channel;
|
|
85
|
+
return {
|
|
86
|
+
normalize(target) {
|
|
87
|
+
const channelId = requireNativeId(target.channelId ?? target.id, options.channel, channelLabel);
|
|
88
|
+
const normalized = {
|
|
89
|
+
channelId,
|
|
90
|
+
id: target.id,
|
|
91
|
+
metadata: target.metadata,
|
|
92
|
+
};
|
|
93
|
+
if (target.threadId) {
|
|
94
|
+
normalized.threadId = requireNativeId(target.threadId, threadRule, threadLabel);
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
},
|
|
98
|
+
resolveThreadId(target) {
|
|
99
|
+
const normalized = this.normalize(target);
|
|
100
|
+
return normalized.threadId ?? normalized.channelId ?? normalized.id;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function createGenericLocalMockTargetCodec(platform) {
|
|
105
|
+
const prefix = `${platform}:`;
|
|
106
|
+
const encode = (value) => (value.startsWith(prefix) ? value : `${prefix}${value}`);
|
|
107
|
+
return {
|
|
108
|
+
normalize(target) {
|
|
109
|
+
const normalized = {
|
|
110
|
+
id: target.id,
|
|
111
|
+
metadata: target.metadata,
|
|
112
|
+
};
|
|
113
|
+
if (target.channelId) {
|
|
114
|
+
normalized.channelId = encode(target.channelId);
|
|
115
|
+
}
|
|
116
|
+
else if (!target.threadId) {
|
|
117
|
+
normalized.channelId = encode(target.id);
|
|
118
|
+
}
|
|
119
|
+
if (target.threadId) {
|
|
120
|
+
normalized.channelId ??= encode(target.id);
|
|
121
|
+
if (target.threadId.startsWith(prefix) &&
|
|
122
|
+
!target.threadId.startsWith(`${normalized.channelId}:`)) {
|
|
123
|
+
throw new CrablineError(`${platform} canonical thread parent must match the target channel.`, { kind: "config" });
|
|
124
|
+
}
|
|
125
|
+
normalized.threadId = target.threadId.startsWith(prefix)
|
|
126
|
+
? target.threadId
|
|
127
|
+
: `${normalized.channelId}:${target.threadId}`;
|
|
128
|
+
}
|
|
129
|
+
return normalized;
|
|
130
|
+
},
|
|
131
|
+
resolveThreadId(target) {
|
|
132
|
+
const normalized = this.normalize(target);
|
|
133
|
+
return normalized.threadId ?? normalized.channelId ?? encode(normalized.id);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function requireSlackSendTargetId(value, label) {
|
|
138
|
+
if (!SLACK_SEND_TARGET_ID_RULE.pattern.test(value)) {
|
|
139
|
+
throw new CrablineError(`Slack ${label} must be a native Slack conversation or user id such as C1234567890, G1234567890, D1234567890, U1234567890, or W1234567890.`, { kind: "config" });
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
function requireSlackThreadTs(value, label) {
|
|
144
|
+
if (!SLACK_TS_RULE.pattern.test(value)) {
|
|
145
|
+
throw new CrablineError(`Slack ${label} must be a Slack timestamp such as 1700000000.000100.`, {
|
|
146
|
+
kind: "config",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
const SLACK_TARGET_CODEC = {
|
|
152
|
+
normalize(target) {
|
|
153
|
+
const channelId = requireSlackSendTargetId(target.channelId ?? target.id, "channelId");
|
|
154
|
+
const normalized = {
|
|
155
|
+
channelId,
|
|
156
|
+
id: target.id,
|
|
157
|
+
metadata: target.metadata,
|
|
158
|
+
};
|
|
159
|
+
if (target.threadId) {
|
|
160
|
+
normalized.threadId = requireSlackThreadTs(target.threadId, "threadId");
|
|
161
|
+
}
|
|
162
|
+
return normalized;
|
|
163
|
+
},
|
|
164
|
+
resolveThreadId(target) {
|
|
165
|
+
const normalized = this.normalize(target);
|
|
166
|
+
const channelId = normalized.channelId ?? normalized.id;
|
|
167
|
+
return slackTargetKey(channelId, normalized.threadId);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
const TELEGRAM_BASE_CODEC = createNativeTargetCodec({
|
|
171
|
+
channel: TELEGRAM_CHAT_ID_RULE,
|
|
172
|
+
channelLabel: "Telegram chat_id",
|
|
173
|
+
thread: TELEGRAM_MESSAGE_THREAD_ID_RULE,
|
|
174
|
+
threadLabel: "Telegram message_thread_id",
|
|
175
|
+
});
|
|
176
|
+
export function parseCanonicalTelegramTopic(value) {
|
|
177
|
+
const separator = value.lastIndexOf(":");
|
|
178
|
+
if (separator <= 0) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
const chatId = value.slice(0, separator);
|
|
182
|
+
const topicId = value.slice(separator + 1);
|
|
183
|
+
if (!TELEGRAM_CHAT_ID_RULE.pattern.test(chatId) ||
|
|
184
|
+
!TELEGRAM_MESSAGE_THREAD_ID_RULE.pattern.test(topicId)) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return { chatId, topicId };
|
|
188
|
+
}
|
|
189
|
+
const TELEGRAM_TARGET_CODEC = {
|
|
190
|
+
normalize(target) {
|
|
191
|
+
const canonicalTopic = target.threadId
|
|
192
|
+
? parseCanonicalTelegramTopic(target.threadId)
|
|
193
|
+
: undefined;
|
|
194
|
+
const targetChatId = target.channelId ?? target.id;
|
|
195
|
+
if (canonicalTopic &&
|
|
196
|
+
requireNativeId(targetChatId, TELEGRAM_CHAT_ID_RULE, "Telegram chat_id") !==
|
|
197
|
+
canonicalTopic.chatId) {
|
|
198
|
+
throw new CrablineError("Telegram canonical topic chat_id must match the target chat_id.", {
|
|
199
|
+
kind: "config",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const normalized = TELEGRAM_BASE_CODEC.normalize({
|
|
203
|
+
...target,
|
|
204
|
+
...(canonicalTopic ? { channelId: canonicalTopic.chatId } : {}),
|
|
205
|
+
threadId: undefined,
|
|
206
|
+
});
|
|
207
|
+
if (!target.threadId) {
|
|
208
|
+
return normalized;
|
|
209
|
+
}
|
|
210
|
+
const topicId = requireNativeId(canonicalTopic?.topicId ?? target.threadId, TELEGRAM_MESSAGE_THREAD_ID_RULE, "Telegram message_thread_id");
|
|
211
|
+
return {
|
|
212
|
+
...normalized,
|
|
213
|
+
threadId: `${normalized.channelId}:${topicId}`,
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
resolveThreadId(target) {
|
|
217
|
+
const normalized = this.normalize(target);
|
|
218
|
+
return normalized.threadId ?? normalized.channelId ?? normalized.id;
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
const BUILTIN_TARGET_CODECS = {
|
|
222
|
+
discord: createNativeTargetCodec({
|
|
223
|
+
channel: DISCORD_SNOWFLAKE_RULE,
|
|
224
|
+
channelLabel: "Discord channel_id",
|
|
225
|
+
thread: DISCORD_SNOWFLAKE_RULE,
|
|
226
|
+
threadLabel: "Discord thread id",
|
|
227
|
+
}),
|
|
228
|
+
feishu: createNativeTargetCodec({
|
|
229
|
+
channel: FEISHU_CHAT_ID_RULE,
|
|
230
|
+
channelLabel: "Feishu chat_id",
|
|
231
|
+
thread: FEISHU_MESSAGE_ID_RULE,
|
|
232
|
+
threadLabel: "Feishu message_id",
|
|
233
|
+
}),
|
|
234
|
+
googlechat: createNativeTargetCodec({
|
|
235
|
+
channel: GOOGLE_CHAT_SPACE_RULE,
|
|
236
|
+
channelLabel: "Google Chat space.name",
|
|
237
|
+
thread: GOOGLE_CHAT_THREAD_RULE,
|
|
238
|
+
threadLabel: "Google Chat thread.name",
|
|
239
|
+
}),
|
|
240
|
+
imessage: createNativeTargetCodec({
|
|
241
|
+
channel: IMESSAGE_THREAD_RULE,
|
|
242
|
+
channelLabel: "iMessage recipient or chat GUID",
|
|
243
|
+
}),
|
|
244
|
+
loopback: createGenericLocalMockTargetCodec("loopback"),
|
|
245
|
+
matrix: createNativeTargetCodec({
|
|
246
|
+
channel: MATRIX_ROOM_ID_RULE,
|
|
247
|
+
channelLabel: "Matrix room_id",
|
|
248
|
+
thread: MATRIX_EVENT_ID_RULE,
|
|
249
|
+
threadLabel: "Matrix event_id",
|
|
250
|
+
}),
|
|
251
|
+
mattermost: createNativeTargetCodec({
|
|
252
|
+
channel: MATTERMOST_ID_RULE,
|
|
253
|
+
channelLabel: "Mattermost channel_id",
|
|
254
|
+
}),
|
|
255
|
+
msteams: createNativeTargetCodec({
|
|
256
|
+
channel: MSTEAMS_CONVERSATION_ID_RULE,
|
|
257
|
+
channelLabel: "Microsoft Teams conversation.id",
|
|
258
|
+
}),
|
|
259
|
+
slack: SLACK_TARGET_CODEC,
|
|
260
|
+
telegram: TELEGRAM_TARGET_CODEC,
|
|
261
|
+
whatsapp: createNativeTargetCodec({
|
|
262
|
+
channel: WHATSAPP_WA_ID_RULE,
|
|
263
|
+
channelLabel: "WhatsApp wa_id",
|
|
264
|
+
}),
|
|
265
|
+
zalo: createNativeTargetCodec({
|
|
266
|
+
channel: ZALO_ID_RULE,
|
|
267
|
+
channelLabel: "Zalo user_id or oa_id",
|
|
268
|
+
}),
|
|
269
|
+
};
|
|
270
|
+
export function getBuiltinTargetCodec(adapter) {
|
|
271
|
+
return BUILTIN_TARGET_CODECS[adapter];
|
|
272
|
+
}
|
|
273
|
+
export function normalizeBuiltinTarget(adapter, target) {
|
|
274
|
+
return getBuiltinTargetCodec(adapter).normalize(target);
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=target-normalizers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target-normalizers.js","sourceRoot":"","sources":["../../../src/providers/target-normalizers.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BuiltinAdapterId, FixtureDefinition, ProviderPlatform } from \"../config/schema.js\";\nimport { CrablineError } from \"../core/errors.js\";\nimport type { LocalMockTargetCodec } from \"./local-mock.js\";\nimport type { NativeIdRule } from \"./native-ids.js\";\nimport { slackTargetKey, SLACK_SEND_TARGET_ID_RULE, SLACK_TS_RULE } from \"./slack-ids.js\";\nimport type { NormalizedTarget } from \"./types.js\";\n\nexport type BuiltinProviderAdapterId = Exclude<BuiltinAdapterId, \"script\">;\n\nexport const DISCORD_SNOWFLAKE_RULE: NativeIdRule = {\n example: \"123456789012345678\",\n name: \"Discord snowflake id\",\n pattern: /^\\d{17,20}$/u,\n};\n\nexport const FEISHU_CHAT_ID_RULE: NativeIdRule = {\n example: \"oc_abc123\",\n name: \"Feishu chat_id\",\n pattern: /^oc_[A-Za-z0-9_-]+$/u,\n};\n\nexport const FEISHU_MESSAGE_ID_RULE: NativeIdRule = {\n example: \"om_abc123\",\n name: \"Feishu message_id\",\n pattern: /^om_[A-Za-z0-9_-]+$/u,\n};\n\nexport const GOOGLE_CHAT_SPACE_RULE: NativeIdRule = {\n example: \"spaces/AAAABbbbCCC\",\n name: \"Google Chat space name\",\n pattern: /^spaces\\/[A-Za-z0-9_-]+$/u,\n};\n\nexport const GOOGLE_CHAT_THREAD_RULE: NativeIdRule = {\n example: \"spaces/AAAABbbbCCC/threads/BBBBccccDDD\",\n name: \"Google Chat thread name\",\n pattern: /^spaces\\/[A-Za-z0-9_-]+\\/threads\\/[A-Za-z0-9_-]+$/u,\n};\n\nexport const IMESSAGE_THREAD_RULE: NativeIdRule = {\n example: \"+15551234567, user@example.com, or iMessage;-;chat-guid\",\n name: \"iMessage recipient or chat GUID\",\n pattern:\n /^(?:\\+[1-9]\\d{6,14}|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|(?:iMessage|SMS);[+-];.+)$/u,\n};\n\nexport const MATRIX_ROOM_ID_RULE: NativeIdRule = {\n example: \"!abcdef:matrix.org\",\n name: \"Matrix room id\",\n pattern: /^!(?:[^:\\s]+:[^\\s]+|[A-Za-z0-9_-]{43})$/u,\n};\n\nexport const MATRIX_EVENT_ID_RULE: NativeIdRule = {\n example: \"$eventid:matrix.org\",\n name: \"Matrix event id\",\n pattern: /^\\$[^\\s]+(?::[^\\s]+)?$/u,\n};\n\nexport const MATTERMOST_ID_RULE: NativeIdRule = {\n example: \"abcdefghijklmnopqrstuvwx12\",\n name: \"Mattermost id\",\n pattern: /^[a-z0-9]{26}$/u,\n};\n\nexport const MSTEAMS_CONVERSATION_ID_RULE: NativeIdRule = {\n example: \"a:opaque-conversation-id\",\n name: \"Microsoft Teams conversation id\",\n pattern: /^.+$/su,\n};\n\nexport const TELEGRAM_CHAT_ID_RULE: NativeIdRule = {\n example: \"-1001234567890 or @channelusername\",\n name: \"Telegram chat id\",\n pattern: /^(?:-?[1-9]\\d*|@[A-Za-z][A-Za-z0-9_]{3,31})$/u,\n};\n\nexport const TELEGRAM_MESSAGE_THREAD_ID_RULE: NativeIdRule = {\n example: \"42\",\n name: \"Telegram message_thread_id\",\n pattern: /^[1-9]\\d*$/u,\n};\n\nexport const WHATSAPP_WA_ID_RULE: NativeIdRule = {\n example: \"15551234567\",\n name: \"WhatsApp wa_id\",\n pattern: /^\\d{7,15}$/u,\n};\n\nexport const ZALO_ID_RULE: NativeIdRule = {\n example: \"user-1\",\n name: \"Zalo string id\",\n pattern: /^\\S+$/u,\n};\n\nfunction requireNativeId(value: string, rule: NativeIdRule, label: string): string {\n if (!rule.pattern.test(value)) {\n throw new CrablineError(`${label} must be a native ${rule.name} such as ${rule.example}.`, {\n kind: \"config\",\n });\n }\n return value;\n}\n\nfunction createNativeTargetCodec(options: {\n channel: NativeIdRule;\n channelLabel?: string | undefined;\n thread?: NativeIdRule | undefined;\n threadLabel?: string | undefined;\n}): LocalMockTargetCodec {\n const channelLabel = options.channelLabel ?? \"channelId\";\n const threadLabel = options.threadLabel ?? \"threadId\";\n const threadRule = options.thread ?? options.channel;\n\n return {\n normalize(target): NormalizedTarget {\n const channelId = requireNativeId(\n target.channelId ?? target.id,\n options.channel,\n channelLabel,\n );\n const normalized: NormalizedTarget = {\n channelId,\n id: target.id,\n metadata: target.metadata,\n };\n if (target.threadId) {\n normalized.threadId = requireNativeId(target.threadId, threadRule, threadLabel);\n }\n return normalized;\n },\n resolveThreadId(target) {\n const normalized = this.normalize(target);\n return normalized.threadId ?? normalized.channelId ?? normalized.id;\n },\n };\n}\n\nexport function createGenericLocalMockTargetCodec(\n platform: ProviderPlatform,\n): LocalMockTargetCodec {\n const prefix = `${platform}:`;\n const encode = (value: string) => (value.startsWith(prefix) ? value : `${prefix}${value}`);\n return {\n normalize(target): NormalizedTarget {\n const normalized: NormalizedTarget = {\n id: target.id,\n metadata: target.metadata,\n };\n if (target.channelId) {\n normalized.channelId = encode(target.channelId);\n } else if (!target.threadId) {\n normalized.channelId = encode(target.id);\n }\n if (target.threadId) {\n normalized.channelId ??= encode(target.id);\n if (\n target.threadId.startsWith(prefix) &&\n !target.threadId.startsWith(`${normalized.channelId}:`)\n ) {\n throw new CrablineError(\n `${platform} canonical thread parent must match the target channel.`,\n { kind: \"config\" },\n );\n }\n normalized.threadId = target.threadId.startsWith(prefix)\n ? target.threadId\n : `${normalized.channelId}:${target.threadId}`;\n }\n return normalized;\n },\n resolveThreadId(target) {\n const normalized = this.normalize(target);\n return normalized.threadId ?? normalized.channelId ?? encode(normalized.id);\n },\n };\n}\n\nfunction requireSlackSendTargetId(value: string, label: string): string {\n if (!SLACK_SEND_TARGET_ID_RULE.pattern.test(value)) {\n throw new CrablineError(\n `Slack ${label} must be a native Slack conversation or user id such as C1234567890, G1234567890, D1234567890, U1234567890, or W1234567890.`,\n { kind: \"config\" },\n );\n }\n return value;\n}\n\nfunction requireSlackThreadTs(value: string, label: string): string {\n if (!SLACK_TS_RULE.pattern.test(value)) {\n throw new CrablineError(`Slack ${label} must be a Slack timestamp such as 1700000000.000100.`, {\n kind: \"config\",\n });\n }\n return value;\n}\n\nconst SLACK_TARGET_CODEC: LocalMockTargetCodec = {\n normalize(target): NormalizedTarget {\n const channelId = requireSlackSendTargetId(target.channelId ?? target.id, \"channelId\");\n const normalized: NormalizedTarget = {\n channelId,\n id: target.id,\n metadata: target.metadata,\n };\n if (target.threadId) {\n normalized.threadId = requireSlackThreadTs(target.threadId, \"threadId\");\n }\n return normalized;\n },\n resolveThreadId(target) {\n const normalized = this.normalize(target);\n const channelId = normalized.channelId ?? normalized.id;\n return slackTargetKey(channelId, normalized.threadId);\n },\n};\n\nconst TELEGRAM_BASE_CODEC = createNativeTargetCodec({\n channel: TELEGRAM_CHAT_ID_RULE,\n channelLabel: \"Telegram chat_id\",\n thread: TELEGRAM_MESSAGE_THREAD_ID_RULE,\n threadLabel: \"Telegram message_thread_id\",\n});\n\nexport function parseCanonicalTelegramTopic(\n value: string,\n): { chatId: string; topicId: string } | undefined {\n const separator = value.lastIndexOf(\":\");\n if (separator <= 0) {\n return undefined;\n }\n const chatId = value.slice(0, separator);\n const topicId = value.slice(separator + 1);\n if (\n !TELEGRAM_CHAT_ID_RULE.pattern.test(chatId) ||\n !TELEGRAM_MESSAGE_THREAD_ID_RULE.pattern.test(topicId)\n ) {\n return undefined;\n }\n return { chatId, topicId };\n}\n\nconst TELEGRAM_TARGET_CODEC: LocalMockTargetCodec = {\n normalize(target) {\n const canonicalTopic = target.threadId\n ? parseCanonicalTelegramTopic(target.threadId)\n : undefined;\n const targetChatId = target.channelId ?? target.id;\n if (\n canonicalTopic &&\n requireNativeId(targetChatId, TELEGRAM_CHAT_ID_RULE, \"Telegram chat_id\") !==\n canonicalTopic.chatId\n ) {\n throw new CrablineError(\"Telegram canonical topic chat_id must match the target chat_id.\", {\n kind: \"config\",\n });\n }\n const normalized = TELEGRAM_BASE_CODEC.normalize({\n ...target,\n ...(canonicalTopic ? { channelId: canonicalTopic.chatId } : {}),\n threadId: undefined,\n });\n if (!target.threadId) {\n return normalized;\n }\n const topicId = requireNativeId(\n canonicalTopic?.topicId ?? target.threadId,\n TELEGRAM_MESSAGE_THREAD_ID_RULE,\n \"Telegram message_thread_id\",\n );\n return {\n ...normalized,\n threadId: `${normalized.channelId}:${topicId}`,\n };\n },\n resolveThreadId(target) {\n const normalized = this.normalize(target);\n return normalized.threadId ?? normalized.channelId ?? normalized.id;\n },\n};\n\nconst BUILTIN_TARGET_CODECS = {\n discord: createNativeTargetCodec({\n channel: DISCORD_SNOWFLAKE_RULE,\n channelLabel: \"Discord channel_id\",\n thread: DISCORD_SNOWFLAKE_RULE,\n threadLabel: \"Discord thread id\",\n }),\n feishu: createNativeTargetCodec({\n channel: FEISHU_CHAT_ID_RULE,\n channelLabel: \"Feishu chat_id\",\n thread: FEISHU_MESSAGE_ID_RULE,\n threadLabel: \"Feishu message_id\",\n }),\n googlechat: createNativeTargetCodec({\n channel: GOOGLE_CHAT_SPACE_RULE,\n channelLabel: \"Google Chat space.name\",\n thread: GOOGLE_CHAT_THREAD_RULE,\n threadLabel: \"Google Chat thread.name\",\n }),\n imessage: createNativeTargetCodec({\n channel: IMESSAGE_THREAD_RULE,\n channelLabel: \"iMessage recipient or chat GUID\",\n }),\n loopback: createGenericLocalMockTargetCodec(\"loopback\"),\n matrix: createNativeTargetCodec({\n channel: MATRIX_ROOM_ID_RULE,\n channelLabel: \"Matrix room_id\",\n thread: MATRIX_EVENT_ID_RULE,\n threadLabel: \"Matrix event_id\",\n }),\n mattermost: createNativeTargetCodec({\n channel: MATTERMOST_ID_RULE,\n channelLabel: \"Mattermost channel_id\",\n }),\n msteams: createNativeTargetCodec({\n channel: MSTEAMS_CONVERSATION_ID_RULE,\n channelLabel: \"Microsoft Teams conversation.id\",\n }),\n slack: SLACK_TARGET_CODEC,\n telegram: TELEGRAM_TARGET_CODEC,\n whatsapp: createNativeTargetCodec({\n channel: WHATSAPP_WA_ID_RULE,\n channelLabel: \"WhatsApp wa_id\",\n }),\n zalo: createNativeTargetCodec({\n channel: ZALO_ID_RULE,\n channelLabel: \"Zalo user_id or oa_id\",\n }),\n} satisfies Record<BuiltinProviderAdapterId, LocalMockTargetCodec>;\n\nexport function getBuiltinTargetCodec(adapter: BuiltinProviderAdapterId): LocalMockTargetCodec {\n return BUILTIN_TARGET_CODECS[adapter];\n}\n\nexport function normalizeBuiltinTarget(\n adapter: BuiltinProviderAdapterId,\n target: FixtureDefinition[\"target\"],\n): NormalizedTarget {\n return getBuiltinTargetCodec(adapter).normalize(target);\n}\n"]}
|
|
@@ -35,6 +35,7 @@ export type ProviderContext = {
|
|
|
35
35
|
fixture: FixtureDefinition;
|
|
36
36
|
manifestPath: string;
|
|
37
37
|
providerId: string;
|
|
38
|
+
signal?: AbortSignal | undefined;
|
|
38
39
|
userName: string;
|
|
39
40
|
};
|
|
40
41
|
export type SendContext = ProviderContext & {
|
|
@@ -43,12 +44,14 @@ export type SendContext = ProviderContext & {
|
|
|
43
44
|
text: string;
|
|
44
45
|
};
|
|
45
46
|
export type WaitContext = ProviderContext & {
|
|
47
|
+
excludeIds?: readonly string[] | undefined;
|
|
46
48
|
nonce: string;
|
|
47
49
|
since: string;
|
|
48
50
|
threadId?: string | undefined;
|
|
49
51
|
timeoutMs: number;
|
|
50
52
|
};
|
|
51
53
|
export type WatchContext = ProviderContext & {
|
|
54
|
+
signal?: AbortSignal | undefined;
|
|
52
55
|
since?: string;
|
|
53
56
|
};
|
|
54
57
|
export interface ProviderAdapter {
|
|
@@ -61,6 +64,8 @@ export interface ProviderAdapter {
|
|
|
61
64
|
send(context: SendContext): Promise<SendResult>;
|
|
62
65
|
waitForInbound(context: WaitContext): Promise<InboundEnvelope | null>;
|
|
63
66
|
watch?(context: WatchContext): AsyncIterable<InboundEnvelope>;
|
|
67
|
+
/** Stops external admission synchronously before cleanup drains accepted work. */
|
|
68
|
+
beginCleanup?(): void;
|
|
64
69
|
cleanup?(): Promise<void>;
|
|
65
70
|
}
|
|
66
71
|
export type LoopbackMessage = {
|