@openclaw/zalo 2026.6.5-beta.1 → 2026.6.5-beta.3

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/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-fwCh0QUi.js";
2
- import { t as zaloPlugin } from "./channel-DSMo5e3B.js";
2
+ import { t as zaloPlugin } from "./channel-fQwAowmS.js";
3
3
  import { n as resolveZaloRuntimeGroupPolicy } from "./group-access-8qHRzDHx.js";
4
4
  import { zaloSetupWizard } from "./setup-api.js";
5
5
  export { createZaloSetupWizardProxy, resolveZaloRuntimeGroupPolicy, zaloDmPolicy, zaloPlugin, zaloSetupAdapter, zaloSetupWizard };
@@ -173,7 +173,7 @@ function normalizeZaloMessagingTarget(raw) {
173
173
  if (!trimmed) return;
174
174
  return trimmed.replace(/^(zalo|zl):/i, "").trim();
175
175
  }
176
- const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-uSuM_HDe.js"));
176
+ const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-Dfy33H5M.js"));
177
177
  const zaloSetupWizard = createZaloSetupWizardProxy(async () => (await import("./setup-surface-8eRimod9.js")).zaloSetupWizard);
178
178
  const zaloTextChunkLimit = 2e3;
179
179
  const zaloRawSendResultAdapter = createRawChannelSendResultAdapter({
@@ -1,2 +1,2 @@
1
- import { t as zaloPlugin } from "./channel-DSMo5e3B.js";
1
+ import { t as zaloPlugin } from "./channel-fQwAowmS.js";
2
2
  export { zaloPlugin };
@@ -86,7 +86,7 @@ async function startZaloGatewayAccount(ctx) {
86
86
  setStatus: ctx.setStatus
87
87
  });
88
88
  ctx.log?.info(`[${account.accountId}] starting provider${zaloBotLabel} mode=${mode}`);
89
- const { monitorZaloProvider } = await import("./monitor-BEIaqyy1.js");
89
+ const { monitorZaloProvider } = await import("./monitor-BcYKNoj4.js");
90
90
  return monitorZaloProvider({
91
91
  token,
92
92
  account,
@@ -11,12 +11,7 @@ import { registerPluginHttpRoute, resolveWebhookPath } from "openclaw/plugin-sdk
11
11
  import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
12
12
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
13
13
  import { waitForAbortSignal } from "openclaw/plugin-sdk/runtime-env";
14
- import { randomBytes } from "node:crypto";
15
- import { readFile, readdir, stat, unlink } from "node:fs/promises";
16
- import { join } from "node:path";
17
- import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
18
- import { privateFileStore } from "openclaw/plugin-sdk/security-runtime";
19
- import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
14
+ import { createHostedOutboundMediaStore } from "openclaw/plugin-sdk/outbound-media";
20
15
  //#region extensions/zalo/src/monitor-durable.ts
21
16
  function prepareZaloDurableReplyPayload(params) {
22
17
  if (!params.payload.text) return params.payload;
@@ -36,63 +31,32 @@ function resolveZaloDurableReplyOptions(params) {
36
31
  const ZALO_OUTBOUND_MEDIA_TTL_MS = 2 * 6e4;
37
32
  const ZALO_OUTBOUND_MEDIA_SEGMENT = "media";
38
33
  const ZALO_OUTBOUND_MEDIA_PREFIX = `/${ZALO_OUTBOUND_MEDIA_SEGMENT}/`;
39
- const ZALO_OUTBOUND_MEDIA_DIR_NAME = "openclaw-zalo-outbound-media";
40
- function resolveHostedZaloMediaDirName() {
41
- const workerId = process.env.VITEST_WORKER_ID ?? process.env.VITEST_POOL_ID;
42
- if (!workerId) return ZALO_OUTBOUND_MEDIA_DIR_NAME;
43
- return `${ZALO_OUTBOUND_MEDIA_DIR_NAME}-${workerId.replaceAll(/[^a-zA-Z0-9_.-]/gu, "_")}`;
44
- }
45
- const ZALO_OUTBOUND_MEDIA_DIR = join(resolvePreferredOpenClawTmpDir(), resolveHostedZaloMediaDirName());
46
34
  const ZALO_OUTBOUND_MEDIA_ID_RE = /^[a-f0-9]{24}$/;
47
- function resolveHostedZaloMediaMetadataPath(id) {
48
- return join(ZALO_OUTBOUND_MEDIA_DIR, `${id}.json`);
49
- }
50
- function resolveHostedZaloMediaBufferPath(id) {
51
- return join(ZALO_OUTBOUND_MEDIA_DIR, `${id}.bin`);
52
- }
53
- function createHostedZaloMediaId() {
54
- return randomBytes(12).toString("hex");
55
- }
56
- function createHostedZaloMediaToken() {
57
- return randomBytes(24).toString("hex");
58
- }
59
- async function ensureHostedZaloMediaDir() {
60
- await privateFileStore(ZALO_OUTBOUND_MEDIA_DIR).writeText(".ready", "");
61
- await unlink(join(ZALO_OUTBOUND_MEDIA_DIR, ".ready")).catch(() => void 0);
62
- }
63
- async function deleteHostedZaloMediaEntry(id) {
64
- await Promise.all([unlink(resolveHostedZaloMediaMetadataPath(id)).catch(() => void 0), unlink(resolveHostedZaloMediaBufferPath(id)).catch(() => void 0)]);
65
- }
66
- async function cleanupExpiredHostedZaloMedia(nowMs = Date.now()) {
67
- const now = asDateTimestampMs(nowMs);
68
- if (now === void 0) return;
69
- let fileNames;
70
- try {
71
- fileNames = await readdir(ZALO_OUTBOUND_MEDIA_DIR);
72
- } catch {
73
- return;
74
- }
75
- await Promise.all(fileNames.filter((fileName) => fileName.endsWith(".json")).map(async (fileName) => {
76
- const id = fileName.slice(0, -5);
77
- try {
78
- const metadataRaw = await readFile(resolveHostedZaloMediaMetadataPath(id), "utf8");
79
- const expiresAt = asDateTimestampMs(JSON.parse(metadataRaw).expiresAt);
80
- if (expiresAt === void 0 || expiresAt <= now) await deleteHostedZaloMediaEntry(id);
81
- } catch {
82
- await deleteHostedZaloMediaEntry(id);
83
- }
84
- }));
35
+ const ZALO_OUTBOUND_MEDIA_NAMESPACE = "hosted-outbound-media";
36
+ const ZALO_OUTBOUND_MEDIA_CHUNKS_NAMESPACE = "hosted-outbound-media-chunks";
37
+ const ZALO_OUTBOUND_MEDIA_MAX_ENTRIES = 64;
38
+ const ZALO_OUTBOUND_MEDIA_MAX_CHUNK_ROWS = ZALO_OUTBOUND_MEDIA_MAX_ENTRIES * 256;
39
+ let hostedZaloMediaStore;
40
+ function createHostedZaloMediaStore() {
41
+ const runtime = getZaloRuntime();
42
+ return createHostedOutboundMediaStore({
43
+ metadataStore: runtime.state.openKeyedStore({
44
+ namespace: ZALO_OUTBOUND_MEDIA_NAMESPACE,
45
+ maxEntries: 80
46
+ }),
47
+ chunkStore: runtime.state.openKeyedStore({
48
+ namespace: ZALO_OUTBOUND_MEDIA_CHUNKS_NAMESPACE,
49
+ maxEntries: ZALO_OUTBOUND_MEDIA_MAX_CHUNK_ROWS
50
+ }),
51
+ ttlMs: ZALO_OUTBOUND_MEDIA_TTL_MS,
52
+ maxEntries: ZALO_OUTBOUND_MEDIA_MAX_ENTRIES,
53
+ maxChunkRows: ZALO_OUTBOUND_MEDIA_MAX_CHUNK_ROWS,
54
+ resolveExpiresAtMs: (ttlMs) => resolveExpiresAtMsFromDurationMs(ttlMs)
55
+ });
85
56
  }
86
- async function readHostedZaloMediaEntry(id) {
87
- try {
88
- const [metadataRaw, buffer] = await Promise.all([readFile(resolveHostedZaloMediaMetadataPath(id), "utf8"), readFile(resolveHostedZaloMediaBufferPath(id))]);
89
- return {
90
- metadata: JSON.parse(metadataRaw),
91
- buffer
92
- };
93
- } catch {
94
- return null;
95
- }
57
+ function getHostedZaloMediaStore() {
58
+ hostedZaloMediaStore ??= createHostedZaloMediaStore();
59
+ return hostedZaloMediaStore;
96
60
  }
97
61
  function resolveHostedZaloMediaRoutePrefix(params) {
98
62
  const webhookRoutePath = resolveWebhookPath({
@@ -107,39 +71,24 @@ function resolveHostedZaloMediaRoutePath(params) {
107
71
  return `${resolveHostedZaloMediaRoutePrefix(params)}/`;
108
72
  }
109
73
  async function prepareHostedZaloMediaUrl(params) {
110
- await ensureHostedZaloMediaDir();
111
- await cleanupExpiredHostedZaloMedia();
112
74
  const now = asDateTimestampMs(Date.now());
113
- const expiresAt = now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(ZALO_OUTBOUND_MEDIA_TTL_MS, { nowMs: now });
114
- if (expiresAt === void 0) throw new Error("Zalo outbound media expiry could not be resolved");
115
- const media = await loadOutboundMediaFromUrl(params.mediaUrl, {
116
- maxBytes: params.maxBytes,
117
- ...params.proxyUrl ? { proxyUrl: params.proxyUrl } : {}
118
- });
75
+ if ((now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(ZALO_OUTBOUND_MEDIA_TTL_MS, { nowMs: now })) === void 0) throw new Error("Zalo outbound media expiry could not be resolved");
119
76
  const routePath = resolveHostedZaloMediaRoutePath({
120
77
  webhookUrl: params.webhookUrl,
121
78
  webhookPath: params.webhookPath
122
79
  });
123
- const id = createHostedZaloMediaId();
124
- const token = createHostedZaloMediaToken();
125
80
  const publicBaseUrl = new URL(params.webhookUrl).origin;
126
- const store = privateFileStore(ZALO_OUTBOUND_MEDIA_DIR);
127
- await store.writeText(`${id}.bin`, media.buffer);
128
- try {
129
- await store.writeJson(`${id}.json`, {
130
- routePath,
131
- token,
132
- contentType: media.contentType,
133
- expiresAt
134
- });
135
- } catch (error) {
136
- await deleteHostedZaloMediaEntry(id);
137
- throw error;
138
- }
139
- return `${publicBaseUrl}${routePath}${id}?token=${token}`;
81
+ return await getHostedZaloMediaStore().prepareUrl({
82
+ mediaUrl: params.mediaUrl,
83
+ routePath,
84
+ publicBaseUrl,
85
+ maxBytes: params.maxBytes,
86
+ ...params.proxyUrl ? { proxyUrl: params.proxyUrl } : {}
87
+ });
140
88
  }
141
89
  async function tryHandleHostedZaloMediaRequest(req, res) {
142
- await cleanupExpiredHostedZaloMedia();
90
+ const store = getHostedZaloMediaStore();
91
+ await store.cleanupExpired();
143
92
  const method = req.method ?? "GET";
144
93
  if (method !== "GET" && method !== "HEAD") return false;
145
94
  let url;
@@ -158,16 +107,22 @@ async function tryHandleHostedZaloMediaRequest(req, res) {
158
107
  res.end("Not Found");
159
108
  return true;
160
109
  }
161
- const entry = await readHostedZaloMediaEntry(id);
110
+ const now = asDateTimestampMs(Date.now());
111
+ if (now === void 0) {
112
+ await store.delete(id);
113
+ res.statusCode = 410;
114
+ res.end("Expired");
115
+ return true;
116
+ }
117
+ const entry = await store.read(id, now);
162
118
  if (!entry || entry.metadata.routePath !== routePath) {
163
119
  res.statusCode = 404;
164
120
  res.end("Not Found");
165
121
  return true;
166
122
  }
167
- const now = asDateTimestampMs(Date.now());
168
123
  const expiresAt = asDateTimestampMs(entry.metadata.expiresAt);
169
- if (now === void 0 || expiresAt === void 0 || expiresAt <= now) {
170
- await deleteHostedZaloMediaEntry(id);
124
+ if (expiresAt === void 0 || expiresAt <= now) {
125
+ await store.delete(id);
171
126
  res.statusCode = 410;
172
127
  res.end("Expired");
173
128
  return true;
@@ -180,8 +135,7 @@ async function tryHandleHostedZaloMediaRequest(req, res) {
180
135
  if (entry.metadata.contentType) res.setHeader("Content-Type", entry.metadata.contentType);
181
136
  res.setHeader("Cache-Control", "no-store");
182
137
  res.setHeader("X-Content-Type-Options", "nosniff");
183
- const bufferStats = await stat(resolveHostedZaloMediaBufferPath(id)).catch(() => null);
184
- if (bufferStats) res.setHeader("Content-Length", String(bufferStats.size));
138
+ res.setHeader("Content-Length", String(entry.metadata.byteLength));
185
139
  if (method === "HEAD") {
186
140
  res.statusCode = 200;
187
141
  res.end();
@@ -189,7 +143,7 @@ async function tryHandleHostedZaloMediaRequest(req, res) {
189
143
  }
190
144
  res.statusCode = 200;
191
145
  res.end(entry.buffer);
192
- await deleteHostedZaloMediaEntry(id);
146
+ await store.delete(id);
193
147
  return true;
194
148
  }
195
149
  //#endregion
@@ -201,7 +155,7 @@ const ZALO_TYPING_TIMEOUT_MS = 5e3;
201
155
  let zaloWebhookModulePromise;
202
156
  const hostedMediaRouteRefs = /* @__PURE__ */ new Map();
203
157
  function loadZaloWebhookModule() {
204
- zaloWebhookModulePromise ??= import("./monitor.webhook-BSYRYNHE.js");
158
+ zaloWebhookModulePromise ??= import("./monitor.webhook-B23mUx-y.js");
205
159
  return zaloWebhookModulePromise;
206
160
  }
207
161
  function registerSharedHostedMediaRoute(params) {
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@openclaw/zalo",
3
- "version": "2026.6.5-beta.1",
3
+ "version": "2026.6.5-beta.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/zalo",
9
- "version": "2026.6.5-beta.1",
9
+ "version": "2026.6.5-beta.3",
10
10
  "dependencies": {
11
11
  "zod": "4.4.3"
12
12
  },
13
13
  "peerDependencies": {
14
- "openclaw": ">=2026.6.5-beta.1"
14
+ "openclaw": ">=2026.6.5-beta.3"
15
15
  },
16
16
  "peerDependenciesMeta": {
17
17
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/zalo",
3
- "version": "2026.6.5-beta.1",
3
+ "version": "2026.6.5-beta.3",
4
4
  "description": "OpenClaw Zalo channel plugin for bot and webhook chats.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "type": "module",
10
10
  "peerDependencies": {
11
- "openclaw": ">=2026.6.5-beta.1"
11
+ "openclaw": ">=2026.6.5-beta.3"
12
12
  },
13
13
  "peerDependenciesMeta": {
14
14
  "openclaw": {
@@ -39,10 +39,10 @@
39
39
  "minHostVersion": ">=2026.4.10"
40
40
  },
41
41
  "compat": {
42
- "pluginApi": ">=2026.6.5-beta.1"
42
+ "pluginApi": ">=2026.6.5-beta.3"
43
43
  },
44
44
  "build": {
45
- "openclawVersion": "2026.6.5-beta.1"
45
+ "openclawVersion": "2026.6.5-beta.3"
46
46
  },
47
47
  "release": {
48
48
  "publishToClawHub": true,
@@ -1,6 +1,6 @@
1
1
  import { B as registerWebhookTargetWithPluginRoute, I as readJsonWebhookBodyOrReject, K as resolveWebhookTargetWithAuthOrRejectSync, Q as withResolvedWebhookRequestPipeline, V as resolveClientIp, a as WEBHOOK_ANOMALY_COUNTER_DEFAULTS, b as createFixedWindowRateLimiter, l as applyBasicWebhookRequestGuards, o as WEBHOOK_RATE_LIMIT_DEFAULTS, x as createWebhookAnomalyTracker, z as registerWebhookTarget } from "./runtime-api-CDwUY_-_.js";
2
- import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
3
2
  import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
3
+ import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
4
4
  //#region extensions/zalo/src/monitor.webhook.ts
5
5
  const ZALO_WEBHOOK_REPLAY_WINDOW_MS = 5 * 6e4;
6
6
  const webhookTargets = /* @__PURE__ */ new Map();