@serverkgg/bridge 0.1.2 → 0.1.3
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 -2
- package/rules/package.md +60 -0
- package/src/blog/articleFrontmatter.test.ts +10 -10
- package/src/blog/compile.test.ts +9 -9
- package/src/bot/botDeploy.test.ts +16 -16
- package/src/bot/botEnvironment.test.ts +8 -8
- package/src/bot/botIdentity.test.ts +12 -12
- package/src/bot/botPanel.test.ts +13 -13
- package/src/bot/botPanel.ts +4 -4
- package/src/bot/botState.test.ts +3 -3
- package/src/bot/botTerminal.test.ts +5 -5
- package/src/bot/botVariables.test.ts +8 -8
- package/src/bot/detect/botToken.test.ts +6 -6
- package/src/bot/detect/detect.test.ts +21 -21
- package/src/bot/detect/detectTable.test.ts +5 -5
- package/src/bot/detect/envExample.test.ts +2 -2
- package/src/bot/detect/envScan.test.ts +4 -4
- package/src/bot/detect/inferBotPlatform.test.ts +4 -4
- package/src/bot/detect/keys.test.ts +3 -3
- package/src/bot/detect/sensitiveKey.test.ts +5 -5
- package/src/bot/detect/serverkJson.test.ts +5 -5
- package/src/catalogs/curseforge.test.ts +35 -35
- package/src/catalogs/thunderstore.test.ts +9 -9
- package/src/cli/detectTable.test.ts +3 -3
- package/src/cli/link.test.ts +11 -11
- package/src/cli/main.ts +8 -0
- package/src/cli/rules.test.ts +198 -0
- package/src/cli/rules.ts +188 -0
- package/src/cli/validate.test.ts +52 -30
- package/src/cli/validate.ts +2 -0
- package/src/codecs/ini.test.ts +13 -13
- package/src/codecs/javaProperties.test.ts +11 -11
- package/src/codecs/sourceCfg.test.ts +14 -14
- package/src/codecs/xmlProperties.test.ts +10 -10
- package/src/core/bridgePort.test.ts +6 -6
- package/src/core/bridgeUserError.test.ts +4 -4
- package/src/core/bridgeValue.test.ts +5 -5
- package/src/core/describeDownloadFailure.test.ts +12 -12
- package/src/core/describeDownloadFailure.ts +10 -10
- package/src/guides/markdown.test.ts +31 -31
- package/src/install/installStamp.test.ts +9 -9
- package/src/manifest/extension.ts +36 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +208 -90
- package/src/manifest/manifest.ts +34 -0
- package/src/net/loopback.test.ts +10 -10
- package/src/presence/rosterSync.test.ts +7 -7
- package/src/probes/a2s.test.ts +5 -5
- package/src/probes/minecraftPing.test.ts +17 -17
- package/src/probes/raknetPing.test.ts +13 -13
- package/src/rcon/rconAccess.test.ts +20 -20
- package/src/rcon/rconAccess.ts +13 -13
- package/src/rcon/rconClient.test.ts +49 -49
- package/src/rcon/rconFrame.test.ts +14 -14
- package/src/rcon/rconIds.test.ts +4 -4
- package/src/rcon/rconWriter.test.ts +6 -6
- package/src/runtime/bridgeActivity.test.ts +15 -15
- package/src/runtime/bridgeBackupLease.test.ts +9 -9
- package/src/runtime/bridgeConfigReader.test.ts +5 -5
- package/src/runtime/bridgeConsoleHandler.test.ts +8 -8
- package/src/runtime/bridgeContext.test.ts +5 -5
- package/src/runtime/bridgeDriver.test.ts +94 -94
- package/src/runtime/bridgeEvents.test.ts +15 -15
- package/src/runtime/bridgeFiles.test.ts +54 -54
- package/src/runtime/bridgeInstall.test.ts +10 -10
- package/src/runtime/bridgeLiveConfig.test.ts +6 -6
- package/src/runtime/bridgePending.test.ts +6 -6
- package/src/runtime/bridgeRedaction.test.ts +16 -16
- package/src/runtime/bridgeRegistry.test.ts +52 -52
- package/src/runtime/bridgeRegistry.ts +2 -2
- package/src/runtime/bridgeServer.test.ts +31 -31
- package/src/runtime/bridgeSetup.test.ts +27 -27
- package/src/runtime/bridgeSetup.ts +1 -1
- package/src/runtime/bridgeSetupHandlers.test.ts +13 -13
- package/src/runtime/bridgeSignal.test.ts +7 -7
- package/src/runtime/bridgeSupervisor.test.ts +42 -42
- package/src/runtime/serve.ts +4 -4
- package/src/steam/avatars.test.ts +13 -13
- package/src/steam/steamApp.test.ts +8 -8
- package/src/steam/steamcmd.test.ts +17 -17
- package/src/steam/steamcmd.ts +4 -4
- package/src/steam/steamcmdNarrator.test.ts +4 -4
- package/src/steam/workshop.test.ts +5 -5
- package/src/utils/changedSettingKeys.test.ts +5 -5
- package/src/utils/describeExecFailure.test.ts +4 -4
- package/src/utils/describeExecFailure.ts +1 -1
- package/src/utils/sameSettingValue.test.ts +7 -7
- package/src/utils/token.test.ts +7 -7
- package/src/wine/protonCommand.test.ts +5 -5
- package/src/wine/wineCommand.test.ts +6 -6
- package/src/wine/winePrefix.test.ts +6 -6
- package/src/wire/activity.test.ts +8 -8
- package/src/wire/panel.test.ts +28 -28
package/src/rcon/rconAccess.ts
CHANGED
|
@@ -55,29 +55,29 @@ const ACCESS_DETAIL_TITLE: LocalizedText = {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
const ACCESS_DESCRIPTION: LocalizedText = {
|
|
58
|
-
ar: "الوصول عن بُعد
|
|
58
|
+
ar: "الوصول عن بُعد يتيح لأداة خارجية التحكم بسيرفرك بكلمة المرور هذه. تنتقل كلمة المرور عبر الإنترنت بدون تشفير، فأوقف الوصول عن بُعد عندما لا تستخدمه أي أداة، ولا تشارك كلمة المرور مع أحد. وإذا تسربت، غيّرها فورًا.",
|
|
59
59
|
en: "Remote access lets an external tool control your server with this password. The password crosses the internet in the clear, so turn remote access off when no tool is using it, and never share the password. Rotate it the moment it leaks.",
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
const OPEN_BADGE: BridgeDetailBadge = {
|
|
63
63
|
label: {
|
|
64
|
-
ar: "
|
|
65
|
-
en: "
|
|
64
|
+
ar: "مفعّل",
|
|
65
|
+
en: "On",
|
|
66
66
|
},
|
|
67
67
|
tone: BridgeDetailTone.Success,
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
const CLOSED_BADGE: BridgeDetailBadge = {
|
|
71
71
|
label: {
|
|
72
|
-
ar: "
|
|
73
|
-
en: "
|
|
72
|
+
ar: "موقوف",
|
|
73
|
+
en: "Off",
|
|
74
74
|
},
|
|
75
75
|
tone: BridgeDetailTone.Neutral,
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
const PENDING_BADGE: BridgeDetailBadge = {
|
|
79
79
|
label: {
|
|
80
|
-
ar: "
|
|
80
|
+
ar: "يُطبّق بعد الريستارت",
|
|
81
81
|
en: "Applies after restart",
|
|
82
82
|
},
|
|
83
83
|
tone: BridgeDetailTone.Warning,
|
|
@@ -213,7 +213,7 @@ export const rconAccessSections = (options: RconAccessSectionsOptions = {}): Bri
|
|
|
213
213
|
reinstall: false,
|
|
214
214
|
confirm: BridgeConfirm.Normal,
|
|
215
215
|
confirmText: {
|
|
216
|
-
ar: "
|
|
216
|
+
ar: "يتم ريستارت سيرفرك ليُطبّق التغيير.",
|
|
217
217
|
en: "We restart your server so the change takes effect.",
|
|
218
218
|
},
|
|
219
219
|
fields: [
|
|
@@ -223,16 +223,16 @@ export const rconAccessSections = (options: RconAccessSectionsOptions = {}): Bri
|
|
|
223
223
|
label: ACCESS_TITLE,
|
|
224
224
|
help: dedicated
|
|
225
225
|
? {
|
|
226
|
-
ar: `يفتح منفذ ${protocol.ar}
|
|
226
|
+
ar: `يفتح منفذ ${protocol.ar} لتتحكم بسيرفرك من أداة خارجية. يظهر المنفذ مع بقية عناوين سيرفرك بعد عودته للتشغيل.`,
|
|
227
227
|
en: `Opens the ${protocol.en} port so an external tool can control your server. The port appears with your server's other addresses once it is back up.`,
|
|
228
228
|
}
|
|
229
229
|
: {
|
|
230
|
-
ar:
|
|
230
|
+
ar: `يفعّل الوصول عن بُعد لتتحكم بسيرفرك من أداة خارجية عبر عنوان اللعبة ومنفذها نفسيهما.`,
|
|
231
231
|
en: `Turns remote access on so an external tool can control your server over the same address and port as the game.`,
|
|
232
232
|
},
|
|
233
233
|
warning: {
|
|
234
|
-
ar: "كلمة المرور
|
|
235
|
-
en: "The password crosses the internet in the clear. Turn it off when you
|
|
234
|
+
ar: "تنتقل كلمة المرور عبر الإنترنت بدون تشفير. أوقف الوصول عن بُعد عندما لا تستخدمه.",
|
|
235
|
+
en: "The password crosses the internet in the clear. Turn it off when you're not using it.",
|
|
236
236
|
},
|
|
237
237
|
},
|
|
238
238
|
],
|
|
@@ -251,13 +251,13 @@ export const rconAccessSections = (options: RconAccessSectionsOptions = {}): Bri
|
|
|
251
251
|
},
|
|
252
252
|
confirm: BridgeConfirm.Strong,
|
|
253
253
|
confirmText: {
|
|
254
|
-
ar: "كل أداة خارجية
|
|
254
|
+
ar: "ستتوقف كل أداة خارجية تستخدمها حتى تضيف إليها كلمة المرور الجديدة.",
|
|
255
255
|
en: "Every external tool you use stops working until you paste the new password into it.",
|
|
256
256
|
},
|
|
257
257
|
},
|
|
258
258
|
],
|
|
259
259
|
empty: {
|
|
260
|
-
ar: "شغّل سيرفرك مرة
|
|
260
|
+
ar: "شغّل سيرفرك مرة واحدة لتوليد كلمة المرور.",
|
|
261
261
|
en: "Start your server once to generate the password.",
|
|
262
262
|
},
|
|
263
263
|
visibleWhen: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import type { Socket } from "bun";
|
|
3
3
|
import { BridgeRconCode, BridgeRconError } from "../core";
|
|
4
4
|
import { RconClient } from "./rconClient";
|
|
@@ -259,7 +259,7 @@ const connect = (fake: RconFake, options: Partial<ConstructorParameters<typeof R
|
|
|
259
259
|
};
|
|
260
260
|
|
|
261
261
|
describe("RconClient authentication", () => {
|
|
262
|
-
|
|
262
|
+
it("connects through the empty preamble and the auth reply", async () => {
|
|
263
263
|
const fake = createFake();
|
|
264
264
|
const client = connect(fake);
|
|
265
265
|
|
|
@@ -275,7 +275,7 @@ describe("RconClient authentication", () => {
|
|
|
275
275
|
}
|
|
276
276
|
});
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
it("connects when the server answers with the auth reply only", async () => {
|
|
279
279
|
const fake = createFake({
|
|
280
280
|
auth(frame, peer) {
|
|
281
281
|
peer.frame({
|
|
@@ -299,7 +299,7 @@ describe("RconClient authentication", () => {
|
|
|
299
299
|
}
|
|
300
300
|
});
|
|
301
301
|
|
|
302
|
-
|
|
302
|
+
it("rejects a wrong password and ends the socket", async () => {
|
|
303
303
|
const fake = createFake();
|
|
304
304
|
|
|
305
305
|
fake.password = "another-password";
|
|
@@ -314,7 +314,7 @@ describe("RconClient authentication", () => {
|
|
|
314
314
|
}
|
|
315
315
|
});
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
it("reports a server that closes during authentication", async () => {
|
|
318
318
|
const fake = createFake({
|
|
319
319
|
auth(_frame, peer) {
|
|
320
320
|
peer.end();
|
|
@@ -332,7 +332,7 @@ describe("RconClient authentication", () => {
|
|
|
332
332
|
}
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
|
|
335
|
+
it("times out an authentication the server never answers", async () => {
|
|
336
336
|
const fake = createFake({
|
|
337
337
|
auth() {},
|
|
338
338
|
});
|
|
@@ -348,7 +348,7 @@ describe("RconClient authentication", () => {
|
|
|
348
348
|
}
|
|
349
349
|
});
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
it("dials once for two sequential connect calls", async () => {
|
|
352
352
|
const fake = createFake();
|
|
353
353
|
const client = connect(fake);
|
|
354
354
|
|
|
@@ -364,7 +364,7 @@ describe("RconClient authentication", () => {
|
|
|
364
364
|
}
|
|
365
365
|
});
|
|
366
366
|
|
|
367
|
-
|
|
367
|
+
it("never starts a third dial when an abandoned dial settles", async () => {
|
|
368
368
|
let dials = 0;
|
|
369
369
|
|
|
370
370
|
const fake = createFake({
|
|
@@ -418,7 +418,7 @@ describe("RconClient authentication", () => {
|
|
|
418
418
|
}
|
|
419
419
|
});
|
|
420
420
|
|
|
421
|
-
|
|
421
|
+
it("shares one dial between concurrent connect calls", async () => {
|
|
422
422
|
const fake = createFake();
|
|
423
423
|
const client = connect(fake);
|
|
424
424
|
|
|
@@ -439,7 +439,7 @@ describe("RconClient authentication", () => {
|
|
|
439
439
|
});
|
|
440
440
|
|
|
441
441
|
describe("RconClient packet ids", () => {
|
|
442
|
-
|
|
442
|
+
it("sends an increasing id for every command", async () => {
|
|
443
443
|
const fake = createFake();
|
|
444
444
|
const client = connect(fake);
|
|
445
445
|
|
|
@@ -460,7 +460,7 @@ describe("RconClient packet ids", () => {
|
|
|
460
460
|
}
|
|
461
461
|
});
|
|
462
462
|
|
|
463
|
-
|
|
463
|
+
it("wraps its ids without ever sending zero or minus one", async () => {
|
|
464
464
|
const fake = createFake();
|
|
465
465
|
|
|
466
466
|
const client = connect(fake, {
|
|
@@ -486,7 +486,7 @@ describe("RconClient packet ids", () => {
|
|
|
486
486
|
}
|
|
487
487
|
});
|
|
488
488
|
|
|
489
|
-
|
|
489
|
+
it("ignores a reply carrying a foreign id", async () => {
|
|
490
490
|
const fake = createFake({
|
|
491
491
|
command(frame, peer) {
|
|
492
492
|
peer.frame({
|
|
@@ -513,7 +513,7 @@ describe("RconClient packet ids", () => {
|
|
|
513
513
|
}
|
|
514
514
|
});
|
|
515
515
|
|
|
516
|
-
|
|
516
|
+
it("ignores unsolicited keep-alive frames around a reply", async () => {
|
|
517
517
|
const fake = createFake({
|
|
518
518
|
command(frame, peer) {
|
|
519
519
|
peer.frame({
|
|
@@ -550,7 +550,7 @@ describe("RconClient packet ids", () => {
|
|
|
550
550
|
}
|
|
551
551
|
});
|
|
552
552
|
|
|
553
|
-
|
|
553
|
+
it("ignores a frame of an unknown type", async () => {
|
|
554
554
|
const fake = createFake({
|
|
555
555
|
command(frame, peer) {
|
|
556
556
|
peer.frame({
|
|
@@ -579,7 +579,7 @@ describe("RconClient packet ids", () => {
|
|
|
579
579
|
});
|
|
580
580
|
|
|
581
581
|
describe("RconClient fragmentation", () => {
|
|
582
|
-
|
|
582
|
+
it("joins a reply written one byte at a time", async () => {
|
|
583
583
|
const fake = createFake({
|
|
584
584
|
command(frame, peer) {
|
|
585
585
|
const bytes = encodeRconFrame({
|
|
@@ -609,7 +609,7 @@ describe("RconClient fragmentation", () => {
|
|
|
609
609
|
}
|
|
610
610
|
});
|
|
611
611
|
|
|
612
|
-
|
|
612
|
+
it("reads two frames delivered in one chunk", async () => {
|
|
613
613
|
const fake = createFake({
|
|
614
614
|
command(frame, peer) {
|
|
615
615
|
peer.raw(
|
|
@@ -640,7 +640,7 @@ describe("RconClient fragmentation", () => {
|
|
|
640
640
|
}
|
|
641
641
|
});
|
|
642
642
|
|
|
643
|
-
|
|
643
|
+
it("joins a reply split at the body limit", async () => {
|
|
644
644
|
const fake = createFake({
|
|
645
645
|
command(frame, peer) {
|
|
646
646
|
peer.frame({
|
|
@@ -669,7 +669,7 @@ describe("RconClient fragmentation", () => {
|
|
|
669
669
|
});
|
|
670
670
|
|
|
671
671
|
describe("RconClient multi-packet replies", () => {
|
|
672
|
-
|
|
672
|
+
it("settles a short reply without ever sending a probe", async () => {
|
|
673
673
|
const fake = createFake();
|
|
674
674
|
const client = connect(fake);
|
|
675
675
|
|
|
@@ -683,7 +683,7 @@ describe("RconClient multi-packet replies", () => {
|
|
|
683
683
|
}
|
|
684
684
|
});
|
|
685
685
|
|
|
686
|
-
|
|
686
|
+
it("joins every value frame in order at the probe echo", async () => {
|
|
687
687
|
const fake = createFake({
|
|
688
688
|
command(frame, peer) {
|
|
689
689
|
for (const part of [
|
|
@@ -711,7 +711,7 @@ describe("RconClient multi-packet replies", () => {
|
|
|
711
711
|
}
|
|
712
712
|
});
|
|
713
713
|
|
|
714
|
-
|
|
714
|
+
it("drops the srcds trailer that follows the echo", async () => {
|
|
715
715
|
const fake = createFake({
|
|
716
716
|
command(frame, peer) {
|
|
717
717
|
peer.frame({
|
|
@@ -746,7 +746,7 @@ describe("RconClient multi-packet replies", () => {
|
|
|
746
746
|
}
|
|
747
747
|
});
|
|
748
748
|
|
|
749
|
-
|
|
749
|
+
it("terminates on the minecraft echo without appending it", async () => {
|
|
750
750
|
const fake = createFake({
|
|
751
751
|
command(frame, peer) {
|
|
752
752
|
peer.frame({
|
|
@@ -775,7 +775,7 @@ describe("RconClient multi-packet replies", () => {
|
|
|
775
775
|
}
|
|
776
776
|
});
|
|
777
777
|
|
|
778
|
-
|
|
778
|
+
it("settles on the first frame and sends no probe when multi-packet is off", async () => {
|
|
779
779
|
const fake = createFake({
|
|
780
780
|
command(frame, peer) {
|
|
781
781
|
peer.frame({
|
|
@@ -800,7 +800,7 @@ describe("RconClient multi-packet replies", () => {
|
|
|
800
800
|
}
|
|
801
801
|
});
|
|
802
802
|
|
|
803
|
-
|
|
803
|
+
it("never puts two frames in one write for a server that refuses them", async () => {
|
|
804
804
|
const fake = createFake({
|
|
805
805
|
strict: true,
|
|
806
806
|
command(frame, peer) {
|
|
@@ -855,7 +855,7 @@ describe("RconClient unmirrored ids", () => {
|
|
|
855
855
|
});
|
|
856
856
|
};
|
|
857
857
|
|
|
858
|
-
|
|
858
|
+
it("times out when the server answers with an id it never sent", async () => {
|
|
859
859
|
const fake = palworld();
|
|
860
860
|
|
|
861
861
|
const client = connect(fake, {
|
|
@@ -869,7 +869,7 @@ describe("RconClient unmirrored ids", () => {
|
|
|
869
869
|
}
|
|
870
870
|
});
|
|
871
871
|
|
|
872
|
-
|
|
872
|
+
it("settles on the first value frame and sends no probe when ids are not mirrored", async () => {
|
|
873
873
|
const fake = palworld();
|
|
874
874
|
|
|
875
875
|
const client = connect(fake, {
|
|
@@ -886,7 +886,7 @@ describe("RconClient unmirrored ids", () => {
|
|
|
886
886
|
}
|
|
887
887
|
});
|
|
888
888
|
|
|
889
|
-
|
|
889
|
+
it("still drops a frame that arrives with no command in flight", async () => {
|
|
890
890
|
const fake = createFake({
|
|
891
891
|
auth(frame, peer) {
|
|
892
892
|
peer.frame({
|
|
@@ -923,7 +923,7 @@ describe("RconClient unmirrored ids", () => {
|
|
|
923
923
|
});
|
|
924
924
|
|
|
925
925
|
describe("RconClient failures", () => {
|
|
926
|
-
|
|
926
|
+
it("drops the link on a malformed frame and reconnects", async () => {
|
|
927
927
|
let broken = false;
|
|
928
928
|
|
|
929
929
|
const fake = createFake({
|
|
@@ -963,7 +963,7 @@ describe("RconClient failures", () => {
|
|
|
963
963
|
}
|
|
964
964
|
});
|
|
965
965
|
|
|
966
|
-
|
|
966
|
+
it("reports a reply above the reply limit", async () => {
|
|
967
967
|
const fake = createFake({
|
|
968
968
|
command(frame, peer) {
|
|
969
969
|
for (let part = 0; part * RCON_BODY_LIMIT <= RCON_REPLY_LIMIT; part += 1) {
|
|
@@ -988,7 +988,7 @@ describe("RconClient failures", () => {
|
|
|
988
988
|
}
|
|
989
989
|
});
|
|
990
990
|
|
|
991
|
-
|
|
991
|
+
it("refuses a body above the packet limit without touching the network", async () => {
|
|
992
992
|
const fake = createFake();
|
|
993
993
|
const client = connect(fake);
|
|
994
994
|
|
|
@@ -1003,7 +1003,7 @@ describe("RconClient failures", () => {
|
|
|
1003
1003
|
}
|
|
1004
1004
|
});
|
|
1005
1005
|
|
|
1006
|
-
|
|
1006
|
+
it("reports a port nothing listens on", async () => {
|
|
1007
1007
|
const client = new RconClient({
|
|
1008
1008
|
host: HOST,
|
|
1009
1009
|
port: CLOSED_PORT,
|
|
@@ -1016,7 +1016,7 @@ describe("RconClient failures", () => {
|
|
|
1016
1016
|
});
|
|
1017
1017
|
|
|
1018
1018
|
describe("RconClient timeouts", () => {
|
|
1019
|
-
|
|
1019
|
+
it("times out a command the server never answers and never resends it", async () => {
|
|
1020
1020
|
const fake = createFake({
|
|
1021
1021
|
command() {},
|
|
1022
1022
|
probe() {},
|
|
@@ -1035,7 +1035,7 @@ describe("RconClient timeouts", () => {
|
|
|
1035
1035
|
}
|
|
1036
1036
|
});
|
|
1037
1037
|
|
|
1038
|
-
|
|
1038
|
+
it("honours a per-command timeout shorter than the default", async () => {
|
|
1039
1039
|
const fake = createFake({
|
|
1040
1040
|
command() {},
|
|
1041
1041
|
probe() {},
|
|
@@ -1061,7 +1061,7 @@ describe("RconClient timeouts", () => {
|
|
|
1061
1061
|
}
|
|
1062
1062
|
});
|
|
1063
1063
|
|
|
1064
|
-
|
|
1064
|
+
it("honours a per-command timeout longer than the default", async () => {
|
|
1065
1065
|
const fake = createFake({
|
|
1066
1066
|
command(frame, peer) {
|
|
1067
1067
|
setTimeout(() => {
|
|
@@ -1092,7 +1092,7 @@ describe("RconClient timeouts", () => {
|
|
|
1092
1092
|
}
|
|
1093
1093
|
});
|
|
1094
1094
|
|
|
1095
|
-
|
|
1095
|
+
it("times out a queued command on its own deadline without writing it", async () => {
|
|
1096
1096
|
const fake = createFake({
|
|
1097
1097
|
command() {},
|
|
1098
1098
|
probe() {},
|
|
@@ -1122,7 +1122,7 @@ describe("RconClient timeouts", () => {
|
|
|
1122
1122
|
}
|
|
1123
1123
|
});
|
|
1124
1124
|
|
|
1125
|
-
|
|
1125
|
+
it("reconnects and re-authenticates after a timeout", async () => {
|
|
1126
1126
|
let served = 0;
|
|
1127
1127
|
|
|
1128
1128
|
const fake = createFake({
|
|
@@ -1157,7 +1157,7 @@ describe("RconClient timeouts", () => {
|
|
|
1157
1157
|
}
|
|
1158
1158
|
});
|
|
1159
1159
|
|
|
1160
|
-
|
|
1160
|
+
it("never writes a command whose deadline passed while the client was dialing", async () => {
|
|
1161
1161
|
const fake = createFake({
|
|
1162
1162
|
auth(frame, peer) {
|
|
1163
1163
|
setTimeout(() => {
|
|
@@ -1200,7 +1200,7 @@ describe("RconClient timeouts", () => {
|
|
|
1200
1200
|
});
|
|
1201
1201
|
|
|
1202
1202
|
describe("RconClient reconnects", () => {
|
|
1203
|
-
|
|
1203
|
+
it("reconnects transparently after the server closes between commands", async () => {
|
|
1204
1204
|
const fake = createFake();
|
|
1205
1205
|
const client = connect(fake);
|
|
1206
1206
|
|
|
@@ -1220,7 +1220,7 @@ describe("RconClient reconnects", () => {
|
|
|
1220
1220
|
}
|
|
1221
1221
|
});
|
|
1222
1222
|
|
|
1223
|
-
|
|
1223
|
+
it("never resends a command the server cut off mid-reply", async () => {
|
|
1224
1224
|
const fake = createFake({
|
|
1225
1225
|
command(frame, peer) {
|
|
1226
1226
|
if (frame.body === "risky") {
|
|
@@ -1251,7 +1251,7 @@ describe("RconClient reconnects", () => {
|
|
|
1251
1251
|
}
|
|
1252
1252
|
});
|
|
1253
1253
|
|
|
1254
|
-
|
|
1254
|
+
it("locks the client when the password stops working on a reconnect", async () => {
|
|
1255
1255
|
const fake = createFake();
|
|
1256
1256
|
const client = connect(fake);
|
|
1257
1257
|
|
|
@@ -1286,7 +1286,7 @@ describe("RconClient reconnects", () => {
|
|
|
1286
1286
|
});
|
|
1287
1287
|
|
|
1288
1288
|
describe("RconClient concurrency", () => {
|
|
1289
|
-
|
|
1289
|
+
it("runs parallel commands in order with distinct ids", async () => {
|
|
1290
1290
|
const fake = createFake();
|
|
1291
1291
|
const client = connect(fake);
|
|
1292
1292
|
|
|
@@ -1311,7 +1311,7 @@ describe("RconClient concurrency", () => {
|
|
|
1311
1311
|
}
|
|
1312
1312
|
});
|
|
1313
1313
|
|
|
1314
|
-
|
|
1314
|
+
it("keeps a timed-out command from failing the next one", async () => {
|
|
1315
1315
|
const fake = createFake({
|
|
1316
1316
|
command(frame, peer) {
|
|
1317
1317
|
if (frame.body === "slow") {
|
|
@@ -1347,7 +1347,7 @@ describe("RconClient concurrency", () => {
|
|
|
1347
1347
|
}
|
|
1348
1348
|
});
|
|
1349
1349
|
|
|
1350
|
-
|
|
1350
|
+
it("rejects queued commands on disconnect and reconnects afterwards", async () => {
|
|
1351
1351
|
const fake = createFake({
|
|
1352
1352
|
command(frame, peer) {
|
|
1353
1353
|
if (frame.body === "stalled") {
|
|
@@ -1387,7 +1387,7 @@ describe("RconClient concurrency", () => {
|
|
|
1387
1387
|
});
|
|
1388
1388
|
|
|
1389
1389
|
describe("RconClient shutdown", () => {
|
|
1390
|
-
|
|
1390
|
+
it("ends the socket and resolves when the server closes it", async () => {
|
|
1391
1391
|
const fake = createFake();
|
|
1392
1392
|
const client = connect(fake);
|
|
1393
1393
|
|
|
@@ -1402,7 +1402,7 @@ describe("RconClient shutdown", () => {
|
|
|
1402
1402
|
}
|
|
1403
1403
|
});
|
|
1404
1404
|
|
|
1405
|
-
|
|
1405
|
+
it("is idempotent", async () => {
|
|
1406
1406
|
const fake = createFake();
|
|
1407
1407
|
const client = connect(fake);
|
|
1408
1408
|
|
|
@@ -1417,7 +1417,7 @@ describe("RconClient shutdown", () => {
|
|
|
1417
1417
|
}
|
|
1418
1418
|
});
|
|
1419
1419
|
|
|
1420
|
-
|
|
1420
|
+
it("resolves when the client never connected", async () => {
|
|
1421
1421
|
const fake = createFake();
|
|
1422
1422
|
const client = connect(fake);
|
|
1423
1423
|
|
|
@@ -1430,7 +1430,7 @@ describe("RconClient shutdown", () => {
|
|
|
1430
1430
|
}
|
|
1431
1431
|
});
|
|
1432
1432
|
|
|
1433
|
-
|
|
1433
|
+
it("tracks connected across a reconnect and a server close", async () => {
|
|
1434
1434
|
const fake = createFake();
|
|
1435
1435
|
const client = connect(fake);
|
|
1436
1436
|
|
|
@@ -1459,7 +1459,7 @@ describe("RconClient shutdown", () => {
|
|
|
1459
1459
|
});
|
|
1460
1460
|
|
|
1461
1461
|
describe("RconClient secrecy", () => {
|
|
1462
|
-
|
|
1462
|
+
it("never names the password in an error", async () => {
|
|
1463
1463
|
const rejected = createFake();
|
|
1464
1464
|
|
|
1465
1465
|
rejected.password = "rotated";
|
|
@@ -1504,7 +1504,7 @@ describe("RconClient secrecy", () => {
|
|
|
1504
1504
|
}
|
|
1505
1505
|
});
|
|
1506
1506
|
|
|
1507
|
-
|
|
1507
|
+
it("never names the command text in a timeout error", async () => {
|
|
1508
1508
|
const fake = createFake({
|
|
1509
1509
|
command() {},
|
|
1510
1510
|
probe() {},
|
|
@@ -1525,7 +1525,7 @@ describe("RconClient secrecy", () => {
|
|
|
1525
1525
|
});
|
|
1526
1526
|
|
|
1527
1527
|
describe("RconClient socket hygiene", () => {
|
|
1528
|
-
|
|
1528
|
+
it("leaves no socket open when the connect deadline passes", async () => {
|
|
1529
1529
|
const fake = createFake({
|
|
1530
1530
|
auth() {},
|
|
1531
1531
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { BridgeRconCode, BridgeRconError } from "../core";
|
|
3
3
|
import {
|
|
4
4
|
concatBytes,
|
|
@@ -35,7 +35,7 @@ const declaredFrame = (size: number) => {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
describe("encodeRconFrame", () => {
|
|
38
|
-
|
|
38
|
+
it("writes size, id and type as little endian int32 followed by two nulls", () => {
|
|
39
39
|
const packet = encodeRconFrame({
|
|
40
40
|
id: 7,
|
|
41
41
|
type: RconRequest.Auth,
|
|
@@ -53,7 +53,7 @@ describe("encodeRconFrame", () => {
|
|
|
53
53
|
expect(packet.at(17)).toBe(0);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
it("refuses a body above the 4096 byte cap", () => {
|
|
57
57
|
const error = thrown(() =>
|
|
58
58
|
encodeRconFrame({
|
|
59
59
|
id: 1,
|
|
@@ -68,7 +68,7 @@ describe("encodeRconFrame", () => {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
describe("decodeRconFrames", () => {
|
|
71
|
-
|
|
71
|
+
it("round-trips a frame", () => {
|
|
72
72
|
const decoded = decodeRconFrames(
|
|
73
73
|
encodeRconFrame({
|
|
74
74
|
id: 3,
|
|
@@ -88,7 +88,7 @@ describe("decodeRconFrames", () => {
|
|
|
88
88
|
expect(decoded.rest).toHaveLength(0);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
it("reads several frames out of one buffer", () => {
|
|
92
92
|
const decoded = decodeRconFrames(
|
|
93
93
|
concatBytes(
|
|
94
94
|
encodeRconFrame({
|
|
@@ -110,7 +110,7 @@ describe("decodeRconFrames", () => {
|
|
|
110
110
|
]);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
it("keeps a partial frame for the next chunk", () => {
|
|
114
114
|
const packet = encodeRconFrame({
|
|
115
115
|
id: 9,
|
|
116
116
|
type: RconResponse.Value,
|
|
@@ -134,7 +134,7 @@ describe("decodeRconFrames", () => {
|
|
|
134
134
|
]);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
it("keeps a frame split inside the size prefix", () => {
|
|
138
138
|
const packet = encodeRconFrame({
|
|
139
139
|
id: 11,
|
|
140
140
|
type: RconResponse.Value,
|
|
@@ -151,7 +151,7 @@ describe("decodeRconFrames", () => {
|
|
|
151
151
|
expect(second.frames.at(0)?.body).toBe("prefix");
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
it("reads an empty body", () => {
|
|
155
155
|
expect(
|
|
156
156
|
decodeRconFrames(
|
|
157
157
|
encodeRconFrame({
|
|
@@ -170,7 +170,7 @@ describe("decodeRconFrames", () => {
|
|
|
170
170
|
]);
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
it("round-trips a multi-byte utf-8 body and counts its bytes", () => {
|
|
174
174
|
const body = "اللاعب انضم — 참가자";
|
|
175
175
|
|
|
176
176
|
const frame = decodeRconFrames(
|
|
@@ -185,7 +185,7 @@ describe("decodeRconFrames", () => {
|
|
|
185
185
|
expect(frame?.bodyBytes).toBe(new TextEncoder().encode(body).length);
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
it("keeps a body with an embedded null", () => {
|
|
189
189
|
const body = `left${NUL}right`;
|
|
190
190
|
|
|
191
191
|
expect(
|
|
@@ -199,7 +199,7 @@ describe("decodeRconFrames", () => {
|
|
|
199
199
|
).toBe(body);
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
it("reads the srcds trailer as an ordinary frame", () => {
|
|
203
203
|
expect(
|
|
204
204
|
decodeRconFrames(
|
|
205
205
|
encodeRconFrame({
|
|
@@ -216,14 +216,14 @@ describe("decodeRconFrames", () => {
|
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
it("rejects a packet declaring an impossible size", () => {
|
|
220
220
|
const error = thrown(() => decodeRconFrames(declaredFrame(2)));
|
|
221
221
|
|
|
222
222
|
expect(error).toBeInstanceOf(BridgeRconError);
|
|
223
223
|
expect((error as BridgeRconError).code).toBe(BridgeRconCode.Malformed);
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
it("reads a frame of exactly the reply limit and rejects one byte more", () => {
|
|
227
227
|
const decoded = decodeRconFrames(declaredFrame(RCON_REPLY_LIMIT));
|
|
228
228
|
|
|
229
229
|
expect(decoded.frames).toHaveLength(1);
|
|
@@ -237,7 +237,7 @@ describe("decodeRconFrames", () => {
|
|
|
237
237
|
});
|
|
238
238
|
|
|
239
239
|
describe("concatBytes", () => {
|
|
240
|
-
|
|
240
|
+
it("copies a non-empty left side and keeps the order", () => {
|
|
241
241
|
const left = new Uint8Array([
|
|
242
242
|
1,
|
|
243
243
|
2,
|
package/src/rcon/rconIds.test.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { createRconIds } from "./rconIds";
|
|
3
3
|
|
|
4
4
|
const LAST_ID = 2_147_483_646;
|
|
5
5
|
|
|
6
6
|
describe("createRconIds", () => {
|
|
7
|
-
|
|
7
|
+
it("hands out one and two first", () => {
|
|
8
8
|
expect(createRconIds().next()).toEqual({
|
|
9
9
|
id: 1,
|
|
10
10
|
probeId: 2,
|
|
11
11
|
});
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
it("hands out strictly increasing pairs", () => {
|
|
15
15
|
const ids = createRconIds();
|
|
16
16
|
|
|
17
17
|
let previous = ids.next();
|
|
@@ -26,7 +26,7 @@ describe("createRconIds", () => {
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
it("wraps back to one without ever handing out zero or minus one", () => {
|
|
30
30
|
const ids = createRconIds(LAST_ID - 3);
|
|
31
31
|
|
|
32
32
|
const pairs = [
|