'use strict'; /** * sys-call dispatcher. * * Routes incoming sys-call messages (action='request'|'cancel') from VPS * to the correct channel handler, and wires the emit callback so that * handler replies are sent back over the WebSocket. * * Message envelope (shared by all sys-call messages): * { * type: 'sys-call', * id: '', * api: 'channel.weixin', * method: 'login', * action: 'request' | 'reply' | 'notify' | 'progress' | 'event' | 'finish' | 'cancel', * event: '', * code: 0, * message: '', * data: {} * } */ const log = require('./logger'); // ── channel handlers ────────────────────────────────────────────────────────── const handlers = { 'channel.weixin': require('./channel/weixin'), }; // ── running tasks: callId → abort() ────────────────────────────────────────── const running = new Map(); /** * Handle an incoming sys-call message from VPS. * * @param {object} msg - parsed message object * @param {function} send - send(replyPayload) → forwards to VPS over WS; * caller prepends { type:'sys-call' } */ function handle(msg, send) { const { id: callId, api, method, action } = msg; if (!callId) { log.warn('sys-call', 'missing id field, ignoring'); return; } // ── cancel ──────────────────────────────────────────────────────────────── if (action === 'cancel') { const abort = running.get(callId); if (abort) { log.info('sys-call', `cancel callId=${callId}`); try { abort(); } catch (e) { log.warn('sys-call', `abort error: ${e.message}`); } running.delete(callId); } else { log.debug('sys-call', `cancel for unknown/finished callId=${callId}`); } return; } // ── request ─────────────────────────────────────────────────────────────── if (action !== 'request') { log.warn('sys-call', `unexpected action=${action} callId=${callId}`); return; } const handler = handlers[api]; if (!handler || typeof handler[method] !== 'function') { log.warn('sys-call', `unknown api=${api} method=${method}`); send({ id: callId, api, method, action: 'finish', event: 'failed', code: 404, message: `unknown api: ${api}.${method}`, }); return; } // emit: wraps handler replies, cleans up running map on finish const emit = (payload) => { send({ id: callId, api, method, event: '', code: 0, message: '', ...payload }); if (payload.action === 'finish') { running.delete(callId); log.info('sys-call', `finished callId=${callId}`); } }; log.info('sys-call', `start api=${api} method=${method} callId=${callId}`); let abort; try { abort = handler[method]({ callId, ...(msg.data || {}), emit }); } catch (e) { log.error('sys-call', `handler threw: ${e.message}`); send({ id: callId, api, method, action: 'finish', event: 'failed', code: 500, message: e.message, }); return; } if (typeof abort === 'function') { running.set(callId, abort); } } module.exports = { handle };