@openclaw/zalo 2026.2.25 → 2026.3.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.
@@ -0,0 +1,19 @@
1
+ import {
2
+ hasConfiguredSecretInput,
3
+ normalizeResolvedSecretInputString,
4
+ normalizeSecretInputString,
5
+ } from "openclaw/plugin-sdk";
6
+ import { z } from "zod";
7
+
8
+ export { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString };
9
+
10
+ export function buildSecretInputSchema() {
11
+ return z.union([
12
+ z.string(),
13
+ z.object({
14
+ source: z.enum(["env", "file", "exec"]),
15
+ provider: z.string().min(1),
16
+ id: z.string().min(1),
17
+ }),
18
+ ]);
19
+ }
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveZaloToken } from "./token.js";
3
+ import type { ZaloConfig } from "./types.js";
4
+
5
+ describe("resolveZaloToken", () => {
6
+ it("falls back to top-level token for non-default accounts without overrides", () => {
7
+ const cfg = {
8
+ botToken: "top-level-token",
9
+ accounts: {
10
+ work: {},
11
+ },
12
+ } as ZaloConfig;
13
+ const res = resolveZaloToken(cfg, "work");
14
+ expect(res.token).toBe("top-level-token");
15
+ expect(res.source).toBe("config");
16
+ });
17
+
18
+ it("uses accounts.default botToken for default account when configured", () => {
19
+ const cfg = {
20
+ botToken: "top-level-token",
21
+ accounts: {
22
+ default: {
23
+ botToken: "default-account-token",
24
+ },
25
+ },
26
+ } as ZaloConfig;
27
+ const res = resolveZaloToken(cfg, "default");
28
+ expect(res.token).toBe("default-account-token");
29
+ expect(res.source).toBe("config");
30
+ });
31
+
32
+ it("does not inherit top-level token when account token is explicitly blank", () => {
33
+ const cfg = {
34
+ botToken: "top-level-token",
35
+ accounts: {
36
+ work: {
37
+ botToken: "",
38
+ },
39
+ },
40
+ } as ZaloConfig;
41
+ const res = resolveZaloToken(cfg, "work");
42
+ expect(res.token).toBe("");
43
+ expect(res.source).toBe("none");
44
+ });
45
+
46
+ it("resolves account token when account key casing differs from normalized id", () => {
47
+ const cfg = {
48
+ accounts: {
49
+ Work: {
50
+ botToken: "work-token",
51
+ },
52
+ },
53
+ } as ZaloConfig;
54
+ const res = resolveZaloToken(cfg, "work");
55
+ expect(res.token).toBe("work-token");
56
+ expect(res.source).toBe("config");
57
+ });
58
+ });
package/src/token.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { type BaseTokenResolution, DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
2
+ import type { BaseTokenResolution } from "openclaw/plugin-sdk";
3
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
4
+ import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
3
5
  import type { ZaloConfig } from "./types.js";
4
6
 
5
7
  export type ZaloTokenResolution = BaseTokenResolution & {
@@ -9,17 +11,36 @@ export type ZaloTokenResolution = BaseTokenResolution & {
9
11
  export function resolveZaloToken(
10
12
  config: ZaloConfig | undefined,
11
13
  accountId?: string | null,
14
+ options?: { allowUnresolvedSecretRef?: boolean },
12
15
  ): ZaloTokenResolution {
13
16
  const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID;
14
17
  const isDefaultAccount = resolvedAccountId === DEFAULT_ACCOUNT_ID;
15
18
  const baseConfig = config;
16
- const accountConfig =
17
- resolvedAccountId !== DEFAULT_ACCOUNT_ID
18
- ? (baseConfig?.accounts?.[resolvedAccountId] as ZaloConfig | undefined)
19
- : undefined;
19
+ const resolveAccountConfig = (id: string): ZaloConfig | undefined => {
20
+ const accounts = baseConfig?.accounts;
21
+ if (!accounts || typeof accounts !== "object") {
22
+ return undefined;
23
+ }
24
+ const direct = accounts[id] as ZaloConfig | undefined;
25
+ if (direct) {
26
+ return direct;
27
+ }
28
+ const normalized = normalizeAccountId(id);
29
+ const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
30
+ return matchKey ? ((accounts as Record<string, ZaloConfig>)[matchKey] ?? undefined) : undefined;
31
+ };
32
+ const accountConfig = resolveAccountConfig(resolvedAccountId);
33
+ const accountHasBotToken = Boolean(
34
+ accountConfig && Object.prototype.hasOwnProperty.call(accountConfig, "botToken"),
35
+ );
20
36
 
21
- if (accountConfig) {
22
- const token = accountConfig.botToken?.trim();
37
+ if (accountConfig && accountHasBotToken) {
38
+ const token = options?.allowUnresolvedSecretRef
39
+ ? normalizeSecretInputString(accountConfig.botToken)
40
+ : normalizeResolvedSecretInputString({
41
+ value: accountConfig.botToken,
42
+ path: `channels.zalo.accounts.${resolvedAccountId}.botToken`,
43
+ });
23
44
  if (token) {
24
45
  return { token, source: "config" };
25
46
  }
@@ -36,8 +57,25 @@ export function resolveZaloToken(
36
57
  }
37
58
  }
38
59
 
39
- if (isDefaultAccount) {
40
- const token = baseConfig?.botToken?.trim();
60
+ const accountTokenFile = accountConfig?.tokenFile?.trim();
61
+ if (!accountHasBotToken && accountTokenFile) {
62
+ try {
63
+ const fileToken = readFileSync(accountTokenFile, "utf8").trim();
64
+ if (fileToken) {
65
+ return { token: fileToken, source: "configFile" };
66
+ }
67
+ } catch {
68
+ // ignore read failures
69
+ }
70
+ }
71
+
72
+ if (!accountHasBotToken) {
73
+ const token = options?.allowUnresolvedSecretRef
74
+ ? normalizeSecretInputString(baseConfig?.botToken)
75
+ : normalizeResolvedSecretInputString({
76
+ value: baseConfig?.botToken,
77
+ path: "channels.zalo.botToken",
78
+ });
41
79
  if (token) {
42
80
  return { token, source: "config" };
43
81
  }
@@ -52,6 +90,9 @@ export function resolveZaloToken(
52
90
  // ignore read failures
53
91
  }
54
92
  }
93
+ }
94
+
95
+ if (isDefaultAccount) {
55
96
  const envToken = process.env.ZALO_BOT_TOKEN?.trim();
56
97
  if (envToken) {
57
98
  return { token: envToken, source: "env" };
package/src/types.ts CHANGED
@@ -1,16 +1,18 @@
1
+ import type { SecretInput } from "openclaw/plugin-sdk";
2
+
1
3
  export type ZaloAccountConfig = {
2
4
  /** Optional display name for this account (used in CLI/UI lists). */
3
5
  name?: string;
4
6
  /** If false, do not start this Zalo account. Default: true. */
5
7
  enabled?: boolean;
6
8
  /** Bot token from Zalo Bot Creator. */
7
- botToken?: string;
9
+ botToken?: SecretInput;
8
10
  /** Path to file containing the bot token. */
9
11
  tokenFile?: string;
10
12
  /** Webhook URL for receiving updates (HTTPS required). */
11
13
  webhookUrl?: string;
12
14
  /** Webhook secret token (8-256 chars) for request verification. */
13
- webhookSecret?: string;
15
+ webhookSecret?: SecretInput;
14
16
  /** Webhook path for the gateway HTTP server (defaults to webhook URL path). */
15
17
  webhookPath?: string;
16
18
  /** Direct message access policy (default: pairing). */