UNPKG

napcat-node-ts-sdk

Version:
259 lines (258 loc) 9.48 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NCWebsocketBase = void 0; const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const nanoid_1 = require("nanoid"); const NCEventBus_1 = require("./NCEventBus"); const Utils_1 = require("./Utils"); class NCWebsocketBase { #debug; #baseUrl; #accessToken; #throwPromise; #reconnection; #socket; #eventBus; #echoMap; constructor(NCWebsocketOptions, debug = false) { this.#accessToken = NCWebsocketOptions.accessToken ?? ''; this.#throwPromise = NCWebsocketOptions.throwPromise ?? false; if ('baseUrl' in NCWebsocketOptions) { this.#baseUrl = NCWebsocketOptions.baseUrl; } else if ('protocol' in NCWebsocketOptions && 'host' in NCWebsocketOptions && 'port' in NCWebsocketOptions) { const { protocol, host, port } = NCWebsocketOptions; this.#baseUrl = protocol + '://' + host + ':' + port; } else { throw new Error('NCWebsocketOptions must contain either "protocol && host && port" or "baseUrl"'); } // 整理重连参数 const { enable = true, attempts = 10, delay = 5000 } = NCWebsocketOptions.reconnection ?? {}; this.#reconnection = { enable, attempts, delay, nowAttempts: 1 }; this.#debug = debug; this.#eventBus = new NCEventBus_1.NCEventBus(this); this.#echoMap = new Map(); } // ==================WebSocket操作============================= /** * await connect() 等待 ws 连接 */ async connect() { return new Promise((resolve, reject) => { this.#eventBus.emit('socket.connecting', { reconnection: this.#reconnection }); const socket = new isomorphic_ws_1.default(`${this.#baseUrl}?access_token=${this.#accessToken}`); socket.onopen = () => { this.#eventBus.emit('socket.open', { reconnection: this.#reconnection }); this.#reconnection.nowAttempts = 1; resolve(); }; socket.onclose = async (event) => { this.#eventBus.emit('socket.close', { code: event.code, reason: event.reason, reconnection: this.#reconnection, }); if (this.#reconnection.enable && this.#reconnection.nowAttempts < this.#reconnection.attempts) { this.#reconnection.nowAttempts++; setTimeout(async () => { try { await this.reconnect(); } catch (error) { reject(error); } }, this.#reconnection.delay); } }; socket.onmessage = (event) => this.#message(event.data); socket.onerror = (event) => { this.#eventBus.emit('socket.error', { reconnection: this.#reconnection, error_type: 'connect_error', errors: event?.error?.errors ?? [event?.error ?? null], }); if (this.#throwPromise) { if (this.#reconnection.enable && this.#reconnection.nowAttempts < this.#reconnection.attempts) { // 重连未到最后一次,等待继续重连,不抛出错误 return; } reject({ reconnection: this.#reconnection, error_type: 'connect_error', errors: event?.error?.errors ?? [event?.error ?? null], }); } }; this.#socket = socket; }); } disconnect() { if (this.#socket) { this.#socket.close(1000); this.#socket = undefined; } } async reconnect() { this.disconnect(); await this.connect(); } #message(data) { let strData; try { strData = data.toString(); // 检查数据是否看起来像有效的JSON (以 { 或 [ 开头) if (!(strData.trim().startsWith('{') || strData.trim().startsWith('['))) { Utils_1.logger.warn('[node-napcat-ts]', '[socket]', 'received non-JSON data:', strData); return; } let json = JSON.parse(strData); if (json.post_type === 'message' || json.post_type === 'message_sent') { if (json.message_format === 'string') { // 直接处理message字段,而不是整个json对象 json.message = (0, Utils_1.convertCQCodeToJSON)((0, Utils_1.CQCodeDecode)(json.message)); json.message_format = 'array'; } if (typeof json.raw_message === 'string') { json.raw_message = (0, Utils_1.CQCodeDecode)(json.raw_message); } } if (this.#debug) { Utils_1.logger.debug('[node-napcat-ts]', '[socket]', 'receive data'); Utils_1.logger.dir(json); } if (json.echo) { const handler = this.#echoMap.get(json.echo); if (handler) { if (json.retcode === 0) { this.#eventBus.emit('api.response.success', json); handler.onSuccess(json); } else { this.#eventBus.emit('api.response.failure', json); handler.onFailure(json); } } } else { if (json?.status === 'failed' && json?.echo === null) { this.#reconnection.enable = false; this.#eventBus.emit('socket.error', { reconnection: this.#reconnection, error_type: 'response_error', info: { url: this.#baseUrl, errno: json.retcode, message: json.message, }, }); if (this.#throwPromise) throw new Error(json.message); this.disconnect(); return; } this.#eventBus.parseMessage(json); } } catch (error) { Utils_1.logger.warn('[node-napcat-ts]', '[socket]', 'failed to parse JSON'); Utils_1.logger.dir(error); return; } } // ==================事件绑定============================= /** * 发送API请求 * @param method API 端点 * @param params 请求参数 */ send(method, params) { const echo = (0, nanoid_1.nanoid)(); const message = { action: method, params: params, echo, }; if (this.#debug) { Utils_1.logger.debug('[node-open-napcat] send request'); Utils_1.logger.dir(message); } return new Promise((resolve, reject) => { const onSuccess = (response) => { this.#echoMap.delete(echo); return resolve(response.data); }; const onFailure = (reason) => { this.#echoMap.delete(echo); return reject(reason); }; this.#echoMap.set(echo, { message, onSuccess, onFailure, }); this.#eventBus.emit('api.preSend', message); if (this.#socket === undefined) { reject({ status: 'failed', retcode: -1, data: null, message: 'api socket is not connected', echo: '', }); } else if (this.#socket.readyState === isomorphic_ws_1.default.CLOSING) { reject({ status: 'failed', retcode: -1, data: null, message: 'api socket is closed', echo: '', }); } else { this.#socket.send(JSON.stringify(message)); } }); } /** * 注册监听方法 * @param event * @param handle */ on(event, handle) { this.#eventBus.on(event, handle); return this; } /** * 只执行一次 * @param event * @param handle */ once(event, handle) { this.#eventBus.once(event, handle); return this; } /** * 解除监听方法 * @param event * @param handle */ off(event, handle) { this.#eventBus.off(event, handle); return this; } /** * 手动模拟触发某个事件 * @param type * @param context */ emit(type, context) { this.#eventBus.emit(type, context); return this; } } exports.NCWebsocketBase = NCWebsocketBase;