You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
20 KiB
152 lines
20 KiB
|
2 weeks ago
|
"use strict";
|
||
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
|
exports.WsServer = void 0;
|
||
|
|
const events_1 = require("events");
|
||
|
|
const ws_1 = require("ws");
|
||
|
|
function parseClientInfo(url) {
|
||
|
|
const m = /[?&]client=([^&]*)/.exec(url !== null && url !== void 0 ? url : '');
|
||
|
|
const type = m ? decodeURIComponent(m[1]) : 'unknown';
|
||
|
|
const id = `${type}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
||
|
|
return { id, type };
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* 主进程内嵌的 WebSocket 服务。
|
||
|
|
* 游戏侧注入的桥接脚本作为客户端连上来,面板/主进程通过该服务与之通信。
|
||
|
|
* 单客户端模型:同一时刻只保留最新连接,新连接会顶替旧连接(旧连接收到 4002 关闭码)。
|
||
|
|
*/
|
||
|
|
class WsServer extends events_1.EventEmitter {
|
||
|
|
constructor() {
|
||
|
|
super(...arguments);
|
||
|
|
this.srv = null;
|
||
|
|
this.port = 0;
|
||
|
|
this._token = '';
|
||
|
|
this.active = null;
|
||
|
|
this.activeInfo = null;
|
||
|
|
}
|
||
|
|
get isRunning() {
|
||
|
|
return !!this.srv;
|
||
|
|
}
|
||
|
|
get boundPort() {
|
||
|
|
return this.port;
|
||
|
|
}
|
||
|
|
/** 当前活跃客户端数量(0 或 1)。 */
|
||
|
|
get clients() {
|
||
|
|
return this.active && this.active.readyState === ws_1.WebSocket.OPEN ? 1 : 0;
|
||
|
|
}
|
||
|
|
/** 当前活跃客户端信息(未连接时为 null)。 */
|
||
|
|
get clientInfo() {
|
||
|
|
return this.active && this.active.readyState === ws_1.WebSocket.OPEN ? this.activeInfo : null;
|
||
|
|
}
|
||
|
|
/** 连接令牌:非空时仅放行携带相同 token 的客户端。 */
|
||
|
|
get token() {
|
||
|
|
return this._token;
|
||
|
|
}
|
||
|
|
set token(v) {
|
||
|
|
this._token = v;
|
||
|
|
}
|
||
|
|
/** 启动监听,返回实际绑定端口(若端口被占用则抛错)。 */
|
||
|
|
start(port) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
if (this.srv) {
|
||
|
|
resolve(this.port);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.port = port;
|
||
|
|
const srv = new ws_1.WebSocketServer({ port });
|
||
|
|
srv.on('error', (err) => reject(err));
|
||
|
|
srv.on('listening', () => {
|
||
|
|
this.emit('listening');
|
||
|
|
});
|
||
|
|
srv.on('connection', (ws, req) => {
|
||
|
|
// 注意:ws 库服务端连接对象的 ws.url 为 undefined,必须从握手请求 req.url
|
||
|
|
// 取路径+query(如 /?token=xxx&client=h5),否则 client 类型解析不到。
|
||
|
|
const reqUrl = (req && req.url) || ws.url || '';
|
||
|
|
// 连接鉴权:设置了 token 时,校验 URL query 中的 token
|
||
|
|
if (this._token) {
|
||
|
|
const m = /[?&]token=([^&]*)/.exec(reqUrl);
|
||
|
|
const got = m ? decodeURIComponent(m[1]) : '';
|
||
|
|
if (got !== this._token) {
|
||
|
|
// 供主进程记录日志:客户端带什么 token,服务端期望什么 token,一目了然
|
||
|
|
this.emit('auth-fail', got || '(无token)', reqUrl);
|
||
|
|
ws.close(4001, 'token mismatch');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// 单客户端:顶替旧连接(4002 = 被新连接替换),避免多实例数据混流
|
||
|
|
if (this.active && this.active.readyState === ws_1.WebSocket.OPEN) {
|
||
|
|
const old = this.active;
|
||
|
|
const oldInfo = this.activeInfo;
|
||
|
|
this.active = null;
|
||
|
|
this.activeInfo = null;
|
||
|
|
try {
|
||
|
|
old.close(4002, 'replaced by new client');
|
||
|
|
}
|
||
|
|
catch (_a) {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
this.emit('disconnect', old, oldInfo);
|
||
|
|
}
|
||
|
|
const { id, type } = parseClientInfo(reqUrl);
|
||
|
|
this.active = ws;
|
||
|
|
this.activeInfo = { id, type, url: reqUrl, connectedAt: Date.now() };
|
||
|
|
this.emit('connection', ws, this.activeInfo);
|
||
|
|
ws.on('message', (raw) => {
|
||
|
|
let data = raw;
|
||
|
|
try {
|
||
|
|
data = JSON.parse(raw.toString());
|
||
|
|
}
|
||
|
|
catch (_a) {
|
||
|
|
/* 非 JSON,原样保留 */
|
||
|
|
}
|
||
|
|
this.emit('message', ws, data);
|
||
|
|
});
|
||
|
|
ws.on('close', () => {
|
||
|
|
if (this.active === ws) {
|
||
|
|
this.active = null;
|
||
|
|
this.activeInfo = null;
|
||
|
|
}
|
||
|
|
this.emit('disconnect', ws, this.activeInfo);
|
||
|
|
});
|
||
|
|
ws.on('error', (err) => this.emit('client-error', ws, err));
|
||
|
|
});
|
||
|
|
this.srv = srv;
|
||
|
|
resolve(this.port);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
stop() {
|
||
|
|
return new Promise((resolve) => {
|
||
|
|
if (!this.srv) {
|
||
|
|
resolve();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const srv = this.srv;
|
||
|
|
for (const c of srv.clients) {
|
||
|
|
try {
|
||
|
|
c.close();
|
||
|
|
}
|
||
|
|
catch (_a) {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.srv = null;
|
||
|
|
this.active = null;
|
||
|
|
this.activeInfo = null;
|
||
|
|
this.port = 0;
|
||
|
|
srv.close(() => resolve());
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/** 向当前活跃客户端发送一条 JSON 消息。 */
|
||
|
|
broadcast(obj) {
|
||
|
|
if (!this.srv || !this.active || this.active.readyState !== ws_1.WebSocket.OPEN) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
this.active.send(JSON.stringify(obj));
|
||
|
|
}
|
||
|
|
catch (_a) {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
exports.WsServer = WsServer;
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3Mtc2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc291cmNlL3dzLXNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtQ0FBc0M7QUFDdEMsMkJBQWdEO0FBVWhELFNBQVMsZUFBZSxDQUFDLEdBQVc7SUFDaEMsTUFBTSxDQUFDLEdBQUcsb0JBQW9CLENBQUMsSUFBSSxDQUFDLEdBQUcsYUFBSCxHQUFHLGNBQUgsR0FBRyxHQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztJQUN0RCxNQUFNLEVBQUUsR0FBRyxHQUFHLElBQUksSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQzFGLE9BQU8sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFDeEIsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFhLFFBQVMsU0FBUSxxQkFBWTtJQUExQzs7UUFDWSxRQUFHLEdBQTJCLElBQUksQ0FBQztRQUNuQyxTQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQ1QsV0FBTSxHQUFHLEVBQUUsQ0FBQztRQUNaLFdBQU0sR0FBcUIsSUFBSSxDQUFDO1FBQ2hDLGVBQVUsR0FBd0IsSUFBSSxDQUFDO0lBa0luRCxDQUFDO0lBaElHLElBQUksU0FBUztRQUNULE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUM7SUFDdEIsQ0FBQztJQUVELElBQUksU0FBUztRQUNULE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQztJQUNyQixDQUFDO0lBRUQs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
|