@serverkgg/bridge 0.1.14 → 0.1.15
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/rules/package.md +3 -0
- package/src/catalogs/curseforge.test.ts +46 -0
- package/src/catalogs/curseforge.ts +20 -0
- package/src/context/bridgeContext.ts +1 -0
- package/src/manifest/extension.ts +1 -0
- package/src/manifest/manifest.test.ts +18 -0
- package/src/modules/bridge.ts +13 -2
- package/src/modules/bridgeModule.ts +19 -4
- package/src/panel/bridgePanel.ts +2 -0
- package/src/protocol/bridgeCatalog.ts +7 -0
- package/src/protocol/bridgeCatalogSource.ts +9 -0
- package/src/protocol/bridgeConfig.ts +1 -0
- package/src/protocol/bridgeLimits.ts +6 -0
- package/src/protocol/bridgeManifest.ts +2 -0
- package/src/protocol/index.ts +1 -0
- package/src/runtime/bridgeActions.ts +9 -2
- package/src/runtime/bridgeConfigReader.test.ts +31 -0
- package/src/runtime/bridgeConfigReader.ts +1 -0
- package/src/runtime/bridgeContext.test.ts +53 -0
- package/src/runtime/bridgeContext.ts +3 -0
- package/src/runtime/bridgeDriver.test.ts +347 -1
- package/src/runtime/bridgeDriver.ts +130 -18
- package/src/runtime/bridgeLiveConfig.test.ts +137 -1
- package/src/runtime/bridgeLiveConfig.ts +46 -2
- package/src/runtime/bridgeRegistry.test.ts +220 -0
- package/src/runtime/bridgeRegistry.ts +27 -1
- package/src/runtime/serve.ts +2 -2
- package/src/steam/workshop.test.ts +19 -1
- package/src/steam/workshop.ts +12 -0
- package/src/wire/panel.test.ts +177 -0
- package/src/wire/panel.ts +16 -0
package/package.json
CHANGED
package/rules/package.md
CHANGED
|
@@ -36,9 +36,12 @@ One Serverk package (a game or a bot runtime) on `@serverkgg/bridge`, with no pi
|
|
|
36
36
|
- A game declares each tab as `const <name>Tab: Bridge.Tab`, listed in `export const panel: Bridge.Panel`; a bot's panel is the tab `createBotPanel()` from `@serverkgg/bridge/bot` builds. Reusable copy is a `Bridge.Text` constant.
|
|
37
37
|
- Every label, title, help text and message is `{ ar, en }`, Arabic first.
|
|
38
38
|
- A settings form targets `BridgeFormTarget.Settings` and module `settings`, with a one-sentence `help` and `restartHint: true`.
|
|
39
|
+
- A variables-form field may declare `default`, which the form shows and `context.variable` returns while the variable is unset or empty. A default redefines unset, so set the current value on existing servers by hand before their next start.
|
|
39
40
|
- In a game, the roster and moderation tables go on `place: BridgePlace.Players`, metrics on `BridgePlace.Overview`.
|
|
40
41
|
- A setup `Form` step points at an existing panel form by `tab`, `section` and field keys, never redeclaring a field; `Form` and `Open` steps state `required`. A game's setup ends with the `Open` step `invite` targeting `GuideOpenTab.Access`.
|
|
41
42
|
- A preview module that knows the change cannot install answers `blocked: true`, with the reason in `lines`. The panel then shows the lines with a close button only, and the platform refuses the change before it rebuilds anything.
|
|
43
|
+
- A catalog whose items the platform downloads is a `Bridge.SourcedCatalog` declaring `source`, with `provided` naming what the image ships. The platform answers its `search` and `preview`; `install` reads the archive at `workshopArchivePath(appId, id)` from `@serverkgg/bridge/steam`, deletes it, and links an item with `workshopItemPageUrl(id)`.
|
|
44
|
+
- A `Catalog` section may carry `upload` when its module defines `add`, which installs the folder the platform extracted the upload into and returns its entry.
|
|
42
45
|
- `BridgeConfirm.None` runs on the click: reads, downloads, start, stop and restart.
|
|
43
46
|
- `BridgeConfirm.Normal` asks once, for anything the player can come back from: a settings form, a variable flip, a reinstall, a version or type switch, a modpack or mod install, op and deop, kick, ban, whitelist, rotating a password.
|
|
44
47
|
- `BridgeConfirm.Strong` is only for an action with no way back, where no restore point is taken and nothing in the panel brings the state back: deleting a world, save, map or character data, resetting a dimension, a wipe. A protected action takes no restore point.
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type CurseforgeCatalogOptions,
|
|
10
10
|
type CurseforgeFile,
|
|
11
11
|
CurseforgeHash,
|
|
12
|
+
CurseforgeModLoader,
|
|
12
13
|
CurseforgeReleaseType,
|
|
13
14
|
CurseforgeSort,
|
|
14
15
|
CurseforgeSortOrder,
|
|
@@ -536,6 +537,51 @@ describe("createCurseforgeCatalog", () => {
|
|
|
536
537
|
expect(second.at(-1)).toBe(150);
|
|
537
538
|
});
|
|
538
539
|
|
|
540
|
+
it("posts the mod ids in one sorted body inside the project window and keeps the latest file indexes", async () => {
|
|
541
|
+
const mod = {
|
|
542
|
+
id: 238_222,
|
|
543
|
+
latestFilesIndexes: [
|
|
544
|
+
{
|
|
545
|
+
gameVersion: "26.3",
|
|
546
|
+
fileId: 7_100_200,
|
|
547
|
+
filename: "jei-26.3-neoforge-31.16.0.90.jar",
|
|
548
|
+
releaseType: CurseforgeReleaseType.Release,
|
|
549
|
+
modLoader: CurseforgeModLoader.NeoForge,
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
};
|
|
553
|
+
const { catalog, calls } = catalogContext({
|
|
554
|
+
result: {
|
|
555
|
+
data: [
|
|
556
|
+
mod,
|
|
557
|
+
],
|
|
558
|
+
},
|
|
559
|
+
options: {
|
|
560
|
+
projectCacheSeconds: 600,
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const mods = await catalog.mods([
|
|
565
|
+
238_222,
|
|
566
|
+
32_274,
|
|
567
|
+
238_222,
|
|
568
|
+
]);
|
|
569
|
+
|
|
570
|
+
expect(calls).toHaveLength(1);
|
|
571
|
+
expect(calls[0]?.url).toBe(`${CURSEFORGE_API}/mods`);
|
|
572
|
+
expect(calls[0]?.httpMethod).toBe(BridgeHttpMethod.Post);
|
|
573
|
+
expect(calls[0]?.cacheSeconds).toBe(600);
|
|
574
|
+
expect(JSON.parse(calls[0]?.body ?? "null")).toEqual({
|
|
575
|
+
modIds: [
|
|
576
|
+
32_274,
|
|
577
|
+
238_222,
|
|
578
|
+
],
|
|
579
|
+
});
|
|
580
|
+
expect(mods).toEqual([
|
|
581
|
+
mod,
|
|
582
|
+
] as unknown as typeof mods);
|
|
583
|
+
});
|
|
584
|
+
|
|
539
585
|
it("refuses to work without a key", async () => {
|
|
540
586
|
const { catalog, calls } = catalogContext({
|
|
541
587
|
key: null,
|
|
@@ -51,6 +51,16 @@ export enum CurseforgeReleaseType {
|
|
|
51
51
|
Release = 1,
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export enum CurseforgeModLoader {
|
|
55
|
+
Any = 0,
|
|
56
|
+
Cauldron = 2,
|
|
57
|
+
Fabric = 4,
|
|
58
|
+
Forge = 1,
|
|
59
|
+
LiteLoader = 3,
|
|
60
|
+
NeoForge = 6,
|
|
61
|
+
Quilt = 5,
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
export enum CurseforgeDependency {
|
|
55
65
|
EmbeddedLibrary = 1,
|
|
56
66
|
Include = 6,
|
|
@@ -85,6 +95,14 @@ export interface CurseforgeCategory {
|
|
|
85
95
|
isClass?: boolean | null;
|
|
86
96
|
}
|
|
87
97
|
|
|
98
|
+
export interface CurseforgeFileIndex {
|
|
99
|
+
gameVersion: string;
|
|
100
|
+
fileId: number;
|
|
101
|
+
filename: string;
|
|
102
|
+
releaseType: number;
|
|
103
|
+
modLoader: number | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
export interface CurseforgeMod {
|
|
89
107
|
id: number;
|
|
90
108
|
classId: number | null;
|
|
@@ -102,6 +120,8 @@ export interface CurseforgeMod {
|
|
|
102
120
|
links: {
|
|
103
121
|
websiteUrl: string | null;
|
|
104
122
|
} | null;
|
|
123
|
+
latestFiles?: CurseforgeFile[];
|
|
124
|
+
latestFilesIndexes?: CurseforgeFileIndex[];
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
export interface CurseforgeHashEntry {
|
|
@@ -322,6 +322,24 @@ describe("extensions", () => {
|
|
|
322
322
|
expect(rendered).toContain(`"${value}"`);
|
|
323
323
|
}
|
|
324
324
|
});
|
|
325
|
+
|
|
326
|
+
it("carry the Steam Workshop as a catalog and render it into the JSON schema", () => {
|
|
327
|
+
const manifest = manifestSchema.parse(
|
|
328
|
+
withExtensions([
|
|
329
|
+
{
|
|
330
|
+
kind: ManifestExtensionKind.Mods,
|
|
331
|
+
sources: [
|
|
332
|
+
ManifestExtensionSource.Workshop,
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
]),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
expect(manifest.metadata.extensions[0]?.sources).toEqual([
|
|
339
|
+
ManifestExtensionSource.Workshop,
|
|
340
|
+
]);
|
|
341
|
+
expect(JSON.stringify(buildJsonSchema())).toContain('"workshop"');
|
|
342
|
+
});
|
|
325
343
|
});
|
|
326
344
|
|
|
327
345
|
describe("port presentation", () => {
|
package/src/modules/bridge.ts
CHANGED
|
@@ -38,7 +38,11 @@ import type {
|
|
|
38
38
|
BridgeCatalogPage,
|
|
39
39
|
BridgeCatalogProvider,
|
|
40
40
|
BridgeCatalogQuery,
|
|
41
|
+
BridgeCatalogRelease,
|
|
42
|
+
BridgeCatalogUpdate,
|
|
41
43
|
} from "../protocol/bridgeCatalog";
|
|
44
|
+
import type { BridgeCatalogSource } from "../protocol/bridgeCatalogSource";
|
|
45
|
+
import { BridgeCatalogSourceKind } from "../protocol/bridgeCatalogSource";
|
|
42
46
|
import type { BridgeCrashReport } from "../protocol/bridgeCrash";
|
|
43
47
|
import type { BridgePendingFile } from "../protocol/bridgeFrame";
|
|
44
48
|
import type { BridgePreview } from "../protocol/bridgePreview";
|
|
@@ -57,7 +61,6 @@ import type {
|
|
|
57
61
|
BridgeActionsModule,
|
|
58
62
|
BridgeAnnounceModule,
|
|
59
63
|
BridgeBackupModule,
|
|
60
|
-
BridgeCatalogModule,
|
|
61
64
|
BridgeCollectionModule,
|
|
62
65
|
BridgeDetailAction,
|
|
63
66
|
BridgeDetailModule,
|
|
@@ -71,9 +74,11 @@ import type {
|
|
|
71
74
|
BridgeQueryModule,
|
|
72
75
|
BridgeRowAction,
|
|
73
76
|
BridgeSample,
|
|
77
|
+
BridgeSearchCatalogModule,
|
|
74
78
|
BridgeServerAction,
|
|
75
79
|
BridgeSettingsModule,
|
|
76
80
|
BridgeSetupModule,
|
|
81
|
+
BridgeSourcedCatalogModule,
|
|
77
82
|
BridgeTerminalModule,
|
|
78
83
|
} from "./bridgeModule";
|
|
79
84
|
|
|
@@ -85,13 +90,18 @@ export namespace Bridge {
|
|
|
85
90
|
export type Announce = BridgeAnnounceModule;
|
|
86
91
|
export type Availability = BridgeAvailability;
|
|
87
92
|
export type Backup = BridgeBackupModule;
|
|
88
|
-
export type Catalog =
|
|
93
|
+
export type Catalog = BridgeSearchCatalogModule;
|
|
89
94
|
export type CatalogEntry = BridgeCatalogEntry;
|
|
90
95
|
export type CatalogFacet = BridgeCatalogFacet;
|
|
91
96
|
export type CatalogHit = BridgeCatalogHit;
|
|
92
97
|
export type CatalogPage = BridgeCatalogPage;
|
|
93
98
|
export type CatalogProvider = BridgeCatalogProvider;
|
|
94
99
|
export type CatalogQuery = BridgeCatalogQuery;
|
|
100
|
+
export type CatalogRelease = BridgeCatalogRelease;
|
|
101
|
+
export type CatalogSource = BridgeCatalogSource;
|
|
102
|
+
export const CatalogSourceKind = BridgeCatalogSourceKind;
|
|
103
|
+
export type CatalogSourceKind = BridgeCatalogSourceKind;
|
|
104
|
+
export type CatalogUpdate = BridgeCatalogUpdate;
|
|
95
105
|
export type Cell = BridgeCell;
|
|
96
106
|
export type Cells = BridgeCells;
|
|
97
107
|
export type Collection<Row extends BridgeRow = BridgeRow> = BridgeCollectionModule<Row>;
|
|
@@ -140,6 +150,7 @@ export namespace Bridge {
|
|
|
140
150
|
export type SetupStep = BridgeSetupStep;
|
|
141
151
|
export type SetupStepStatus = BridgeSetupStepStatus;
|
|
142
152
|
export type SetupSummary = BridgeSetupSummary;
|
|
153
|
+
export type SourcedCatalog = BridgeSourcedCatalogModule;
|
|
143
154
|
export type Terminal = BridgeTerminalModule;
|
|
144
155
|
export type TerminalArg = BridgeTerminalArg;
|
|
145
156
|
export type TerminalCommand = BridgeTerminalCommand;
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
BridgeCatalogQuery,
|
|
16
16
|
BridgeCatalogRelease,
|
|
17
17
|
} from "../protocol/bridgeCatalog";
|
|
18
|
+
import type { BridgeCatalogSource } from "../protocol/bridgeCatalogSource";
|
|
18
19
|
import type { BridgeCrashReport } from "../protocol/bridgeCrash";
|
|
19
20
|
import type { BridgeBackupSnapshot, BridgeDescribeResult, BridgePendingFile } from "../protocol/bridgeFrame";
|
|
20
21
|
import type { BridgePreview } from "../protocol/bridgePreview";
|
|
@@ -171,20 +172,34 @@ export interface BridgeDetailModule {
|
|
|
171
172
|
actions?: Record<string, BridgeDetailAction>;
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
|
|
175
|
+
interface BridgeCatalogModuleBase {
|
|
175
176
|
kind: BridgeKind.Catalog;
|
|
176
177
|
protectedActions?: string[];
|
|
177
178
|
pageSize?: number;
|
|
178
|
-
search(context: BridgeContext, query: BridgeCatalogQuery): Promise<BridgeCatalogPage>;
|
|
179
179
|
installed(context: BridgeContext): Promise<BridgeCatalogEntry[]>;
|
|
180
|
-
releases?(context: BridgeContext, id: string): Promise<BridgeCatalogRelease[]>;
|
|
181
|
-
preview?(context: BridgeContext, id: string, releaseId?: string): Promise<BridgePreview>;
|
|
182
180
|
install(context: BridgeContext, id: string, releaseId?: string): Promise<BridgeCatalogEntry>;
|
|
181
|
+
add?(context: BridgeContext, input: string): Promise<BridgeCatalogEntry>;
|
|
183
182
|
remove(context: BridgeContext, id: string): Promise<void>;
|
|
184
183
|
toggle?(context: BridgeContext, id: string, enabled: boolean): Promise<void>;
|
|
185
184
|
unavailable?(context: BridgeContext, entry: BridgeCatalogEntry): Promise<BridgeUnavailable>;
|
|
186
185
|
}
|
|
187
186
|
|
|
187
|
+
export interface BridgeSearchCatalogModule extends BridgeCatalogModuleBase {
|
|
188
|
+
source?: never;
|
|
189
|
+
search(context: BridgeContext, query: BridgeCatalogQuery): Promise<BridgeCatalogPage>;
|
|
190
|
+
releases?(context: BridgeContext, id: string): Promise<BridgeCatalogRelease[]>;
|
|
191
|
+
preview?(context: BridgeContext, id: string, releaseId?: string): Promise<BridgePreview>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface BridgeSourcedCatalogModule extends BridgeCatalogModuleBase {
|
|
195
|
+
source: BridgeCatalogSource;
|
|
196
|
+
search?: never;
|
|
197
|
+
releases?: never;
|
|
198
|
+
preview?: never;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export type BridgeCatalogModule = BridgeSearchCatalogModule | BridgeSourcedCatalogModule;
|
|
202
|
+
|
|
188
203
|
export type BridgePanelModule =
|
|
189
204
|
| BridgeActionsModule
|
|
190
205
|
| BridgeCatalogModule
|
package/src/panel/bridgePanel.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface BridgeField {
|
|
|
29
29
|
help?: LocalizedText;
|
|
30
30
|
warning?: LocalizedText;
|
|
31
31
|
placeholder?: string;
|
|
32
|
+
default?: string;
|
|
32
33
|
min?: number;
|
|
33
34
|
max?: number;
|
|
34
35
|
step?: number;
|
|
@@ -149,6 +150,7 @@ export interface BridgeCatalogSection {
|
|
|
149
150
|
variable?: string;
|
|
150
151
|
confirm?: BridgeConfirm;
|
|
151
152
|
confirmText?: LocalizedText;
|
|
153
|
+
upload?: BridgeSectionUpload;
|
|
152
154
|
empty?: LocalizedText;
|
|
153
155
|
visibleWhen?: BridgeVisibleWhen;
|
|
154
156
|
access?: BridgeAccess;
|
|
@@ -39,6 +39,11 @@ export interface BridgeCatalogPage {
|
|
|
39
39
|
sorts: BridgeCatalogFacet[];
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export interface BridgeCatalogUpdate {
|
|
43
|
+
version: string;
|
|
44
|
+
releaseId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
export interface BridgeCatalogEntry {
|
|
43
48
|
id: string;
|
|
44
49
|
provider: string | null;
|
|
@@ -52,6 +57,8 @@ export interface BridgeCatalogEntry {
|
|
|
52
57
|
pageUrl: string | null;
|
|
53
58
|
icon: string | null;
|
|
54
59
|
variables?: Record<string, string> | null;
|
|
60
|
+
update?: BridgeCatalogUpdate | null;
|
|
61
|
+
note?: LocalizedText;
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
export interface BridgeCatalogQuery {
|
|
@@ -28,6 +28,8 @@ export const BRIDGE_FORM_CLEARS_LIMIT = 8;
|
|
|
28
28
|
|
|
29
29
|
export const BRIDGE_UPLOAD_EXTENSIONS_LIMIT = 8;
|
|
30
30
|
|
|
31
|
+
export const BRIDGE_CATALOG_PROVIDED_LIMIT = 16;
|
|
32
|
+
|
|
31
33
|
export const BRIDGE_MODULES_LIMIT = 32;
|
|
32
34
|
|
|
33
35
|
export const BRIDGE_MODULE_ACTIONS_LIMIT = 16;
|
|
@@ -52,10 +54,14 @@ export const BRIDGE_OPTION_VALUE_LENGTH_LIMIT = 128;
|
|
|
52
54
|
|
|
53
55
|
export const BRIDGE_PLACEHOLDER_LENGTH_LIMIT = 256;
|
|
54
56
|
|
|
57
|
+
export const BRIDGE_DEFAULT_LENGTH_LIMIT = 256;
|
|
58
|
+
|
|
55
59
|
export const BRIDGE_STAGING_LENGTH_LIMIT = 128;
|
|
56
60
|
|
|
57
61
|
export const BRIDGE_EXTENSION_LENGTH_LIMIT = 16;
|
|
58
62
|
|
|
63
|
+
export const BRIDGE_PROVIDED_NAME_LENGTH_LIMIT = 128;
|
|
64
|
+
|
|
59
65
|
export const BRIDGE_PATTERN_LENGTH_LIMIT = 256;
|
|
60
66
|
|
|
61
67
|
export const BRIDGE_SETUP_CODE_LENGTH_LIMIT = 64;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BridgeKind, BridgeStream } from "../core";
|
|
2
2
|
import type { BridgePanel } from "../panel";
|
|
3
3
|
import type { BridgeTerminalCommand, BridgeTerminalLevel } from "../terminal";
|
|
4
|
+
import type { BridgeCatalogSource } from "./bridgeCatalogSource";
|
|
4
5
|
import type { BridgeSetupManifest } from "./bridgeSetup";
|
|
5
6
|
|
|
6
7
|
export interface BridgeTerminalRuleManifest {
|
|
@@ -25,6 +26,7 @@ export interface BridgeModuleManifest {
|
|
|
25
26
|
dependsOn: string[];
|
|
26
27
|
actions: string[];
|
|
27
28
|
protectedActions?: string[];
|
|
29
|
+
source?: BridgeCatalogSource;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface BridgeManifest {
|
package/src/protocol/index.ts
CHANGED
|
@@ -66,13 +66,15 @@ export const actionsOf = (module: BridgePanelModule): string[] => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
case BridgeKind.Catalog: {
|
|
69
|
+
const sourced = module.source !== undefined;
|
|
70
|
+
|
|
69
71
|
return [
|
|
70
|
-
...(module.releases
|
|
72
|
+
...(module.releases && !sourced
|
|
71
73
|
? [
|
|
72
74
|
BridgeAction.Releases,
|
|
73
75
|
]
|
|
74
76
|
: []),
|
|
75
|
-
...(module.preview
|
|
77
|
+
...(module.preview || sourced
|
|
76
78
|
? [
|
|
77
79
|
BridgeAction.Preview,
|
|
78
80
|
]
|
|
@@ -81,6 +83,11 @@ export const actionsOf = (module: BridgePanelModule): string[] => {
|
|
|
81
83
|
BridgeAction.Installed,
|
|
82
84
|
BridgeAction.Remove,
|
|
83
85
|
BridgeAction.Search,
|
|
86
|
+
...(module.add
|
|
87
|
+
? [
|
|
88
|
+
BridgeAction.Add,
|
|
89
|
+
]
|
|
90
|
+
: []),
|
|
84
91
|
...(module.toggle
|
|
85
92
|
? [
|
|
86
93
|
BridgeAction.Toggle,
|
|
@@ -68,6 +68,37 @@ describe("parseConfig", () => {
|
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it("reads a file an older agent wrote, with no player address", () => {
|
|
72
|
+
expect(parseConfig(written()).playerAddress).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("reads the player address when the agent wrote it", () => {
|
|
76
|
+
expect(
|
|
77
|
+
parseConfig(
|
|
78
|
+
written({
|
|
79
|
+
playerAddress: "lobby.srvk.gg",
|
|
80
|
+
}),
|
|
81
|
+
).playerAddress,
|
|
82
|
+
).toBe("lobby.srvk.gg");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("ignores a malformed or empty player address", () => {
|
|
86
|
+
expect(
|
|
87
|
+
parseConfig(
|
|
88
|
+
written({
|
|
89
|
+
playerAddress: 25_565,
|
|
90
|
+
}),
|
|
91
|
+
).playerAddress,
|
|
92
|
+
).toBeUndefined();
|
|
93
|
+
expect(
|
|
94
|
+
parseConfig(
|
|
95
|
+
written({
|
|
96
|
+
playerAddress: "",
|
|
97
|
+
}),
|
|
98
|
+
).playerAddress,
|
|
99
|
+
).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
71
102
|
it("still refuses a game that would run as root", () => {
|
|
72
103
|
expect(() =>
|
|
73
104
|
parseConfig(
|
|
@@ -117,6 +117,7 @@ export const parseConfig = (text: string): BridgeConfig => {
|
|
|
117
117
|
ports: numberRecord(source, "ports"),
|
|
118
118
|
portCounts: optionalNumberRecord(source, "portCounts"),
|
|
119
119
|
publicAddress: optionalString(source, "publicAddress"),
|
|
120
|
+
playerAddress: optionalString(source, "playerAddress"),
|
|
120
121
|
memoryMb: requireNumber(source, "memoryMb"),
|
|
121
122
|
cpuPercent: requireNumber(source, "cpuPercent"),
|
|
122
123
|
gameUid: requireNumber(source, "gameUid"),
|
|
@@ -250,4 +250,57 @@ describe("the context's ports and address", () => {
|
|
|
250
250
|
|
|
251
251
|
expect(context.server.publicAddress).toBe("198.51.100.4");
|
|
252
252
|
});
|
|
253
|
+
|
|
254
|
+
it("hand a driver the address players type", async () => {
|
|
255
|
+
const boot = configWith({
|
|
256
|
+
publicAddress: "203.0.113.7",
|
|
257
|
+
playerAddress: "lobby.srvk.gg",
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await writeFile(path, JSON.stringify(boot));
|
|
261
|
+
|
|
262
|
+
const { context } = contextOf(boot);
|
|
263
|
+
|
|
264
|
+
expect(context.server.playerAddress).toBe("lobby.srvk.gg");
|
|
265
|
+
expect(context.server.publicAddress).toBe("203.0.113.7");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("treat the player address as unknown for a file an older agent wrote", async () => {
|
|
269
|
+
const boot = configWith({});
|
|
270
|
+
|
|
271
|
+
await writeFile(path, JSON.stringify(boot));
|
|
272
|
+
|
|
273
|
+
const { context } = contextOf(boot);
|
|
274
|
+
|
|
275
|
+
expect(context.server.playerAddress).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("follow a rename and a clear of the player address", async () => {
|
|
279
|
+
const boot = configWith({
|
|
280
|
+
playerAddress: "lobby.srvk.gg",
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await writeFile(path, JSON.stringify(boot));
|
|
284
|
+
|
|
285
|
+
const { context, live } = contextOf(boot);
|
|
286
|
+
|
|
287
|
+
await writeFile(
|
|
288
|
+
path,
|
|
289
|
+
JSON.stringify(
|
|
290
|
+
configWith({
|
|
291
|
+
playerAddress: "arena.srvk.gg",
|
|
292
|
+
}),
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
live.reload();
|
|
297
|
+
|
|
298
|
+
expect(context.server.playerAddress).toBe("arena.srvk.gg");
|
|
299
|
+
|
|
300
|
+
await writeFile(path, JSON.stringify(configWith({})));
|
|
301
|
+
|
|
302
|
+
live.reload();
|
|
303
|
+
|
|
304
|
+
expect(context.server.playerAddress).toBeNull();
|
|
305
|
+
});
|
|
253
306
|
});
|