121 lines
4.1 KiB
JavaScript
121 lines
4.1 KiB
JavaScript
'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
|
|
* reply — forward input to a running task (e.g. verify code)
|
|
*
|
|
* Handler interface:
|
|
* handler[method](params) → { abort, onReply? }
|
|
* abort() — called on cancel
|
|
* onReply(msg) — called on reply (optional)
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
// ── reply (e.g. verify code from frontend) ────────────────────────────────
|
|
if (action === 'reply') {
|
|
const task = running.get(callId);
|
|
if (task && typeof task.onReply === 'function') {
|
|
log.info('sys-call', `reply callId=${callId} event=${msg.event || ''}`);
|
|
try { task.onReply(msg); } catch (e) { log.warn('sys-call', `onReply error: ${e.message}`); }
|
|
} else {
|
|
log.debug('sys-call', `reply for unknown/no-onReply 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, onReply }
|
|
if (typeof task === 'function') {
|
|
task = { abort: task };
|
|
}
|
|
|
|
if (task && typeof task.abort === 'function') {
|
|
running.set(callId, task);
|
|
}
|
|
}
|
|
|
|
module.exports = { handle };
|