napcat-node-ts-sdk
Version:
napcat SDK for Node
259 lines (258 loc) • 9.48 kB
JavaScript
"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;