@serverkgg/bridge 0.0.11 → 0.0.12
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/src/blog/articleFrontmatter.test.ts +95 -0
- package/src/blog/articleFrontmatter.ts +99 -0
- package/src/blog/compile.test.ts +148 -0
- package/src/blog/compile.ts +172 -0
- package/src/blog/compiled.ts +22 -0
- package/src/blog/index.ts +3 -0
- package/src/cli/validate.ts +7 -0
- package/src/context/bridgeContext.ts +20 -0
- package/src/guides/compile.ts +70 -54
- package/src/manifest/manifest.test.ts +33 -0
- package/src/probes/index.ts +9 -0
- package/src/probes/minecraftPing.test.ts +344 -0
- package/src/probes/minecraftPing.ts +283 -0
- package/src/probes/raknetPing.test.ts +197 -0
- package/src/probes/raknetPing.ts +228 -0
- package/src/runtime/bridgeProbe.ts +13 -1
package/src/guides/compile.ts
CHANGED
|
@@ -52,49 +52,65 @@ const compileTags = (guideTags: GuideTag[], tags: string[]): CompiledGuideTag[]
|
|
|
52
52
|
});
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
export
|
|
56
|
-
|
|
55
|
+
export interface MarkdownBodyReader {
|
|
56
|
+
files: string[];
|
|
57
|
+
commands: string[];
|
|
58
|
+
images: Map<string, GuideImageAsset>;
|
|
59
|
+
read(path: string, text?: string): ParsedBlock[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const markdownBodyReader = (source: PackageSource): MarkdownBodyReader => {
|
|
57
63
|
const files: string[] = [];
|
|
58
64
|
const commands: string[] = [];
|
|
59
|
-
const
|
|
65
|
+
const images = new Map<string, GuideImageAsset>();
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
return {
|
|
68
|
+
files,
|
|
69
|
+
commands,
|
|
70
|
+
images,
|
|
71
|
+
read(path, text) {
|
|
72
|
+
const body = source.read(path);
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
if (!body) {
|
|
75
|
+
throw new Error(`missing markdown body ${path}`);
|
|
76
|
+
}
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
files.push(path);
|
|
69
79
|
|
|
70
|
-
|
|
80
|
+
const parsed = parseGuideMarkdown(path, text ?? new TextDecoder().decode(body));
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
commands.push(...parsed.commands);
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
for (const imagePath of parsed.images) {
|
|
85
|
+
if (images.has(imagePath)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
const imageBytes = source.read(imagePath);
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
if (!imageBytes) {
|
|
92
|
+
throw new Error(`missing guide image ${imagePath}`);
|
|
93
|
+
}
|
|
84
94
|
|
|
85
|
-
|
|
95
|
+
const verified = verifyAsset(imagePath, imageBytes);
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
images.set(imagePath, {
|
|
98
|
+
path: imagePath,
|
|
99
|
+
bytes: imageBytes,
|
|
100
|
+
mediaType: verified.mediaType,
|
|
101
|
+
width: verified.dimensions?.width ?? null,
|
|
102
|
+
height: verified.dimensions?.height ?? null,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
95
105
|
|
|
96
|
-
|
|
106
|
+
return parsed.blocks;
|
|
107
|
+
},
|
|
97
108
|
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const compileGuides = (source: PackageSource, guideTags: GuideTag[], entries: GuideEntry[]): GuidePack => {
|
|
112
|
+
const drafts: DraftGuide[] = [];
|
|
113
|
+
const reader = markdownBodyReader(source);
|
|
98
114
|
|
|
99
115
|
for (const entry of entries) {
|
|
100
116
|
drafts.push({
|
|
@@ -105,8 +121,8 @@ export const compileGuides = (source: PackageSource, guideTags: GuideTag[], entr
|
|
|
105
121
|
surfaces: entry.surfaces,
|
|
106
122
|
videoId: entry.video ?? null,
|
|
107
123
|
body: {
|
|
108
|
-
ar:
|
|
109
|
-
en:
|
|
124
|
+
ar: reader.read(entry.bodyPath.ar),
|
|
125
|
+
en: reader.read(entry.bodyPath.en),
|
|
110
126
|
},
|
|
111
127
|
});
|
|
112
128
|
}
|
|
@@ -114,13 +130,13 @@ export const compileGuides = (source: PackageSource, guideTags: GuideTag[], entr
|
|
|
114
130
|
return {
|
|
115
131
|
drafts,
|
|
116
132
|
files: [
|
|
117
|
-
...files,
|
|
118
|
-
...
|
|
133
|
+
...reader.files,
|
|
134
|
+
...reader.images.keys(),
|
|
119
135
|
],
|
|
120
136
|
images: [
|
|
121
|
-
...
|
|
137
|
+
...reader.images.values(),
|
|
122
138
|
],
|
|
123
|
-
commands,
|
|
139
|
+
commands: reader.commands,
|
|
124
140
|
};
|
|
125
141
|
};
|
|
126
142
|
|
|
@@ -142,10 +158,7 @@ export interface ResolvedGuideImage {
|
|
|
142
158
|
height: number | null;
|
|
143
159
|
}
|
|
144
160
|
|
|
145
|
-
export const
|
|
146
|
-
drafts: DraftGuide[],
|
|
147
|
-
resolved: Map<string, ResolvedGuideImage>,
|
|
148
|
-
): CompiledGuide[] => {
|
|
161
|
+
export const resolveGuideBlocks = (blocks: ParsedBlock[], resolved: Map<string, ResolvedGuideImage>): GuideBlock[] => {
|
|
149
162
|
const resolveLeaf = (block: ParsedLeafBlock): GuideLeafBlock => {
|
|
150
163
|
if (block.type !== "image") {
|
|
151
164
|
return block;
|
|
@@ -166,27 +179,30 @@ export const resolveGuideImages = (
|
|
|
166
179
|
};
|
|
167
180
|
};
|
|
168
181
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
182
|
+
return blocks.map((block) => {
|
|
183
|
+
if (block.type !== "conditional") {
|
|
184
|
+
return resolveLeaf(block);
|
|
185
|
+
}
|
|
174
186
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
187
|
+
return {
|
|
188
|
+
type: "conditional",
|
|
189
|
+
key: block.key,
|
|
190
|
+
when: block.when.map(resolveLeaf),
|
|
191
|
+
otherwise: block.otherwise.map(resolveLeaf),
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
};
|
|
183
195
|
|
|
196
|
+
export const resolveGuideImages = (
|
|
197
|
+
drafts: DraftGuide[],
|
|
198
|
+
resolved: Map<string, ResolvedGuideImage>,
|
|
199
|
+
): CompiledGuide[] => {
|
|
184
200
|
return drafts.map((draft) => {
|
|
185
201
|
return compiledGuideSchema.parse({
|
|
186
202
|
...draft,
|
|
187
203
|
body: {
|
|
188
|
-
ar:
|
|
189
|
-
en:
|
|
204
|
+
ar: resolveGuideBlocks(draft.body.ar, resolved),
|
|
205
|
+
en: resolveGuideBlocks(draft.body.en, resolved),
|
|
190
206
|
},
|
|
191
207
|
});
|
|
192
208
|
});
|
|
@@ -10,6 +10,26 @@ const text = (value: string) => ({
|
|
|
10
10
|
en: value,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
const guide = (id: string, extra: Record<string, unknown> = {}) => ({
|
|
14
|
+
id,
|
|
15
|
+
title: text(id),
|
|
16
|
+
summary: text(id),
|
|
17
|
+
bodyPath: {
|
|
18
|
+
ar: `guides/${id}/ar.md`,
|
|
19
|
+
en: `guides/${id}/en.md`,
|
|
20
|
+
},
|
|
21
|
+
...extra,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const guidePackage = (guides: unknown[]) => ({
|
|
25
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
26
|
+
kind: "GuidePackage",
|
|
27
|
+
metadata: {
|
|
28
|
+
id: "platform",
|
|
29
|
+
},
|
|
30
|
+
guides,
|
|
31
|
+
});
|
|
32
|
+
|
|
13
33
|
const base = () => ({
|
|
14
34
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
15
35
|
kind: "GamePackage",
|
|
@@ -586,6 +606,19 @@ describe("packageSchema", () => {
|
|
|
586
606
|
expect(result.success).toBe(false);
|
|
587
607
|
expect(result.error && z.prettifyError(result.error)).toContain('tag "start" is not declared in guideTags');
|
|
588
608
|
});
|
|
609
|
+
|
|
610
|
+
test("carries up to 64 guides", () => {
|
|
611
|
+
const many = (count: number) =>
|
|
612
|
+
Array.from(
|
|
613
|
+
{
|
|
614
|
+
length: count,
|
|
615
|
+
},
|
|
616
|
+
(_, index) => guide(`guide-${index}`),
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
expect(guidePackageSchema.safeParse(guidePackage(many(64))).success).toBe(true);
|
|
620
|
+
expect(guidePackageSchema.safeParse(guidePackage(many(65))).success).toBe(false);
|
|
621
|
+
});
|
|
589
622
|
});
|
|
590
623
|
|
|
591
624
|
describe("presence", () => {
|
package/src/probes/index.ts
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
BridgeA2sInfo,
|
|
3
|
+
BridgeMinecraftPingStatus,
|
|
4
|
+
BridgePingPlayers,
|
|
5
|
+
BridgeRaknetPingStatus,
|
|
6
|
+
} from "../context";
|
|
7
|
+
export { BridgeProbeCode, BridgeProbeError } from "../core";
|
|
1
8
|
export * from "./a2s";
|
|
9
|
+
export * from "./minecraftPing";
|
|
10
|
+
export * from "./raknetPing";
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { BridgeProbeCode, BridgeProbeError } from "../core";
|
|
3
|
+
import { parseStatus, probeMinecraftPing } from "./minecraftPing";
|
|
4
|
+
|
|
5
|
+
const HOST = "127.0.0.1";
|
|
6
|
+
|
|
7
|
+
const CLOSED_PORT = 59_441;
|
|
8
|
+
|
|
9
|
+
const varInt = (value: number) => {
|
|
10
|
+
const bytes: number[] = [];
|
|
11
|
+
|
|
12
|
+
let current = value >>> 0;
|
|
13
|
+
|
|
14
|
+
while (true) {
|
|
15
|
+
if ((current & ~0x7f) === 0) {
|
|
16
|
+
bytes.push(current);
|
|
17
|
+
|
|
18
|
+
return bytes;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
bytes.push((current & 0x7f) | 0x80);
|
|
22
|
+
|
|
23
|
+
current >>>= 7;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const readVarInt = (view: Uint8Array, offset: number) => {
|
|
28
|
+
let value = 0;
|
|
29
|
+
let size = 0;
|
|
30
|
+
|
|
31
|
+
while (size < 5) {
|
|
32
|
+
const byte = view[offset + size];
|
|
33
|
+
|
|
34
|
+
if (byte === undefined) {
|
|
35
|
+
throw new Error("varint is truncated");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
value |= (byte & 0x7f) << (7 * size);
|
|
39
|
+
size += 1;
|
|
40
|
+
|
|
41
|
+
if ((byte & 0x80) === 0) {
|
|
42
|
+
return {
|
|
43
|
+
value,
|
|
44
|
+
size,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error("varint is malformed");
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const response = (payload: unknown, packetId = 0x00) => {
|
|
53
|
+
const json = new TextEncoder().encode(JSON.stringify(payload));
|
|
54
|
+
|
|
55
|
+
const body = [
|
|
56
|
+
...varInt(packetId),
|
|
57
|
+
...varInt(json.length),
|
|
58
|
+
...json,
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
return new Uint8Array([
|
|
62
|
+
...varInt(body.length),
|
|
63
|
+
...body,
|
|
64
|
+
]);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const status = {
|
|
68
|
+
players: {
|
|
69
|
+
online: 3,
|
|
70
|
+
max: 20,
|
|
71
|
+
},
|
|
72
|
+
version: {
|
|
73
|
+
name: "1.21.11",
|
|
74
|
+
},
|
|
75
|
+
description: "A Serverk Minecraft Server",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handshakeServerName = (frame: Uint8Array) => {
|
|
79
|
+
const length = readVarInt(frame, 0);
|
|
80
|
+
const packetId = readVarInt(frame, length.size);
|
|
81
|
+
const protocol = readVarInt(frame, length.size + packetId.size);
|
|
82
|
+
const address = readVarInt(frame, length.size + packetId.size + protocol.size);
|
|
83
|
+
const start = length.size + packetId.size + protocol.size + address.size;
|
|
84
|
+
|
|
85
|
+
return new TextDecoder().decode(frame.subarray(start, start + address.value));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const listen = async (reply: Uint8Array | null, onServerName?: (serverName: string) => void) => {
|
|
89
|
+
return Bun.listen({
|
|
90
|
+
hostname: HOST,
|
|
91
|
+
port: 0,
|
|
92
|
+
socket: {
|
|
93
|
+
data(socket, data) {
|
|
94
|
+
if (onServerName) {
|
|
95
|
+
onServerName(handshakeServerName(data));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (reply) {
|
|
99
|
+
socket.write(reply);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
error() {},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
describe("reading a status response off the wire", () => {
|
|
108
|
+
test("a complete response gives the player counts, the version and the motd", () => {
|
|
109
|
+
expect(parseStatus(response(status))).toEqual({
|
|
110
|
+
players: {
|
|
111
|
+
online: 3,
|
|
112
|
+
max: 20,
|
|
113
|
+
},
|
|
114
|
+
version: "1.21.11",
|
|
115
|
+
description: "A Serverk Minecraft Server",
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("a response longer than one packet is still read, because the frame carries its own length", () => {
|
|
120
|
+
const packet = response(status);
|
|
121
|
+
const padded = new Uint8Array(packet.length + 4);
|
|
122
|
+
|
|
123
|
+
padded.set(packet);
|
|
124
|
+
|
|
125
|
+
expect(parseStatus(padded)?.players).toEqual({
|
|
126
|
+
online: 3,
|
|
127
|
+
max: 20,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a modern description object is read through its text", () => {
|
|
132
|
+
expect(
|
|
133
|
+
parseStatus(
|
|
134
|
+
response({
|
|
135
|
+
...status,
|
|
136
|
+
description: {
|
|
137
|
+
text: "hello",
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
)?.description,
|
|
141
|
+
).toBe("hello");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("a description we cannot read is dropped rather than failing the sample", () => {
|
|
145
|
+
expect(
|
|
146
|
+
parseStatus(
|
|
147
|
+
response({
|
|
148
|
+
...status,
|
|
149
|
+
description: {
|
|
150
|
+
extra: [],
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
)?.description,
|
|
154
|
+
).toBeNull();
|
|
155
|
+
expect(
|
|
156
|
+
parseStatus(
|
|
157
|
+
response({
|
|
158
|
+
...status,
|
|
159
|
+
description: null,
|
|
160
|
+
}),
|
|
161
|
+
)?.description,
|
|
162
|
+
).toBeNull();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("a missing version name is dropped rather than failing the sample", () => {
|
|
166
|
+
expect(
|
|
167
|
+
parseStatus(
|
|
168
|
+
response({
|
|
169
|
+
players: status.players,
|
|
170
|
+
}),
|
|
171
|
+
)?.version,
|
|
172
|
+
).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("fractional or negative counts are clamped to whole players", () => {
|
|
176
|
+
expect(
|
|
177
|
+
parseStatus(
|
|
178
|
+
response({
|
|
179
|
+
players: {
|
|
180
|
+
online: -1,
|
|
181
|
+
max: 20.7,
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
)?.players,
|
|
185
|
+
).toEqual({
|
|
186
|
+
online: 0,
|
|
187
|
+
max: 20,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("a response we have not fully received yet asks for more bytes", () => {
|
|
192
|
+
const packet = response(status);
|
|
193
|
+
|
|
194
|
+
expect(parseStatus(new Uint8Array(0))).toBeNull();
|
|
195
|
+
expect(parseStatus(packet.subarray(0, 1))).toBeNull();
|
|
196
|
+
expect(parseStatus(packet.subarray(0, packet.length - 4))).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("a large payload whose length needs two varint bytes is read whole", () => {
|
|
200
|
+
const long = {
|
|
201
|
+
players: status.players,
|
|
202
|
+
description: "n".repeat(400),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
expect(parseStatus(response(long))?.description).toBe(long.description);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("a packet that is not a status response is refused", () => {
|
|
209
|
+
expect(() => parseStatus(response(status, 0x01))).toThrow();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("a response with no player counts is refused, because a sample without them is a lie", () => {
|
|
213
|
+
expect(() =>
|
|
214
|
+
parseStatus(
|
|
215
|
+
response({
|
|
216
|
+
version: {
|
|
217
|
+
name: "1.21.11",
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
),
|
|
221
|
+
).toThrow();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("probeMinecraftPing", () => {
|
|
226
|
+
test("answers with the status a listening server sends back", async () => {
|
|
227
|
+
const server = await listen(response(status));
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const sample = await probeMinecraftPing(
|
|
231
|
+
{
|
|
232
|
+
address: HOST,
|
|
233
|
+
port: server.port,
|
|
234
|
+
},
|
|
235
|
+
2000,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
expect(sample.players).toEqual({
|
|
239
|
+
online: 3,
|
|
240
|
+
max: 20,
|
|
241
|
+
});
|
|
242
|
+
expect(sample.version).toBe("1.21.11");
|
|
243
|
+
expect(sample.latencyMs).toBeGreaterThanOrEqual(0);
|
|
244
|
+
} finally {
|
|
245
|
+
server.stop(true);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("puts the server name in the handshake so a virtual host still answers", async () => {
|
|
250
|
+
const seen: string[] = [];
|
|
251
|
+
|
|
252
|
+
const server = await listen(response(status), (serverName) => {
|
|
253
|
+
seen.push(serverName);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
await probeMinecraftPing(
|
|
258
|
+
{
|
|
259
|
+
address: HOST,
|
|
260
|
+
port: server.port,
|
|
261
|
+
serverName: "play.example.com",
|
|
262
|
+
},
|
|
263
|
+
2000,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(seen.at(0)).toBe("play.example.com");
|
|
267
|
+
} finally {
|
|
268
|
+
server.stop(true);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("falls back to the address when no server name is given", async () => {
|
|
273
|
+
const seen: string[] = [];
|
|
274
|
+
|
|
275
|
+
const server = await listen(response(status), (serverName) => {
|
|
276
|
+
seen.push(serverName);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
await probeMinecraftPing(
|
|
281
|
+
{
|
|
282
|
+
address: HOST,
|
|
283
|
+
port: server.port,
|
|
284
|
+
},
|
|
285
|
+
2000,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
expect(seen.at(0)).toBe(HOST);
|
|
289
|
+
} finally {
|
|
290
|
+
server.stop(true);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("rejects as unreachable when nothing listens on the port", async () => {
|
|
295
|
+
const error = await probeMinecraftPing(
|
|
296
|
+
{
|
|
297
|
+
address: HOST,
|
|
298
|
+
port: CLOSED_PORT,
|
|
299
|
+
},
|
|
300
|
+
2000,
|
|
301
|
+
).catch((reason: unknown) => reason);
|
|
302
|
+
|
|
303
|
+
expect(error).toBeInstanceOf(BridgeProbeError);
|
|
304
|
+
expect((error as BridgeProbeError).code).toBe(BridgeProbeCode.Unreachable);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("times out against a port that never answers", async () => {
|
|
308
|
+
const silent = await listen(null);
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const error = await probeMinecraftPing(
|
|
312
|
+
{
|
|
313
|
+
address: HOST,
|
|
314
|
+
port: silent.port,
|
|
315
|
+
},
|
|
316
|
+
250,
|
|
317
|
+
).catch((reason: unknown) => reason);
|
|
318
|
+
|
|
319
|
+
expect(error).toBeInstanceOf(BridgeProbeError);
|
|
320
|
+
expect((error as BridgeProbeError).code).toBe(BridgeProbeCode.TimedOut);
|
|
321
|
+
} finally {
|
|
322
|
+
silent.stop(true);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("rejects a reply that is not a status response", async () => {
|
|
327
|
+
const noisy = await listen(response(status, 0x01));
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const error = await probeMinecraftPing(
|
|
331
|
+
{
|
|
332
|
+
address: HOST,
|
|
333
|
+
port: noisy.port,
|
|
334
|
+
},
|
|
335
|
+
2000,
|
|
336
|
+
).catch((reason: unknown) => reason);
|
|
337
|
+
|
|
338
|
+
expect(error).toBeInstanceOf(BridgeProbeError);
|
|
339
|
+
expect((error as BridgeProbeError).code).toBe(BridgeProbeCode.Malformed);
|
|
340
|
+
} finally {
|
|
341
|
+
noisy.stop(true);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
});
|