@kodelyth/zalouser 2026.5.42 → 2026.6.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.
Files changed (71) hide show
  1. package/klaw.plugin.json +286 -3
  2. package/package.json +19 -6
  3. package/api.ts +0 -9
  4. package/channel-plugin-api.ts +0 -3
  5. package/contract-api.ts +0 -2
  6. package/doctor-contract-api.ts +0 -1
  7. package/index.ts +0 -34
  8. package/runtime-api.ts +0 -62
  9. package/secret-contract-api.ts +0 -4
  10. package/setup-entry.ts +0 -9
  11. package/setup-plugin-api.ts +0 -2
  12. package/src/accounts.runtime.ts +0 -1
  13. package/src/accounts.test-mocks.ts +0 -14
  14. package/src/accounts.test.ts +0 -298
  15. package/src/accounts.ts +0 -136
  16. package/src/channel-api.ts +0 -16
  17. package/src/channel.adapters.ts +0 -432
  18. package/src/channel.directory.test.ts +0 -59
  19. package/src/channel.runtime.ts +0 -12
  20. package/src/channel.sendpayload.test.ts +0 -311
  21. package/src/channel.setup.test.ts +0 -30
  22. package/src/channel.setup.ts +0 -12
  23. package/src/channel.test.ts +0 -424
  24. package/src/channel.ts +0 -221
  25. package/src/config-schema.ts +0 -33
  26. package/src/directory.ts +0 -54
  27. package/src/doctor-contract.ts +0 -156
  28. package/src/doctor.test.ts +0 -87
  29. package/src/doctor.ts +0 -37
  30. package/src/group-policy.test.ts +0 -61
  31. package/src/group-policy.ts +0 -83
  32. package/src/message-sid.test.ts +0 -66
  33. package/src/message-sid.ts +0 -80
  34. package/src/monitor.account-scope.test.ts +0 -122
  35. package/src/monitor.group-gating.test.ts +0 -967
  36. package/src/monitor.send-mocks.ts +0 -20
  37. package/src/monitor.ts +0 -1057
  38. package/src/probe.test.ts +0 -60
  39. package/src/probe.ts +0 -35
  40. package/src/qr-temp-file.ts +0 -19
  41. package/src/reaction.test.ts +0 -19
  42. package/src/reaction.ts +0 -32
  43. package/src/runtime.ts +0 -9
  44. package/src/security-audit.test.ts +0 -83
  45. package/src/security-audit.ts +0 -71
  46. package/src/send-receipt.ts +0 -31
  47. package/src/send.test.ts +0 -424
  48. package/src/send.ts +0 -280
  49. package/src/session-route.ts +0 -121
  50. package/src/setup-core.ts +0 -36
  51. package/src/setup-surface.test.ts +0 -367
  52. package/src/setup-surface.ts +0 -481
  53. package/src/setup-test-helpers.ts +0 -42
  54. package/src/shared.ts +0 -92
  55. package/src/status-issues.test.ts +0 -31
  56. package/src/status-issues.ts +0 -55
  57. package/src/test-helpers.ts +0 -26
  58. package/src/text-styles.test.ts +0 -203
  59. package/src/text-styles.ts +0 -540
  60. package/src/tool.test.ts +0 -212
  61. package/src/tool.ts +0 -200
  62. package/src/types.ts +0 -127
  63. package/src/zalo-js.credentials.test.ts +0 -465
  64. package/src/zalo-js.test-mocks.ts +0 -89
  65. package/src/zalo-js.ts +0 -1889
  66. package/src/zca-client.test.ts +0 -27
  67. package/src/zca-client.ts +0 -259
  68. package/src/zca-constants.ts +0 -55
  69. package/src/zca-js-exports.d.ts +0 -22
  70. package/test-api.ts +0 -21
  71. package/tsconfig.json +0 -16
@@ -1,121 +0,0 @@
1
- import {
2
- buildChannelOutboundSessionRoute,
3
- type ChannelOutboundSessionRouteParams,
4
- } from "klaw/plugin-sdk/core";
5
- import {
6
- normalizeLowercaseStringOrEmpty,
7
- normalizeOptionalLowercaseString,
8
- } from "klaw/plugin-sdk/string-coerce-runtime";
9
-
10
- function stripZalouserTargetPrefix(raw: string): string {
11
- return raw
12
- .trim()
13
- .replace(/^(zalouser|zlu):/i, "")
14
- .trim();
15
- }
16
-
17
- export function normalizeZalouserTarget(raw: string): string | undefined {
18
- const trimmed = stripZalouserTargetPrefix(raw);
19
- if (!trimmed) {
20
- return undefined;
21
- }
22
-
23
- const lower = normalizeLowercaseStringOrEmpty(trimmed);
24
- if (lower.startsWith("group:")) {
25
- const id = trimmed.slice("group:".length).trim();
26
- return id ? `group:${id}` : undefined;
27
- }
28
- if (lower.startsWith("g:")) {
29
- const id = trimmed.slice("g:".length).trim();
30
- return id ? `group:${id}` : undefined;
31
- }
32
- if (lower.startsWith("user:")) {
33
- const id = trimmed.slice("user:".length).trim();
34
- return id ? `user:${id}` : undefined;
35
- }
36
- if (lower.startsWith("dm:")) {
37
- const id = trimmed.slice("dm:".length).trim();
38
- return id ? `user:${id}` : undefined;
39
- }
40
- if (lower.startsWith("u:")) {
41
- const id = trimmed.slice("u:".length).trim();
42
- return id ? `user:${id}` : undefined;
43
- }
44
- if (/^g-\S+$/i.test(trimmed)) {
45
- return `group:${trimmed}`;
46
- }
47
- if (/^u-\S+$/i.test(trimmed)) {
48
- return `user:${trimmed}`;
49
- }
50
-
51
- return trimmed;
52
- }
53
-
54
- export function parseZalouserOutboundTarget(raw: string): {
55
- threadId: string;
56
- isGroup: boolean;
57
- } {
58
- const normalized = normalizeZalouserTarget(raw);
59
- if (!normalized) {
60
- throw new Error("Zalouser target is required");
61
- }
62
- const lowered = normalizeLowercaseStringOrEmpty(normalized);
63
- if (lowered.startsWith("group:")) {
64
- const threadId = normalized.slice("group:".length).trim();
65
- if (!threadId) {
66
- throw new Error("Zalouser group target is missing group id");
67
- }
68
- return { threadId, isGroup: true };
69
- }
70
- if (lowered.startsWith("user:")) {
71
- const threadId = normalized.slice("user:".length).trim();
72
- if (!threadId) {
73
- throw new Error("Zalouser user target is missing user id");
74
- }
75
- return { threadId, isGroup: false };
76
- }
77
- // Backward-compatible fallback for bare IDs.
78
- // Group sends should use explicit `group:<id>` targets.
79
- return { threadId: normalized, isGroup: false };
80
- }
81
-
82
- export function parseZalouserDirectoryGroupId(raw: string): string {
83
- const normalized = normalizeZalouserTarget(raw);
84
- if (!normalized) {
85
- throw new Error("Zalouser group target is required");
86
- }
87
- const lowered = normalizeLowercaseStringOrEmpty(normalized);
88
- if (lowered.startsWith("group:")) {
89
- const groupId = normalized.slice("group:".length).trim();
90
- if (!groupId) {
91
- throw new Error("Zalouser group target is missing group id");
92
- }
93
- return groupId;
94
- }
95
- if (lowered.startsWith("user:")) {
96
- throw new Error("Zalouser group members lookup requires a group target (group:<id>)");
97
- }
98
- return normalized;
99
- }
100
-
101
- export function resolveZalouserOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
102
- const normalized = normalizeZalouserTarget(params.target);
103
- if (!normalized) {
104
- return null;
105
- }
106
- const isGroup = (normalizeOptionalLowercaseString(normalized) ?? "").startsWith("group:");
107
- const peerId = normalized.replace(/^(group|user):/i, "").trim();
108
- return buildChannelOutboundSessionRoute({
109
- cfg: params.cfg,
110
- agentId: params.agentId,
111
- channel: "zalouser",
112
- accountId: params.accountId,
113
- peer: {
114
- kind: isGroup ? "group" : "direct",
115
- id: peerId,
116
- },
117
- chatType: isGroup ? "group" : "direct",
118
- from: isGroup ? `zalouser:group:${peerId}` : `zalouser:${peerId}`,
119
- to: `zalouser:${peerId}`,
120
- });
121
- }
package/src/setup-core.ts DELETED
@@ -1,36 +0,0 @@
1
- import {
2
- createDelegatedSetupWizardProxy,
3
- createPatchedAccountSetupAdapter,
4
- createSetupTranslator,
5
- type ChannelSetupWizard,
6
- } from "klaw/plugin-sdk/setup-runtime";
7
-
8
- const t = createSetupTranslator();
9
-
10
- const channel = "zalouser" as const;
11
-
12
- export const zalouserSetupAdapter = createPatchedAccountSetupAdapter({
13
- channelKey: channel,
14
- validateInput: () => null,
15
- buildPatch: () => ({}),
16
- });
17
-
18
- export function createZalouserSetupWizardProxy(
19
- loadWizard: () => Promise<ChannelSetupWizard>,
20
- ): ChannelSetupWizard {
21
- return createDelegatedSetupWizardProxy({
22
- channel,
23
- loadWizard,
24
- status: {
25
- configuredLabel: t("wizard.channels.statusLoggedIn"),
26
- unconfiguredLabel: t("wizard.channels.statusNeedsQrLogin"),
27
- configuredHint: t("wizard.channels.statusRecommendedLoggedIn"),
28
- unconfiguredHint: t("wizard.channels.statusRecommendedQrLogin"),
29
- configuredScore: 1,
30
- unconfiguredScore: 15,
31
- },
32
- credentials: [],
33
- delegatePrepare: true,
34
- delegateFinalize: true,
35
- });
36
- }
@@ -1,367 +0,0 @@
1
- import {
2
- createPluginSetupWizardConfigure,
3
- createTestWizardPrompter,
4
- runSetupWizardConfigure,
5
- } from "klaw/plugin-sdk/plugin-test-runtime";
6
- import { describe, expect, it, vi } from "vitest";
7
- import type { KlawConfig } from "../runtime-api.js";
8
- import "./zalo-js.test-mocks.js";
9
- import { zalouserSetupWizard } from "./setup-surface.js";
10
- import { zalouserSetupPlugin } from "./setup-test-helpers.js";
11
-
12
- const zalouserConfigure = createPluginSetupWizardConfigure(zalouserSetupPlugin);
13
-
14
- async function runSetup(params: {
15
- cfg?: KlawConfig;
16
- prompter: ReturnType<typeof createTestWizardPrompter>;
17
- options?: Record<string, unknown>;
18
- forceAllowFrom?: boolean;
19
- }) {
20
- return await runSetupWizardConfigure({
21
- configure: zalouserConfigure,
22
- cfg: params.cfg,
23
- prompter: params.prompter,
24
- options: params.options,
25
- forceAllowFrom: params.forceAllowFrom,
26
- });
27
- }
28
-
29
- describe("zalouser setup wizard", () => {
30
- function expectEnabledDefaultSetup(
31
- result: Awaited<ReturnType<typeof runSetup>>,
32
- dmPolicy?: "pairing" | "allowlist",
33
- ) {
34
- expect(result.accountId).toBe("default");
35
- const channelConfig = result.cfg.channels?.zalouser;
36
- if (!channelConfig) {
37
- throw new Error("expected Zalo Personal channel config");
38
- }
39
- const pluginEntry = result.cfg.plugins?.entries?.zalouser;
40
- if (!pluginEntry) {
41
- throw new Error("expected Zalo Personal plugin entry");
42
- }
43
- expect(channelConfig.enabled).toBe(true);
44
- expect(pluginEntry.enabled).toBe(true);
45
- if (dmPolicy) {
46
- expect(channelConfig.dmPolicy).toBe(dmPolicy);
47
- }
48
- }
49
-
50
- function createQuickstartPrompter(params?: {
51
- note?: ReturnType<typeof createTestWizardPrompter>["note"];
52
- seen?: string[];
53
- dmPolicy?: "pairing" | "allowlist";
54
- groupAccess?: boolean;
55
- groupPolicy?: "allowlist";
56
- textByMessage?: Record<string, string>;
57
- }) {
58
- const select = vi.fn(
59
- async ({ message, options }: { message: string; options: Array<{ value: string }> }) => {
60
- const first = options[0];
61
- if (!first) {
62
- throw new Error("no options");
63
- }
64
- params?.seen?.push(message);
65
- if (message === "Zalo Personal DM policy" && params?.dmPolicy) {
66
- return params.dmPolicy;
67
- }
68
- if (message === "Zalo groups access" && params?.groupPolicy) {
69
- return params.groupPolicy;
70
- }
71
- return first.value;
72
- },
73
- ) as ReturnType<typeof createTestWizardPrompter>["select"];
74
- const text = vi.fn(
75
- async ({ message }: { message: string }) => params?.textByMessage?.[message] ?? "",
76
- ) as ReturnType<typeof createTestWizardPrompter>["text"];
77
- return createTestWizardPrompter({
78
- ...(params?.note ? { note: params.note } : {}),
79
- confirm: vi.fn(async ({ message }: { message: string }) => {
80
- params?.seen?.push(message);
81
- if (message === "Login via QR code now?") {
82
- return false;
83
- }
84
- if (message === "Configure Zalo groups access?") {
85
- return params?.groupAccess ?? false;
86
- }
87
- return false;
88
- }),
89
- select,
90
- text,
91
- });
92
- }
93
-
94
- it("enables the account without forcing QR login", async () => {
95
- const prompter = createTestWizardPrompter({
96
- confirm: vi.fn(async ({ message }: { message: string }) => {
97
- if (message === "Login via QR code now?") {
98
- return false;
99
- }
100
- if (message === "Configure Zalo groups access?") {
101
- return false;
102
- }
103
- return false;
104
- }),
105
- });
106
-
107
- const result = await runSetup({ prompter });
108
-
109
- expectEnabledDefaultSetup(result);
110
- });
111
-
112
- it("prompts DM policy before group access in quickstart", async () => {
113
- const seen: string[] = [];
114
- const prompter = createQuickstartPrompter({ seen, dmPolicy: "pairing" });
115
-
116
- const result = await runSetup({
117
- prompter,
118
- options: { quickstartDefaults: true },
119
- });
120
-
121
- expectEnabledDefaultSetup(result, "pairing");
122
- expect(seen.indexOf("Zalo Personal DM policy")).toBeGreaterThanOrEqual(0);
123
- expect(seen.indexOf("Configure Zalo groups access?")).toBeGreaterThanOrEqual(0);
124
- expect(seen.indexOf("Zalo Personal DM policy")).toBeLessThan(
125
- seen.indexOf("Configure Zalo groups access?"),
126
- );
127
- });
128
-
129
- it("allows an empty quickstart DM allowlist with a warning", async () => {
130
- const note = vi.fn(async (_message: string, _title?: string) => {});
131
- const prompter = createQuickstartPrompter({
132
- note,
133
- dmPolicy: "allowlist",
134
- textByMessage: {
135
- "Zalouser allowFrom (name or user id)": "",
136
- },
137
- });
138
-
139
- const result = await runSetup({
140
- prompter,
141
- options: { quickstartDefaults: true },
142
- });
143
-
144
- expectEnabledDefaultSetup(result, "allowlist");
145
- expect(result.cfg.channels?.zalouser?.allowFrom).toStrictEqual([]);
146
- expect(
147
- note.mock.calls.some(([message]) => message.includes("No DM allowlist entries added yet.")),
148
- ).toBe(true);
149
- });
150
-
151
- it("allows an empty group allowlist with a warning", async () => {
152
- const note = vi.fn(async (_message: string, _title?: string) => {});
153
- const prompter = createQuickstartPrompter({
154
- note,
155
- groupAccess: true,
156
- groupPolicy: "allowlist",
157
- textByMessage: {
158
- "Zalo groups allowlist (comma-separated)": "",
159
- },
160
- });
161
-
162
- const result = await runSetup({ prompter });
163
-
164
- expect(result.cfg.channels?.zalouser?.groupPolicy).toBe("allowlist");
165
- expect(result.cfg.channels?.zalouser?.groups).toStrictEqual({});
166
- expect(
167
- note.mock.calls.some(([message]) =>
168
- message.includes("No group allowlist entries added yet."),
169
- ),
170
- ).toBe(true);
171
- });
172
-
173
- it("writes canonical enabled entries for configured groups", async () => {
174
- const prompter = createQuickstartPrompter({
175
- groupAccess: true,
176
- groupPolicy: "allowlist",
177
- textByMessage: {
178
- "Zalo groups allowlist (comma-separated)": "Family, Work",
179
- },
180
- });
181
-
182
- const result = await runSetup({ prompter });
183
-
184
- expect(result.cfg.channels?.zalouser?.groups).toEqual({
185
- Family: { enabled: true, requireMention: true },
186
- Work: { enabled: true, requireMention: true },
187
- });
188
- });
189
-
190
- it("preserves non-quickstart forceAllowFrom behavior", async () => {
191
- const note = vi.fn(async (_message: string, _title?: string) => {});
192
- const seen: string[] = [];
193
- const prompter = createTestWizardPrompter({
194
- note,
195
- confirm: vi.fn(async ({ message }: { message: string }) => {
196
- seen.push(message);
197
- if (message === "Login via QR code now?") {
198
- return false;
199
- }
200
- if (message === "Configure Zalo groups access?") {
201
- return false;
202
- }
203
- return false;
204
- }),
205
- text: vi.fn(async ({ message }: { message: string }) => {
206
- seen.push(message);
207
- if (message === "Zalouser allowFrom (name or user id)") {
208
- return "";
209
- }
210
- return "";
211
- }) as ReturnType<typeof createTestWizardPrompter>["text"],
212
- });
213
-
214
- const result = await runSetup({ prompter, forceAllowFrom: true });
215
-
216
- expect(result.cfg.channels?.zalouser?.dmPolicy).toBe("allowlist");
217
- expect(result.cfg.channels?.zalouser?.allowFrom).toStrictEqual([]);
218
- expect(seen).not.toContain("Zalo Personal DM policy");
219
- expect(seen).toContain("Zalouser allowFrom (name or user id)");
220
- expect(
221
- note.mock.calls.some(([message]) => message.includes("No DM allowlist entries added yet.")),
222
- ).toBe(true);
223
- });
224
-
225
- it("allowlists the plugin when a plugin allowlist already exists", async () => {
226
- const prompter = createTestWizardPrompter({
227
- confirm: vi.fn(async ({ message }: { message: string }) => {
228
- if (message === "Login via QR code now?") {
229
- return false;
230
- }
231
- if (message === "Configure Zalo groups access?") {
232
- return false;
233
- }
234
- return false;
235
- }),
236
- });
237
-
238
- const result = await runSetup({
239
- cfg: {
240
- plugins: {
241
- allow: ["telegram"],
242
- },
243
- } as KlawConfig,
244
- prompter,
245
- });
246
-
247
- expect(result.cfg.plugins?.entries?.zalouser?.enabled).toBe(true);
248
- expect(result.cfg.plugins?.allow).toEqual(["telegram", "zalouser"]);
249
- });
250
-
251
- it("reads the named-account DM policy instead of the channel root", () => {
252
- expect(
253
- zalouserSetupWizard.dmPolicy?.getCurrent(
254
- {
255
- channels: {
256
- zalouser: {
257
- dmPolicy: "disabled",
258
- accounts: {
259
- work: {
260
- profile: "work",
261
- dmPolicy: "allowlist",
262
- },
263
- },
264
- },
265
- },
266
- } as KlawConfig,
267
- "work",
268
- ),
269
- ).toBe("allowlist");
270
- });
271
-
272
- it("reports account-scoped config keys for named accounts", () => {
273
- expect(zalouserSetupWizard.dmPolicy?.resolveConfigKeys?.({} as KlawConfig, "work")).toEqual({
274
- policyKey: "channels.zalouser.accounts.work.dmPolicy",
275
- allowFromKey: "channels.zalouser.accounts.work.allowFrom",
276
- });
277
- });
278
-
279
- it("uses configured defaultAccount for omitted DM policy account context", () => {
280
- const cfg = {
281
- channels: {
282
- zalouser: {
283
- defaultAccount: "work",
284
- dmPolicy: "disabled",
285
- allowFrom: ["123456789"],
286
- accounts: {
287
- work: {
288
- dmPolicy: "allowlist",
289
- profile: "work-profile",
290
- },
291
- },
292
- },
293
- },
294
- } as KlawConfig;
295
-
296
- expect(zalouserSetupWizard.dmPolicy?.getCurrent(cfg)).toBe("allowlist");
297
- expect(zalouserSetupWizard.dmPolicy?.resolveConfigKeys?.(cfg)).toEqual({
298
- policyKey: "channels.zalouser.accounts.work.dmPolicy",
299
- allowFromKey: "channels.zalouser.accounts.work.allowFrom",
300
- });
301
-
302
- const next = zalouserSetupWizard.dmPolicy?.setPolicy(cfg, "open");
303
- expect(next?.channels?.zalouser?.dmPolicy).toBe("disabled");
304
- const workAccount = next?.channels?.zalouser?.accounts?.work as
305
- | { dmPolicy?: string; allowFrom?: Array<string | number> }
306
- | undefined;
307
- expect(workAccount?.dmPolicy).toBe("open");
308
- });
309
-
310
- it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => {
311
- const next = zalouserSetupWizard.dmPolicy?.setPolicy(
312
- {
313
- channels: {
314
- zalouser: {
315
- allowFrom: ["123456789"],
316
- accounts: {
317
- work: {
318
- profile: "work",
319
- },
320
- },
321
- },
322
- },
323
- } as KlawConfig,
324
- "open",
325
- "work",
326
- );
327
-
328
- expect(next?.channels?.zalouser?.dmPolicy).toBeUndefined();
329
- const workAccount = next?.channels?.zalouser?.accounts?.work as
330
- | { dmPolicy?: string; allowFrom?: Array<string | number> }
331
- | undefined;
332
- expect(workAccount?.dmPolicy).toBe("open");
333
- expect(workAccount?.allowFrom).toEqual(["123456789", "*"]);
334
- });
335
-
336
- it("shows the account-scoped current DM policy in quickstart notes", async () => {
337
- const note = vi.fn(async (_message: string, _title?: string) => {});
338
- const prompter = createQuickstartPrompter({ note, dmPolicy: "pairing" });
339
-
340
- await runSetupWizardConfigure({
341
- configure: zalouserConfigure,
342
- cfg: {
343
- channels: {
344
- zalouser: {
345
- dmPolicy: "disabled",
346
- accounts: {
347
- work: {
348
- profile: "work",
349
- dmPolicy: "allowlist",
350
- allowFrom: ["123456789"],
351
- },
352
- },
353
- },
354
- },
355
- } as KlawConfig,
356
- prompter,
357
- options: { quickstartDefaults: true },
358
- accountOverrides: { zalouser: "work" },
359
- });
360
-
361
- expect(
362
- note.mock.calls.some(([message]) =>
363
- message.includes("Current: dmPolicy=allowlist, allowFrom=123456789"),
364
- ),
365
- ).toBe(true);
366
- });
367
- });