refactor: share zalo monitor processing context

This commit is contained in:
Peter Steinberger 2026-03-13 21:45:01 +00:00
parent b9f0effd55
commit 58baf22230
1 changed files with 72 additions and 103 deletions

View File

@ -75,6 +75,35 @@ const WEBHOOK_CLEANUP_TIMEOUT_MS = 5_000;
const ZALO_TYPING_TIMEOUT_MS = 5_000; const ZALO_TYPING_TIMEOUT_MS = 5_000;
type ZaloCoreRuntime = ReturnType<typeof getZaloRuntime>; type ZaloCoreRuntime = ReturnType<typeof getZaloRuntime>;
type ZaloStatusSink = (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
type ZaloProcessingContext = {
token: string;
account: ResolvedZaloAccount;
config: OpenClawConfig;
runtime: ZaloRuntimeEnv;
core: ZaloCoreRuntime;
statusSink?: ZaloStatusSink;
fetcher?: ZaloFetch;
};
type ZaloPollingLoopParams = ZaloProcessingContext & {
abortSignal: AbortSignal;
isStopped: () => boolean;
mediaMaxMb: number;
};
type ZaloUpdateProcessingParams = ZaloProcessingContext & {
update: ZaloUpdate;
mediaMaxMb: number;
};
type ZaloMessagePipelineParams = ZaloProcessingContext & {
message: ZaloMessage;
text?: string;
mediaPath?: string;
mediaType?: string;
};
type ZaloImageMessageParams = ZaloProcessingContext & {
message: ZaloMessage;
mediaMaxMb: number;
};
function formatZaloError(error: unknown): string { function formatZaloError(error: unknown): string {
if (error instanceof Error) { if (error instanceof Error) {
@ -135,32 +164,21 @@ export async function handleZaloWebhookRequest(
res: ServerResponse, res: ServerResponse,
): Promise<boolean> { ): Promise<boolean> {
return handleZaloWebhookRequestInternal(req, res, async ({ update, target }) => { return handleZaloWebhookRequestInternal(req, res, async ({ update, target }) => {
await processUpdate( await processUpdate({
update, update,
target.token, token: target.token,
target.account, account: target.account,
target.config, config: target.config,
target.runtime, runtime: target.runtime,
target.core as ZaloCoreRuntime, core: target.core as ZaloCoreRuntime,
target.mediaMaxMb, mediaMaxMb: target.mediaMaxMb,
target.statusSink, statusSink: target.statusSink,
target.fetcher, fetcher: target.fetcher,
); });
}); });
} }
function startPollingLoop(params: { function startPollingLoop(params: ZaloPollingLoopParams) {
token: string;
account: ResolvedZaloAccount;
config: OpenClawConfig;
runtime: ZaloRuntimeEnv;
core: ZaloCoreRuntime;
abortSignal: AbortSignal;
isStopped: () => boolean;
mediaMaxMb: number;
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
fetcher?: ZaloFetch;
}) {
const { const {
token, token,
account, account,
@ -174,6 +192,16 @@ function startPollingLoop(params: {
fetcher, fetcher,
} = params; } = params;
const pollTimeout = 30; const pollTimeout = 30;
const processingContext = {
token,
account,
config,
runtime,
core,
mediaMaxMb,
statusSink,
fetcher,
};
runtime.log?.(`[${account.accountId}] Zalo polling loop started timeout=${String(pollTimeout)}s`); runtime.log?.(`[${account.accountId}] Zalo polling loop started timeout=${String(pollTimeout)}s`);
@ -186,17 +214,10 @@ function startPollingLoop(params: {
const response = await getUpdates(token, { timeout: pollTimeout }, fetcher); const response = await getUpdates(token, { timeout: pollTimeout }, fetcher);
if (response.ok && response.result) { if (response.ok && response.result) {
statusSink?.({ lastInboundAt: Date.now() }); statusSink?.({ lastInboundAt: Date.now() });
await processUpdate( await processUpdate({
response.result, update: response.result,
token, ...processingContext,
account, });
config,
runtime,
core,
mediaMaxMb,
statusSink,
fetcher,
);
} }
} catch (err) { } catch (err) {
if (err instanceof ZaloApiError && err.isPollingTimeout) { if (err instanceof ZaloApiError && err.isPollingTimeout) {
@ -215,38 +236,27 @@ function startPollingLoop(params: {
void poll(); void poll();
} }
async function processUpdate( async function processUpdate(params: ZaloUpdateProcessingParams): Promise<void> {
update: ZaloUpdate, const { update, token, account, config, runtime, core, mediaMaxMb, statusSink, fetcher } = params;
token: string,
account: ResolvedZaloAccount,
config: OpenClawConfig,
runtime: ZaloRuntimeEnv,
core: ZaloCoreRuntime,
mediaMaxMb: number,
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
fetcher?: ZaloFetch,
): Promise<void> {
const { event_name, message } = update; const { event_name, message } = update;
const sharedContext = { token, account, config, runtime, core, statusSink, fetcher };
if (!message) { if (!message) {
return; return;
} }
switch (event_name) { switch (event_name) {
case "message.text.received": case "message.text.received":
await handleTextMessage(message, token, account, config, runtime, core, statusSink, fetcher); await handleTextMessage({
message,
...sharedContext,
});
break; break;
case "message.image.received": case "message.image.received":
await handleImageMessage( await handleImageMessage({
message, message,
token, ...sharedContext,
account,
config,
runtime,
core,
mediaMaxMb, mediaMaxMb,
statusSink, });
fetcher,
);
break; break;
case "message.sticker.received": case "message.sticker.received":
logVerbose(core, runtime, `[${account.accountId}] Received sticker from ${message.from.id}`); logVerbose(core, runtime, `[${account.accountId}] Received sticker from ${message.from.id}`);
@ -262,46 +272,24 @@ async function processUpdate(
} }
async function handleTextMessage( async function handleTextMessage(
message: ZaloMessage, params: ZaloProcessingContext & { message: ZaloMessage },
token: string,
account: ResolvedZaloAccount,
config: OpenClawConfig,
runtime: ZaloRuntimeEnv,
core: ZaloCoreRuntime,
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
fetcher?: ZaloFetch,
): Promise<void> { ): Promise<void> {
const { message } = params;
const { text } = message; const { text } = message;
if (!text?.trim()) { if (!text?.trim()) {
return; return;
} }
await processMessageWithPipeline({ await processMessageWithPipeline({
message, ...params,
token,
account,
config,
runtime,
core,
text, text,
mediaPath: undefined, mediaPath: undefined,
mediaType: undefined, mediaType: undefined,
statusSink,
fetcher,
}); });
} }
async function handleImageMessage( async function handleImageMessage(params: ZaloImageMessageParams): Promise<void> {
message: ZaloMessage, const { message, mediaMaxMb } = params;
token: string,
account: ResolvedZaloAccount,
config: OpenClawConfig,
runtime: ZaloRuntimeEnv,
core: ZaloCoreRuntime,
mediaMaxMb: number,
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
fetcher?: ZaloFetch,
): Promise<void> {
const { photo, caption } = message; const { photo, caption } = message;
let mediaPath: string | undefined; let mediaPath: string | undefined;
@ -325,33 +313,14 @@ async function handleImageMessage(
} }
await processMessageWithPipeline({ await processMessageWithPipeline({
message, ...params,
token,
account,
config,
runtime,
core,
text: caption, text: caption,
mediaPath, mediaPath,
mediaType, mediaType,
statusSink,
fetcher,
}); });
} }
async function processMessageWithPipeline(params: { async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Promise<void> {
message: ZaloMessage;
token: string;
account: ResolvedZaloAccount;
config: OpenClawConfig;
runtime: ZaloRuntimeEnv;
core: ZaloCoreRuntime;
text?: string;
mediaPath?: string;
mediaType?: string;
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
fetcher?: ZaloFetch;
}): Promise<void> {
const { const {
message, message,
token, token,
@ -609,7 +578,7 @@ async function deliverZaloReply(params: {
core: ZaloCoreRuntime; core: ZaloCoreRuntime;
config: OpenClawConfig; config: OpenClawConfig;
accountId?: string; accountId?: string;
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void; statusSink?: ZaloStatusSink;
fetcher?: ZaloFetch; fetcher?: ZaloFetch;
tableMode?: MarkdownTableMode; tableMode?: MarkdownTableMode;
}): Promise<void> { }): Promise<void> {