@openclaw/zalo 2026.1.29 → 2026.2.2

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/CHANGELOG.md CHANGED
@@ -1,58 +1,87 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.2.2
4
+
5
+ ### Changes
6
+
7
+ - Version alignment with core OpenClaw release numbers.
8
+
9
+ ## 2026.1.31
10
+
11
+ ### Changes
12
+
13
+ - Version alignment with core OpenClaw release numbers.
14
+
15
+ ## 2026.1.30
16
+
17
+ ### Changes
18
+
19
+ - Version alignment with core OpenClaw release numbers.
20
+
3
21
  ## 2026.1.29
4
22
 
5
23
  ### Changes
24
+
6
25
  - Version alignment with core OpenClaw release numbers.
7
26
 
8
27
  ## 2026.1.23
9
28
 
10
29
  ### Changes
30
+
11
31
  - Version alignment with core OpenClaw release numbers.
12
32
 
13
33
  ## 2026.1.22
14
34
 
15
35
  ### Changes
36
+
16
37
  - Version alignment with core OpenClaw release numbers.
17
38
 
18
39
  ## 2026.1.21
19
40
 
20
41
  ### Changes
42
+
21
43
  - Version alignment with core OpenClaw release numbers.
22
44
 
23
45
  ## 2026.1.20
24
46
 
25
47
  ### Changes
48
+
26
49
  - Version alignment with core OpenClaw release numbers.
27
50
 
28
51
  ## 2026.1.17-1
29
52
 
30
53
  ### Changes
54
+
31
55
  - Version alignment with core OpenClaw release numbers.
32
56
 
33
57
  ## 2026.1.17
34
58
 
35
59
  ### Changes
60
+
36
61
  - Version alignment with core OpenClaw release numbers.
37
62
 
38
63
  ## 2026.1.16
39
64
 
40
65
  ### Changes
66
+
41
67
  - Version alignment with core OpenClaw release numbers.
42
68
 
43
69
  ## 2026.1.15
44
70
 
45
71
  ### Changes
72
+
46
73
  - Version alignment with core OpenClaw release numbers.
47
74
 
48
75
  ## 2026.1.14
49
76
 
50
77
  ### Changes
78
+
51
79
  - Version alignment with core OpenClaw release numbers.
52
80
 
53
81
  ## 0.1.0
54
82
 
55
83
  ### Features
84
+
56
85
  - Zalo Bot API channel plugin with token-based auth (env/config/file).
57
86
  - Direct message support (DMs only) with pairing/allowlist/open/disabled policies.
58
87
  - Polling and webhook delivery modes.
package/README.md CHANGED
@@ -25,9 +25,9 @@ Onboarding: select Zalo and confirm the install prompt to fetch the plugin autom
25
25
  enabled: true,
26
26
  botToken: "12345689:abc-xyz",
27
27
  dmPolicy: "pairing",
28
- proxy: "http://proxy.local:8080"
29
- }
30
- }
28
+ proxy: "http://proxy.local:8080",
29
+ },
30
+ },
31
31
  }
32
32
  ```
33
33
 
@@ -39,9 +39,9 @@ Onboarding: select Zalo and confirm the install prompt to fetch the plugin autom
39
39
  zalo: {
40
40
  webhookUrl: "https://example.com/zalo-webhook",
41
41
  webhookSecret: "your-secret-8-plus-chars",
42
- webhookPath: "/zalo-webhook"
43
- }
44
- }
42
+ webhookPath: "/zalo-webhook",
43
+ },
44
+ },
45
45
  }
46
46
  ```
47
47
 
package/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
-
4
3
  import { zaloDock, zaloPlugin } from "./src/channel.js";
5
4
  import { handleZaloWebhookRequest } from "./src/monitor.js";
6
5
  import { setZaloRuntime } from "./src/runtime.js";
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "id": "zalo",
3
- "channels": [
4
- "zalo"
5
- ],
3
+ "channels": ["zalo"],
6
4
  "configSchema": {
7
5
  "type": "object",
8
6
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@openclaw/zalo",
3
- "version": "2026.1.29",
4
- "type": "module",
3
+ "version": "2026.2.2",
5
4
  "description": "OpenClaw Zalo channel plugin",
5
+ "type": "module",
6
+ "dependencies": {
7
+ "openclaw": "workspace:*",
8
+ "undici": "7.20.0"
9
+ },
10
+ "devDependencies": {
11
+ "openclaw": "workspace:*"
12
+ },
6
13
  "openclaw": {
7
14
  "extensions": [
8
15
  "./index.ts"
@@ -25,9 +32,5 @@
25
32
  "localPath": "extensions/zalo",
26
33
  "defaultChoice": "npm"
27
34
  }
28
- },
29
- "dependencies": {
30
- "openclaw": "workspace:*",
31
- "undici": "7.19.0"
32
35
  }
33
36
  }
package/src/accounts.ts CHANGED
@@ -1,26 +1,33 @@
1
1
  import type { OpenClawConfig } from "openclaw/plugin-sdk";
2
2
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
3
-
4
3
  import type { ResolvedZaloAccount, ZaloAccountConfig, ZaloConfig } from "./types.js";
5
4
  import { resolveZaloToken } from "./token.js";
6
5
 
7
6
  function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
8
7
  const accounts = (cfg.channels?.zalo as ZaloConfig | undefined)?.accounts;
9
- if (!accounts || typeof accounts !== "object") return [];
8
+ if (!accounts || typeof accounts !== "object") {
9
+ return [];
10
+ }
10
11
  return Object.keys(accounts).filter(Boolean);
11
12
  }
12
13
 
13
14
  export function listZaloAccountIds(cfg: OpenClawConfig): string[] {
14
15
  const ids = listConfiguredAccountIds(cfg);
15
- if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
16
- return ids.sort((a, b) => a.localeCompare(b));
16
+ if (ids.length === 0) {
17
+ return [DEFAULT_ACCOUNT_ID];
18
+ }
19
+ return ids.toSorted((a, b) => a.localeCompare(b));
17
20
  }
18
21
 
19
22
  export function resolveDefaultZaloAccountId(cfg: OpenClawConfig): string {
20
23
  const zaloConfig = cfg.channels?.zalo as ZaloConfig | undefined;
21
- if (zaloConfig?.defaultAccount?.trim()) return zaloConfig.defaultAccount.trim();
24
+ if (zaloConfig?.defaultAccount?.trim()) {
25
+ return zaloConfig.defaultAccount.trim();
26
+ }
22
27
  const ids = listZaloAccountIds(cfg);
23
- if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
28
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
29
+ return DEFAULT_ACCOUNT_ID;
30
+ }
24
31
  return ids[0] ?? DEFAULT_ACCOUNT_ID;
25
32
  }
26
33
 
@@ -29,7 +36,9 @@ function resolveAccountConfig(
29
36
  accountId: string,
30
37
  ): ZaloAccountConfig | undefined {
31
38
  const accounts = (cfg.channels?.zalo as ZaloConfig | undefined)?.accounts;
32
- if (!accounts || typeof accounts !== "object") return undefined;
39
+ if (!accounts || typeof accounts !== "object") {
40
+ return undefined;
41
+ }
33
42
  return accounts[accountId] as ZaloAccountConfig | undefined;
34
43
  }
35
44
 
package/src/actions.ts CHANGED
@@ -4,7 +4,6 @@ import type {
4
4
  OpenClawConfig,
5
5
  } from "openclaw/plugin-sdk";
6
6
  import { jsonResult, readStringParam } from "openclaw/plugin-sdk";
7
-
8
7
  import { listEnabledZaloAccounts } from "./accounts.js";
9
8
  import { sendMessageZalo } from "./send.js";
10
9
 
@@ -18,17 +17,23 @@ function listEnabledAccounts(cfg: OpenClawConfig) {
18
17
 
19
18
  export const zaloMessageActions: ChannelMessageActionAdapter = {
20
19
  listActions: ({ cfg }) => {
21
- const accounts = listEnabledAccounts(cfg as OpenClawConfig);
22
- if (accounts.length === 0) return [];
20
+ const accounts = listEnabledAccounts(cfg);
21
+ if (accounts.length === 0) {
22
+ return [];
23
+ }
23
24
  const actions = new Set<ChannelMessageActionName>(["send"]);
24
25
  return Array.from(actions);
25
26
  },
26
27
  supportsButtons: () => false,
27
28
  extractToolSend: ({ args }) => {
28
29
  const action = typeof args.action === "string" ? args.action.trim() : "";
29
- if (action !== "sendMessage") return null;
30
+ if (action !== "sendMessage") {
31
+ return null;
32
+ }
30
33
  const to = typeof args.to === "string" ? args.to : undefined;
31
- if (!to) return null;
34
+ if (!to) {
35
+ return null;
36
+ }
32
37
  const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
33
38
  return { to, accountId };
34
39
  },
@@ -44,7 +49,7 @@ export const zaloMessageActions: ChannelMessageActionAdapter = {
44
49
  const result = await sendMessageZalo(to ?? "", content ?? "", {
45
50
  accountId: accountId ?? undefined,
46
51
  mediaUrl: mediaUrl ?? undefined,
47
- cfg: cfg as OpenClawConfig,
52
+ cfg: cfg,
48
53
  });
49
54
 
50
55
  if (!result.ok) {
package/src/api.ts CHANGED
@@ -122,7 +122,9 @@ export async function callZaloApi<T = unknown>(
122
122
 
123
123
  return data;
124
124
  } finally {
125
- if (timeoutId) clearTimeout(timeoutId);
125
+ if (timeoutId) {
126
+ clearTimeout(timeoutId);
127
+ }
126
128
  }
127
129
  }
128
130
 
@@ -1,7 +1,5 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
1
  import type { OpenClawConfig } from "openclaw/plugin-sdk";
4
-
2
+ import { describe, expect, it } from "vitest";
5
3
  import { zaloPlugin } from "./channel.js";
6
4
 
7
5
  describe("zalo directory", () => {
@@ -19,7 +17,12 @@ describe("zalo directory", () => {
19
17
  expect(zaloPlugin.directory?.listGroups).toBeTruthy();
20
18
 
21
19
  await expect(
22
- zaloPlugin.directory!.listPeers({ cfg, accountId: undefined, query: undefined, limit: undefined }),
20
+ zaloPlugin.directory!.listPeers({
21
+ cfg,
22
+ accountId: undefined,
23
+ query: undefined,
24
+ limit: undefined,
25
+ }),
23
26
  ).resolves.toEqual(
24
27
  expect.arrayContaining([
25
28
  { kind: "user", id: "123" },
@@ -28,8 +31,13 @@ describe("zalo directory", () => {
28
31
  ]),
29
32
  );
30
33
 
31
- await expect(zaloPlugin.directory!.listGroups({ cfg, accountId: undefined, query: undefined, limit: undefined })).resolves.toEqual(
32
- [],
33
- );
34
+ await expect(
35
+ zaloPlugin.directory!.listGroups({
36
+ cfg,
37
+ accountId: undefined,
38
+ query: undefined,
39
+ limit: undefined,
40
+ }),
41
+ ).resolves.toEqual([]);
34
42
  });
35
43
  });
package/src/channel.ts CHANGED
@@ -15,13 +15,17 @@ import {
15
15
  PAIRING_APPROVED_MESSAGE,
16
16
  setAccountEnabledInConfigSection,
17
17
  } from "openclaw/plugin-sdk";
18
-
19
- import { listZaloAccountIds, resolveDefaultZaloAccountId, resolveZaloAccount, type ResolvedZaloAccount } from "./accounts.js";
18
+ import {
19
+ listZaloAccountIds,
20
+ resolveDefaultZaloAccountId,
21
+ resolveZaloAccount,
22
+ type ResolvedZaloAccount,
23
+ } from "./accounts.js";
20
24
  import { zaloMessageActions } from "./actions.js";
21
25
  import { ZaloConfigSchema } from "./config-schema.js";
22
26
  import { zaloOnboardingAdapter } from "./onboarding.js";
23
- import { resolveZaloProxyFetch } from "./proxy.js";
24
27
  import { probeZalo } from "./probe.js";
28
+ import { resolveZaloProxyFetch } from "./proxy.js";
25
29
  import { sendMessageZalo } from "./send.js";
26
30
  import { collectZaloStatusIssues } from "./status-issues.js";
27
31
 
@@ -39,7 +43,9 @@ const meta = {
39
43
 
40
44
  function normalizeZaloMessagingTarget(raw: string): string | undefined {
41
45
  const trimmed = raw?.trim();
42
- if (!trimmed) return undefined;
46
+ if (!trimmed) {
47
+ return undefined;
48
+ }
43
49
  return trimmed.replace(/^(zalo|zl):/i, "");
44
50
  }
45
51
 
@@ -53,8 +59,8 @@ export const zaloDock: ChannelDock = {
53
59
  outbound: { textChunkLimit: 2000 },
54
60
  config: {
55
61
  resolveAllowFrom: ({ cfg, accountId }) =>
56
- (resolveZaloAccount({ cfg: cfg as OpenClawConfig, accountId }).config.allowFrom ?? []).map(
57
- (entry) => String(entry),
62
+ (resolveZaloAccount({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) =>
63
+ String(entry),
58
64
  ),
59
65
  formatAllowFrom: ({ allowFrom }) =>
60
66
  allowFrom
@@ -87,12 +93,12 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
87
93
  reload: { configPrefixes: ["channels.zalo"] },
88
94
  configSchema: buildChannelConfigSchema(ZaloConfigSchema),
89
95
  config: {
90
- listAccountIds: (cfg) => listZaloAccountIds(cfg as OpenClawConfig),
91
- resolveAccount: (cfg, accountId) => resolveZaloAccount({ cfg: cfg as OpenClawConfig, accountId }),
92
- defaultAccountId: (cfg) => resolveDefaultZaloAccountId(cfg as OpenClawConfig),
96
+ listAccountIds: (cfg) => listZaloAccountIds(cfg),
97
+ resolveAccount: (cfg, accountId) => resolveZaloAccount({ cfg: cfg, accountId }),
98
+ defaultAccountId: (cfg) => resolveDefaultZaloAccountId(cfg),
93
99
  setAccountEnabled: ({ cfg, accountId, enabled }) =>
94
100
  setAccountEnabledInConfigSection({
95
- cfg: cfg as OpenClawConfig,
101
+ cfg: cfg,
96
102
  sectionKey: "zalo",
97
103
  accountId,
98
104
  enabled,
@@ -100,7 +106,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
100
106
  }),
101
107
  deleteAccount: ({ cfg, accountId }) =>
102
108
  deleteAccountFromConfigSection({
103
- cfg: cfg as OpenClawConfig,
109
+ cfg: cfg,
104
110
  sectionKey: "zalo",
105
111
  accountId,
106
112
  clearBaseFields: ["botToken", "tokenFile", "name"],
@@ -114,8 +120,8 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
114
120
  tokenSource: account.tokenSource,
115
121
  }),
116
122
  resolveAllowFrom: ({ cfg, accountId }) =>
117
- (resolveZaloAccount({ cfg: cfg as OpenClawConfig, accountId }).config.allowFrom ?? []).map(
118
- (entry) => String(entry),
123
+ (resolveZaloAccount({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) =>
124
+ String(entry),
119
125
  ),
120
126
  formatAllowFrom: ({ allowFrom }) =>
121
127
  allowFrom
@@ -127,9 +133,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
127
133
  security: {
128
134
  resolveDmPolicy: ({ cfg, accountId, account }) => {
129
135
  const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
130
- const useAccountPath = Boolean(
131
- (cfg as OpenClawConfig).channels?.zalo?.accounts?.[resolvedAccountId],
132
- );
136
+ const useAccountPath = Boolean(cfg.channels?.zalo?.accounts?.[resolvedAccountId]);
133
137
  const basePath = useAccountPath
134
138
  ? `channels.zalo.accounts.${resolvedAccountId}.`
135
139
  : "channels.zalo.";
@@ -155,7 +159,9 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
155
159
  targetResolver: {
156
160
  looksLikeId: (raw) => {
157
161
  const trimmed = raw.trim();
158
- if (!trimmed) return false;
162
+ if (!trimmed) {
163
+ return false;
164
+ }
159
165
  return /^\d{3,}$/.test(trimmed);
160
166
  },
161
167
  hint: "<chatId>",
@@ -164,7 +170,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
164
170
  directory: {
165
171
  self: async () => null,
166
172
  listPeers: async ({ cfg, accountId, query, limit }) => {
167
- const account = resolveZaloAccount({ cfg: cfg as OpenClawConfig, accountId });
173
+ const account = resolveZaloAccount({ cfg: cfg, accountId });
168
174
  const q = query?.trim().toLowerCase() || "";
169
175
  const peers = Array.from(
170
176
  new Set(
@@ -185,7 +191,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
185
191
  resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
186
192
  applyAccountName: ({ cfg, accountId, name }) =>
187
193
  applyAccountNameToChannelSection({
188
- cfg: cfg as OpenClawConfig,
194
+ cfg: cfg,
189
195
  channelKey: "zalo",
190
196
  accountId,
191
197
  name,
@@ -201,7 +207,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
201
207
  },
202
208
  applyAccountConfig: ({ cfg, accountId, input }) => {
203
209
  const namedConfig = applyAccountNameToChannelSection({
204
- cfg: cfg as OpenClawConfig,
210
+ cfg: cfg,
205
211
  channelKey: "zalo",
206
212
  accountId,
207
213
  name: input.name,
@@ -240,9 +246,9 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
240
246
  ...next.channels?.zalo,
241
247
  enabled: true,
242
248
  accounts: {
243
- ...(next.channels?.zalo?.accounts ?? {}),
249
+ ...next.channels?.zalo?.accounts,
244
250
  [accountId]: {
245
- ...(next.channels?.zalo?.accounts?.[accountId] ?? {}),
251
+ ...next.channels?.zalo?.accounts?.[accountId],
246
252
  enabled: true,
247
253
  ...(input.tokenFile
248
254
  ? { tokenFile: input.tokenFile }
@@ -260,16 +266,22 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
260
266
  idLabel: "zaloUserId",
261
267
  normalizeAllowEntry: (entry) => entry.replace(/^(zalo|zl):/i, ""),
262
268
  notifyApproval: async ({ cfg, id }) => {
263
- const account = resolveZaloAccount({ cfg: cfg as OpenClawConfig });
264
- if (!account.token) throw new Error("Zalo token not configured");
269
+ const account = resolveZaloAccount({ cfg: cfg });
270
+ if (!account.token) {
271
+ throw new Error("Zalo token not configured");
272
+ }
265
273
  await sendMessageZalo(id, PAIRING_APPROVED_MESSAGE, { token: account.token });
266
274
  },
267
275
  },
268
276
  outbound: {
269
277
  deliveryMode: "direct",
270
278
  chunker: (text, limit) => {
271
- if (!text) return [];
272
- if (limit <= 0 || text.length <= limit) return [text];
279
+ if (!text) {
280
+ return [];
281
+ }
282
+ if (limit <= 0 || text.length <= limit) {
283
+ return [text];
284
+ }
273
285
  const chunks: string[] = [];
274
286
  let remaining = text;
275
287
  while (remaining.length > limit) {
@@ -277,15 +289,21 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
277
289
  const lastNewline = window.lastIndexOf("\n");
278
290
  const lastSpace = window.lastIndexOf(" ");
279
291
  let breakIdx = lastNewline > 0 ? lastNewline : lastSpace;
280
- if (breakIdx <= 0) breakIdx = limit;
292
+ if (breakIdx <= 0) {
293
+ breakIdx = limit;
294
+ }
281
295
  const rawChunk = remaining.slice(0, breakIdx);
282
296
  const chunk = rawChunk.trimEnd();
283
- if (chunk.length > 0) chunks.push(chunk);
297
+ if (chunk.length > 0) {
298
+ chunks.push(chunk);
299
+ }
284
300
  const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
285
301
  const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
286
302
  remaining = remaining.slice(nextStart).trimStart();
287
303
  }
288
- if (remaining.length) chunks.push(remaining);
304
+ if (remaining.length) {
305
+ chunks.push(remaining);
306
+ }
289
307
  return chunks;
290
308
  },
291
309
  chunkerMode: "text",
@@ -293,7 +311,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
293
311
  sendText: async ({ to, text, accountId, cfg }) => {
294
312
  const result = await sendMessageZalo(to, text, {
295
313
  accountId: accountId ?? undefined,
296
- cfg: cfg as OpenClawConfig,
314
+ cfg: cfg,
297
315
  });
298
316
  return {
299
317
  channel: "zalo",
@@ -306,7 +324,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
306
324
  const result = await sendMessageZalo(to, text, {
307
325
  accountId: accountId ?? undefined,
308
326
  mediaUrl,
309
- cfg: cfg as OpenClawConfig,
327
+ cfg: cfg,
310
328
  });
311
329
  return {
312
330
  channel: "zalo",
@@ -366,7 +384,9 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
366
384
  try {
367
385
  const probe = await probeZalo(token, 2500, fetcher);
368
386
  const name = probe.ok ? probe.bot?.name?.trim() : null;
369
- if (name) zaloBotLabel = ` (${name})`;
387
+ if (name) {
388
+ zaloBotLabel = ` (${name})`;
389
+ }
370
390
  ctx.setStatus({
371
391
  accountId: account.accountId,
372
392
  bot: probe.bot,
@@ -379,7 +399,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
379
399
  return monitorZaloProvider({
380
400
  token,
381
401
  account,
382
- config: ctx.cfg as OpenClawConfig,
402
+ config: ctx.cfg,
383
403
  runtime: ctx.runtime,
384
404
  abortSignal: ctx.abortSignal,
385
405
  useWebhook: Boolean(account.config.webhookUrl),
package/src/monitor.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
-
3
2
  import type { OpenClawConfig, MarkdownTableMode } from "openclaw/plugin-sdk";
4
-
5
3
  import type { ResolvedZaloAccount } from "./accounts.js";
6
4
  import {
7
5
  ZaloApiError,
@@ -52,7 +50,9 @@ function logVerbose(core: ZaloCoreRuntime, runtime: ZaloRuntimeEnv, message: str
52
50
  }
53
51
 
54
52
  function isSenderAllowed(senderId: string, allowFrom: string[]): boolean {
55
- if (allowFrom.includes("*")) return true;
53
+ if (allowFrom.includes("*")) {
54
+ return true;
55
+ }
56
56
  const normalizedSenderId = senderId.toLowerCase();
57
57
  return allowFrom.some((entry) => {
58
58
  const normalized = entry.toLowerCase().replace(/^(zalo|zl):/i, "");
@@ -108,7 +108,9 @@ const webhookTargets = new Map<string, WebhookTarget[]>();
108
108
 
109
109
  function normalizeWebhookPath(raw: string): string {
110
110
  const trimmed = raw.trim();
111
- if (!trimmed) return "/";
111
+ if (!trimmed) {
112
+ return "/";
113
+ }
112
114
  const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
113
115
  if (withSlash.length > 1 && withSlash.endsWith("/")) {
114
116
  return withSlash.slice(0, -1);
@@ -118,7 +120,9 @@ function normalizeWebhookPath(raw: string): string {
118
120
 
119
121
  function resolveWebhookPath(webhookPath?: string, webhookUrl?: string): string | null {
120
122
  const trimmedPath = webhookPath?.trim();
121
- if (trimmedPath) return normalizeWebhookPath(trimmedPath);
123
+ if (trimmedPath) {
124
+ return normalizeWebhookPath(trimmedPath);
125
+ }
122
126
  if (webhookUrl?.trim()) {
123
127
  try {
124
128
  const parsed = new URL(webhookUrl);
@@ -137,9 +141,7 @@ export function registerZaloWebhookTarget(target: WebhookTarget): () => void {
137
141
  const next = [...existing, normalizedTarget];
138
142
  webhookTargets.set(key, next);
139
143
  return () => {
140
- const updated = (webhookTargets.get(key) ?? []).filter(
141
- (entry) => entry !== normalizedTarget,
142
- );
144
+ const updated = (webhookTargets.get(key) ?? []).filter((entry) => entry !== normalizedTarget);
143
145
  if (updated.length > 0) {
144
146
  webhookTargets.set(key, updated);
145
147
  } else {
@@ -155,7 +157,9 @@ export async function handleZaloWebhookRequest(
155
157
  const url = new URL(req.url ?? "/", "http://localhost");
156
158
  const path = normalizeWebhookPath(url.pathname);
157
159
  const targets = webhookTargets.get(path);
158
- if (!targets || targets.length === 0) return false;
160
+ if (!targets || targets.length === 0) {
161
+ return false;
162
+ }
159
163
 
160
164
  if (req.method !== "POST") {
161
165
  res.statusCode = 405;
@@ -181,12 +185,11 @@ export async function handleZaloWebhookRequest(
181
185
 
182
186
  // Zalo sends updates directly as { event_name, message, ... }, not wrapped in { ok, result }
183
187
  const raw = body.value;
184
- const record =
185
- raw && typeof raw === "object" ? (raw as Record<string, unknown>) : null;
188
+ const record = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : null;
186
189
  const update: ZaloUpdate | undefined =
187
190
  record && record.ok === true && record.result
188
191
  ? (record.result as ZaloUpdate)
189
- : (record as ZaloUpdate | null) ?? undefined;
192
+ : ((record as ZaloUpdate | null) ?? undefined);
190
193
 
191
194
  if (!update?.event_name) {
192
195
  res.statusCode = 400;
@@ -241,7 +244,9 @@ function startPollingLoop(params: {
241
244
  const pollTimeout = 30;
242
245
 
243
246
  const poll = async () => {
244
- if (isStopped() || abortSignal.aborted) return;
247
+ if (isStopped() || abortSignal.aborted) {
248
+ return;
249
+ }
245
250
 
246
251
  try {
247
252
  const response = await getUpdates(token, { timeout: pollTimeout }, fetcher);
@@ -288,20 +293,13 @@ async function processUpdate(
288
293
  fetcher?: ZaloFetch,
289
294
  ): Promise<void> {
290
295
  const { event_name, message } = update;
291
- if (!message) return;
296
+ if (!message) {
297
+ return;
298
+ }
292
299
 
293
300
  switch (event_name) {
294
301
  case "message.text.received":
295
- await handleTextMessage(
296
- message,
297
- token,
298
- account,
299
- config,
300
- runtime,
301
- core,
302
- statusSink,
303
- fetcher,
304
- );
302
+ await handleTextMessage(message, token, account, config, runtime, core, statusSink, fetcher);
305
303
  break;
306
304
  case "message.image.received":
307
305
  await handleImageMessage(
@@ -338,7 +336,9 @@ async function handleTextMessage(
338
336
  fetcher?: ZaloFetch,
339
337
  ): Promise<void> {
340
338
  const { text } = message;
341
- if (!text?.trim()) return;
339
+ if (!text?.trim()) {
340
+ return;
341
+ }
342
342
 
343
343
  await processMessageWithPipeline({
344
344
  message,
@@ -439,10 +439,7 @@ async function processMessageWithPipeline(params: {
439
439
  const dmPolicy = account.config.dmPolicy ?? "pairing";
440
440
  const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v));
441
441
  const rawBody = text?.trim() || (mediaPath ? "<media:image>" : "");
442
- const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(
443
- rawBody,
444
- config,
445
- );
442
+ const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, config);
446
443
  const storeAllowFrom =
447
444
  !isGroup && (dmPolicy !== "open" || shouldComputeAuth)
448
445
  ? await core.channel.pairing.readAllowFromStore("zalo").catch(() => [])
@@ -453,7 +450,9 @@ async function processMessageWithPipeline(params: {
453
450
  const commandAuthorized = shouldComputeAuth
454
451
  ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
455
452
  useAccessGroups,
456
- authorizers: [{ configured: effectiveAllowFrom.length > 0, allowed: senderAllowedForCommands }],
453
+ authorizers: [
454
+ { configured: effectiveAllowFrom.length > 0, allowed: senderAllowedForCommands },
455
+ ],
457
456
  })
458
457
  : undefined;
459
458
 
@@ -649,11 +648,7 @@ async function deliverZaloReply(params: {
649
648
 
650
649
  if (text) {
651
650
  const chunkMode = core.channel.text.resolveChunkMode(config, "zalo", accountId);
652
- const chunks = core.channel.text.chunkMarkdownTextWithMode(
653
- text,
654
- ZALO_TEXT_LIMIT,
655
- chunkMode,
656
- );
651
+ const chunks = core.channel.text.chunkMarkdownTextWithMode(text, ZALO_TEXT_LIMIT, chunkMode);
657
652
  for (const chunk of chunks) {
658
653
  try {
659
654
  await sendMessage(token, { chat_id: chatId, text: chunk }, fetcher);
@@ -665,9 +660,7 @@ async function deliverZaloReply(params: {
665
660
  }
666
661
  }
667
662
 
668
- export async function monitorZaloProvider(
669
- options: ZaloMonitorOptions,
670
- ): Promise<ZaloMonitorResult> {
663
+ export async function monitorZaloProvider(options: ZaloMonitorOptions): Promise<ZaloMonitorResult> {
671
664
  const {
672
665
  token,
673
666
  account,
@@ -1,9 +1,7 @@
1
- import { createServer } from "node:http";
2
1
  import type { AddressInfo } from "node:net";
3
-
4
- import { describe, expect, it } from "vitest";
5
-
6
2
  import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
3
+ import { createServer } from "node:http";
4
+ import { describe, expect, it } from "vitest";
7
5
  import type { ResolvedZaloAccount } from "./types.js";
8
6
  import { handleZaloWebhookRequest, registerZaloWebhookTarget } from "./monitor.js";
9
7
 
@@ -16,7 +14,9 @@ async function withServer(
16
14
  server.listen(0, "127.0.0.1", () => resolve());
17
15
  });
18
16
  const address = server.address() as AddressInfo | null;
19
- if (!address) throw new Error("missing server address");
17
+ if (!address) {
18
+ throw new Error("missing server address");
19
+ }
20
20
  try {
21
21
  await fn(`http://127.0.0.1:${address.port}`);
22
22
  } finally {
@@ -46,23 +46,26 @@ describe("handleZaloWebhookRequest", () => {
46
46
  });
47
47
 
48
48
  try {
49
- await withServer(async (req, res) => {
50
- const handled = await handleZaloWebhookRequest(req, res);
51
- if (!handled) {
52
- res.statusCode = 404;
53
- res.end("not found");
54
- }
55
- }, async (baseUrl) => {
56
- const response = await fetch(`${baseUrl}/hook`, {
57
- method: "POST",
58
- headers: {
59
- "x-bot-api-secret-token": "secret",
60
- },
61
- body: "null",
62
- });
49
+ await withServer(
50
+ async (req, res) => {
51
+ const handled = await handleZaloWebhookRequest(req, res);
52
+ if (!handled) {
53
+ res.statusCode = 404;
54
+ res.end("not found");
55
+ }
56
+ },
57
+ async (baseUrl) => {
58
+ const response = await fetch(`${baseUrl}/hook`, {
59
+ method: "POST",
60
+ headers: {
61
+ "x-bot-api-secret-token": "secret",
62
+ },
63
+ body: "null",
64
+ });
63
65
 
64
- expect(response.status).toBe(400);
65
- });
66
+ expect(response.status).toBe(400);
67
+ },
68
+ );
66
69
  } finally {
67
70
  unregister();
68
71
  }
package/src/onboarding.ts CHANGED
@@ -10,12 +10,7 @@ import {
10
10
  normalizeAccountId,
11
11
  promptAccountId,
12
12
  } from "openclaw/plugin-sdk";
13
-
14
- import {
15
- listZaloAccountIds,
16
- resolveDefaultZaloAccountId,
17
- resolveZaloAccount,
18
- } from "./accounts.js";
13
+ import { listZaloAccountIds, resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js";
19
14
 
20
15
  const channel = "zalo" as const;
21
16
 
@@ -25,7 +20,8 @@ function setZaloDmPolicy(
25
20
  cfg: OpenClawConfig,
26
21
  dmPolicy: "pairing" | "allowlist" | "open" | "disabled",
27
22
  ) {
28
- const allowFrom = dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.zalo?.allowFrom) : undefined;
23
+ const allowFrom =
24
+ dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.zalo?.allowFrom) : undefined;
29
25
  return {
30
26
  ...cfg,
31
27
  channels: {
@@ -64,17 +60,9 @@ function setZaloUpdateMode(
64
60
  },
65
61
  } as OpenClawConfig;
66
62
  }
67
- const accounts = { ...(cfg.channels?.zalo?.accounts ?? {}) } as Record<
68
- string,
69
- Record<string, unknown>
70
- >;
63
+ const accounts = { ...cfg.channels?.zalo?.accounts } as Record<string, Record<string, unknown>>;
71
64
  const existing = accounts[accountId] ?? {};
72
- const {
73
- webhookUrl: _url,
74
- webhookSecret: _secret,
75
- webhookPath: _path,
76
- ...rest
77
- } = existing;
65
+ const { webhookUrl: _url, webhookSecret: _secret, webhookPath: _path, ...rest } = existing;
78
66
  accounts[accountId] = rest;
79
67
  return {
80
68
  ...cfg,
@@ -103,12 +91,9 @@ function setZaloUpdateMode(
103
91
  } as OpenClawConfig;
104
92
  }
105
93
 
106
- const accounts = { ...(cfg.channels?.zalo?.accounts ?? {}) } as Record<
107
- string,
108
- Record<string, unknown>
109
- >;
94
+ const accounts = { ...cfg.channels?.zalo?.accounts } as Record<string, Record<string, unknown>>;
110
95
  accounts[accountId] = {
111
- ...(accounts[accountId] ?? {}),
96
+ ...accounts[accountId],
112
97
  webhookUrl,
113
98
  webhookSecret,
114
99
  webhookPath,
@@ -152,8 +137,12 @@ async function promptZaloAllowFrom(params: {
152
137
  initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
153
138
  validate: (value) => {
154
139
  const raw = String(value ?? "").trim();
155
- if (!raw) return "Required";
156
- if (!/^\d+$/.test(raw)) return "Use a numeric Zalo user id";
140
+ if (!raw) {
141
+ return "Required";
142
+ }
143
+ if (!/^\d+$/.test(raw)) {
144
+ return "Use a numeric Zalo user id";
145
+ }
157
146
  return undefined;
158
147
  },
159
148
  });
@@ -187,9 +176,9 @@ async function promptZaloAllowFrom(params: {
187
176
  ...cfg.channels?.zalo,
188
177
  enabled: true,
189
178
  accounts: {
190
- ...(cfg.channels?.zalo?.accounts ?? {}),
179
+ ...cfg.channels?.zalo?.accounts,
191
180
  [accountId]: {
192
- ...(cfg.channels?.zalo?.accounts?.[accountId] ?? {}),
181
+ ...cfg.channels?.zalo?.accounts?.[accountId],
193
182
  enabled: cfg.channels?.zalo?.accounts?.[accountId]?.enabled ?? true,
194
183
  dmPolicy: "allowlist",
195
184
  allowFrom: unique,
@@ -206,14 +195,14 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
206
195
  policyKey: "channels.zalo.dmPolicy",
207
196
  allowFromKey: "channels.zalo.allowFrom",
208
197
  getCurrent: (cfg) => (cfg.channels?.zalo?.dmPolicy ?? "pairing") as "pairing",
209
- setPolicy: (cfg, policy) => setZaloDmPolicy(cfg as OpenClawConfig, policy),
198
+ setPolicy: (cfg, policy) => setZaloDmPolicy(cfg, policy),
210
199
  promptAllowFrom: async ({ cfg, prompter, accountId }) => {
211
200
  const id =
212
201
  accountId && normalizeAccountId(accountId)
213
- ? normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID
214
- : resolveDefaultZaloAccountId(cfg as OpenClawConfig);
202
+ ? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
203
+ : resolveDefaultZaloAccountId(cfg);
215
204
  return promptZaloAllowFrom({
216
- cfg: cfg as OpenClawConfig,
205
+ cfg: cfg,
217
206
  prompter,
218
207
  accountId: id,
219
208
  });
@@ -224,8 +213,8 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
224
213
  channel,
225
214
  dmPolicy,
226
215
  getStatus: async ({ cfg }) => {
227
- const configured = listZaloAccountIds(cfg as OpenClawConfig).some((accountId) =>
228
- Boolean(resolveZaloAccount({ cfg: cfg as OpenClawConfig, accountId }).token),
216
+ const configured = listZaloAccountIds(cfg).some((accountId) =>
217
+ Boolean(resolveZaloAccount({ cfg: cfg, accountId }).token),
229
218
  );
230
219
  return {
231
220
  channel,
@@ -235,15 +224,19 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
235
224
  quickstartScore: configured ? 1 : 10,
236
225
  };
237
226
  },
238
- configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds, forceAllowFrom }) => {
227
+ configure: async ({
228
+ cfg,
229
+ prompter,
230
+ accountOverrides,
231
+ shouldPromptAccountIds,
232
+ forceAllowFrom,
233
+ }) => {
239
234
  const zaloOverride = accountOverrides.zalo?.trim();
240
- const defaultZaloAccountId = resolveDefaultZaloAccountId(cfg as OpenClawConfig);
241
- let zaloAccountId = zaloOverride
242
- ? normalizeAccountId(zaloOverride)
243
- : defaultZaloAccountId;
235
+ const defaultZaloAccountId = resolveDefaultZaloAccountId(cfg);
236
+ let zaloAccountId = zaloOverride ? normalizeAccountId(zaloOverride) : defaultZaloAccountId;
244
237
  if (shouldPromptAccountIds && !zaloOverride) {
245
238
  zaloAccountId = await promptAccountId({
246
- cfg: cfg as OpenClawConfig,
239
+ cfg: cfg,
247
240
  prompter,
248
241
  label: "Zalo",
249
242
  currentId: zaloAccountId,
@@ -252,7 +245,7 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
252
245
  });
253
246
  }
254
247
 
255
- let next = cfg as OpenClawConfig;
248
+ let next = cfg;
256
249
  const resolvedAccount = resolveZaloAccount({ cfg: next, accountId: zaloAccountId });
257
250
  const accountConfigured = Boolean(resolvedAccount.token);
258
251
  const allowEnv = zaloAccountId === DEFAULT_ACCOUNT_ID;
@@ -333,9 +326,9 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
333
326
  ...next.channels?.zalo,
334
327
  enabled: true,
335
328
  accounts: {
336
- ...(next.channels?.zalo?.accounts ?? {}),
329
+ ...next.channels?.zalo?.accounts,
337
330
  [zaloAccountId]: {
338
- ...(next.channels?.zalo?.accounts?.[zaloAccountId] ?? {}),
331
+ ...next.channels?.zalo?.accounts?.[zaloAccountId],
339
332
  enabled: true,
340
333
  botToken: token,
341
334
  },
@@ -354,7 +347,8 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
354
347
  const webhookUrl = String(
355
348
  await prompter.text({
356
349
  message: "Webhook URL (https://...) ",
357
- validate: (value) => (value?.trim()?.startsWith("https://") ? undefined : "HTTPS URL required"),
350
+ validate: (value) =>
351
+ value?.trim()?.startsWith("https://") ? undefined : "HTTPS URL required",
358
352
  }),
359
353
  ).trim();
360
354
  const defaultPath = (() => {
@@ -369,7 +363,9 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
369
363
  message: "Webhook secret (8-256 chars)",
370
364
  validate: (value) => {
371
365
  const raw = String(value ?? "");
372
- if (raw.length < 8 || raw.length > 256) return "8-256 chars";
366
+ if (raw.length < 8 || raw.length > 256) {
367
+ return "8-256 chars";
368
+ }
373
369
  return undefined;
374
370
  },
375
371
  }),
package/src/proxy.ts CHANGED
@@ -1,18 +1,21 @@
1
- import { ProxyAgent, fetch as undiciFetch } from "undici";
2
1
  import type { Dispatcher } from "undici";
3
-
2
+ import { ProxyAgent, fetch as undiciFetch } from "undici";
4
3
  import type { ZaloFetch } from "./api.js";
5
4
 
6
5
  const proxyCache = new Map<string, ZaloFetch>();
7
6
 
8
7
  export function resolveZaloProxyFetch(proxyUrl?: string | null): ZaloFetch | undefined {
9
8
  const trimmed = proxyUrl?.trim();
10
- if (!trimmed) return undefined;
9
+ if (!trimmed) {
10
+ return undefined;
11
+ }
11
12
  const cached = proxyCache.get(trimmed);
12
- if (cached) return cached;
13
+ if (cached) {
14
+ return cached;
15
+ }
13
16
  const agent = new ProxyAgent(trimmed);
14
17
  const fetcher: ZaloFetch = (input, init) =>
15
- undiciFetch(input, { ...(init ?? {}), dispatcher: agent as Dispatcher });
18
+ undiciFetch(input, { ...init, dispatcher: agent as Dispatcher });
16
19
  proxyCache.set(trimmed, fetcher);
17
20
  return fetcher;
18
21
  }
package/src/send.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import type { OpenClawConfig } from "openclaw/plugin-sdk";
2
-
3
2
  import type { ZaloFetch } from "./api.js";
4
- import { sendMessage, sendPhoto } from "./api.js";
5
3
  import { resolveZaloAccount } from "./accounts.js";
4
+ import { sendMessage, sendPhoto } from "./api.js";
6
5
  import { resolveZaloProxyFetch } from "./proxy.js";
7
6
  import { resolveZaloToken } from "./token.js";
8
7
 
@@ -65,10 +64,14 @@ export async function sendMessageZalo(
65
64
  }
66
65
 
67
66
  try {
68
- const response = await sendMessage(token, {
69
- chat_id: chatId.trim(),
70
- text: text.slice(0, 2000),
71
- }, fetcher);
67
+ const response = await sendMessage(
68
+ token,
69
+ {
70
+ chat_id: chatId.trim(),
71
+ text: text.slice(0, 2000),
72
+ },
73
+ fetcher,
74
+ );
72
75
 
73
76
  if (response.ok && response.result) {
74
77
  return { ok: true, messageId: response.result.message_id };
@@ -100,11 +103,15 @@ export async function sendPhotoZalo(
100
103
  }
101
104
 
102
105
  try {
103
- const response = await sendPhoto(token, {
104
- chat_id: chatId.trim(),
105
- photo: photoUrl.trim(),
106
- caption: options.caption?.slice(0, 2000),
107
- }, fetcher);
106
+ const response = await sendPhoto(
107
+ token,
108
+ {
109
+ chat_id: chatId.trim(),
110
+ photo: photoUrl.trim(),
111
+ caption: options.caption?.slice(0, 2000),
112
+ },
113
+ fetcher,
114
+ );
108
115
 
109
116
  if (response.ok && response.result) {
110
117
  return { ok: true, messageId: response.result.message_id };
@@ -14,7 +14,9 @@ const asString = (value: unknown): string | undefined =>
14
14
  typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined;
15
15
 
16
16
  function readZaloAccountStatus(value: ChannelAccountSnapshot): ZaloAccountStatus | null {
17
- if (!isRecord(value)) return null;
17
+ if (!isRecord(value)) {
18
+ return null;
19
+ }
18
20
  return {
19
21
  accountId: value.accountId,
20
22
  enabled: value.enabled,
@@ -23,25 +25,26 @@ function readZaloAccountStatus(value: ChannelAccountSnapshot): ZaloAccountStatus
23
25
  };
24
26
  }
25
27
 
26
- export function collectZaloStatusIssues(
27
- accounts: ChannelAccountSnapshot[],
28
- ): ChannelStatusIssue[] {
28
+ export function collectZaloStatusIssues(accounts: ChannelAccountSnapshot[]): ChannelStatusIssue[] {
29
29
  const issues: ChannelStatusIssue[] = [];
30
30
  for (const entry of accounts) {
31
31
  const account = readZaloAccountStatus(entry);
32
- if (!account) continue;
32
+ if (!account) {
33
+ continue;
34
+ }
33
35
  const accountId = asString(account.accountId) ?? "default";
34
36
  const enabled = account.enabled !== false;
35
37
  const configured = account.configured === true;
36
- if (!enabled || !configured) continue;
38
+ if (!enabled || !configured) {
39
+ continue;
40
+ }
37
41
 
38
42
  if (account.dmPolicy === "open") {
39
43
  issues.push({
40
44
  channel: "zalo",
41
45
  accountId,
42
46
  kind: "config",
43
- message:
44
- 'Zalo dmPolicy is "open", allowing any user to message the bot without pairing.',
47
+ message: 'Zalo dmPolicy is "open", allowing any user to message the bot without pairing.',
45
48
  fix: 'Set channels.zalo.dmPolicy to "pairing" or "allowlist" to restrict access.',
46
49
  });
47
50
  }
package/src/token.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
-
3
2
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
4
-
5
3
  import type { ZaloConfig } from "./types.js";
6
4
 
7
5
  export type ZaloTokenResolution = {
@@ -23,12 +21,16 @@ export function resolveZaloToken(
23
21
 
24
22
  if (accountConfig) {
25
23
  const token = accountConfig.botToken?.trim();
26
- if (token) return { token, source: "config" };
24
+ if (token) {
25
+ return { token, source: "config" };
26
+ }
27
27
  const tokenFile = accountConfig.tokenFile?.trim();
28
28
  if (tokenFile) {
29
29
  try {
30
30
  const fileToken = readFileSync(tokenFile, "utf8").trim();
31
- if (fileToken) return { token: fileToken, source: "configFile" };
31
+ if (fileToken) {
32
+ return { token: fileToken, source: "configFile" };
33
+ }
32
34
  } catch {
33
35
  // ignore read failures
34
36
  }
@@ -37,18 +39,24 @@ export function resolveZaloToken(
37
39
 
38
40
  if (isDefaultAccount) {
39
41
  const token = baseConfig?.botToken?.trim();
40
- if (token) return { token, source: "config" };
42
+ if (token) {
43
+ return { token, source: "config" };
44
+ }
41
45
  const tokenFile = baseConfig?.tokenFile?.trim();
42
46
  if (tokenFile) {
43
47
  try {
44
48
  const fileToken = readFileSync(tokenFile, "utf8").trim();
45
- if (fileToken) return { token: fileToken, source: "configFile" };
49
+ if (fileToken) {
50
+ return { token: fileToken, source: "configFile" };
51
+ }
46
52
  } catch {
47
53
  // ignore read failures
48
54
  }
49
55
  }
50
56
  const envToken = process.env.ZALO_BOT_TOKEN?.trim();
51
- if (envToken) return { token: envToken, source: "env" };
57
+ if (envToken) {
58
+ return { token: envToken, source: "env" };
59
+ }
52
60
  }
53
61
 
54
62
  return { token: "", source: "none" };