'use strict'; /** * sys-call dispatcher. * * Routes incoming sys-call messages from VPS to channel handlers. * * Supported actions (incoming from VPS): * request — start a new task * cancel — abort a running task * * Handler interface: * handler[method](params) → { abort } | function * abort() — called on cancel */ const log = require('./logger'); // ── channel handlers ────────────────────────────────────────────────────────── const handlers = { 'channel.weixin': require('./channel/weixin'), }; // ── running tasks: callId → { abort, onReply? } ─────────────────────────────── const running = new Map(); /** * Handle an incoming sys-call message from VPS. * * @param {object} msg - parsed message object * @param {function} send - send(replyPayload) → forwarded 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 task = running.get(callId); if (task) { log.info('sys-call', `cancel callId=${callId}`); try { task.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: merges envelope fields, 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 task; try { task = 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; } // Normalise: handler may return a function (abort only) or { abort } if (typeof task === 'function') { task = { abort: task }; } if (task && typeof task.abort === 'function') { running.set(callId, task); } } module.exports = { handle };