@serverkgg/bridge 0.0.13 → 0.0.14
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 +1 -1
- package/src/context/bridgeContext.ts +9 -2
- package/src/manifest/manifest.ts +1 -1
- package/src/modules/bridgeModule.ts +2 -1
- package/src/rcon/index.ts +3 -2
- package/src/rcon/rconAccess.test.ts +286 -0
- package/src/rcon/rconAccess.ts +252 -0
- package/src/rcon/rconClient.test.ts +1544 -0
- package/src/rcon/rconClient.ts +275 -0
- package/src/rcon/rconError.ts +21 -0
- package/src/rcon/rconFrame.test.ts +269 -0
- package/src/rcon/{sourceRconFrame.ts → rconFrame.ts} +23 -14
- package/src/rcon/rconIds.test.ts +59 -0
- package/src/rcon/rconIds.ts +33 -0
- package/src/rcon/rconLink.ts +427 -0
- package/src/rcon/rconWriter.test.ts +95 -0
- package/src/rcon/rconWriter.ts +63 -0
- package/src/runtime/bridgeConsoleHandler.test.ts +196 -0
- package/src/runtime/bridgeConsoleHandler.ts +26 -0
- package/src/runtime/bridgeRcon.ts +6 -2
- package/src/runtime/serve.ts +10 -1
- package/src/rcon/sourceRcon.test.ts +0 -308
- package/src/rcon/sourceRcon.ts +0 -276
- package/src/rcon/sourceRconFrame.test.ts +0 -152
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { BridgeCommandOptions, BridgeContext, BridgeReply } from "../context";
|
|
3
|
+
import { BridgeKind, BridgeUserError } from "../core";
|
|
4
|
+
import type { BridgeDriver, BridgeTerminalModule } from "../modules";
|
|
5
|
+
import { createConsoleHandler } from "./bridgeConsoleHandler";
|
|
6
|
+
|
|
7
|
+
const context = {} as unknown as BridgeContext;
|
|
8
|
+
|
|
9
|
+
const driverWith = (terminal: BridgeTerminalModule | undefined): BridgeDriver => ({
|
|
10
|
+
install: {
|
|
11
|
+
kind: BridgeKind.Install,
|
|
12
|
+
run: async () => {
|
|
13
|
+
return;
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
lifecycle: {
|
|
17
|
+
kind: BridgeKind.Lifecycle,
|
|
18
|
+
command: async () => [
|
|
19
|
+
"run",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
terminal,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const terminalWith = (run: BridgeTerminalModule["run"]): BridgeTerminalModule => ({
|
|
26
|
+
kind: BridgeKind.Terminal,
|
|
27
|
+
commands: [
|
|
28
|
+
{
|
|
29
|
+
name: "Save",
|
|
30
|
+
summary: {
|
|
31
|
+
ar: "يحفظ العالم.",
|
|
32
|
+
en: "Save the world.",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
run,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const handlerFor = (driver: BridgeDriver) => {
|
|
40
|
+
const written: string[] = [];
|
|
41
|
+
|
|
42
|
+
const handler = createConsoleHandler({
|
|
43
|
+
driver,
|
|
44
|
+
context,
|
|
45
|
+
async onStdin(input: string, options: BridgeCommandOptions): Promise<BridgeReply> {
|
|
46
|
+
written.push(input);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
sent: input,
|
|
50
|
+
line: options.expect ? "matched" : null,
|
|
51
|
+
groups: {},
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
handler,
|
|
58
|
+
written,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe("routing a console line to the driver before the game's stdin", () => {
|
|
63
|
+
test("answers with the driver's reply when the driver handles the line", async () => {
|
|
64
|
+
const { handler, written } = handlerFor(
|
|
65
|
+
driverWith(
|
|
66
|
+
terminalWith(async (_context, input) => {
|
|
67
|
+
return {
|
|
68
|
+
sent: input,
|
|
69
|
+
line: "Save requested",
|
|
70
|
+
groups: {},
|
|
71
|
+
};
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(await handler("Save", {})).toEqual({
|
|
77
|
+
sent: "Save",
|
|
78
|
+
line: "Save requested",
|
|
79
|
+
groups: {},
|
|
80
|
+
});
|
|
81
|
+
expect(written).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("hands the driver the same line the caller typed", async () => {
|
|
85
|
+
const seen: string[] = [];
|
|
86
|
+
|
|
87
|
+
const { handler } = handlerFor(
|
|
88
|
+
driverWith(
|
|
89
|
+
terminalWith(async (_context, input) => {
|
|
90
|
+
seen.push(input);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
sent: input,
|
|
94
|
+
line: null,
|
|
95
|
+
groups: {},
|
|
96
|
+
};
|
|
97
|
+
}),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await handler("Broadcast hello world", {});
|
|
102
|
+
|
|
103
|
+
expect(seen).toEqual([
|
|
104
|
+
"Broadcast hello world",
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("writes the line to stdin when the driver answers null", async () => {
|
|
109
|
+
const { handler, written } = handlerFor(
|
|
110
|
+
driverWith(
|
|
111
|
+
terminalWith(async () => {
|
|
112
|
+
return null;
|
|
113
|
+
}),
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(await handler("save-all", {})).toEqual({
|
|
118
|
+
sent: "save-all",
|
|
119
|
+
line: null,
|
|
120
|
+
groups: {},
|
|
121
|
+
});
|
|
122
|
+
expect(written).toEqual([
|
|
123
|
+
"save-all",
|
|
124
|
+
]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("keeps the expect and timeout of a line the driver leaves alone", async () => {
|
|
128
|
+
const { handler } = handlerFor(
|
|
129
|
+
driverWith(
|
|
130
|
+
terminalWith(async () => {
|
|
131
|
+
return null;
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(
|
|
137
|
+
(
|
|
138
|
+
await handler("list", {
|
|
139
|
+
expect: /players online/,
|
|
140
|
+
timeoutMs: 1000,
|
|
141
|
+
})
|
|
142
|
+
).line,
|
|
143
|
+
).toBe("matched");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("writes the line to stdin when the driver declares no handler", async () => {
|
|
147
|
+
const { handler, written } = handlerFor(
|
|
148
|
+
driverWith({
|
|
149
|
+
kind: BridgeKind.Terminal,
|
|
150
|
+
commands: [],
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await handler("list", {});
|
|
155
|
+
|
|
156
|
+
expect(written).toEqual([
|
|
157
|
+
"list",
|
|
158
|
+
]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("writes the line to stdin when the game declares no terminal at all", async () => {
|
|
162
|
+
const { handler, written } = handlerFor(driverWith(undefined));
|
|
163
|
+
|
|
164
|
+
await handler("list", {});
|
|
165
|
+
|
|
166
|
+
expect(written).toEqual([
|
|
167
|
+
"list",
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("refuses the line with the driver's own text when the driver rejects it", async () => {
|
|
172
|
+
const refusal = {
|
|
173
|
+
ar: "هذا الأمر يشتغل داخل اللعبة بس.",
|
|
174
|
+
en: "That command only works inside the game.",
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const { handler, written } = handlerFor(
|
|
178
|
+
driverWith(
|
|
179
|
+
terminalWith(async () => {
|
|
180
|
+
throw new BridgeUserError(refusal);
|
|
181
|
+
}),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
await handler("TeleportToPlayer meslzy", {});
|
|
187
|
+
|
|
188
|
+
expect.unreachable();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
expect(error).toBeInstanceOf(BridgeUserError);
|
|
191
|
+
expect((error as BridgeUserError).text).toEqual(refusal);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
expect(written).toEqual([]);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BridgeCommandOptions, BridgeContext, BridgeReply } from "../context";
|
|
2
|
+
import type { BridgeDriver } from "../modules";
|
|
3
|
+
|
|
4
|
+
export type BridgeConsoleHandler = (input: string, options: BridgeCommandOptions) => Promise<BridgeReply>;
|
|
5
|
+
|
|
6
|
+
export interface BridgeConsoleOptions {
|
|
7
|
+
driver: BridgeDriver;
|
|
8
|
+
context: BridgeContext;
|
|
9
|
+
onStdin: BridgeConsoleHandler;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const createConsoleHandler = (options: BridgeConsoleOptions): BridgeConsoleHandler => {
|
|
13
|
+
return async (input, commandOptions) => {
|
|
14
|
+
const run = options.driver.terminal?.run;
|
|
15
|
+
|
|
16
|
+
if (run) {
|
|
17
|
+
const reply = await run(options.context, input);
|
|
18
|
+
|
|
19
|
+
if (reply) {
|
|
20
|
+
return reply;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return await options.onStdin(input, commandOptions);
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import type { BridgeRcons } from "../context";
|
|
2
|
-
import {
|
|
2
|
+
import { RconClient } from "../rcon";
|
|
3
3
|
|
|
4
4
|
const RCON_HOST = "127.0.0.1";
|
|
5
5
|
|
|
6
6
|
export const createBridgeRcons = (): BridgeRcons => {
|
|
7
7
|
return {
|
|
8
8
|
async source(options) {
|
|
9
|
-
|
|
9
|
+
const client = new RconClient({
|
|
10
10
|
...options,
|
|
11
11
|
host: RCON_HOST,
|
|
12
12
|
});
|
|
13
|
+
|
|
14
|
+
await client.connect();
|
|
15
|
+
|
|
16
|
+
return client;
|
|
13
17
|
},
|
|
14
18
|
};
|
|
15
19
|
};
|
package/src/runtime/serve.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type BridgeStatus,
|
|
11
11
|
} from "../protocol";
|
|
12
12
|
import { configPath, readConfig, socketPath, statePath } from "./bridgeConfigReader";
|
|
13
|
+
import { createConsoleHandler } from "./bridgeConsoleHandler";
|
|
13
14
|
import { createContext } from "./bridgeContext";
|
|
14
15
|
import { validateDriver } from "./bridgeDriver";
|
|
15
16
|
import { createEventRing } from "./bridgeEvents";
|
|
@@ -111,6 +112,14 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
111
112
|
|
|
112
113
|
const registry = createRegistry(driver, context, supervisor, ring);
|
|
113
114
|
|
|
115
|
+
const runConsole = createConsoleHandler({
|
|
116
|
+
driver,
|
|
117
|
+
context,
|
|
118
|
+
async onStdin(input, commandOptions) {
|
|
119
|
+
return await supervisor.command(input, commandOptions);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
114
123
|
let current = desired;
|
|
115
124
|
|
|
116
125
|
const status = (): BridgeStatus => {
|
|
@@ -205,7 +214,7 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
205
214
|
await supervisor.stop();
|
|
206
215
|
},
|
|
207
216
|
async onCommand(input, expect, timeoutMs) {
|
|
208
|
-
return await
|
|
217
|
+
return await runConsole(input, {
|
|
209
218
|
expect: expect === undefined ? undefined : new RegExp(expect),
|
|
210
219
|
timeoutMs,
|
|
211
220
|
});
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { BridgeRconCode, BridgeRconError } from "../core";
|
|
3
|
-
import { sourceRcon } from "./sourceRcon";
|
|
4
|
-
import {
|
|
5
|
-
concatBytes,
|
|
6
|
-
decodeSourceRconFrames,
|
|
7
|
-
encodeSourceRconFrame,
|
|
8
|
-
SOURCE_RCON_AUTH_FAILED_ID,
|
|
9
|
-
type SourceRconFrame,
|
|
10
|
-
SourceRconRequest,
|
|
11
|
-
SourceRconResponse,
|
|
12
|
-
} from "./sourceRconFrame";
|
|
13
|
-
|
|
14
|
-
type Reply = (frame: SourceRconFrame) => void;
|
|
15
|
-
|
|
16
|
-
const HOST = "127.0.0.1";
|
|
17
|
-
|
|
18
|
-
const rconServer = (handle: (frame: SourceRconFrame, reply: Reply) => void) => {
|
|
19
|
-
const pending = new WeakMap<object, Uint8Array>();
|
|
20
|
-
|
|
21
|
-
return Bun.listen({
|
|
22
|
-
hostname: HOST,
|
|
23
|
-
port: 0,
|
|
24
|
-
socket: {
|
|
25
|
-
data(socket, chunk) {
|
|
26
|
-
const decoded = decodeSourceRconFrames(concatBytes(pending.get(socket) ?? new Uint8Array(0), chunk));
|
|
27
|
-
|
|
28
|
-
pending.set(socket, decoded.rest);
|
|
29
|
-
|
|
30
|
-
for (const frame of decoded.frames) {
|
|
31
|
-
handle(frame, (out) => {
|
|
32
|
-
socket.write(encodeSourceRconFrame(out));
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const authOk = (frame: SourceRconFrame, reply: Reply) => {
|
|
41
|
-
reply({
|
|
42
|
-
id: frame.id,
|
|
43
|
-
type: SourceRconResponse.Value,
|
|
44
|
-
body: "",
|
|
45
|
-
});
|
|
46
|
-
reply({
|
|
47
|
-
id: frame.id,
|
|
48
|
-
type: SourceRconResponse.Auth,
|
|
49
|
-
body: "",
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
describe("sourceRcon", () => {
|
|
54
|
-
test("rejects a wrong password with an auth-failed error", async () => {
|
|
55
|
-
const server = rconServer((frame, reply) => {
|
|
56
|
-
if (frame.type !== SourceRconRequest.Auth) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
reply({
|
|
61
|
-
id: frame.id,
|
|
62
|
-
type: SourceRconResponse.Value,
|
|
63
|
-
body: "",
|
|
64
|
-
});
|
|
65
|
-
reply({
|
|
66
|
-
id: SOURCE_RCON_AUTH_FAILED_ID,
|
|
67
|
-
type: SourceRconResponse.Auth,
|
|
68
|
-
body: "",
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const error = await sourceRcon({
|
|
74
|
-
host: HOST,
|
|
75
|
-
port: server.port,
|
|
76
|
-
password: "wrong",
|
|
77
|
-
timeoutMs: 2000,
|
|
78
|
-
}).catch((reason: unknown) => reason);
|
|
79
|
-
|
|
80
|
-
expect(error).toBeInstanceOf(BridgeRconError);
|
|
81
|
-
expect((error as BridgeRconError).code).toBe(BridgeRconCode.AuthFailed);
|
|
82
|
-
} finally {
|
|
83
|
-
server.stop(true);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("authenticates and returns a single-packet reply", async () => {
|
|
88
|
-
const server = rconServer((frame, reply) => {
|
|
89
|
-
if (frame.type === SourceRconRequest.Auth) {
|
|
90
|
-
authOk(frame, reply);
|
|
91
|
-
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
reply({
|
|
96
|
-
id: frame.id,
|
|
97
|
-
type: SourceRconResponse.Value,
|
|
98
|
-
body: frame.body.length === 0 ? "" : `ran ${frame.body}`,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const client = await sourceRcon({
|
|
104
|
-
host: HOST,
|
|
105
|
-
port: server.port,
|
|
106
|
-
password: "right",
|
|
107
|
-
timeoutMs: 2000,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(await client.command("ListPlayers")).toBe("ran ListPlayers");
|
|
111
|
-
|
|
112
|
-
client.close();
|
|
113
|
-
} finally {
|
|
114
|
-
server.stop(true);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("joins a multi-packet reply at the probe echo", async () => {
|
|
119
|
-
const server = rconServer((frame, reply) => {
|
|
120
|
-
if (frame.type === SourceRconRequest.Auth) {
|
|
121
|
-
authOk(frame, reply);
|
|
122
|
-
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (frame.type === SourceRconResponse.Value) {
|
|
127
|
-
reply({
|
|
128
|
-
id: frame.id,
|
|
129
|
-
type: SourceRconResponse.Value,
|
|
130
|
-
body: "",
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
for (const part of [
|
|
137
|
-
"first-",
|
|
138
|
-
"second-",
|
|
139
|
-
"third",
|
|
140
|
-
]) {
|
|
141
|
-
reply({
|
|
142
|
-
id: frame.id,
|
|
143
|
-
type: SourceRconResponse.Value,
|
|
144
|
-
body: part,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
const client = await sourceRcon({
|
|
151
|
-
host: HOST,
|
|
152
|
-
port: server.port,
|
|
153
|
-
password: "right",
|
|
154
|
-
timeoutMs: 2000,
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
expect(await client.command("status")).toBe("first-second-third");
|
|
158
|
-
|
|
159
|
-
client.close();
|
|
160
|
-
} finally {
|
|
161
|
-
server.stop(true);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("settles on the first value frame when multi-packet is off", async () => {
|
|
166
|
-
let probes = 0;
|
|
167
|
-
|
|
168
|
-
const server = rconServer((frame, reply) => {
|
|
169
|
-
if (frame.type === SourceRconRequest.Auth) {
|
|
170
|
-
authOk(frame, reply);
|
|
171
|
-
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (frame.type === SourceRconResponse.Value) {
|
|
176
|
-
probes += 1;
|
|
177
|
-
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
reply({
|
|
182
|
-
id: frame.id,
|
|
183
|
-
type: SourceRconResponse.Value,
|
|
184
|
-
body: "single",
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const client = await sourceRcon({
|
|
190
|
-
host: HOST,
|
|
191
|
-
port: server.port,
|
|
192
|
-
password: "right",
|
|
193
|
-
timeoutMs: 2000,
|
|
194
|
-
multiPacket: false,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
expect(await client.command("/players")).toBe("single");
|
|
198
|
-
expect(probes).toBe(0);
|
|
199
|
-
|
|
200
|
-
client.close();
|
|
201
|
-
} finally {
|
|
202
|
-
server.stop(true);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("runs queued commands one after another", async () => {
|
|
207
|
-
const server = rconServer((frame, reply) => {
|
|
208
|
-
if (frame.type === SourceRconRequest.Auth) {
|
|
209
|
-
authOk(frame, reply);
|
|
210
|
-
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
reply({
|
|
215
|
-
id: frame.id,
|
|
216
|
-
type: SourceRconResponse.Value,
|
|
217
|
-
body: frame.body.length === 0 ? "" : frame.body.toUpperCase(),
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const client = await sourceRcon({
|
|
223
|
-
host: HOST,
|
|
224
|
-
port: server.port,
|
|
225
|
-
password: "right",
|
|
226
|
-
timeoutMs: 2000,
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
expect(
|
|
230
|
-
await Promise.all([
|
|
231
|
-
client.command("one"),
|
|
232
|
-
client.command("two"),
|
|
233
|
-
client.command("three"),
|
|
234
|
-
]),
|
|
235
|
-
).toEqual([
|
|
236
|
-
"ONE",
|
|
237
|
-
"TWO",
|
|
238
|
-
"THREE",
|
|
239
|
-
]);
|
|
240
|
-
|
|
241
|
-
client.close();
|
|
242
|
-
} finally {
|
|
243
|
-
server.stop(true);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("times out a command the server never answers", async () => {
|
|
248
|
-
const server = rconServer((frame, reply) => {
|
|
249
|
-
if (frame.type === SourceRconRequest.Auth) {
|
|
250
|
-
authOk(frame, reply);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
const client = await sourceRcon({
|
|
256
|
-
host: HOST,
|
|
257
|
-
port: server.port,
|
|
258
|
-
password: "right",
|
|
259
|
-
timeoutMs: 250,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
const error = await client.command("status").catch((reason: unknown) => reason);
|
|
263
|
-
|
|
264
|
-
expect(error).toBeInstanceOf(BridgeRconError);
|
|
265
|
-
expect((error as BridgeRconError).code).toBe(BridgeRconCode.TimedOut);
|
|
266
|
-
} finally {
|
|
267
|
-
server.stop(true);
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
test("refuses a command body above the packet limit", async () => {
|
|
272
|
-
const server = rconServer((frame, reply) => {
|
|
273
|
-
if (frame.type === SourceRconRequest.Auth) {
|
|
274
|
-
authOk(frame, reply);
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
try {
|
|
279
|
-
const client = await sourceRcon({
|
|
280
|
-
host: HOST,
|
|
281
|
-
port: server.port,
|
|
282
|
-
password: "right",
|
|
283
|
-
timeoutMs: 2000,
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
const error = await client.command("x".repeat(5000)).catch((reason: unknown) => reason);
|
|
287
|
-
|
|
288
|
-
expect(error).toBeInstanceOf(BridgeRconError);
|
|
289
|
-
expect((error as BridgeRconError).code).toBe(BridgeRconCode.Oversized);
|
|
290
|
-
|
|
291
|
-
client.close();
|
|
292
|
-
} finally {
|
|
293
|
-
server.stop(true);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test("reports an unreachable port", async () => {
|
|
298
|
-
const error = await sourceRcon({
|
|
299
|
-
host: HOST,
|
|
300
|
-
port: 59_431,
|
|
301
|
-
password: "right",
|
|
302
|
-
timeoutMs: 1000,
|
|
303
|
-
}).catch((reason: unknown) => reason);
|
|
304
|
-
|
|
305
|
-
expect(error).toBeInstanceOf(BridgeRconError);
|
|
306
|
-
expect((error as BridgeRconError).code).toBe(BridgeRconCode.Unreachable);
|
|
307
|
-
});
|
|
308
|
-
});
|