@openclaw/crabline 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +43 -1
  2. package/dist/src/cli/program.js +16 -2
  3. package/dist/src/cli/program.js.map +1 -1
  4. package/dist/src/index.d.ts +4 -0
  5. package/dist/src/index.js +1 -0
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/openclaw/bridges/zalo.d.ts +1 -0
  8. package/dist/src/openclaw/bridges/zalo.js +102 -0
  9. package/dist/src/openclaw/bridges/zalo.js.map +1 -0
  10. package/dist/src/openclaw/shared.d.ts +2 -0
  11. package/dist/src/openclaw/shared.js.map +1 -1
  12. package/dist/src/openclaw.js +3 -0
  13. package/dist/src/openclaw.js.map +1 -1
  14. package/dist/src/servers/index.d.ts +9 -3
  15. package/dist/src/servers/index.js +5 -0
  16. package/dist/src/servers/index.js.map +1 -1
  17. package/dist/src/servers/matrix.d.ts +2 -0
  18. package/dist/src/servers/matrix.js +3 -3
  19. package/dist/src/servers/matrix.js.map +1 -1
  20. package/dist/src/servers/mattermost.d.ts +2 -0
  21. package/dist/src/servers/mattermost.js +3 -3
  22. package/dist/src/servers/mattermost.js.map +1 -1
  23. package/dist/src/servers/recorder.d.ts +7 -0
  24. package/dist/src/servers/recorder.js +8 -0
  25. package/dist/src/servers/recorder.js.map +1 -0
  26. package/dist/src/servers/signal.d.ts +2 -0
  27. package/dist/src/servers/signal.js +3 -3
  28. package/dist/src/servers/signal.js.map +1 -1
  29. package/dist/src/servers/slack.d.ts +2 -0
  30. package/dist/src/servers/slack.js +3 -3
  31. package/dist/src/servers/slack.js.map +1 -1
  32. package/dist/src/servers/telegram.d.ts +2 -0
  33. package/dist/src/servers/telegram.js +3 -3
  34. package/dist/src/servers/telegram.js.map +1 -1
  35. package/dist/src/servers/whatsapp.d.ts +2 -0
  36. package/dist/src/servers/whatsapp.js +3 -3
  37. package/dist/src/servers/whatsapp.js.map +1 -1
  38. package/dist/src/servers/zalo.d.ts +33 -0
  39. package/dist/src/servers/zalo.js +276 -0
  40. package/dist/src/servers/zalo.js.map +1 -0
  41. package/docs/channel-setup.md +39 -0
  42. package/package.json +1 -1
@@ -0,0 +1,276 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import path from "node:path";
3
+ import { adminAuthError, hasAdminToken, jsonResponse, parseRequestBody, queryRecord, readTrimmedString, startHttpJsonServer, } from "./http.js";
4
+ import { recordServerEvent } from "./recorder.js";
5
+ async function appendEvent(state, event) {
6
+ await recordServerEvent({ event, onEvent: state.onEvent, recorderPath: state.recorderPath });
7
+ }
8
+ function zaloOk(result) {
9
+ return jsonResponse(result === undefined ? { ok: true } : { ok: true, result });
10
+ }
11
+ function zaloError(description, status) {
12
+ return jsonResponse({ description, error_code: status, ok: false }, status);
13
+ }
14
+ function messageId(state) {
15
+ return `${Date.now().toString(16)}${(state.nextMessage++).toString(16).padStart(6, "0")}`;
16
+ }
17
+ function readChatType(value) {
18
+ return readTrimmedString(value)?.toUpperCase() === "GROUP" ? "GROUP" : "PRIVATE";
19
+ }
20
+ function requestParams(url, body) {
21
+ return { ...Object.fromEntries(url.searchParams.entries()), ...body };
22
+ }
23
+ function redactParams(params) {
24
+ return Object.fromEntries(Object.entries(params).map(([key, value]) => [
25
+ key,
26
+ key === "secret_token" ? "<redacted>" : value,
27
+ ]));
28
+ }
29
+ function requireParam(body, name) {
30
+ return readTrimmedString(body[name]) ?? zaloError(`${name} is required`, 400);
31
+ }
32
+ function firstError(...values) {
33
+ return values.find((value) => value instanceof Response);
34
+ }
35
+ function nextUpdate(state) {
36
+ const update = state.updates.shift();
37
+ return update ? zaloOk(update) : undefined;
38
+ }
39
+ function waitForUpdate(state, timeoutSeconds) {
40
+ const queued = nextUpdate(state);
41
+ if (queued) {
42
+ return Promise.resolve(queued);
43
+ }
44
+ if (timeoutSeconds <= 0) {
45
+ return Promise.resolve(zaloError("Request timeout", 408));
46
+ }
47
+ return new Promise((resolve) => {
48
+ const pending = {
49
+ resolve,
50
+ timeout: setTimeout(() => {
51
+ const index = state.pendingRequests.indexOf(pending);
52
+ if (index >= 0) {
53
+ state.pendingRequests.splice(index, 1);
54
+ }
55
+ resolve(zaloError("Request timeout", 408));
56
+ }, timeoutSeconds * 1000),
57
+ };
58
+ state.pendingRequests.push(pending);
59
+ });
60
+ }
61
+ function deliverPollingUpdate(state, update) {
62
+ const pending = state.pendingRequests.shift();
63
+ if (!pending) {
64
+ state.updates.push(update);
65
+ return;
66
+ }
67
+ clearTimeout(pending.timeout);
68
+ pending.resolve(zaloOk(update));
69
+ }
70
+ async function deliverWebhookUpdate(webhook, update) {
71
+ try {
72
+ const response = await fetch(webhook.url, {
73
+ body: JSON.stringify({ ok: true, result: update }),
74
+ headers: {
75
+ "content-type": "application/json",
76
+ "x-bot-api-secret-token": webhook.secretToken,
77
+ },
78
+ method: "POST",
79
+ });
80
+ if (!response.ok) {
81
+ return zaloError(`Webhook delivery failed with HTTP ${response.status}`, 502);
82
+ }
83
+ }
84
+ catch (error) {
85
+ return zaloError(`Webhook delivery failed: ${error instanceof Error ? error.message : String(error)}`, 502);
86
+ }
87
+ return undefined;
88
+ }
89
+ async function handleAdminInbound(request, state, url) {
90
+ if (!hasAdminToken(request, state.adminToken)) {
91
+ return adminAuthError();
92
+ }
93
+ const body = await parseRequestBody(request);
94
+ const chatId = requireParam(body, "chatId");
95
+ const senderId = requireParam(body, "senderId");
96
+ const text = requireParam(body, "text");
97
+ if (chatId instanceof Response || senderId instanceof Response || text instanceof Response) {
98
+ return [chatId, senderId, text].find((value) => value instanceof Response);
99
+ }
100
+ const update = {
101
+ event_name: "message.text.received",
102
+ message: {
103
+ chat: { chat_type: readChatType(body.chatType), id: chatId },
104
+ date: Date.now(),
105
+ from: {
106
+ display_name: readTrimmedString(body.senderName) ?? senderId,
107
+ id: senderId,
108
+ is_bot: false,
109
+ },
110
+ message_id: messageId(state),
111
+ text,
112
+ },
113
+ };
114
+ await appendEvent(state, {
115
+ at: new Date().toISOString(),
116
+ body,
117
+ method: request.method ?? "POST",
118
+ path: url.pathname,
119
+ query: queryRecord(url),
120
+ type: "admin",
121
+ });
122
+ if (state.webhook?.url) {
123
+ const error = await deliverWebhookUpdate(state.webhook, update);
124
+ if (error) {
125
+ return error;
126
+ }
127
+ }
128
+ else {
129
+ deliverPollingUpdate(state, update);
130
+ }
131
+ return zaloOk(update);
132
+ }
133
+ async function handleZaloMethod(request, state, url, method) {
134
+ const body = requestParams(url, await parseRequestBody(request));
135
+ await appendEvent(state, {
136
+ at: new Date().toISOString(),
137
+ ...(Object.keys(body).length > 0 ? { body: redactParams(body) } : {}),
138
+ method: request.method ?? "GET",
139
+ path: `/bot<redacted>/${method}`,
140
+ query: redactParams(queryRecord(url)),
141
+ type: "api",
142
+ });
143
+ if (method === "getMe") {
144
+ return zaloOk({
145
+ account_name: state.botName,
146
+ account_type: "BASIC",
147
+ can_join_groups: true,
148
+ id: state.botId,
149
+ });
150
+ }
151
+ if (method === "getUpdates") {
152
+ if (state.webhook?.url) {
153
+ return zaloError("Webhook is configured; delete it before using getUpdates", 400);
154
+ }
155
+ const parsedTimeout = Number(readTrimmedString(body.timeout) ?? "30");
156
+ const timeout = Number.isFinite(parsedTimeout) ? Math.max(0, Math.min(parsedTimeout, 50)) : 30;
157
+ return await waitForUpdate(state, timeout);
158
+ }
159
+ if (method === "sendMessage" || method === "sendPhoto") {
160
+ const chatId = requireParam(body, "chat_id");
161
+ const content = requireParam(body, method === "sendMessage" ? "text" : "photo");
162
+ const error = firstError(chatId, content);
163
+ if (error) {
164
+ return error;
165
+ }
166
+ return zaloOk({ date: Date.now(), message_id: messageId(state) });
167
+ }
168
+ if (method === "sendChatAction") {
169
+ const chatId = requireParam(body, "chat_id");
170
+ const action = requireParam(body, "action");
171
+ const error = firstError(chatId, action);
172
+ if (error) {
173
+ return error;
174
+ }
175
+ if (action !== "typing" && action !== "upload_photo") {
176
+ return zaloError("action must be typing or upload_photo", 400);
177
+ }
178
+ return zaloOk();
179
+ }
180
+ if (method === "setWebhook") {
181
+ const webhookUrl = requireParam(body, "url");
182
+ const secretToken = requireParam(body, "secret_token");
183
+ const error = firstError(webhookUrl, secretToken);
184
+ if (error) {
185
+ return error;
186
+ }
187
+ if (typeof webhookUrl !== "string" || typeof secretToken !== "string") {
188
+ return zaloError("Invalid webhook parameters", 400);
189
+ }
190
+ let parsedUrl;
191
+ try {
192
+ parsedUrl = new URL(webhookUrl);
193
+ }
194
+ catch {
195
+ return zaloError("url must be a valid HTTP or HTTPS URL", 400);
196
+ }
197
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
198
+ return zaloError("url must be a valid HTTP or HTTPS URL", 400);
199
+ }
200
+ state.updates.length = 0;
201
+ const webhook = { secretToken, updatedAt: Date.now(), url: parsedUrl.href };
202
+ state.webhook = webhook;
203
+ return zaloOk({ updated_at: webhook.updatedAt, url: webhook.url });
204
+ }
205
+ if (method === "deleteWebhook") {
206
+ const updatedAt = Date.now();
207
+ state.webhook = undefined;
208
+ return zaloOk({ updated_at: updatedAt, url: "" });
209
+ }
210
+ if (method === "getWebhookInfo") {
211
+ return zaloOk(state.webhook ? { updated_at: state.webhook.updatedAt, url: state.webhook.url } : { url: "" });
212
+ }
213
+ return zaloError("Bad request - invalid API name", 400);
214
+ }
215
+ export async function startZaloServer(params = {}) {
216
+ const host = params.host ?? "127.0.0.1";
217
+ const state = {
218
+ adminToken: params.adminToken ?? randomBytes(24).toString("hex"),
219
+ botId: params.botId ?? "1459232241454765289",
220
+ botName: params.botName ?? "bot.crabline",
221
+ botToken: params.botToken ?? "crabline-zalo-bot-token",
222
+ nextMessage: 1,
223
+ onEvent: params.onEvent,
224
+ pendingRequests: [],
225
+ recorderPath: params.recorderPath ?? path.resolve(".crabline", "servers", "zalo.jsonl"),
226
+ updates: [],
227
+ webhook: undefined,
228
+ };
229
+ const httpServer = await startHttpJsonServer({
230
+ handle: async (request) => {
231
+ const url = new URL(request.url ?? "/", `http://${request.headers.host ?? host}`);
232
+ if (request.method === "POST" && url.pathname === "/crabline/zalo/inbound") {
233
+ return await handleAdminInbound(request, state, url);
234
+ }
235
+ const match = /^\/bot([^/]+)\/([^/]+)$/u.exec(url.pathname);
236
+ if (!match || (request.method !== "GET" && request.method !== "POST")) {
237
+ return zaloError("Not found", 404);
238
+ }
239
+ if (match[1] !== state.botToken) {
240
+ return zaloError("Unauthorized", 401);
241
+ }
242
+ return await handleZaloMethod(request, state, url, match[2] ?? "");
243
+ },
244
+ host,
245
+ port: params.port ?? 0,
246
+ serverName: "Zalo",
247
+ });
248
+ const manifest = {
249
+ adminToken: state.adminToken,
250
+ baseUrl: httpServer.baseUrl,
251
+ botId: state.botId,
252
+ botToken: state.botToken,
253
+ endpoints: {
254
+ adminInboundUrl: `${httpServer.baseUrl}/crabline/zalo/inbound`,
255
+ apiRoot: httpServer.baseUrl,
256
+ },
257
+ env: {
258
+ ZALO_API_URL: httpServer.baseUrl,
259
+ ZALO_BOT_TOKEN: state.botToken,
260
+ },
261
+ provider: "zalo",
262
+ recorderPath: state.recorderPath,
263
+ version: 1,
264
+ };
265
+ return {
266
+ async close() {
267
+ for (const pending of state.pendingRequests.splice(0)) {
268
+ clearTimeout(pending.timeout);
269
+ pending.resolve(zaloError("Server shutting down", 503));
270
+ }
271
+ await httpServer.close();
272
+ },
273
+ manifest,
274
+ };
275
+ }
276
+ //# sourceMappingURL=zalo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zalo.js","sourceRoot":"","sources":["../../../src/servers/zalo.ts"],"names":[],"mappings":"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"}
@@ -263,6 +263,7 @@ token is generated randomly unless `--admin-token <token>` is provided.
263
263
  OpenClaw bridge callers should post injected user messages with the
264
264
  `providerUrl`, `providerHeaders`, and `providerBody` returned by
265
265
  `createOpenClawCrablineInbound()`.
266
+
266
267
  The admin ingress accepts JSON like:
267
268
 
268
269
  ```json
@@ -276,6 +277,44 @@ The admin ingress accepts JSON like:
276
277
  Outbound text sends and composing presence are recorded through the fake
277
278
  provider messages and presence endpoints.
278
279
 
280
+ ### Zalo Server
281
+
282
+ Start the server:
283
+
284
+ ```bash
285
+ crabline --json serve zalo --ready-file .crabline/zalo-server.json
286
+ ```
287
+
288
+ Set trusted `ZALO_API_URL` to `endpoints.apiRoot` and configure the emitted
289
+ `botToken` as `channels.zalo.botToken` or `ZALO_BOT_TOKEN`. The OpenClaw bridge
290
+ does this mapping without adding provider-server-specific behavior to the Zalo
291
+ adapter.
292
+
293
+ The server accepts the provider-native `/bot<TOKEN>/<METHOD>` API shape over
294
+ GET or POST. It implements bot identity, single-update long polling, text and
295
+ photo sends, chat actions, and webhook lifecycle calls. A configured webhook
296
+ receives the native Zalo `{ ok, result }` event envelope with
297
+ `X-Bot-Api-Secret-Token`;
298
+ otherwise injected messages are returned by `getUpdates`. Provider errors use
299
+ Zalo's `{ ok, error_code, description }` shape.
300
+
301
+ The authenticated admin ingress is only a test control plane. Post a message
302
+ such as:
303
+
304
+ ```json
305
+ {
306
+ "chatId": "group-1",
307
+ "chatType": "GROUP",
308
+ "senderId": "user-1",
309
+ "senderName": "Alice",
310
+ "text": "user nonce-123"
311
+ }
312
+ ```
313
+
314
+ Crabline translates that state change into the normal Zalo polling or webhook
315
+ transport. Outbound `sendMessage` and `sendPhoto` calls are written to the
316
+ manifest's `recorderPath` with the bot token redacted.
317
+
279
318
  ## Webhook Payload
280
319
 
281
320
  Mock webhooks accept provider-native event payloads where Crabline has a built-in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/crabline",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Standalone CLI for deterministic messaging provider E2E tests",
5
5
  "homepage": "https://github.com/openclaw/crabline#readme",
6
6
  "bugs": {