quake2ts 0.0.557 → 0.0.562
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/packages/client/dist/browser/index.global.js +15 -15
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +343 -1
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +343 -1
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/index.d.ts.map +1 -1
- package/packages/client/dist/types/net/connection.d.ts +2 -0
- package/packages/client/dist/types/net/connection.d.ts.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/render/bloom.d.ts +19 -0
- package/packages/engine/dist/types/render/bloom.d.ts.map +1 -0
- package/packages/engine/dist/types/render/frame.d.ts +2 -0
- package/packages/engine/dist/types/render/frame.d.ts.map +1 -1
- package/packages/engine/dist/types/render/renderer.d.ts +2 -0
- package/packages/engine/dist/types/render/renderer.d.ts.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/server/dist/client.d.ts +51 -0
- package/packages/server/dist/client.js +100 -0
- package/packages/server/dist/dedicated.d.ts +69 -0
- package/packages/server/dist/dedicated.js +1013 -0
- package/packages/server/dist/index.cjs +27 -2
- package/packages/server/dist/index.d.ts +7 -161
- package/packages/server/dist/index.js +26 -2
- package/packages/server/dist/net/nodeWsDriver.d.ts +16 -0
- package/packages/server/dist/net/nodeWsDriver.js +122 -0
- package/packages/server/dist/protocol/player.d.ts +23 -0
- package/packages/server/dist/protocol/player.js +137 -0
- package/packages/server/dist/protocol/write.d.ts +7 -0
- package/packages/server/dist/protocol/write.js +167 -0
- package/packages/server/dist/protocol.d.ts +17 -0
- package/packages/server/dist/protocol.js +71 -0
- package/packages/server/dist/server.d.ts +50 -0
- package/packages/server/dist/server.js +12 -0
- package/packages/server/dist/server.test.d.ts +1 -0
- package/packages/server/dist/server.test.js +69 -0
- package/packages/server/dist/transport.d.ts +7 -0
- package/packages/server/dist/transport.js +1 -0
- package/packages/server/dist/transports/websocket.d.ts +11 -0
- package/packages/server/dist/transports/websocket.js +38 -0
- package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/test-utils/dist/index.cjs +498 -284
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +215 -146
- package/packages/test-utils/dist/index.d.ts +215 -146
- package/packages/test-utils/dist/index.js +488 -278
- package/packages/test-utils/dist/index.js.map +1 -1
- package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/server/dist/index.d.cts +0 -161
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { ServerCommand, TempEntity } from '@quake2ts/shared';
|
|
2
|
+
/**
|
|
3
|
+
* Writes a server command and its arguments to a BinaryWriter.
|
|
4
|
+
* This handles the serialization of generic arguments passed to multicast/unicast
|
|
5
|
+
* into the specific binary format expected by the protocol.
|
|
6
|
+
*/
|
|
7
|
+
export function writeServerCommand(writer, event, ...args) {
|
|
8
|
+
writer.writeByte(event);
|
|
9
|
+
switch (event) {
|
|
10
|
+
case ServerCommand.print: {
|
|
11
|
+
// args: [level: number, text: string]
|
|
12
|
+
const level = args[0];
|
|
13
|
+
const text = args[1];
|
|
14
|
+
writer.writeByte(level);
|
|
15
|
+
writer.writeString(text);
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
case ServerCommand.centerprint: {
|
|
19
|
+
// args: [text: string]
|
|
20
|
+
const text = args[0];
|
|
21
|
+
writer.writeString(text);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case ServerCommand.stufftext: {
|
|
25
|
+
// args: [text: string]
|
|
26
|
+
const text = args[0];
|
|
27
|
+
writer.writeString(text);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case ServerCommand.sound: {
|
|
31
|
+
// args: [flags, soundNum, volume?, attenuation?, offset?, ent?, pos?]
|
|
32
|
+
const flags = args[0];
|
|
33
|
+
const soundNum = args[1];
|
|
34
|
+
const volume = args[2];
|
|
35
|
+
const attenuation = args[3];
|
|
36
|
+
const offset = args[4];
|
|
37
|
+
const ent = args[5];
|
|
38
|
+
const pos = args[6];
|
|
39
|
+
writer.writeByte(flags);
|
|
40
|
+
writer.writeByte(soundNum);
|
|
41
|
+
if (flags & 1) { // SND_VOLUME
|
|
42
|
+
writer.writeByte(volume || 0);
|
|
43
|
+
}
|
|
44
|
+
if (flags & 2) { // SND_ATTENUATION
|
|
45
|
+
writer.writeByte(attenuation || 0);
|
|
46
|
+
}
|
|
47
|
+
if (flags & 16) { // SND_OFFSET
|
|
48
|
+
writer.writeByte(offset || 0);
|
|
49
|
+
}
|
|
50
|
+
if (flags & 8) { // SND_ENT
|
|
51
|
+
writer.writeShort(ent || 0);
|
|
52
|
+
}
|
|
53
|
+
if (flags & 4) { // SND_POS
|
|
54
|
+
if (pos) {
|
|
55
|
+
writer.writePos(pos);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
writer.writePos({ x: 0, y: 0, z: 0 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case ServerCommand.muzzleflash: {
|
|
64
|
+
// args: [entityIndex: number, flashType: number]
|
|
65
|
+
const entIndex = args[0];
|
|
66
|
+
const flashType = args[1];
|
|
67
|
+
writer.writeShort(entIndex);
|
|
68
|
+
writer.writeByte(flashType);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case ServerCommand.temp_entity: {
|
|
72
|
+
// args: [type: TempEntity, ...params]
|
|
73
|
+
const type = args[0];
|
|
74
|
+
writer.writeByte(type);
|
|
75
|
+
writeTempEntity(writer, type, args.slice(1));
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
console.warn(`writeServerCommand: Unhandled command ${event}`);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function writeTempEntity(writer, type, args) {
|
|
84
|
+
switch (type) {
|
|
85
|
+
case TempEntity.ROCKET_EXPLOSION:
|
|
86
|
+
case TempEntity.GRENADE_EXPLOSION:
|
|
87
|
+
case TempEntity.EXPLOSION1:
|
|
88
|
+
case TempEntity.EXPLOSION2:
|
|
89
|
+
case TempEntity.ROCKET_EXPLOSION_WATER:
|
|
90
|
+
case TempEntity.GRENADE_EXPLOSION_WATER:
|
|
91
|
+
case TempEntity.BFG_EXPLOSION:
|
|
92
|
+
case TempEntity.BFG_BIGEXPLOSION:
|
|
93
|
+
case TempEntity.PLASMA_EXPLOSION:
|
|
94
|
+
case TempEntity.PLAIN_EXPLOSION:
|
|
95
|
+
case TempEntity.TRACKER_EXPLOSION:
|
|
96
|
+
case TempEntity.EXPLOSION1_BIG:
|
|
97
|
+
case TempEntity.EXPLOSION1_NP:
|
|
98
|
+
case TempEntity.EXPLOSION1_NL:
|
|
99
|
+
case TempEntity.EXPLOSION2_NL:
|
|
100
|
+
case TempEntity.BERSERK_SLAM:
|
|
101
|
+
// Format: [pos]
|
|
102
|
+
writer.writePos(args[0]);
|
|
103
|
+
break;
|
|
104
|
+
case TempEntity.BLASTER:
|
|
105
|
+
case TempEntity.FLECHETTE:
|
|
106
|
+
// Format: [pos, dir]
|
|
107
|
+
writer.writePos(args[0]);
|
|
108
|
+
writer.writeDir(args[1]);
|
|
109
|
+
break;
|
|
110
|
+
case TempEntity.RAILTRAIL:
|
|
111
|
+
case TempEntity.DEBUGTRAIL:
|
|
112
|
+
case TempEntity.BUBBLETRAIL:
|
|
113
|
+
case TempEntity.BUBBLETRAIL2:
|
|
114
|
+
case TempEntity.BFG_LASER:
|
|
115
|
+
case TempEntity.LIGHTNING_BEAM:
|
|
116
|
+
case TempEntity.LIGHTNING:
|
|
117
|
+
// Format: [start, end]
|
|
118
|
+
writer.writePos(args[0]);
|
|
119
|
+
writer.writePos(args[1]);
|
|
120
|
+
break;
|
|
121
|
+
case TempEntity.LASER_SPARKS:
|
|
122
|
+
case TempEntity.WELDING_SPARKS:
|
|
123
|
+
case TempEntity.TUNNEL_SPARKS:
|
|
124
|
+
case TempEntity.ELECTRIC_SPARKS:
|
|
125
|
+
case TempEntity.HEATBEAM_SPARKS:
|
|
126
|
+
case TempEntity.HEATBEAM_STEAM:
|
|
127
|
+
case TempEntity.STEAM:
|
|
128
|
+
// Format: [count, pos, dir, color?]
|
|
129
|
+
// Q2: writeByte(count), writePos(start), writeDir(normal), writeByte(skin/color)
|
|
130
|
+
writer.writeByte(args[0]);
|
|
131
|
+
writer.writePos(args[1]);
|
|
132
|
+
writer.writeDir(args[2]);
|
|
133
|
+
writer.writeByte(args[3] || 0);
|
|
134
|
+
break;
|
|
135
|
+
case TempEntity.PARASITE_ATTACK:
|
|
136
|
+
case TempEntity.MEDIC_CABLE_ATTACK:
|
|
137
|
+
// Format: [entIndex, start, end]
|
|
138
|
+
// args[0] is Entity usually
|
|
139
|
+
const ent = args[0];
|
|
140
|
+
writer.writeShort(ent ? ent.index : 0);
|
|
141
|
+
writer.writePos(args[1]);
|
|
142
|
+
writer.writePos(args[2]);
|
|
143
|
+
break;
|
|
144
|
+
case TempEntity.GUNSHOT:
|
|
145
|
+
case TempEntity.BLOOD:
|
|
146
|
+
case TempEntity.SPARKS:
|
|
147
|
+
case TempEntity.BULLET_SPARKS:
|
|
148
|
+
case TempEntity.SCREEN_SPARKS:
|
|
149
|
+
case TempEntity.SHIELD_SPARKS:
|
|
150
|
+
// Format: [pos, dir]
|
|
151
|
+
writer.writePos(args[0]);
|
|
152
|
+
writer.writeDir(args[1]);
|
|
153
|
+
break;
|
|
154
|
+
case TempEntity.SPLASH:
|
|
155
|
+
case TempEntity.POWER_SPLASH:
|
|
156
|
+
case TempEntity.WIDOWSPLASH:
|
|
157
|
+
// Format: [count, pos, dir, color]
|
|
158
|
+
writer.writeByte(args[0]);
|
|
159
|
+
writer.writePos(args[1]);
|
|
160
|
+
writer.writeDir(args[2]);
|
|
161
|
+
writer.writeByte(args[3] || 0);
|
|
162
|
+
break;
|
|
163
|
+
default:
|
|
164
|
+
console.warn(`writeTempEntity: Unhandled TempEntity ${type}`);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BinaryStream, UserCommand } from '@quake2ts/shared';
|
|
2
|
+
export interface ClientMessageHandler {
|
|
3
|
+
onMove(checksum: number, lastFrame: number, userCmd: UserCommand): void;
|
|
4
|
+
onUserInfo(info: string): void;
|
|
5
|
+
onStringCmd(cmd: string): void;
|
|
6
|
+
onNop(): void;
|
|
7
|
+
onBad(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare class ClientMessageParser {
|
|
10
|
+
private stream;
|
|
11
|
+
private handler;
|
|
12
|
+
constructor(stream: BinaryStream, handler: ClientMessageHandler);
|
|
13
|
+
parseMessage(): void;
|
|
14
|
+
private parseMove;
|
|
15
|
+
private parseUserInfo;
|
|
16
|
+
private parseStringCmd;
|
|
17
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ClientCommand } from '@quake2ts/shared';
|
|
2
|
+
export class ClientMessageParser {
|
|
3
|
+
constructor(stream, handler) {
|
|
4
|
+
this.stream = stream;
|
|
5
|
+
this.handler = handler;
|
|
6
|
+
}
|
|
7
|
+
parseMessage() {
|
|
8
|
+
while (this.stream.hasMore()) {
|
|
9
|
+
const cmdId = this.stream.readByte();
|
|
10
|
+
if (cmdId === -1)
|
|
11
|
+
break;
|
|
12
|
+
switch (cmdId) {
|
|
13
|
+
case ClientCommand.move:
|
|
14
|
+
this.parseMove();
|
|
15
|
+
break;
|
|
16
|
+
case ClientCommand.userinfo:
|
|
17
|
+
this.parseUserInfo();
|
|
18
|
+
break;
|
|
19
|
+
case ClientCommand.stringcmd:
|
|
20
|
+
this.parseStringCmd();
|
|
21
|
+
break;
|
|
22
|
+
case ClientCommand.nop:
|
|
23
|
+
this.handler.onNop();
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
console.warn(`Unknown client command: ${cmdId}`);
|
|
27
|
+
this.handler.onBad();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
parseMove() {
|
|
33
|
+
const checksum = this.stream.readByte();
|
|
34
|
+
const lastFrame = this.stream.readLong();
|
|
35
|
+
// Read UserCmd
|
|
36
|
+
// TODO: support delta compression if needed (Q2 uses delta compression for usercmds)
|
|
37
|
+
// For now, assume full UserCmd or implement basic read
|
|
38
|
+
const msec = this.stream.readByte();
|
|
39
|
+
const buttons = this.stream.readByte();
|
|
40
|
+
const angles = {
|
|
41
|
+
x: this.stream.readShort() * (360.0 / 65536.0),
|
|
42
|
+
y: this.stream.readShort() * (360.0 / 65536.0),
|
|
43
|
+
z: this.stream.readShort() * (360.0 / 65536.0),
|
|
44
|
+
};
|
|
45
|
+
const forwardmove = this.stream.readShort();
|
|
46
|
+
const sidemove = this.stream.readShort();
|
|
47
|
+
const upmove = this.stream.readShort();
|
|
48
|
+
const impulse = this.stream.readByte();
|
|
49
|
+
const lightlevel = this.stream.readByte(); // Used for light-based stealth, usually ignored by server logic except for stats
|
|
50
|
+
const userCmd = {
|
|
51
|
+
msec,
|
|
52
|
+
buttons,
|
|
53
|
+
angles,
|
|
54
|
+
forwardmove,
|
|
55
|
+
sidemove,
|
|
56
|
+
upmove,
|
|
57
|
+
impulse,
|
|
58
|
+
lightlevel,
|
|
59
|
+
sequence: 0 // Server doesn't read sequence from packet body in standard protocol, it tracks it
|
|
60
|
+
};
|
|
61
|
+
this.handler.onMove(checksum, lastFrame, userCmd);
|
|
62
|
+
}
|
|
63
|
+
parseUserInfo() {
|
|
64
|
+
const info = this.stream.readString();
|
|
65
|
+
this.handler.onUserInfo(info);
|
|
66
|
+
}
|
|
67
|
+
parseStringCmd() {
|
|
68
|
+
const cmd = this.stream.readString();
|
|
69
|
+
this.handler.onStringCmd(cmd);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CollisionModel } from "@quake2ts/shared";
|
|
2
|
+
import { EntityState } from "@quake2ts/shared";
|
|
3
|
+
import { Client } from "./client.js";
|
|
4
|
+
/**
|
|
5
|
+
* ServerState corresponds to server_state_t in the original source.
|
|
6
|
+
*/
|
|
7
|
+
export declare enum ServerState {
|
|
8
|
+
Dead = 0,// no map loaded
|
|
9
|
+
Loading = 1,// spawning level edicts
|
|
10
|
+
Game = 2,// actively running
|
|
11
|
+
Cinematic = 3,
|
|
12
|
+
Demo = 4,
|
|
13
|
+
Pic = 5
|
|
14
|
+
}
|
|
15
|
+
export interface Challenge {
|
|
16
|
+
adr: string;
|
|
17
|
+
challenge: number;
|
|
18
|
+
time: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* ServerStatic holds the state that is constant across server restarts.
|
|
22
|
+
* Corresponds to server_static_t in the original source.
|
|
23
|
+
*/
|
|
24
|
+
export interface ServerStatic {
|
|
25
|
+
initialized: boolean;
|
|
26
|
+
realTime: number;
|
|
27
|
+
mapCmd: string;
|
|
28
|
+
spawnCount: number;
|
|
29
|
+
clients: (Client | null)[];
|
|
30
|
+
lastHeartbeat: number;
|
|
31
|
+
challenges: Challenge[];
|
|
32
|
+
demoFile?: any;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Server holds the state for the current running server instance.
|
|
36
|
+
* Corresponds to server_t in the original source.
|
|
37
|
+
*/
|
|
38
|
+
export interface Server {
|
|
39
|
+
state: ServerState;
|
|
40
|
+
attractLoop: boolean;
|
|
41
|
+
loadGame: boolean;
|
|
42
|
+
startTime: number;
|
|
43
|
+
time: number;
|
|
44
|
+
frame: number;
|
|
45
|
+
name: string;
|
|
46
|
+
collisionModel: CollisionModel | null;
|
|
47
|
+
configStrings: string[];
|
|
48
|
+
baselines: (EntityState | null)[];
|
|
49
|
+
multicastBuf: Uint8Array;
|
|
50
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServerState corresponds to server_state_t in the original source.
|
|
3
|
+
*/
|
|
4
|
+
export var ServerState;
|
|
5
|
+
(function (ServerState) {
|
|
6
|
+
ServerState[ServerState["Dead"] = 0] = "Dead";
|
|
7
|
+
ServerState[ServerState["Loading"] = 1] = "Loading";
|
|
8
|
+
ServerState[ServerState["Game"] = 2] = "Game";
|
|
9
|
+
ServerState[ServerState["Cinematic"] = 3] = "Cinematic";
|
|
10
|
+
ServerState[ServerState["Demo"] = 4] = "Demo";
|
|
11
|
+
ServerState[ServerState["Pic"] = 5] = "Pic";
|
|
12
|
+
})(ServerState || (ServerState = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { DedicatedServer, createServer } from './dedicated.js';
|
|
3
|
+
// Mock dependencies
|
|
4
|
+
vi.mock('node:fs/promises', () => ({
|
|
5
|
+
default: {
|
|
6
|
+
readFile: vi.fn().mockResolvedValue(Buffer.alloc(100))
|
|
7
|
+
}
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('@quake2ts/engine', () => ({
|
|
10
|
+
parseBsp: vi.fn().mockReturnValue({
|
|
11
|
+
planes: [],
|
|
12
|
+
nodes: [],
|
|
13
|
+
leafs: [],
|
|
14
|
+
brushes: [],
|
|
15
|
+
models: [],
|
|
16
|
+
leafLists: { leafBrushes: [] },
|
|
17
|
+
texInfo: [],
|
|
18
|
+
brushSides: [],
|
|
19
|
+
visibility: { numClusters: 0, clusters: [] } // Add visibility to mock
|
|
20
|
+
})
|
|
21
|
+
}));
|
|
22
|
+
// Mock Transport
|
|
23
|
+
class MockTransport {
|
|
24
|
+
async listen(port) {
|
|
25
|
+
return Promise.resolve();
|
|
26
|
+
}
|
|
27
|
+
close() { }
|
|
28
|
+
onConnection(callback) {
|
|
29
|
+
this.onConnectionCallback = callback;
|
|
30
|
+
}
|
|
31
|
+
onError(callback) {
|
|
32
|
+
this.onErrorCallback = callback;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
describe('DedicatedServer', () => {
|
|
36
|
+
let server;
|
|
37
|
+
let transport;
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
transport = new MockTransport();
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
if (server) {
|
|
43
|
+
server.stopServer();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
it('should create a server with default options', () => {
|
|
47
|
+
server = createServer({ transport });
|
|
48
|
+
expect(server).toBeInstanceOf(DedicatedServer);
|
|
49
|
+
});
|
|
50
|
+
it('should start and stop with custom transport', async () => {
|
|
51
|
+
server = createServer({ transport });
|
|
52
|
+
await expect(server.startServer('maps/test.bsp')).resolves.not.toThrow();
|
|
53
|
+
server.stopServer();
|
|
54
|
+
});
|
|
55
|
+
it('should fail to start without map', async () => {
|
|
56
|
+
server = createServer({ transport });
|
|
57
|
+
await expect(server.startServer()).rejects.toThrow('No map specified');
|
|
58
|
+
});
|
|
59
|
+
it('should kick player', () => {
|
|
60
|
+
server = createServer({ transport });
|
|
61
|
+
// Just verify method exists and runs safely on empty server
|
|
62
|
+
expect(() => server.kickPlayer(0)).not.toThrow();
|
|
63
|
+
});
|
|
64
|
+
it('should change map', async () => {
|
|
65
|
+
server = createServer({ transport });
|
|
66
|
+
await server.startServer('maps/q2dm1.bsp');
|
|
67
|
+
await expect(server.changeMap('maps/q2dm2.bsp')).resolves.not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NetworkTransport } from '../transport.js';
|
|
2
|
+
import { NetDriver } from '@quake2ts/shared';
|
|
3
|
+
export declare class WebSocketTransport implements NetworkTransport {
|
|
4
|
+
private wss;
|
|
5
|
+
private connectionCallback;
|
|
6
|
+
private errorCallback;
|
|
7
|
+
listen(port: number): Promise<void>;
|
|
8
|
+
close(): void;
|
|
9
|
+
onConnection(callback: (driver: NetDriver, info?: any) => void): void;
|
|
10
|
+
onError(callback: (error: Error) => void): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { WebSocketServer } from 'ws';
|
|
2
|
+
import { WebSocketNetDriver } from '../net/nodeWsDriver.js';
|
|
3
|
+
export class WebSocketTransport {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.wss = null;
|
|
6
|
+
this.connectionCallback = null;
|
|
7
|
+
this.errorCallback = null;
|
|
8
|
+
}
|
|
9
|
+
async listen(port) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
this.wss = new WebSocketServer({ port });
|
|
12
|
+
this.wss.on('listening', () => resolve());
|
|
13
|
+
this.wss.on('connection', (ws, req) => {
|
|
14
|
+
const driver = new WebSocketNetDriver();
|
|
15
|
+
driver.attach(ws);
|
|
16
|
+
if (this.connectionCallback) {
|
|
17
|
+
this.connectionCallback(driver, req);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
this.wss.on('error', (err) => {
|
|
21
|
+
if (this.errorCallback)
|
|
22
|
+
this.errorCallback(err);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
close() {
|
|
27
|
+
if (this.wss) {
|
|
28
|
+
this.wss.close();
|
|
29
|
+
this.wss = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
onConnection(callback) {
|
|
33
|
+
this.connectionCallback = callback;
|
|
34
|
+
}
|
|
35
|
+
onError(callback) {
|
|
36
|
+
this.errorCallback = callback;
|
|
37
|
+
}
|
|
38
|
+
}
|