@openclaw/zalo 2026.3.13 → 2026.5.1-beta.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/README.md +1 -1
- package/api.ts +9 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +5 -0
- package/index.test.ts +15 -0
- package/index.ts +16 -13
- package/openclaw.plugin.json +514 -1
- package/package.json +31 -5
- package/runtime-api.test.ts +17 -0
- package/runtime-api.ts +75 -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 +70 -0
- package/src/accounts.ts +19 -19
- package/src/actions.runtime.ts +5 -0
- package/src/actions.test.ts +32 -0
- package/src/actions.ts +20 -14
- package/src/api.test.ts +93 -2
- package/src/api.ts +29 -2
- package/src/approval-auth.test.ts +17 -0
- package/src/approval-auth.ts +25 -0
- package/src/channel.directory.test.ts +19 -6
- package/src/channel.runtime.ts +93 -0
- package/src/channel.startup.test.ts +26 -19
- package/src/channel.ts +228 -336
- package/src/config-schema.ts +3 -3
- package/src/group-access.ts +4 -3
- package/src/monitor.group-policy.test.ts +0 -12
- package/src/monitor.image.polling.test.ts +110 -0
- package/src/monitor.lifecycle.test.ts +41 -22
- package/src/monitor.pairing.lifecycle.test.ts +141 -0
- package/src/monitor.polling.media-reply.test.ts +425 -0
- package/src/monitor.reply-once.lifecycle.test.ts +171 -0
- package/src/monitor.ts +460 -206
- package/src/monitor.types.ts +4 -0
- package/src/monitor.webhook.test.ts +392 -62
- package/src/monitor.webhook.ts +73 -36
- package/src/outbound-media.test.ts +182 -0
- package/src/outbound-media.ts +241 -0
- package/src/outbound-payload.contract.test.ts +45 -0
- package/src/probe.ts +1 -1
- package/src/proxy.ts +1 -1
- package/src/runtime-api.ts +75 -0
- package/src/runtime-support.ts +91 -0
- package/src/runtime.ts +6 -3
- package/src/secret-contract.ts +109 -0
- package/src/secret-input.ts +1 -9
- package/src/send.test.ts +120 -0
- package/src/send.ts +15 -13
- package/src/session-route.ts +32 -0
- package/src/setup-allow-from.ts +94 -0
- package/src/setup-core.ts +149 -0
- package/src/{onboarding.status.test.ts → setup-status.test.ts} +13 -4
- package/src/setup-surface.test.ts +175 -0
- package/src/{onboarding.ts → setup-surface.ts} +59 -177
- package/src/status-issues.test.ts +2 -14
- package/src/status-issues.ts +8 -2
- package/src/test-support/lifecycle-test-support.ts +413 -0
- package/src/test-support/monitor-mocks-test-support.ts +209 -0
- package/src/token.test.ts +15 -0
- package/src/token.ts +8 -17
- package/src/types.ts +2 -2
- package/test-api.ts +1 -0
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -101
- package/src/channel.sendpayload.test.ts +0 -44
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
|
|
2
|
+
import { probeZalo } from "./probe.js";
|
|
3
|
+
import { resolveZaloProxyFetch } from "./proxy.js";
|
|
4
|
+
import {
|
|
5
|
+
PAIRING_APPROVED_MESSAGE,
|
|
6
|
+
type ChannelPlugin,
|
|
7
|
+
type OpenClawConfig,
|
|
8
|
+
} from "./runtime-api.js";
|
|
9
|
+
import { normalizeSecretInputString } from "./secret-input.js";
|
|
10
|
+
import { sendMessageZalo } from "./send.js";
|
|
11
|
+
import type { ResolvedZaloAccount } from "./types.js";
|
|
12
|
+
|
|
13
|
+
export async function notifyZaloPairingApproval(params: { cfg: OpenClawConfig; id: string }) {
|
|
14
|
+
const { resolveZaloAccount } = await import("./accounts.js");
|
|
15
|
+
const account = resolveZaloAccount({ cfg: params.cfg });
|
|
16
|
+
if (!account.token) {
|
|
17
|
+
throw new Error("Zalo token not configured");
|
|
18
|
+
}
|
|
19
|
+
await sendMessageZalo(params.id, PAIRING_APPROVED_MESSAGE, {
|
|
20
|
+
token: account.token,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function sendZaloText(
|
|
25
|
+
params: Parameters<typeof sendMessageZalo>[2] & {
|
|
26
|
+
to: string;
|
|
27
|
+
text: string;
|
|
28
|
+
},
|
|
29
|
+
) {
|
|
30
|
+
return await sendMessageZalo(params.to, params.text, params);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function probeZaloAccount(params: {
|
|
34
|
+
account: import("./accounts.js").ResolvedZaloAccount;
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
}) {
|
|
37
|
+
return await probeZalo(
|
|
38
|
+
params.account.token,
|
|
39
|
+
params.timeoutMs,
|
|
40
|
+
resolveZaloProxyFetch(params.account.config.proxy),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function startZaloGatewayAccount(
|
|
45
|
+
ctx: Parameters<
|
|
46
|
+
NonNullable<NonNullable<ChannelPlugin<ResolvedZaloAccount>["gateway"]>["startAccount"]>
|
|
47
|
+
>[0],
|
|
48
|
+
) {
|
|
49
|
+
const account = ctx.account;
|
|
50
|
+
const token = account.token.trim();
|
|
51
|
+
const mode = account.config.webhookUrl ? "webhook" : "polling";
|
|
52
|
+
let zaloBotLabel = "";
|
|
53
|
+
const fetcher = resolveZaloProxyFetch(account.config.proxy);
|
|
54
|
+
try {
|
|
55
|
+
const probe = await probeZalo(token, 2500, fetcher);
|
|
56
|
+
const name = probe.ok ? probe.bot?.name?.trim() : null;
|
|
57
|
+
if (name) {
|
|
58
|
+
zaloBotLabel = ` (${name})`;
|
|
59
|
+
}
|
|
60
|
+
if (!probe.ok) {
|
|
61
|
+
ctx.log?.warn?.(
|
|
62
|
+
`[${account.accountId}] Zalo probe failed before provider start (${String(probe.elapsedMs)}ms): ${probe.error}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
ctx.setStatus({
|
|
66
|
+
accountId: account.accountId,
|
|
67
|
+
bot: probe.bot,
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
ctx.log?.warn?.(
|
|
71
|
+
`[${account.accountId}] Zalo probe threw before provider start: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const statusSink = createAccountStatusSink({
|
|
75
|
+
accountId: ctx.accountId,
|
|
76
|
+
setStatus: ctx.setStatus,
|
|
77
|
+
});
|
|
78
|
+
ctx.log?.info(`[${account.accountId}] starting provider${zaloBotLabel} mode=${mode}`);
|
|
79
|
+
const { monitorZaloProvider } = await import("./monitor.js");
|
|
80
|
+
return monitorZaloProvider({
|
|
81
|
+
token,
|
|
82
|
+
account,
|
|
83
|
+
config: ctx.cfg,
|
|
84
|
+
runtime: ctx.runtime,
|
|
85
|
+
abortSignal: ctx.abortSignal,
|
|
86
|
+
useWebhook: Boolean(account.config.webhookUrl),
|
|
87
|
+
webhookUrl: account.config.webhookUrl,
|
|
88
|
+
webhookSecret: normalizeSecretInputString(account.config.webhookSecret),
|
|
89
|
+
webhookPath: account.config.webhookPath,
|
|
90
|
+
fetcher,
|
|
91
|
+
statusSink,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/zalo";
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
1
|
import {
|
|
2
|
+
expectLifecyclePatch,
|
|
4
3
|
expectPendingUntilAbort,
|
|
5
4
|
startAccountAndTrackLifecycle,
|
|
6
|
-
|
|
5
|
+
waitForStartedMocks,
|
|
6
|
+
} from "openclaw/plugin-sdk/channel-test-helpers";
|
|
7
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
8
|
import type { ResolvedZaloAccount } from "./accounts.js";
|
|
8
9
|
|
|
9
10
|
const hoisted = vi.hoisted(() => ({
|
|
@@ -15,22 +16,36 @@ const hoisted = vi.hoisted(() => ({
|
|
|
15
16
|
})),
|
|
16
17
|
}));
|
|
17
18
|
|
|
18
|
-
vi.mock("./monitor.js",
|
|
19
|
-
const actual = await vi.importActual<typeof import("./monitor.js")>("./monitor.js");
|
|
19
|
+
vi.mock("./monitor.js", () => {
|
|
20
20
|
return {
|
|
21
|
-
...actual,
|
|
22
21
|
monitorZaloProvider: hoisted.monitorZaloProvider,
|
|
23
22
|
};
|
|
24
23
|
});
|
|
25
24
|
|
|
26
|
-
vi.mock("./probe.js",
|
|
27
|
-
const actual = await vi.importActual<typeof import("./probe.js")>("./probe.js");
|
|
25
|
+
vi.mock("./probe.js", () => {
|
|
28
26
|
return {
|
|
29
|
-
...actual,
|
|
30
27
|
probeZalo: hoisted.probeZalo,
|
|
31
28
|
};
|
|
32
29
|
});
|
|
33
30
|
|
|
31
|
+
vi.mock("./channel.runtime.js", () => ({
|
|
32
|
+
probeZaloAccount: hoisted.probeZalo,
|
|
33
|
+
startZaloGatewayAccount: async (ctx: {
|
|
34
|
+
account: ResolvedZaloAccount;
|
|
35
|
+
abortSignal: AbortSignal;
|
|
36
|
+
setStatus: (patch: Partial<ResolvedZaloAccount>) => void;
|
|
37
|
+
}) => {
|
|
38
|
+
await hoisted.probeZalo();
|
|
39
|
+
ctx.setStatus({ accountId: ctx.account.accountId });
|
|
40
|
+
return await hoisted.monitorZaloProvider({
|
|
41
|
+
token: ctx.account.token,
|
|
42
|
+
account: ctx.account,
|
|
43
|
+
abortSignal: ctx.abortSignal,
|
|
44
|
+
useWebhook: false,
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
|
|
34
49
|
import { zaloPlugin } from "./channel.js";
|
|
35
50
|
|
|
36
51
|
function buildAccount(): ResolvedZaloAccount {
|
|
@@ -66,21 +81,13 @@ describe("zaloPlugin gateway.startAccount", () => {
|
|
|
66
81
|
});
|
|
67
82
|
|
|
68
83
|
await expectPendingUntilAbort({
|
|
69
|
-
waitForStarted: ()
|
|
70
|
-
vi.waitFor(() => {
|
|
71
|
-
expect(hoisted.probeZalo).toHaveBeenCalledOnce();
|
|
72
|
-
expect(hoisted.monitorZaloProvider).toHaveBeenCalledOnce();
|
|
73
|
-
}),
|
|
84
|
+
waitForStarted: waitForStartedMocks(hoisted.probeZalo, hoisted.monitorZaloProvider),
|
|
74
85
|
isSettled,
|
|
75
86
|
abort,
|
|
76
87
|
task,
|
|
77
88
|
});
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
expect.objectContaining({
|
|
81
|
-
accountId: "default",
|
|
82
|
-
}),
|
|
83
|
-
);
|
|
90
|
+
expectLifecyclePatch(patches, { accountId: "default" });
|
|
84
91
|
expect(isSettled()).toBe(true);
|
|
85
92
|
expect(hoisted.monitorZaloProvider).toHaveBeenCalledWith(
|
|
86
93
|
expect.objectContaining({
|