feat: handle clientSideHandshake

This commit is contained in:
Wesley F. Young 2024-10-09 12:21:22 +08:00
parent e5518b7284
commit ca9614f8e3

View File

@ -3,7 +3,7 @@ import { NapCatLaanaAdapter } from '..';
import { RawData, WebSocket, WebSocketServer } from 'ws'; import { RawData, WebSocket, WebSocketServer } from 'ws';
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import { NapCatCore } from '@/core'; import { NapCatCore } from '@/core';
import { LaanaDataWrapper, LaanaEventWrapper, LaanaMessage } from '@laana-proto/def'; import { LaanaDataWrapper, LaanaEventWrapper, LaanaMessage, LaanaServerSideHandshake_Result } from '@laana-proto/def';
export class LaanaWsServerAdapter implements ILaanaNetworkAdapter { export class LaanaWsServerAdapter implements ILaanaNetworkAdapter {
wsServer: WebSocketServer; wsServer: WebSocketServer;
@ -26,79 +26,47 @@ export class LaanaWsServerAdapter implements ILaanaNetworkAdapter {
port: port, port: port,
host: ip, host: ip,
}); });
this.wsServer.on('connection', async (wsClient) => { this.wsServer.on('connection', async (wsClient, request) => {
if (!this.isOpen) { if (!this.isOpen) {
wsClient.close(); wsClient.close();
return; return;
} }
this.core.context.logger.log(`接收到来自 ${request.socket.remoteAddress} 的连接`);
wsClient.on('error', (err) => wsClient.on('error', (err) =>
this.core.context.logger.log('连接出现错误', err.message)); this.core.context.logger.log('连接出现错误', err.message));
wsClient.on('message', (message) => { wsClient.once('message', (message) => {
let binaryData: Uint8Array; const data = this.handleRawData(message);
if (message instanceof Buffer) { if (data.data.oneofKind === 'clientSideHandshake') {
binaryData = message; // TODO: verify protocol version
} else if (message instanceof ArrayBuffer) { const token = data.data.clientSideHandshake.token;
binaryData = new Uint8Array(message); if (token !== this.token) {
} else { // message is an array of Buffers this.core.context.logger.logWarn(`${request.socket.remoteAddress} 的客户端握手失败token 不匹配`);
binaryData = Buffer.concat(message); this.checkStateAndReply(LaanaDataWrapper.toBinary({
} data: {
this.wsClientsMutex.runExclusive(async () => { oneofKind: 'serverSideHandshake',
const data = LaanaDataWrapper.fromBinary( serverSideHandshake: {
Uint8Array.from(binaryData), serverVersion: '',
); result: LaanaServerSideHandshake_Result.wrongToken,
if (data.data.oneofKind === 'actionPing') { },
const actionName = data.data.actionPing.ping.oneofKind; },
if (actionName === undefined) { }), wsClient);
this.core.context.logger.logError('未知的动作名', actionName); wsClient.close();
return; return;
} }
const actionHandler = this.laana.actions[actionName];
if (!actionHandler) {
this.core.context.logger.logError('未实现的动作名', actionName);
return;
}
try {
// eslint-disable-next-line
// @ts-ignore
const ret = await actionHandler(data.data.actionPing.ping[actionName]);
this.checkStateAndReply(LaanaDataWrapper.toBinary({ this.checkStateAndReply(LaanaDataWrapper.toBinary({
data: { data: {
oneofKind: 'actionPong', oneofKind: 'serverSideHandshake',
actionPong: { serverSideHandshake: {
clientPingId: data.data.actionPing.clientPingId, serverVersion: '',
// eslint-disable-next-line result: LaanaServerSideHandshake_Result.success,
// @ts-ignore
pong: {
oneofKind: actionName,
[actionName]: ret,
},
}, },
}, },
}), wsClient); }), wsClient);
} catch (e: any) { this.addClient(wsClient);
this.core.context.logger.logError('处理动作时出现错误', e);
this.checkStateAndReply(LaanaDataWrapper.toBinary({
data: {
oneofKind: 'actionPong',
actionPong: {
clientPingId: data.data.actionPing.clientPingId,
pong: {
oneofKind: 'failed',
failed: {
reason: e.toString(),
} }
},
},
},
}), wsClient);
}
} else {
this.core.context.logger.logWarn('未知的数据包类型', data.data.oneofKind);
}
}).catch((e) => this.core.context.logger.logError('处理请求时出现错误', e));
}); });
wsClient.on('ping', () => { wsClient.on('ping', () => {
@ -110,10 +78,6 @@ export class LaanaWsServerAdapter implements ILaanaNetworkAdapter {
this.wsClients = this.wsClients.filter(client => client !== wsClient); this.wsClients = this.wsClients.filter(client => client !== wsClient);
}); });
}); });
await this.wsClientsMutex.runExclusive(async () => {
this.wsClients.push(wsClient);
});
}); });
this.wsServer.on('error', (err) => { this.wsServer.on('error', (err) => {
@ -172,6 +136,78 @@ export class LaanaWsServerAdapter implements ILaanaNetworkAdapter {
} }
} }
private handleRawData(message: RawData) {
let binaryData: Uint8Array;
if (message instanceof Buffer) {
binaryData = message;
} else if (message instanceof ArrayBuffer) {
binaryData = new Uint8Array(message);
} else { // message is an array of Buffers
binaryData = Buffer.concat(message);
}
return LaanaDataWrapper.fromBinary(binaryData);
}
private async addClient(wsClient: WebSocket) {
wsClient.on('message', (message) => {
const data = this.handleRawData(message);
if (data.data.oneofKind === 'actionPing') {
const actionName = data.data.actionPing.ping.oneofKind;
if (actionName === undefined) {
this.core.context.logger.logError('未知的动作名', actionName);
return;
}
const actionHandler = this.laana.actions[actionName];
if (!actionHandler) {
this.core.context.logger.logError('未实现的动作名', actionName);
return;
}
try {
// eslint-disable-next-line
// @ts-ignore
const ret = await actionHandler(data.data.actionPing.ping[actionName]);
this.checkStateAndReply(LaanaDataWrapper.toBinary({
data: {
oneofKind: 'actionPong',
actionPong: {
clientPingId: data.data.actionPing.clientPingId,
// eslint-disable-next-line
// @ts-ignore
pong: {
oneofKind: actionName,
[actionName]: ret,
},
},
},
}), wsClient);
} catch (e: any) {
this.core.context.logger.logError('处理动作时出现错误', e);
this.checkStateAndReply(LaanaDataWrapper.toBinary({
data: {
oneofKind: 'actionPong',
actionPong: {
clientPingId: data.data.actionPing.clientPingId,
pong: {
oneofKind: 'failed',
failed: {
reason: e.toString(),
},
},
},
},
}), wsClient);
}
} else {
this.core.context.logger.logWarn('未知的数据包类型', data.data.oneofKind);
}
});
await this.wsClientsMutex.runExclusive(async () => {
this.wsClients.push(wsClient);
});
}
private checkStateAndReply(data: RawData, wsClient: WebSocket) { private checkStateAndReply(data: RawData, wsClient: WebSocket) {
if (wsClient.readyState === WebSocket.OPEN) { if (wsClient.readyState === WebSocket.OPEN) {
wsClient.send(data); wsClient.send(data);