@kodelyth/zalo 2026.5.39 → 2026.5.42
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 +50 -0
- package/api.ts +8 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +5 -0
- package/dist/actions.runtime-C61oPfyd.js +5 -0
- package/dist/api.js +5 -0
- package/dist/channel-D8ylaEdN.js +367 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-sf-rx5n-.js +105 -0
- package/dist/contract-api.js +3 -0
- package/dist/group-access-DTQVR6Nd.js +15 -0
- package/dist/index.js +22 -0
- package/dist/monitor-CQ1bjGih.js +825 -0
- package/dist/monitor.webhook-CDxUxa9l.js +175 -0
- package/dist/runtime-api-CxXTp1Q2.js +23 -0
- package/dist/runtime-api.js +2 -0
- package/dist/secret-contract-CRFukr2n.js +87 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/send-CGAqdfSA.js +270 -0
- package/dist/setup-api.js +30 -0
- package/dist/setup-core-Dr75wK6l.js +287 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-surface-C8zxrnzG.js +216 -0
- package/dist/test-api.js +2 -0
- package/index.test.ts +15 -0
- package/index.ts +20 -0
- package/klaw.plugin.json +2 -509
- package/package.json +4 -4
- package/runtime-api.test.ts +10 -0
- package/runtime-api.ts +71 -0
- package/secret-contract-api.ts +5 -0
- package/setup-api.ts +34 -0
- package/setup-entry.ts +13 -0
- package/src/accounts.test.ts +95 -0
- package/src/accounts.ts +65 -0
- package/src/actions.runtime.ts +5 -0
- package/src/actions.test.ts +32 -0
- package/src/actions.ts +62 -0
- package/src/api.test.ts +166 -0
- package/src/api.ts +265 -0
- package/src/approval-auth.test.ts +17 -0
- package/src/approval-auth.ts +25 -0
- package/src/channel.directory.test.ts +56 -0
- package/src/channel.runtime.ts +89 -0
- package/src/channel.startup.test.ts +121 -0
- package/src/channel.ts +309 -0
- package/src/config-schema.test.ts +30 -0
- package/src/config-schema.ts +29 -0
- package/src/group-access.ts +23 -0
- package/src/monitor-durable.test.ts +49 -0
- package/src/monitor-durable.ts +38 -0
- package/src/monitor.group-policy.test.ts +213 -0
- package/src/monitor.image.polling.test.ts +113 -0
- package/src/monitor.lifecycle.test.ts +194 -0
- package/src/monitor.pairing.lifecycle.test.ts +139 -0
- package/src/monitor.polling.media-reply.test.ts +433 -0
- package/src/monitor.reply-once.lifecycle.test.ts +178 -0
- package/src/monitor.ts +1009 -0
- package/src/monitor.types.ts +4 -0
- package/src/monitor.webhook.test.ts +808 -0
- package/src/monitor.webhook.ts +278 -0
- package/src/outbound-media.test.ts +186 -0
- package/src/outbound-media.ts +236 -0
- package/src/outbound-payload.contract.test.ts +143 -0
- package/src/probe.ts +45 -0
- package/src/proxy.ts +18 -0
- package/src/runtime-api.ts +71 -0
- package/src/runtime-support.ts +82 -0
- package/src/runtime.ts +9 -0
- package/src/secret-contract.ts +109 -0
- package/src/secret-input.ts +5 -0
- package/src/send.test.ts +150 -0
- package/src/send.ts +207 -0
- package/src/session-route.ts +32 -0
- package/src/setup-allow-from.ts +97 -0
- package/src/setup-core.ts +152 -0
- package/src/setup-status.test.ts +33 -0
- package/src/setup-surface.test.ts +193 -0
- package/src/setup-surface.ts +294 -0
- package/src/status-issues.test.ts +17 -0
- package/src/status-issues.ts +34 -0
- package/src/test-support/lifecycle-test-support.ts +456 -0
- package/src/test-support/monitor-mocks-test-support.ts +209 -0
- package/src/token.test.ts +92 -0
- package/src/token.ts +79 -0
- package/src/types.ts +50 -0
- package/test-api.ts +1 -0
- package/tsconfig.json +16 -0
- package/api.js +0 -7
- package/channel-plugin-api.js +0 -7
- package/contract-api.js +0 -7
- package/index.js +0 -7
- package/runtime-api.js +0 -7
- package/secret-contract-api.js +0 -7
- package/setup-api.js +0 -7
- package/setup-entry.js +0 -7
- package/test-api.js +0 -7
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { createAccountListHelpers, resolveMergedAccountConfig } from "klaw/plugin-sdk/account-helpers";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "klaw/plugin-sdk/account-id";
|
|
3
|
+
import { normalizeOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
|
|
4
|
+
import { tryReadSecretFileSync } from "klaw/plugin-sdk/core";
|
|
5
|
+
import { resolveAccountEntry } from "klaw/plugin-sdk/routing";
|
|
6
|
+
import { buildSecretInputSchema, normalizeResolvedSecretInputString, normalizeSecretInputString } from "klaw/plugin-sdk/secret-input";
|
|
7
|
+
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, addWildcardAllowFrom, createDelegatedSetupWizardProxy, createPatchedAccountSetupAdapter, createSetupInputPresenceValidator, createSetupTranslator, formatDocsLink, mergeAllowFromEntries, normalizeAccountId as normalizeAccountId$1 } from "klaw/plugin-sdk/setup";
|
|
8
|
+
//#region \0rolldown/runtime.js
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __exportAll = (all, no_symbols) => {
|
|
11
|
+
let target = {};
|
|
12
|
+
for (var name in all) __defProp(target, name, {
|
|
13
|
+
get: all[name],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region extensions/zalo/src/token.ts
|
|
21
|
+
function readTokenFromFile(tokenFile) {
|
|
22
|
+
return tryReadSecretFileSync(tokenFile, "Zalo token file", { rejectSymlink: true }) ?? "";
|
|
23
|
+
}
|
|
24
|
+
function resolveZaloToken(config, accountId, options) {
|
|
25
|
+
const resolvedAccountId = normalizeAccountId(accountId ?? config?.defaultAccount);
|
|
26
|
+
const isDefaultAccount = resolvedAccountId === DEFAULT_ACCOUNT_ID;
|
|
27
|
+
const baseConfig = config;
|
|
28
|
+
const accountConfig = resolveAccountEntry(baseConfig?.accounts, normalizeAccountId(resolvedAccountId));
|
|
29
|
+
const accountHasBotToken = Boolean(accountConfig && Object.prototype.hasOwnProperty.call(accountConfig, "botToken"));
|
|
30
|
+
if (accountConfig && accountHasBotToken) {
|
|
31
|
+
const token = options?.allowUnresolvedSecretRef ? normalizeSecretInputString(accountConfig.botToken) : normalizeResolvedSecretInputString({
|
|
32
|
+
value: accountConfig.botToken,
|
|
33
|
+
path: `channels.zalo.accounts.${resolvedAccountId}.botToken`
|
|
34
|
+
});
|
|
35
|
+
if (token) return {
|
|
36
|
+
token,
|
|
37
|
+
source: "config"
|
|
38
|
+
};
|
|
39
|
+
const fileToken = readTokenFromFile(accountConfig.tokenFile);
|
|
40
|
+
if (fileToken) return {
|
|
41
|
+
token: fileToken,
|
|
42
|
+
source: "configFile"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!accountHasBotToken) {
|
|
46
|
+
const fileToken = readTokenFromFile(accountConfig?.tokenFile);
|
|
47
|
+
if (fileToken) return {
|
|
48
|
+
token: fileToken,
|
|
49
|
+
source: "configFile"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!accountHasBotToken) {
|
|
53
|
+
const token = options?.allowUnresolvedSecretRef ? normalizeSecretInputString(baseConfig?.botToken) : normalizeResolvedSecretInputString({
|
|
54
|
+
value: baseConfig?.botToken,
|
|
55
|
+
path: "channels.zalo.botToken"
|
|
56
|
+
});
|
|
57
|
+
if (token) return {
|
|
58
|
+
token,
|
|
59
|
+
source: "config"
|
|
60
|
+
};
|
|
61
|
+
const fileToken = readTokenFromFile(baseConfig?.tokenFile);
|
|
62
|
+
if (fileToken) return {
|
|
63
|
+
token: fileToken,
|
|
64
|
+
source: "configFile"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (isDefaultAccount) {
|
|
68
|
+
const envToken = process.env.ZALO_BOT_TOKEN?.trim();
|
|
69
|
+
if (envToken) return {
|
|
70
|
+
token: envToken,
|
|
71
|
+
source: "env"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
token: "",
|
|
76
|
+
source: "none"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region extensions/zalo/src/accounts.ts
|
|
81
|
+
var accounts_exports = /* @__PURE__ */ __exportAll({
|
|
82
|
+
listEnabledZaloAccounts: () => listEnabledZaloAccounts,
|
|
83
|
+
listZaloAccountIds: () => listZaloAccountIds,
|
|
84
|
+
resolveDefaultZaloAccountId: () => resolveDefaultZaloAccountId,
|
|
85
|
+
resolveZaloAccount: () => resolveZaloAccount
|
|
86
|
+
});
|
|
87
|
+
const { listAccountIds: listZaloAccountIds, resolveDefaultAccountId: resolveDefaultZaloAccountId } = createAccountListHelpers("zalo", { implicitDefaultAccount: {
|
|
88
|
+
channelKeys: ["botToken", "tokenFile"],
|
|
89
|
+
envVars: ["ZALO_BOT_TOKEN"]
|
|
90
|
+
} });
|
|
91
|
+
function mergeZaloAccountConfig(cfg, accountId) {
|
|
92
|
+
return resolveMergedAccountConfig({
|
|
93
|
+
channelConfig: cfg.channels?.zalo,
|
|
94
|
+
accounts: (cfg.channels?.zalo)?.accounts,
|
|
95
|
+
accountId,
|
|
96
|
+
omitKeys: ["defaultAccount"]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function resolveZaloAccount(params) {
|
|
100
|
+
const accountId = normalizeAccountId(params.accountId ?? (params.cfg.channels?.zalo)?.defaultAccount);
|
|
101
|
+
const baseEnabled = (params.cfg.channels?.zalo)?.enabled !== false;
|
|
102
|
+
const merged = mergeZaloAccountConfig(params.cfg, accountId);
|
|
103
|
+
const accountEnabled = merged.enabled !== false;
|
|
104
|
+
const enabled = baseEnabled && accountEnabled;
|
|
105
|
+
const tokenResolution = resolveZaloToken(params.cfg.channels?.zalo, accountId, { allowUnresolvedSecretRef: params.allowUnresolvedSecretRef });
|
|
106
|
+
return {
|
|
107
|
+
accountId,
|
|
108
|
+
name: normalizeOptionalString(merged.name),
|
|
109
|
+
enabled,
|
|
110
|
+
token: tokenResolution.token,
|
|
111
|
+
tokenSource: tokenResolution.source,
|
|
112
|
+
config: merged
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function listEnabledZaloAccounts(cfg) {
|
|
116
|
+
return listZaloAccountIds(cfg).map((accountId) => resolveZaloAccount({
|
|
117
|
+
cfg,
|
|
118
|
+
accountId
|
|
119
|
+
})).filter((account) => account.enabled);
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region extensions/zalo/src/setup-allow-from.ts
|
|
123
|
+
const t$1 = createSetupTranslator();
|
|
124
|
+
async function noteZaloTokenHelp(prompter) {
|
|
125
|
+
await prompter.note([
|
|
126
|
+
t$1("wizard.zalo.helpOpenPlatform"),
|
|
127
|
+
t$1("wizard.zalo.helpCreateBot"),
|
|
128
|
+
t$1("wizard.zalo.helpTokenFormat"),
|
|
129
|
+
t$1("wizard.zalo.helpEnvTip"),
|
|
130
|
+
`Docs: ${formatDocsLink("/channels/zalo", "zalo")}`
|
|
131
|
+
].join("\n"), t$1("wizard.zalo.botTokenTitle"));
|
|
132
|
+
}
|
|
133
|
+
async function promptZaloAllowFrom(params) {
|
|
134
|
+
const { cfg, prompter } = params;
|
|
135
|
+
const accountId = params.accountId ?? resolveDefaultZaloAccountId(cfg);
|
|
136
|
+
const existingAllowFrom = resolveZaloAccount({
|
|
137
|
+
cfg,
|
|
138
|
+
accountId
|
|
139
|
+
}).config.allowFrom ?? [];
|
|
140
|
+
const unique = mergeAllowFromEntries(existingAllowFrom, [(await prompter.text({
|
|
141
|
+
message: t$1("wizard.zalo.allowFromPrompt"),
|
|
142
|
+
placeholder: "123456789",
|
|
143
|
+
initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : void 0,
|
|
144
|
+
validate: (value) => {
|
|
145
|
+
const raw = (value ?? "").trim();
|
|
146
|
+
if (!raw) return t$1("common.required");
|
|
147
|
+
if (!/^\d+$/.test(raw)) return t$1("wizard.zalo.allowFromNumeric");
|
|
148
|
+
}
|
|
149
|
+
})).trim()]);
|
|
150
|
+
if (accountId === DEFAULT_ACCOUNT_ID$1) return {
|
|
151
|
+
...cfg,
|
|
152
|
+
channels: {
|
|
153
|
+
...cfg.channels,
|
|
154
|
+
zalo: {
|
|
155
|
+
...cfg.channels?.zalo,
|
|
156
|
+
enabled: true,
|
|
157
|
+
dmPolicy: "allowlist",
|
|
158
|
+
allowFrom: unique
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const currentAccount = cfg.channels?.zalo?.accounts?.[accountId];
|
|
163
|
+
return {
|
|
164
|
+
...cfg,
|
|
165
|
+
channels: {
|
|
166
|
+
...cfg.channels,
|
|
167
|
+
zalo: {
|
|
168
|
+
...cfg.channels?.zalo,
|
|
169
|
+
enabled: true,
|
|
170
|
+
accounts: {
|
|
171
|
+
...cfg.channels?.zalo?.accounts,
|
|
172
|
+
[accountId]: {
|
|
173
|
+
...currentAccount,
|
|
174
|
+
enabled: currentAccount?.enabled ?? true,
|
|
175
|
+
dmPolicy: "allowlist",
|
|
176
|
+
allowFrom: unique
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region extensions/zalo/src/setup-core.ts
|
|
185
|
+
const t = createSetupTranslator();
|
|
186
|
+
const channel = "zalo";
|
|
187
|
+
const zaloSetupAdapter = createPatchedAccountSetupAdapter({
|
|
188
|
+
channelKey: channel,
|
|
189
|
+
validateInput: createSetupInputPresenceValidator({
|
|
190
|
+
defaultAccountOnlyEnvError: "ZALO_BOT_TOKEN can only be used for the default account.",
|
|
191
|
+
whenNotUseEnv: [{
|
|
192
|
+
someOf: ["token", "tokenFile"],
|
|
193
|
+
message: "Zalo requires token or --token-file (or --use-env)."
|
|
194
|
+
}]
|
|
195
|
+
}),
|
|
196
|
+
buildPatch: (input) => input.useEnv ? {} : input.tokenFile ? { tokenFile: input.tokenFile } : input.token ? { botToken: input.token } : {}
|
|
197
|
+
});
|
|
198
|
+
const zaloDmPolicy = {
|
|
199
|
+
label: "Zalo",
|
|
200
|
+
channel,
|
|
201
|
+
policyKey: "channels.zalo.dmPolicy",
|
|
202
|
+
allowFromKey: "channels.zalo.allowFrom",
|
|
203
|
+
resolveConfigKeys: (cfg, accountId) => (accountId ?? resolveDefaultZaloAccountId(cfg)) !== DEFAULT_ACCOUNT_ID$1 ? {
|
|
204
|
+
policyKey: `channels.zalo.accounts.${accountId ?? resolveDefaultZaloAccountId(cfg)}.dmPolicy`,
|
|
205
|
+
allowFromKey: `channels.zalo.accounts.${accountId ?? resolveDefaultZaloAccountId(cfg)}.allowFrom`
|
|
206
|
+
} : {
|
|
207
|
+
policyKey: "channels.zalo.dmPolicy",
|
|
208
|
+
allowFromKey: "channels.zalo.allowFrom"
|
|
209
|
+
},
|
|
210
|
+
getCurrent: (cfg, accountId) => resolveZaloAccount({
|
|
211
|
+
cfg,
|
|
212
|
+
accountId: accountId ?? resolveDefaultZaloAccountId(cfg)
|
|
213
|
+
}).config.dmPolicy ?? "pairing",
|
|
214
|
+
setPolicy: (cfg, policy, accountId) => {
|
|
215
|
+
const resolvedAccountId = accountId && normalizeAccountId$1(accountId) ? normalizeAccountId$1(accountId) ?? DEFAULT_ACCOUNT_ID$1 : resolveDefaultZaloAccountId(cfg);
|
|
216
|
+
const resolved = resolveZaloAccount({
|
|
217
|
+
cfg,
|
|
218
|
+
accountId: resolvedAccountId
|
|
219
|
+
});
|
|
220
|
+
if (resolvedAccountId === DEFAULT_ACCOUNT_ID$1) return {
|
|
221
|
+
...cfg,
|
|
222
|
+
channels: {
|
|
223
|
+
...cfg.channels,
|
|
224
|
+
zalo: {
|
|
225
|
+
...cfg.channels?.zalo,
|
|
226
|
+
enabled: true,
|
|
227
|
+
dmPolicy: policy,
|
|
228
|
+
...policy === "open" ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } : {}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const currentAccount = cfg.channels?.zalo?.accounts?.[resolvedAccountId];
|
|
233
|
+
return {
|
|
234
|
+
...cfg,
|
|
235
|
+
channels: {
|
|
236
|
+
...cfg.channels,
|
|
237
|
+
zalo: {
|
|
238
|
+
...cfg.channels?.zalo,
|
|
239
|
+
enabled: true,
|
|
240
|
+
accounts: {
|
|
241
|
+
...cfg.channels?.zalo?.accounts,
|
|
242
|
+
[resolvedAccountId]: {
|
|
243
|
+
...currentAccount,
|
|
244
|
+
enabled: currentAccount?.enabled ?? true,
|
|
245
|
+
dmPolicy: policy,
|
|
246
|
+
...policy === "open" ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } : {}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
promptAllowFrom: async ({ cfg, prompter, accountId }) => promptZaloAllowFrom({
|
|
254
|
+
cfg,
|
|
255
|
+
prompter,
|
|
256
|
+
accountId: accountId ?? resolveDefaultZaloAccountId(cfg)
|
|
257
|
+
})
|
|
258
|
+
};
|
|
259
|
+
function createZaloSetupWizardProxy(loadWizard) {
|
|
260
|
+
return createDelegatedSetupWizardProxy({
|
|
261
|
+
channel,
|
|
262
|
+
loadWizard,
|
|
263
|
+
status: {
|
|
264
|
+
configuredLabel: t("wizard.channels.statusConfigured"),
|
|
265
|
+
unconfiguredLabel: t("wizard.channels.statusNeedsToken"),
|
|
266
|
+
configuredHint: t("wizard.channels.statusRecommendedConfigured"),
|
|
267
|
+
unconfiguredHint: t("wizard.channels.statusRecommendedNewcomerFriendly"),
|
|
268
|
+
configuredScore: 1,
|
|
269
|
+
unconfiguredScore: 10
|
|
270
|
+
},
|
|
271
|
+
credentials: [],
|
|
272
|
+
delegateFinalize: true,
|
|
273
|
+
dmPolicy: zaloDmPolicy,
|
|
274
|
+
disable: (cfg) => ({
|
|
275
|
+
...cfg,
|
|
276
|
+
channels: {
|
|
277
|
+
...cfg.channels,
|
|
278
|
+
zalo: {
|
|
279
|
+
...cfg.channels?.zalo,
|
|
280
|
+
enabled: false
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
export { promptZaloAllowFrom as a, listZaloAccountIds as c, resolveZaloToken as d, buildSecretInputSchema as f, noteZaloTokenHelp as i, resolveDefaultZaloAccountId as l, zaloDmPolicy as n, accounts_exports as o, normalizeSecretInputString as p, zaloSetupAdapter as r, listEnabledZaloAccounts as s, createZaloSetupWizardProxy as t, resolveZaloAccount as u };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineBundledChannelSetupEntry } from "klaw/plugin-sdk/channel-entry-contract";
|
|
2
|
+
//#region extensions/zalo/setup-entry.ts
|
|
3
|
+
var setup_entry_default = defineBundledChannelSetupEntry({
|
|
4
|
+
importMetaUrl: import.meta.url,
|
|
5
|
+
plugin: {
|
|
6
|
+
specifier: "./api.js",
|
|
7
|
+
exportName: "zaloPlugin"
|
|
8
|
+
},
|
|
9
|
+
secrets: {
|
|
10
|
+
specifier: "./secret-contract-api.js",
|
|
11
|
+
exportName: "channelSecrets"
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { setup_entry_default as default };
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { a as promptZaloAllowFrom, i as noteZaloTokenHelp, n as zaloDmPolicy, u as resolveZaloAccount } from "./setup-core-Dr75wK6l.js";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, buildSingleChannelSecretPromptState, createSetupTranslator, createStandardChannelSetupStatus, hasConfiguredSecretInput, promptSingleChannelSecretInput, runSingleChannelSecretStep } from "klaw/plugin-sdk/setup";
|
|
3
|
+
//#region extensions/zalo/src/setup-surface.ts
|
|
4
|
+
const t = createSetupTranslator();
|
|
5
|
+
const channel = "zalo";
|
|
6
|
+
function setZaloUpdateMode(cfg, accountId, mode, webhookUrl, webhookSecret, webhookPath) {
|
|
7
|
+
const isDefault = accountId === DEFAULT_ACCOUNT_ID;
|
|
8
|
+
if (mode === "polling") {
|
|
9
|
+
if (isDefault) {
|
|
10
|
+
const { webhookUrl: _url, webhookSecret: _secret, webhookPath: _path, ...rest } = cfg.channels?.zalo ?? {};
|
|
11
|
+
return {
|
|
12
|
+
...cfg,
|
|
13
|
+
channels: {
|
|
14
|
+
...cfg.channels,
|
|
15
|
+
zalo: rest
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const accounts = { ...cfg.channels?.zalo?.accounts };
|
|
20
|
+
const { webhookUrl: _url, webhookSecret: _secret, webhookPath: _path, ...rest } = accounts[accountId] ?? {};
|
|
21
|
+
accounts[accountId] = rest;
|
|
22
|
+
return {
|
|
23
|
+
...cfg,
|
|
24
|
+
channels: {
|
|
25
|
+
...cfg.channels,
|
|
26
|
+
zalo: {
|
|
27
|
+
...cfg.channels?.zalo,
|
|
28
|
+
accounts
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (isDefault) return {
|
|
34
|
+
...cfg,
|
|
35
|
+
channels: {
|
|
36
|
+
...cfg.channels,
|
|
37
|
+
zalo: {
|
|
38
|
+
...cfg.channels?.zalo,
|
|
39
|
+
webhookUrl,
|
|
40
|
+
webhookSecret,
|
|
41
|
+
webhookPath
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const accounts = { ...cfg.channels?.zalo?.accounts };
|
|
46
|
+
accounts[accountId] = {
|
|
47
|
+
...accounts[accountId],
|
|
48
|
+
webhookUrl,
|
|
49
|
+
webhookSecret,
|
|
50
|
+
webhookPath
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
...cfg,
|
|
54
|
+
channels: {
|
|
55
|
+
...cfg.channels,
|
|
56
|
+
zalo: {
|
|
57
|
+
...cfg.channels?.zalo,
|
|
58
|
+
accounts
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const zaloSetupWizard = {
|
|
64
|
+
channel,
|
|
65
|
+
status: createStandardChannelSetupStatus({
|
|
66
|
+
channelLabel: "Zalo",
|
|
67
|
+
configuredLabel: t("wizard.channels.statusConfigured"),
|
|
68
|
+
unconfiguredLabel: t("wizard.channels.statusNeedsToken"),
|
|
69
|
+
configuredHint: t("wizard.channels.statusRecommendedConfigured"),
|
|
70
|
+
unconfiguredHint: t("wizard.channels.statusRecommendedNewcomerFriendly"),
|
|
71
|
+
configuredScore: 1,
|
|
72
|
+
unconfiguredScore: 10,
|
|
73
|
+
includeStatusLine: true,
|
|
74
|
+
resolveConfigured: ({ cfg, accountId }) => {
|
|
75
|
+
const account = resolveZaloAccount({
|
|
76
|
+
cfg,
|
|
77
|
+
accountId,
|
|
78
|
+
allowUnresolvedSecretRef: true
|
|
79
|
+
});
|
|
80
|
+
return Boolean(account.token) || hasConfiguredSecretInput(account.config.botToken) || Boolean(account.config.tokenFile?.trim());
|
|
81
|
+
}
|
|
82
|
+
}),
|
|
83
|
+
credentials: [],
|
|
84
|
+
finalize: async ({ cfg, accountId, forceAllowFrom, options, prompter }) => {
|
|
85
|
+
let next = cfg;
|
|
86
|
+
const resolvedAccount = resolveZaloAccount({
|
|
87
|
+
cfg: next,
|
|
88
|
+
accountId,
|
|
89
|
+
allowUnresolvedSecretRef: true
|
|
90
|
+
});
|
|
91
|
+
const accountConfigured = Boolean(resolvedAccount.token);
|
|
92
|
+
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
|
|
93
|
+
const hasConfigToken = Boolean(hasConfiguredSecretInput(resolvedAccount.config.botToken) || resolvedAccount.config.tokenFile);
|
|
94
|
+
next = (await runSingleChannelSecretStep({
|
|
95
|
+
cfg: next,
|
|
96
|
+
prompter,
|
|
97
|
+
providerHint: "zalo",
|
|
98
|
+
credentialLabel: t("wizard.zalo.botToken"),
|
|
99
|
+
secretInputMode: options?.secretInputMode,
|
|
100
|
+
accountConfigured,
|
|
101
|
+
hasConfigToken,
|
|
102
|
+
allowEnv,
|
|
103
|
+
envValue: process.env.ZALO_BOT_TOKEN,
|
|
104
|
+
envPrompt: t("wizard.zalo.tokenEnvPrompt"),
|
|
105
|
+
keepPrompt: t("wizard.zalo.tokenKeep"),
|
|
106
|
+
inputPrompt: t("wizard.zalo.tokenInput"),
|
|
107
|
+
preferredEnvVar: "ZALO_BOT_TOKEN",
|
|
108
|
+
onMissingConfigured: async () => await noteZaloTokenHelp(prompter),
|
|
109
|
+
applyUseEnv: async (currentCfg) => accountId === DEFAULT_ACCOUNT_ID ? {
|
|
110
|
+
...currentCfg,
|
|
111
|
+
channels: {
|
|
112
|
+
...currentCfg.channels,
|
|
113
|
+
zalo: {
|
|
114
|
+
...currentCfg.channels?.zalo,
|
|
115
|
+
enabled: true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} : currentCfg,
|
|
119
|
+
applySet: async (currentCfg, value) => accountId === DEFAULT_ACCOUNT_ID ? {
|
|
120
|
+
...currentCfg,
|
|
121
|
+
channels: {
|
|
122
|
+
...currentCfg.channels,
|
|
123
|
+
zalo: {
|
|
124
|
+
...currentCfg.channels?.zalo,
|
|
125
|
+
enabled: true,
|
|
126
|
+
botToken: value
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} : {
|
|
130
|
+
...currentCfg,
|
|
131
|
+
channels: {
|
|
132
|
+
...currentCfg.channels,
|
|
133
|
+
zalo: {
|
|
134
|
+
...currentCfg.channels?.zalo,
|
|
135
|
+
enabled: true,
|
|
136
|
+
accounts: {
|
|
137
|
+
...currentCfg.channels?.zalo?.accounts,
|
|
138
|
+
[accountId]: {
|
|
139
|
+
...currentCfg.channels?.zalo?.accounts?.[accountId],
|
|
140
|
+
enabled: true,
|
|
141
|
+
botToken: value
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})).cfg;
|
|
148
|
+
if (await prompter.confirm({
|
|
149
|
+
message: t("wizard.zalo.webhookModePrompt"),
|
|
150
|
+
initialValue: Boolean(resolvedAccount.config.webhookUrl)
|
|
151
|
+
})) {
|
|
152
|
+
const webhookUrl = (await prompter.text({
|
|
153
|
+
message: t("wizard.zalo.webhookUrlPrompt"),
|
|
154
|
+
initialValue: resolvedAccount.config.webhookUrl,
|
|
155
|
+
validate: (value) => value?.trim()?.startsWith("https://") ? void 0 : "HTTPS URL required"
|
|
156
|
+
})).trim();
|
|
157
|
+
const defaultPath = (() => {
|
|
158
|
+
try {
|
|
159
|
+
return new URL(webhookUrl).pathname || "/zalo-webhook";
|
|
160
|
+
} catch {
|
|
161
|
+
return "/zalo-webhook";
|
|
162
|
+
}
|
|
163
|
+
})();
|
|
164
|
+
let webhookSecretResult = await promptSingleChannelSecretInput({
|
|
165
|
+
cfg: next,
|
|
166
|
+
prompter,
|
|
167
|
+
providerHint: "zalo-webhook",
|
|
168
|
+
credentialLabel: t("wizard.zalo.webhookSecret"),
|
|
169
|
+
secretInputMode: options?.secretInputMode,
|
|
170
|
+
...buildSingleChannelSecretPromptState({
|
|
171
|
+
accountConfigured: hasConfiguredSecretInput(resolvedAccount.config.webhookSecret),
|
|
172
|
+
hasConfigToken: hasConfiguredSecretInput(resolvedAccount.config.webhookSecret),
|
|
173
|
+
allowEnv: false
|
|
174
|
+
}),
|
|
175
|
+
envPrompt: "",
|
|
176
|
+
keepPrompt: t("wizard.zalo.webhookSecretKeep"),
|
|
177
|
+
inputPrompt: t("wizard.zalo.webhookSecretInput"),
|
|
178
|
+
preferredEnvVar: "ZALO_WEBHOOK_SECRET"
|
|
179
|
+
});
|
|
180
|
+
while (webhookSecretResult.action === "set" && typeof webhookSecretResult.value === "string" && (webhookSecretResult.value.length < 8 || webhookSecretResult.value.length > 256)) {
|
|
181
|
+
await prompter.note(t("wizard.zalo.webhookSecretLength"), t("wizard.zalo.webhookTitle"));
|
|
182
|
+
webhookSecretResult = await promptSingleChannelSecretInput({
|
|
183
|
+
cfg: next,
|
|
184
|
+
prompter,
|
|
185
|
+
providerHint: "zalo-webhook",
|
|
186
|
+
credentialLabel: t("wizard.zalo.webhookSecret"),
|
|
187
|
+
secretInputMode: options?.secretInputMode,
|
|
188
|
+
...buildSingleChannelSecretPromptState({
|
|
189
|
+
accountConfigured: false,
|
|
190
|
+
hasConfigToken: false,
|
|
191
|
+
allowEnv: false
|
|
192
|
+
}),
|
|
193
|
+
envPrompt: "",
|
|
194
|
+
keepPrompt: t("wizard.zalo.webhookSecretKeep"),
|
|
195
|
+
inputPrompt: t("wizard.zalo.webhookSecretInput"),
|
|
196
|
+
preferredEnvVar: "ZALO_WEBHOOK_SECRET"
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const webhookSecret = webhookSecretResult.action === "set" ? webhookSecretResult.value : resolvedAccount.config.webhookSecret;
|
|
200
|
+
const webhookPath = (await prompter.text({
|
|
201
|
+
message: t("wizard.zalo.webhookPathPrompt"),
|
|
202
|
+
initialValue: resolvedAccount.config.webhookPath ?? defaultPath
|
|
203
|
+
})).trim();
|
|
204
|
+
next = setZaloUpdateMode(next, accountId, "webhook", webhookUrl, webhookSecret, webhookPath || void 0);
|
|
205
|
+
} else next = setZaloUpdateMode(next, accountId, "polling");
|
|
206
|
+
if (forceAllowFrom) next = await promptZaloAllowFrom({
|
|
207
|
+
cfg: next,
|
|
208
|
+
prompter,
|
|
209
|
+
accountId
|
|
210
|
+
});
|
|
211
|
+
return { cfg: next };
|
|
212
|
+
},
|
|
213
|
+
dmPolicy: zaloDmPolicy
|
|
214
|
+
};
|
|
215
|
+
//#endregion
|
|
216
|
+
export { zaloSetupWizard };
|
package/dist/test-api.js
ADDED
package/index.test.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { assertBundledChannelEntries } from "klaw/plugin-sdk/channel-test-helpers";
|
|
2
|
+
import { describe } from "vitest";
|
|
3
|
+
import entry from "./index.js";
|
|
4
|
+
import setupEntry from "./setup-entry.js";
|
|
5
|
+
|
|
6
|
+
describe("zalo bundled entries", () => {
|
|
7
|
+
assertBundledChannelEntries({
|
|
8
|
+
entry,
|
|
9
|
+
expectedId: "zalo",
|
|
10
|
+
expectedName: "Zalo",
|
|
11
|
+
setupEntry,
|
|
12
|
+
channelMessage: "declares the channel plugin without a runtime-barrel cycle",
|
|
13
|
+
setupMessage: "declares the setup plugin without a runtime-barrel cycle",
|
|
14
|
+
});
|
|
15
|
+
});
|
package/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineBundledChannelEntry } from "klaw/plugin-sdk/channel-entry-contract";
|
|
2
|
+
|
|
3
|
+
export default defineBundledChannelEntry({
|
|
4
|
+
id: "zalo",
|
|
5
|
+
name: "Zalo",
|
|
6
|
+
description: "Zalo channel plugin",
|
|
7
|
+
importMetaUrl: import.meta.url,
|
|
8
|
+
plugin: {
|
|
9
|
+
specifier: "./channel-plugin-api.js",
|
|
10
|
+
exportName: "zaloPlugin",
|
|
11
|
+
},
|
|
12
|
+
secrets: {
|
|
13
|
+
specifier: "./secret-contract-api.js",
|
|
14
|
+
exportName: "channelSecrets",
|
|
15
|
+
},
|
|
16
|
+
runtime: {
|
|
17
|
+
specifier: "./runtime-api.js",
|
|
18
|
+
exportName: "setZaloRuntime",
|
|
19
|
+
},
|
|
20
|
+
});
|