@kodelyth/zalo 2026.5.42 → 2026.6.1
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/klaw.plugin.json +509 -2
- package/package.json +17 -4
- package/api.ts +0 -8
- package/channel-plugin-api.ts +0 -1
- package/contract-api.ts +0 -5
- package/index.test.ts +0 -15
- package/index.ts +0 -20
- package/runtime-api.test.ts +0 -10
- package/runtime-api.ts +0 -71
- package/secret-contract-api.ts +0 -5
- package/setup-api.ts +0 -34
- package/setup-entry.ts +0 -13
- package/src/accounts.test.ts +0 -95
- package/src/accounts.ts +0 -65
- package/src/actions.runtime.ts +0 -5
- package/src/actions.test.ts +0 -32
- package/src/actions.ts +0 -62
- package/src/api.test.ts +0 -166
- package/src/api.ts +0 -265
- package/src/approval-auth.test.ts +0 -17
- package/src/approval-auth.ts +0 -25
- package/src/channel.directory.test.ts +0 -56
- package/src/channel.runtime.ts +0 -89
- package/src/channel.startup.test.ts +0 -121
- package/src/channel.ts +0 -309
- package/src/config-schema.test.ts +0 -30
- package/src/config-schema.ts +0 -29
- package/src/group-access.ts +0 -23
- package/src/monitor-durable.test.ts +0 -49
- package/src/monitor-durable.ts +0 -38
- package/src/monitor.group-policy.test.ts +0 -213
- package/src/monitor.image.polling.test.ts +0 -113
- package/src/monitor.lifecycle.test.ts +0 -194
- package/src/monitor.pairing.lifecycle.test.ts +0 -139
- package/src/monitor.polling.media-reply.test.ts +0 -433
- package/src/monitor.reply-once.lifecycle.test.ts +0 -178
- package/src/monitor.ts +0 -1009
- package/src/monitor.types.ts +0 -4
- package/src/monitor.webhook.test.ts +0 -808
- package/src/monitor.webhook.ts +0 -278
- package/src/outbound-media.test.ts +0 -186
- package/src/outbound-media.ts +0 -236
- package/src/outbound-payload.contract.test.ts +0 -143
- package/src/probe.ts +0 -45
- package/src/proxy.ts +0 -18
- package/src/runtime-api.ts +0 -71
- package/src/runtime-support.ts +0 -82
- package/src/runtime.ts +0 -9
- package/src/secret-contract.ts +0 -109
- package/src/secret-input.ts +0 -5
- package/src/send.test.ts +0 -150
- package/src/send.ts +0 -207
- package/src/session-route.ts +0 -32
- package/src/setup-allow-from.ts +0 -97
- package/src/setup-core.ts +0 -152
- package/src/setup-status.test.ts +0 -33
- package/src/setup-surface.test.ts +0 -193
- package/src/setup-surface.ts +0 -294
- package/src/status-issues.test.ts +0 -17
- package/src/status-issues.ts +0 -34
- package/src/test-support/lifecycle-test-support.ts +0 -456
- package/src/test-support/monitor-mocks-test-support.ts +0 -209
- package/src/token.test.ts +0 -92
- package/src/token.ts +0 -79
- package/src/types.ts +0 -50
- package/test-api.ts +0 -1
- package/tsconfig.json +0 -16
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { adaptScopedAccountAccessor } from "klaw/plugin-sdk/channel-config-helpers";
|
|
2
|
-
import {
|
|
3
|
-
createPluginSetupWizardConfigure,
|
|
4
|
-
createTestWizardPrompter,
|
|
5
|
-
runSetupWizardConfigure,
|
|
6
|
-
} from "klaw/plugin-sdk/plugin-test-runtime";
|
|
7
|
-
import type { WizardPrompter } from "klaw/plugin-sdk/plugin-test-runtime";
|
|
8
|
-
import { describe, expect, it, vi } from "vitest";
|
|
9
|
-
import type { KlawConfig } from "../runtime-api.js";
|
|
10
|
-
import { listZaloAccountIds, resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js";
|
|
11
|
-
import { zaloDmPolicy } from "./setup-core.js";
|
|
12
|
-
import { zaloSetupAdapter, zaloSetupWizard } from "./setup-surface.js";
|
|
13
|
-
|
|
14
|
-
const zaloSetupPlugin = {
|
|
15
|
-
id: "zalo",
|
|
16
|
-
meta: {
|
|
17
|
-
id: "zalo",
|
|
18
|
-
label: "Zalo",
|
|
19
|
-
selectionLabel: "Zalo (Bot API)",
|
|
20
|
-
docsPath: "/channels/zalo",
|
|
21
|
-
blurb: "Vietnam-focused messaging platform with Bot API.",
|
|
22
|
-
},
|
|
23
|
-
capabilities: {
|
|
24
|
-
chatTypes: ["direct", "group"] as Array<"direct" | "group">,
|
|
25
|
-
},
|
|
26
|
-
config: {
|
|
27
|
-
listAccountIds: (cfg: unknown) => listZaloAccountIds(cfg as never),
|
|
28
|
-
defaultAccountId: (cfg: unknown) => resolveDefaultZaloAccountId(cfg as never),
|
|
29
|
-
resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount),
|
|
30
|
-
},
|
|
31
|
-
setup: zaloSetupAdapter,
|
|
32
|
-
setupWizard: zaloSetupWizard,
|
|
33
|
-
} as const;
|
|
34
|
-
|
|
35
|
-
const zaloConfigure = createPluginSetupWizardConfigure(zaloSetupPlugin);
|
|
36
|
-
|
|
37
|
-
describe("zalo setup wizard", () => {
|
|
38
|
-
it("configures a polling token flow", async () => {
|
|
39
|
-
const prompter = createTestWizardPrompter({
|
|
40
|
-
select: vi.fn(async () => "plaintext") as WizardPrompter["select"],
|
|
41
|
-
text: vi.fn(async ({ message }: { message: string }) => {
|
|
42
|
-
if (message === "Enter Zalo bot token") {
|
|
43
|
-
return "12345689:abc-xyz";
|
|
44
|
-
}
|
|
45
|
-
throw new Error(`Unexpected prompt: ${message}`);
|
|
46
|
-
}) as WizardPrompter["text"],
|
|
47
|
-
confirm: vi.fn(async ({ message }: { message: string }) => {
|
|
48
|
-
if (message === "Use webhook mode for Zalo?") {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
52
|
-
}),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const result = await runSetupWizardConfigure({
|
|
56
|
-
configure: zaloConfigure,
|
|
57
|
-
cfg: {} as KlawConfig,
|
|
58
|
-
prompter,
|
|
59
|
-
options: { secretInputMode: "plaintext" as const },
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
expect(result.accountId).toBe("default");
|
|
63
|
-
const zaloConfig = result.cfg.channels?.zalo;
|
|
64
|
-
if (!zaloConfig) {
|
|
65
|
-
throw new Error("expected Zalo config");
|
|
66
|
-
}
|
|
67
|
-
expect(zaloConfig.enabled).toBe(true);
|
|
68
|
-
expect(zaloConfig.botToken).toBe("12345689:abc-xyz");
|
|
69
|
-
expect(zaloConfig.webhookUrl).toBeUndefined();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("reads the named-account DM policy instead of the channel root", () => {
|
|
73
|
-
expect(
|
|
74
|
-
zaloDmPolicy.getCurrent(
|
|
75
|
-
{
|
|
76
|
-
channels: {
|
|
77
|
-
zalo: {
|
|
78
|
-
dmPolicy: "disabled",
|
|
79
|
-
accounts: {
|
|
80
|
-
work: {
|
|
81
|
-
botToken: "12345689:abc-xyz",
|
|
82
|
-
dmPolicy: "allowlist",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
} as KlawConfig,
|
|
88
|
-
"work",
|
|
89
|
-
),
|
|
90
|
-
).toBe("allowlist");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("reports account-scoped config keys for named accounts", () => {
|
|
94
|
-
expect(zaloDmPolicy.resolveConfigKeys?.({} as KlawConfig, "work")).toEqual({
|
|
95
|
-
policyKey: "channels.zalo.accounts.work.dmPolicy",
|
|
96
|
-
allowFromKey: "channels.zalo.accounts.work.allowFrom",
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("uses configured defaultAccount for omitted DM policy account context", () => {
|
|
101
|
-
const cfg = {
|
|
102
|
-
channels: {
|
|
103
|
-
zalo: {
|
|
104
|
-
defaultAccount: "work",
|
|
105
|
-
dmPolicy: "disabled",
|
|
106
|
-
allowFrom: ["123456789"],
|
|
107
|
-
accounts: {
|
|
108
|
-
work: {
|
|
109
|
-
botToken: "12345689:abc-xyz",
|
|
110
|
-
dmPolicy: "allowlist",
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
} as KlawConfig;
|
|
116
|
-
|
|
117
|
-
expect(zaloDmPolicy.getCurrent(cfg)).toBe("allowlist");
|
|
118
|
-
expect(zaloDmPolicy.resolveConfigKeys?.(cfg)).toEqual({
|
|
119
|
-
policyKey: "channels.zalo.accounts.work.dmPolicy",
|
|
120
|
-
allowFromKey: "channels.zalo.accounts.work.allowFrom",
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const next = zaloDmPolicy.setPolicy(cfg, "open");
|
|
124
|
-
const zaloConfig = next.channels?.zalo;
|
|
125
|
-
if (!zaloConfig) {
|
|
126
|
-
throw new Error("expected Zalo config");
|
|
127
|
-
}
|
|
128
|
-
expect(zaloConfig.dmPolicy).toBe("disabled");
|
|
129
|
-
const workAccount = next.channels?.zalo?.accounts?.work as
|
|
130
|
-
| { dmPolicy?: string; allowFrom?: Array<string | number> }
|
|
131
|
-
| undefined;
|
|
132
|
-
if (!workAccount) {
|
|
133
|
-
throw new Error("expected Zalo work account");
|
|
134
|
-
}
|
|
135
|
-
expect(workAccount.dmPolicy).toBe("open");
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => {
|
|
139
|
-
const next = zaloDmPolicy.setPolicy(
|
|
140
|
-
{
|
|
141
|
-
channels: {
|
|
142
|
-
zalo: {
|
|
143
|
-
allowFrom: ["123456789"],
|
|
144
|
-
accounts: {
|
|
145
|
-
work: {
|
|
146
|
-
botToken: "12345689:abc-xyz",
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
} as KlawConfig,
|
|
152
|
-
"open",
|
|
153
|
-
"work",
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
const zaloConfig = next.channels?.zalo;
|
|
157
|
-
if (!zaloConfig) {
|
|
158
|
-
throw new Error("expected Zalo config");
|
|
159
|
-
}
|
|
160
|
-
expect(zaloConfig.dmPolicy).toBeUndefined();
|
|
161
|
-
const workAccount = next.channels?.zalo?.accounts?.work as
|
|
162
|
-
| { dmPolicy?: string; allowFrom?: Array<string | number> }
|
|
163
|
-
| undefined;
|
|
164
|
-
if (!workAccount) {
|
|
165
|
-
throw new Error("expected Zalo work account");
|
|
166
|
-
}
|
|
167
|
-
expect(workAccount.dmPolicy).toBe("open");
|
|
168
|
-
expect(workAccount.allowFrom).toEqual(["123456789", "*"]);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("uses configured defaultAccount for omitted setup configured state", async () => {
|
|
172
|
-
const configured = await zaloSetupWizard.status.resolveConfigured({
|
|
173
|
-
cfg: {
|
|
174
|
-
channels: {
|
|
175
|
-
zalo: {
|
|
176
|
-
defaultAccount: "work",
|
|
177
|
-
botToken: "root-token",
|
|
178
|
-
accounts: {
|
|
179
|
-
alerts: {
|
|
180
|
-
botToken: "alerts-token",
|
|
181
|
-
},
|
|
182
|
-
work: {
|
|
183
|
-
botToken: "",
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
} as KlawConfig,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
expect(configured).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
});
|
package/src/setup-surface.ts
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildSingleChannelSecretPromptState,
|
|
3
|
-
createStandardChannelSetupStatus,
|
|
4
|
-
DEFAULT_ACCOUNT_ID,
|
|
5
|
-
hasConfiguredSecretInput,
|
|
6
|
-
promptSingleChannelSecretInput,
|
|
7
|
-
runSingleChannelSecretStep,
|
|
8
|
-
type ChannelSetupWizard,
|
|
9
|
-
type KlawConfig,
|
|
10
|
-
type SecretInput,
|
|
11
|
-
createSetupTranslator,
|
|
12
|
-
} from "klaw/plugin-sdk/setup";
|
|
13
|
-
import { resolveZaloAccount } from "./accounts.js";
|
|
14
|
-
import { noteZaloTokenHelp, promptZaloAllowFrom } from "./setup-allow-from.js";
|
|
15
|
-
import { zaloDmPolicy } from "./setup-core.js";
|
|
16
|
-
|
|
17
|
-
const t = createSetupTranslator();
|
|
18
|
-
|
|
19
|
-
const channel = "zalo" as const;
|
|
20
|
-
|
|
21
|
-
type UpdateMode = "polling" | "webhook";
|
|
22
|
-
|
|
23
|
-
function setZaloUpdateMode(
|
|
24
|
-
cfg: KlawConfig,
|
|
25
|
-
accountId: string,
|
|
26
|
-
mode: UpdateMode,
|
|
27
|
-
webhookUrl?: string,
|
|
28
|
-
webhookSecret?: SecretInput,
|
|
29
|
-
webhookPath?: string,
|
|
30
|
-
): KlawConfig {
|
|
31
|
-
const isDefault = accountId === DEFAULT_ACCOUNT_ID;
|
|
32
|
-
if (mode === "polling") {
|
|
33
|
-
if (isDefault) {
|
|
34
|
-
const {
|
|
35
|
-
webhookUrl: _url,
|
|
36
|
-
webhookSecret: _secret,
|
|
37
|
-
webhookPath: _path,
|
|
38
|
-
...rest
|
|
39
|
-
} = cfg.channels?.zalo ?? {};
|
|
40
|
-
return {
|
|
41
|
-
...cfg,
|
|
42
|
-
channels: {
|
|
43
|
-
...cfg.channels,
|
|
44
|
-
zalo: rest,
|
|
45
|
-
},
|
|
46
|
-
} as KlawConfig;
|
|
47
|
-
}
|
|
48
|
-
const accounts = { ...cfg.channels?.zalo?.accounts } as Record<string, Record<string, unknown>>;
|
|
49
|
-
const existing = accounts[accountId] ?? {};
|
|
50
|
-
const { webhookUrl: _url, webhookSecret: _secret, webhookPath: _path, ...rest } = existing;
|
|
51
|
-
accounts[accountId] = rest;
|
|
52
|
-
return {
|
|
53
|
-
...cfg,
|
|
54
|
-
channels: {
|
|
55
|
-
...cfg.channels,
|
|
56
|
-
zalo: {
|
|
57
|
-
...cfg.channels?.zalo,
|
|
58
|
-
accounts,
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
} as KlawConfig;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (isDefault) {
|
|
65
|
-
return {
|
|
66
|
-
...cfg,
|
|
67
|
-
channels: {
|
|
68
|
-
...cfg.channels,
|
|
69
|
-
zalo: {
|
|
70
|
-
...cfg.channels?.zalo,
|
|
71
|
-
webhookUrl,
|
|
72
|
-
webhookSecret,
|
|
73
|
-
webhookPath,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
} as KlawConfig;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const accounts = { ...cfg.channels?.zalo?.accounts } as Record<string, Record<string, unknown>>;
|
|
80
|
-
accounts[accountId] = {
|
|
81
|
-
...accounts[accountId],
|
|
82
|
-
webhookUrl,
|
|
83
|
-
webhookSecret,
|
|
84
|
-
webhookPath,
|
|
85
|
-
};
|
|
86
|
-
return {
|
|
87
|
-
...cfg,
|
|
88
|
-
channels: {
|
|
89
|
-
...cfg.channels,
|
|
90
|
-
zalo: {
|
|
91
|
-
...cfg.channels?.zalo,
|
|
92
|
-
accounts,
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
} as KlawConfig;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export { zaloSetupAdapter } from "./setup-core.js";
|
|
99
|
-
|
|
100
|
-
export const zaloSetupWizard: ChannelSetupWizard = {
|
|
101
|
-
channel,
|
|
102
|
-
status: createStandardChannelSetupStatus({
|
|
103
|
-
channelLabel: "Zalo",
|
|
104
|
-
configuredLabel: t("wizard.channels.statusConfigured"),
|
|
105
|
-
unconfiguredLabel: t("wizard.channels.statusNeedsToken"),
|
|
106
|
-
configuredHint: t("wizard.channels.statusRecommendedConfigured"),
|
|
107
|
-
unconfiguredHint: t("wizard.channels.statusRecommendedNewcomerFriendly"),
|
|
108
|
-
configuredScore: 1,
|
|
109
|
-
unconfiguredScore: 10,
|
|
110
|
-
includeStatusLine: true,
|
|
111
|
-
resolveConfigured: ({ cfg, accountId }) => {
|
|
112
|
-
const account = resolveZaloAccount({
|
|
113
|
-
cfg,
|
|
114
|
-
accountId,
|
|
115
|
-
allowUnresolvedSecretRef: true,
|
|
116
|
-
});
|
|
117
|
-
return (
|
|
118
|
-
Boolean(account.token) ||
|
|
119
|
-
hasConfiguredSecretInput(account.config.botToken) ||
|
|
120
|
-
Boolean(account.config.tokenFile?.trim())
|
|
121
|
-
);
|
|
122
|
-
},
|
|
123
|
-
}),
|
|
124
|
-
credentials: [],
|
|
125
|
-
finalize: async ({ cfg, accountId, forceAllowFrom, options, prompter }) => {
|
|
126
|
-
let next = cfg;
|
|
127
|
-
const resolvedAccount = resolveZaloAccount({
|
|
128
|
-
cfg: next,
|
|
129
|
-
accountId,
|
|
130
|
-
allowUnresolvedSecretRef: true,
|
|
131
|
-
});
|
|
132
|
-
const accountConfigured = Boolean(resolvedAccount.token);
|
|
133
|
-
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
|
|
134
|
-
const hasConfigToken = Boolean(
|
|
135
|
-
hasConfiguredSecretInput(resolvedAccount.config.botToken) || resolvedAccount.config.tokenFile,
|
|
136
|
-
);
|
|
137
|
-
const tokenStep = await runSingleChannelSecretStep({
|
|
138
|
-
cfg: next,
|
|
139
|
-
prompter,
|
|
140
|
-
providerHint: "zalo",
|
|
141
|
-
credentialLabel: t("wizard.zalo.botToken"),
|
|
142
|
-
secretInputMode: options?.secretInputMode,
|
|
143
|
-
accountConfigured,
|
|
144
|
-
hasConfigToken,
|
|
145
|
-
allowEnv,
|
|
146
|
-
envValue: process.env.ZALO_BOT_TOKEN,
|
|
147
|
-
envPrompt: t("wizard.zalo.tokenEnvPrompt"),
|
|
148
|
-
keepPrompt: t("wizard.zalo.tokenKeep"),
|
|
149
|
-
inputPrompt: t("wizard.zalo.tokenInput"),
|
|
150
|
-
preferredEnvVar: "ZALO_BOT_TOKEN",
|
|
151
|
-
onMissingConfigured: async () => await noteZaloTokenHelp(prompter),
|
|
152
|
-
applyUseEnv: async (currentCfg) =>
|
|
153
|
-
accountId === DEFAULT_ACCOUNT_ID
|
|
154
|
-
? ({
|
|
155
|
-
...currentCfg,
|
|
156
|
-
channels: {
|
|
157
|
-
...currentCfg.channels,
|
|
158
|
-
zalo: {
|
|
159
|
-
...currentCfg.channels?.zalo,
|
|
160
|
-
enabled: true,
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
} as KlawConfig)
|
|
164
|
-
: currentCfg,
|
|
165
|
-
applySet: async (currentCfg, value) =>
|
|
166
|
-
accountId === DEFAULT_ACCOUNT_ID
|
|
167
|
-
? ({
|
|
168
|
-
...currentCfg,
|
|
169
|
-
channels: {
|
|
170
|
-
...currentCfg.channels,
|
|
171
|
-
zalo: {
|
|
172
|
-
...currentCfg.channels?.zalo,
|
|
173
|
-
enabled: true,
|
|
174
|
-
botToken: value,
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
} as KlawConfig)
|
|
178
|
-
: ({
|
|
179
|
-
...currentCfg,
|
|
180
|
-
channels: {
|
|
181
|
-
...currentCfg.channels,
|
|
182
|
-
zalo: {
|
|
183
|
-
...currentCfg.channels?.zalo,
|
|
184
|
-
enabled: true,
|
|
185
|
-
accounts: {
|
|
186
|
-
...currentCfg.channels?.zalo?.accounts,
|
|
187
|
-
[accountId]: {
|
|
188
|
-
...(currentCfg.channels?.zalo?.accounts?.[accountId] as
|
|
189
|
-
| Record<string, unknown>
|
|
190
|
-
| undefined),
|
|
191
|
-
enabled: true,
|
|
192
|
-
botToken: value,
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
} as KlawConfig),
|
|
198
|
-
});
|
|
199
|
-
next = tokenStep.cfg;
|
|
200
|
-
|
|
201
|
-
const wantsWebhook = await prompter.confirm({
|
|
202
|
-
message: t("wizard.zalo.webhookModePrompt"),
|
|
203
|
-
initialValue: Boolean(resolvedAccount.config.webhookUrl),
|
|
204
|
-
});
|
|
205
|
-
if (wantsWebhook) {
|
|
206
|
-
const webhookUrl = (
|
|
207
|
-
await prompter.text({
|
|
208
|
-
message: t("wizard.zalo.webhookUrlPrompt"),
|
|
209
|
-
initialValue: resolvedAccount.config.webhookUrl,
|
|
210
|
-
validate: (value) =>
|
|
211
|
-
value?.trim()?.startsWith("https://") ? undefined : "HTTPS URL required",
|
|
212
|
-
})
|
|
213
|
-
).trim();
|
|
214
|
-
const defaultPath = (() => {
|
|
215
|
-
try {
|
|
216
|
-
return new URL(webhookUrl).pathname || "/zalo-webhook";
|
|
217
|
-
} catch {
|
|
218
|
-
return "/zalo-webhook";
|
|
219
|
-
}
|
|
220
|
-
})();
|
|
221
|
-
|
|
222
|
-
let webhookSecretResult = await promptSingleChannelSecretInput({
|
|
223
|
-
cfg: next,
|
|
224
|
-
prompter,
|
|
225
|
-
providerHint: "zalo-webhook",
|
|
226
|
-
credentialLabel: t("wizard.zalo.webhookSecret"),
|
|
227
|
-
secretInputMode: options?.secretInputMode,
|
|
228
|
-
...buildSingleChannelSecretPromptState({
|
|
229
|
-
accountConfigured: hasConfiguredSecretInput(resolvedAccount.config.webhookSecret),
|
|
230
|
-
hasConfigToken: hasConfiguredSecretInput(resolvedAccount.config.webhookSecret),
|
|
231
|
-
allowEnv: false,
|
|
232
|
-
}),
|
|
233
|
-
envPrompt: "",
|
|
234
|
-
keepPrompt: t("wizard.zalo.webhookSecretKeep"),
|
|
235
|
-
inputPrompt: t("wizard.zalo.webhookSecretInput"),
|
|
236
|
-
preferredEnvVar: "ZALO_WEBHOOK_SECRET",
|
|
237
|
-
});
|
|
238
|
-
while (
|
|
239
|
-
webhookSecretResult.action === "set" &&
|
|
240
|
-
typeof webhookSecretResult.value === "string" &&
|
|
241
|
-
(webhookSecretResult.value.length < 8 || webhookSecretResult.value.length > 256)
|
|
242
|
-
) {
|
|
243
|
-
await prompter.note(t("wizard.zalo.webhookSecretLength"), t("wizard.zalo.webhookTitle"));
|
|
244
|
-
webhookSecretResult = await promptSingleChannelSecretInput({
|
|
245
|
-
cfg: next,
|
|
246
|
-
prompter,
|
|
247
|
-
providerHint: "zalo-webhook",
|
|
248
|
-
credentialLabel: t("wizard.zalo.webhookSecret"),
|
|
249
|
-
secretInputMode: options?.secretInputMode,
|
|
250
|
-
...buildSingleChannelSecretPromptState({
|
|
251
|
-
accountConfigured: false,
|
|
252
|
-
hasConfigToken: false,
|
|
253
|
-
allowEnv: false,
|
|
254
|
-
}),
|
|
255
|
-
envPrompt: "",
|
|
256
|
-
keepPrompt: t("wizard.zalo.webhookSecretKeep"),
|
|
257
|
-
inputPrompt: t("wizard.zalo.webhookSecretInput"),
|
|
258
|
-
preferredEnvVar: "ZALO_WEBHOOK_SECRET",
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
const webhookSecret =
|
|
262
|
-
webhookSecretResult.action === "set"
|
|
263
|
-
? webhookSecretResult.value
|
|
264
|
-
: resolvedAccount.config.webhookSecret;
|
|
265
|
-
const webhookPath = (
|
|
266
|
-
await prompter.text({
|
|
267
|
-
message: t("wizard.zalo.webhookPathPrompt"),
|
|
268
|
-
initialValue: resolvedAccount.config.webhookPath ?? defaultPath,
|
|
269
|
-
})
|
|
270
|
-
).trim();
|
|
271
|
-
next = setZaloUpdateMode(
|
|
272
|
-
next,
|
|
273
|
-
accountId,
|
|
274
|
-
"webhook",
|
|
275
|
-
webhookUrl,
|
|
276
|
-
webhookSecret,
|
|
277
|
-
webhookPath || undefined,
|
|
278
|
-
);
|
|
279
|
-
} else {
|
|
280
|
-
next = setZaloUpdateMode(next, accountId, "polling");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (forceAllowFrom) {
|
|
284
|
-
next = await promptZaloAllowFrom({
|
|
285
|
-
cfg: next,
|
|
286
|
-
prompter,
|
|
287
|
-
accountId,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return { cfg: next };
|
|
292
|
-
},
|
|
293
|
-
dmPolicy: zaloDmPolicy,
|
|
294
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { expectOpenDmPolicyConfigIssue } from "klaw/plugin-sdk/channel-test-helpers";
|
|
2
|
-
import { describe, it } from "vitest";
|
|
3
|
-
import { collectZaloStatusIssues } from "./status-issues.js";
|
|
4
|
-
|
|
5
|
-
describe("collectZaloStatusIssues", () => {
|
|
6
|
-
it("warns when dmPolicy is open", () => {
|
|
7
|
-
expectOpenDmPolicyConfigIssue({
|
|
8
|
-
collectIssues: collectZaloStatusIssues,
|
|
9
|
-
account: {
|
|
10
|
-
accountId: "default",
|
|
11
|
-
enabled: true,
|
|
12
|
-
configured: true,
|
|
13
|
-
dmPolicy: "open",
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
});
|
package/src/status-issues.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ChannelAccountSnapshot, ChannelStatusIssue } from "klaw/plugin-sdk/channel-contract";
|
|
2
|
-
import {
|
|
3
|
-
coerceStatusIssueAccountId,
|
|
4
|
-
readStatusIssueFields,
|
|
5
|
-
} from "klaw/plugin-sdk/extension-shared";
|
|
6
|
-
|
|
7
|
-
const ZALO_STATUS_FIELDS = ["accountId", "enabled", "configured", "dmPolicy"] as const;
|
|
8
|
-
|
|
9
|
-
export function collectZaloStatusIssues(accounts: ChannelAccountSnapshot[]): ChannelStatusIssue[] {
|
|
10
|
-
const issues: ChannelStatusIssue[] = [];
|
|
11
|
-
for (const entry of accounts) {
|
|
12
|
-
const account = readStatusIssueFields(entry, ZALO_STATUS_FIELDS);
|
|
13
|
-
if (!account) {
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default";
|
|
17
|
-
const enabled = account.enabled !== false;
|
|
18
|
-
const configured = account.configured === true;
|
|
19
|
-
if (!enabled || !configured) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (account.dmPolicy === "open") {
|
|
24
|
-
issues.push({
|
|
25
|
-
channel: "zalo",
|
|
26
|
-
accountId,
|
|
27
|
-
kind: "config",
|
|
28
|
-
message: 'Zalo dmPolicy is "open", allowing any user to message the bot without pairing.',
|
|
29
|
-
fix: 'Set channels.zalo.dmPolicy to "pairing" or "allowlist" to restrict access.',
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return issues;
|
|
34
|
-
}
|