Add Zalo channel adapter with webhook and send path

This commit is contained in:
William Valentin
2026-02-16 13:11:51 -08:00
parent 891ccb696e
commit 8bed99c770
16 changed files with 491 additions and 6 deletions
+2
View File
@@ -37,6 +37,7 @@ function makeBaseConfig(): Config {
bluebubbles: undefined,
line: undefined,
feishu: undefined,
zalo: undefined,
} as unknown as Config;
}
@@ -57,6 +58,7 @@ describe('discoverServices', () => {
expect.objectContaining({ name: 'bluebubbles', status: 'not_configured' }),
expect.objectContaining({ name: 'line', status: 'not_configured' }),
expect.objectContaining({ name: 'feishu', status: 'not_configured' }),
expect.objectContaining({ name: 'zalo', status: 'not_configured' }),
expect.objectContaining({ name: 'cron', status: 'not_configured' }),
expect.objectContaining({ name: 'mcp', status: 'not_configured' }),
expect.objectContaining({ name: 'web_search', status: 'configured' }),
+1
View File
@@ -60,6 +60,7 @@ export function discoverServices(
{ key: 'bluebubbles', name: 'bluebubbles', description: 'iMessage via BlueBubbles' },
{ key: 'line', name: 'line', description: 'LINE Messaging API bot' },
{ key: 'feishu', name: 'feishu', description: 'Feishu/Lark bot' },
{ key: 'zalo', name: 'zalo', description: 'Zalo OA bot' },
];
for (const { key, name, description } of channelConfigs) {
+14
View File
@@ -53,6 +53,7 @@ import type { GoogleChatAdapter } from '../channels/googleChat/adapter.js';
import type { BlueBubblesAdapter } from '../channels/bluebubbles/adapter.js';
import type { LineAdapter } from '../channels/line/adapter.js';
import type { FeishuAdapter } from '../channels/feishu/adapter.js';
import type { ZaloAdapter } from '../channels/zalo/adapter.js';
export interface GatewayServerConfig {
port: number;
@@ -126,6 +127,8 @@ export interface GatewayServerConfig {
lineHandler?: Pick<LineAdapter, 'handleRequest'>;
/** Optional Feishu adapter for inbound webhook events. */
feishuHandler?: Pick<FeishuAdapter, 'handleRequest'>;
/** Optional Zalo adapter for inbound webhook events. */
zaloHandler?: Pick<ZaloAdapter, 'handleRequest'>;
}
export class GatewayServer {
@@ -742,6 +745,12 @@ export class GatewayServer {
return;
}
// Zalo events route — bypass gateway auth (Zalo webhook posts directly)
if (this.config.zaloHandler && req.method === 'POST' && req.url?.startsWith('/zalo/events')) {
await this.config.zaloHandler.handleRequest(req, res);
return;
}
// Apply auth to HTTP requests when configured
const authConfig = this.config.auth ?? {};
if (this.config.authHttp !== false && authConfig.token) {
@@ -870,6 +879,11 @@ export class GatewayServer {
this.config.feishuHandler = handler;
}
/** Set the Zalo handler for inbound webhook HTTP routes (late binding). */
setZaloHandler(handler: Pick<ZaloAdapter, 'handleRequest'>): void {
this.config.zaloHandler = handler;
}
private async startDiscovery(host: string, port: number): Promise<void> {
const discovery = this.config.discovery;
if (!discovery?.enabled) {