@serverkgg/bridge 0.0.21 → 0.1.2
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/bot/botDeploy.test.ts +363 -0
- package/src/bot/botDeploy.ts +136 -0
- package/src/bot/botEnvironment.test.ts +189 -0
- package/src/bot/botEnvironment.ts +141 -0
- package/src/bot/botIdentity.test.ts +251 -0
- package/src/bot/botIdentity.ts +140 -0
- package/src/bot/botPanel.test.ts +246 -0
- package/src/bot/botPanel.ts +129 -0
- package/src/bot/botState.test.ts +45 -0
- package/src/bot/botState.ts +54 -0
- package/src/bot/botTerminal.test.ts +79 -0
- package/src/bot/botTerminal.ts +62 -0
- package/src/bot/botVariables.test.ts +193 -0
- package/src/bot/botVariables.ts +73 -0
- package/src/bot/detect/botFramework.ts +23 -0
- package/src/bot/detect/botFrameworks.ts +198 -0
- package/src/bot/detect/botInspectWarning.ts +15 -0
- package/src/bot/detect/botPackageManager.ts +6 -0
- package/src/bot/detect/botPlatform.ts +10 -0
- package/src/bot/detect/botRuntime.ts +15 -0
- package/src/bot/detect/botToken.test.ts +71 -0
- package/src/bot/detect/botToken.ts +126 -0
- package/src/bot/detect/detect.test.ts +543 -0
- package/src/bot/detect/detect.ts +842 -0
- package/src/bot/detect/detectResult.ts +40 -0
- package/src/bot/detect/detectTable.test.ts +72 -0
- package/src/bot/detect/detectTable.ts +166 -0
- package/src/bot/detect/envExample.test.ts +29 -0
- package/src/bot/detect/envExample.ts +48 -0
- package/src/bot/detect/envKey.ts +7 -0
- package/src/bot/detect/envScan.test.ts +39 -0
- package/src/bot/detect/envScan.ts +87 -0
- package/src/bot/detect/index.ts +17 -0
- package/src/bot/detect/inferBotPlatform.test.ts +60 -0
- package/src/bot/detect/inferBotPlatform.ts +28 -0
- package/src/bot/detect/keys.test.ts +28 -0
- package/src/bot/detect/keys.ts +64 -0
- package/src/bot/detect/sensitiveKey.test.ts +91 -0
- package/src/bot/detect/sensitiveKey.ts +44 -0
- package/src/bot/detect/serverkJson.test.ts +88 -0
- package/src/bot/detect/serverkJson.ts +174 -0
- package/src/bot/index.ts +8 -0
- package/src/cli/detectTable.test.ts +66 -0
- package/src/cli/detectTable.ts +43 -0
- package/src/cli/main.ts +12 -4
- package/src/cli/renderedFile.ts +35 -0
- package/src/cli/schema.ts +7 -26
- package/src/cli/validate.test.ts +283 -3
- package/src/cli/validate.ts +105 -7
- package/src/context/bridgeContext.ts +17 -1
- package/src/core/bridgeAction.ts +2 -0
- package/src/core/bridgeActivity.ts +9 -0
- package/src/core/bridgeAvailability.ts +10 -0
- package/src/core/bridgePort.test.ts +45 -0
- package/src/core/bridgePort.ts +26 -0
- package/src/core/bridgeUserError.test.ts +10 -0
- package/src/core/bridgeUserError.ts +6 -2
- package/src/core/describeDownloadFailure.test.ts +58 -0
- package/src/core/describeDownloadFailure.ts +69 -0
- package/src/core/index.ts +3 -0
- package/src/guides/compile.ts +2 -2
- package/src/manifest/index.ts +1 -0
- package/src/manifest/kind.ts +5 -0
- package/src/manifest/manifest.test.ts +630 -3
- package/src/manifest/manifest.ts +391 -87
- package/src/manifest/runtime.ts +2 -2
- package/src/manifest/source.ts +4 -4
- package/src/modules/bridge.ts +8 -1
- package/src/modules/bridgeLifecycleSubject.ts +8 -0
- package/src/modules/bridgeModule.ts +19 -1
- package/src/modules/bridgeStopSignal.ts +13 -0
- package/src/modules/index.ts +2 -0
- package/src/panel/bridgeDetail.ts +4 -1
- package/src/protocol/bridgeActivity.ts +54 -0
- package/src/protocol/bridgeConfig.ts +2 -0
- package/src/protocol/bridgeEvents.ts +30 -0
- package/src/protocol/bridgeFrame.ts +10 -1
- package/src/protocol/index.ts +1 -0
- package/src/runtime/bridgeActions.ts +19 -2
- package/src/runtime/bridgeActivity.test.ts +318 -0
- package/src/runtime/bridgeActivity.ts +268 -0
- package/src/runtime/bridgeConfigReader.test.ts +80 -0
- package/src/runtime/bridgeConfigReader.ts +20 -0
- package/src/runtime/bridgeContext.test.ts +247 -0
- package/src/runtime/bridgeContext.ts +43 -10
- package/src/runtime/bridgeDriver.test.ts +153 -1
- package/src/runtime/bridgeDriver.ts +64 -8
- package/src/runtime/bridgeFiles.test.ts +62 -0
- package/src/runtime/bridgeFiles.ts +12 -2
- package/src/runtime/bridgeLiveConfig.ts +18 -0
- package/src/runtime/bridgeLog.ts +10 -0
- package/src/runtime/bridgeRedaction.test.ts +410 -0
- package/src/runtime/bridgeRedaction.ts +214 -0
- package/src/runtime/bridgeRefusalError.ts +13 -0
- package/src/runtime/bridgeRegistry.test.ts +1056 -10
- package/src/runtime/bridgeRegistry.ts +464 -50
- package/src/runtime/bridgeScopedContext.ts +55 -0
- package/src/runtime/bridgeServer.test.ts +534 -4
- package/src/runtime/bridgeServer.ts +145 -38
- package/src/runtime/bridgeSetupHandlers.test.ts +156 -5
- package/src/runtime/bridgeSetupHandlers.ts +59 -14
- package/src/runtime/bridgeSupervisor.test.ts +670 -2
- package/src/runtime/bridgeSupervisor.ts +219 -67
- package/src/runtime/serve.ts +87 -11
- package/src/steam/steamcmd.test.ts +32 -1
- package/src/steam/steamcmd.ts +27 -4
- package/src/utils/changedSettingKeys.test.ts +65 -0
- package/src/utils/changedSettingKeys.ts +6 -0
- package/src/utils/describeExecFailure.test.ts +34 -0
- package/src/utils/describeExecFailure.ts +9 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/sameSettingValue.test.ts +35 -0
- package/src/utils/sameSettingValue.ts +40 -0
- package/src/wire/activity.test.ts +95 -0
- package/src/wire/activity.ts +41 -0
- package/src/wire/index.ts +1 -0
- package/src/wire/panel.test.ts +67 -1
- package/src/wire/panel.ts +11 -0
|
@@ -1,17 +1,50 @@
|
|
|
1
1
|
import type { BridgeContext } from "../context";
|
|
2
|
-
import { BridgeAction, BridgeKind, type BridgeRow, type BridgeValues, isReadAction } from "../core";
|
|
3
|
-
import type { BridgeDriver, BridgePanelModule, BridgeSetupModule, BridgeTerminalModule } from "../modules";
|
|
4
|
-
import { BridgeLayout } from "../panel";
|
|
5
2
|
import {
|
|
3
|
+
BRIDGE_AVAILABILITY_KEY_PATTERN,
|
|
4
|
+
BridgeAction,
|
|
5
|
+
type BridgeActivity,
|
|
6
|
+
type BridgeAvailability,
|
|
7
|
+
BridgeKind,
|
|
8
|
+
type BridgeRow,
|
|
9
|
+
type BridgeUnavailable,
|
|
10
|
+
type BridgeValues,
|
|
11
|
+
isLocalizedText,
|
|
12
|
+
isReadAction,
|
|
13
|
+
type LocalizedText,
|
|
14
|
+
} from "../core";
|
|
15
|
+
import type {
|
|
16
|
+
BridgeCatalogModule,
|
|
17
|
+
BridgeCollectionModule,
|
|
18
|
+
BridgeDriver,
|
|
19
|
+
BridgePanelModule,
|
|
20
|
+
BridgeSettingsModule,
|
|
21
|
+
BridgeSetupModule,
|
|
22
|
+
BridgeTerminalModule,
|
|
23
|
+
} from "../modules";
|
|
24
|
+
import { type BridgeDetail, BridgeLayout } from "../panel";
|
|
25
|
+
import {
|
|
26
|
+
BRIDGE_KEY_LENGTH_LIMIT,
|
|
27
|
+
BRIDGE_TEXT_LENGTH_LIMIT,
|
|
6
28
|
BRIDGE_VERSION,
|
|
29
|
+
BridgeEventName,
|
|
7
30
|
type BridgeManifest,
|
|
8
31
|
type BridgeModuleManifest,
|
|
9
32
|
type BridgeSetupManifest,
|
|
10
33
|
type BridgeTerminalManifest,
|
|
11
34
|
} from "../protocol";
|
|
35
|
+
import { changedSettingKeys } from "../utils";
|
|
12
36
|
import { actionsOf } from "./bridgeActions";
|
|
37
|
+
import {
|
|
38
|
+
type ActivityNormalizeOptions,
|
|
39
|
+
type BridgeActivityAnswer,
|
|
40
|
+
type BridgeActivityScope,
|
|
41
|
+
createActivityScope,
|
|
42
|
+
} from "./bridgeActivity";
|
|
13
43
|
import { BridgeDriverError } from "./bridgeDriver";
|
|
14
44
|
import type { BridgeEventRing } from "./bridgeEvents";
|
|
45
|
+
import { errorText } from "./bridgeLog";
|
|
46
|
+
import { BridgeRefusalError } from "./bridgeRefusalError";
|
|
47
|
+
import { scopedContext } from "./bridgeScopedContext";
|
|
15
48
|
import type { BridgeSupervisor } from "./bridgeSupervisor";
|
|
16
49
|
|
|
17
50
|
interface CacheEntry {
|
|
@@ -19,6 +52,11 @@ interface CacheEntry {
|
|
|
19
52
|
expiresAt: number;
|
|
20
53
|
}
|
|
21
54
|
|
|
55
|
+
const UNAVAILABLE_TEXT: LocalizedText = {
|
|
56
|
+
ar: "هذا الإجراء مو متاح الحين",
|
|
57
|
+
en: "This action is not available right now",
|
|
58
|
+
};
|
|
59
|
+
|
|
22
60
|
const requiresRunning = (module: BridgePanelModule) => {
|
|
23
61
|
if (
|
|
24
62
|
module.kind === BridgeKind.Actions
|
|
@@ -87,38 +125,9 @@ const setupManifestOf = (module: BridgeSetupModule | undefined): BridgeSetupMani
|
|
|
87
125
|
};
|
|
88
126
|
};
|
|
89
127
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return context.server;
|
|
94
|
-
},
|
|
95
|
-
variable(key) {
|
|
96
|
-
return Object.hasOwn(overlay, key) ? (overlay[key] ?? null) : context.variable(key);
|
|
97
|
-
},
|
|
98
|
-
secret(key) {
|
|
99
|
-
return context.secret(key);
|
|
100
|
-
},
|
|
101
|
-
port(key) {
|
|
102
|
-
return context.port(key);
|
|
103
|
-
},
|
|
104
|
-
files: context.files,
|
|
105
|
-
codec: context.codec,
|
|
106
|
-
net: context.net,
|
|
107
|
-
probe: context.probe,
|
|
108
|
-
rcon: context.rcon,
|
|
109
|
-
command(input, options) {
|
|
110
|
-
return context.command(input, options);
|
|
111
|
-
},
|
|
112
|
-
exec(command, options) {
|
|
113
|
-
return context.exec(command, options);
|
|
114
|
-
},
|
|
115
|
-
logs: context.logs,
|
|
116
|
-
setup: context.setup,
|
|
117
|
-
emit(event, payload) {
|
|
118
|
-
context.emit(event, payload);
|
|
119
|
-
},
|
|
120
|
-
log: context.log,
|
|
121
|
-
};
|
|
128
|
+
const SETTINGS_CHANGED_TEXT: LocalizedText = {
|
|
129
|
+
ar: "تم تغيير الإعدادات",
|
|
130
|
+
en: "Changed the settings",
|
|
122
131
|
};
|
|
123
132
|
|
|
124
133
|
const asString = (args: BridgeValues, key: string) => {
|
|
@@ -161,6 +170,65 @@ const asPage = (args: BridgeValues) => {
|
|
|
161
170
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
162
171
|
};
|
|
163
172
|
|
|
173
|
+
const guardedActionsOf = (module: BridgePanelModule): string[] => {
|
|
174
|
+
switch (module.kind) {
|
|
175
|
+
case BridgeKind.Actions: {
|
|
176
|
+
return Object.keys(module.actions);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case BridgeKind.Collection:
|
|
180
|
+
case BridgeKind.Detail: {
|
|
181
|
+
return Object.keys(module.actions ?? {});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case BridgeKind.Settings: {
|
|
185
|
+
return module.write
|
|
186
|
+
? [
|
|
187
|
+
BridgeAction.Write,
|
|
188
|
+
]
|
|
189
|
+
: [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case BridgeKind.Catalog: {
|
|
193
|
+
return [
|
|
194
|
+
BridgeAction.Remove,
|
|
195
|
+
...(module.toggle
|
|
196
|
+
? [
|
|
197
|
+
BridgeAction.Toggle,
|
|
198
|
+
]
|
|
199
|
+
: []),
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
default: {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const isAvailabilityKey = (key: string) => {
|
|
210
|
+
return key.length <= BRIDGE_KEY_LENGTH_LIMIT && BRIDGE_AVAILABILITY_KEY_PATTERN.test(key);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const refuse = (name: string, action: string, reasons: BridgeUnavailable) => {
|
|
214
|
+
const text = reasons[action];
|
|
215
|
+
|
|
216
|
+
if (text) {
|
|
217
|
+
throw new BridgeRefusalError(`module "${name}" cannot run "${action}" right now`, text);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const settingsChanged = (keys: string[]): BridgeActivity => {
|
|
222
|
+
return {
|
|
223
|
+
name: BridgeEventName.SettingsChanged,
|
|
224
|
+
text: SETTINGS_CHANGED_TEXT,
|
|
225
|
+
details: {
|
|
226
|
+
keys: keys.join(","),
|
|
227
|
+
count: keys.length,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
164
232
|
const offlineActionsOf = (driver: BridgeDriver): Map<string, Set<string>> => {
|
|
165
233
|
const offline = new Map<string, Set<string>>();
|
|
166
234
|
|
|
@@ -186,9 +254,10 @@ const offlineActionsOf = (driver: BridgeDriver): Map<string, Set<string>> => {
|
|
|
186
254
|
|
|
187
255
|
export const createRegistry = (
|
|
188
256
|
driver: BridgeDriver,
|
|
189
|
-
|
|
257
|
+
base: BridgeContext,
|
|
190
258
|
supervisor: BridgeSupervisor,
|
|
191
259
|
ring: BridgeEventRing,
|
|
260
|
+
activity: ActivityNormalizeOptions,
|
|
192
261
|
) => {
|
|
193
262
|
const cache = new Map<string, CacheEntry>();
|
|
194
263
|
const inflight = new Map<string, Promise<unknown>>();
|
|
@@ -196,7 +265,11 @@ export const createRegistry = (
|
|
|
196
265
|
const offlineActions = offlineActionsOf(driver);
|
|
197
266
|
|
|
198
267
|
for (const [id, module] of Object.entries(modules)) {
|
|
199
|
-
if (
|
|
268
|
+
if (
|
|
269
|
+
module.kind !== BridgeKind.Actions
|
|
270
|
+
&& module.kind !== BridgeKind.Collection
|
|
271
|
+
&& module.kind !== BridgeKind.Detail
|
|
272
|
+
) {
|
|
200
273
|
continue;
|
|
201
274
|
}
|
|
202
275
|
|
|
@@ -247,12 +320,286 @@ export const createRegistry = (
|
|
|
247
320
|
}
|
|
248
321
|
};
|
|
249
322
|
|
|
250
|
-
const
|
|
323
|
+
const listings = new Map<string, Promise<BridgeRow[]>>();
|
|
324
|
+
|
|
325
|
+
const listFresh = (module: BridgeCollectionModule, name: string, context: BridgeContext) => {
|
|
326
|
+
const rows = Promise.resolve().then(() => module.list(context));
|
|
327
|
+
|
|
328
|
+
listings.set(name, rows);
|
|
329
|
+
|
|
330
|
+
const settle = () => {
|
|
331
|
+
if (listings.get(name) === rows) {
|
|
332
|
+
listings.delete(name);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
rows.then(settle, settle);
|
|
337
|
+
|
|
338
|
+
return rows;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const listForAvailability = (module: BridgeCollectionModule, name: string, context: BridgeContext) => {
|
|
342
|
+
return listings.get(name) ?? listFresh(module, name, context);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const reasonOf = (text: LocalizedText): LocalizedText => {
|
|
346
|
+
const ar = activity.redact(text.ar.trim()).slice(0, BRIDGE_TEXT_LENGTH_LIMIT);
|
|
347
|
+
const en = activity.redact(text.en.trim()).slice(0, BRIDGE_TEXT_LENGTH_LIMIT);
|
|
348
|
+
|
|
349
|
+
return ar.length > 0 && en.length > 0
|
|
350
|
+
? {
|
|
351
|
+
ar,
|
|
352
|
+
en,
|
|
353
|
+
}
|
|
354
|
+
: UNAVAILABLE_TEXT;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const unavailableOf = (name: string, raw: unknown, guarded: readonly string[]): BridgeUnavailable => {
|
|
358
|
+
const kept: BridgeUnavailable = {};
|
|
359
|
+
|
|
360
|
+
if (raw === undefined || raw === null) {
|
|
361
|
+
return kept;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
365
|
+
activity.warn("availability answer is not an object, it is ignored", {
|
|
366
|
+
module: name,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return kept;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const dropped: string[] = [];
|
|
373
|
+
|
|
374
|
+
for (const [action, text] of Object.entries(raw)) {
|
|
375
|
+
if (!guarded.includes(action) || !isAvailabilityKey(action) || !isLocalizedText(text)) {
|
|
376
|
+
dropped.push(action);
|
|
377
|
+
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
kept[action] = reasonOf(text);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (dropped.length > 0) {
|
|
385
|
+
activity.warn("availability names actions the module cannot refuse, they are ignored", {
|
|
386
|
+
module: name,
|
|
387
|
+
actions: dropped.join(","),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return kept;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const isSubjectId = (name: string, id: unknown): id is string => {
|
|
395
|
+
if (typeof id === "string" && id.length > 0) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
activity.warn("availability skipped a subject without an id", {
|
|
400
|
+
module: name,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
return false;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const askUnavailable = async (
|
|
407
|
+
name: string,
|
|
408
|
+
guarded: readonly string[],
|
|
409
|
+
ask: () => Promise<unknown> | undefined,
|
|
410
|
+
): Promise<BridgeUnavailable> => {
|
|
411
|
+
try {
|
|
412
|
+
return unavailableOf(name, await ask(), guarded);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
activity.warn("availability could not be read, the actions count as available", {
|
|
415
|
+
module: name,
|
|
416
|
+
error: errorText(error),
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return {};
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const detailUnavailable = (name: string, detail: BridgeDetail, guarded: readonly string[]): BridgeUnavailable => {
|
|
424
|
+
const reasons = unavailableOf(name, detail.unavailable, guarded);
|
|
425
|
+
const pressable = Array.isArray(detail.actions) ? detail.actions : [];
|
|
426
|
+
const closed: BridgeUnavailable = {};
|
|
427
|
+
|
|
428
|
+
for (const action of guarded) {
|
|
429
|
+
if (!pressable.includes(action) && isAvailabilityKey(action)) {
|
|
430
|
+
closed[action] = reasons[action] ?? UNAVAILABLE_TEXT;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return closed;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const availabilityOf = async (
|
|
438
|
+
module: BridgePanelModule,
|
|
439
|
+
name: string,
|
|
440
|
+
args: BridgeValues,
|
|
441
|
+
context: BridgeContext,
|
|
442
|
+
): Promise<BridgeAvailability> => {
|
|
443
|
+
const guarded = guardedActionsOf(module);
|
|
444
|
+
const id = asOptional(args, "id");
|
|
445
|
+
const availability: BridgeAvailability = {
|
|
446
|
+
module: {},
|
|
447
|
+
subjects: {},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
switch (module.kind) {
|
|
451
|
+
case BridgeKind.Collection: {
|
|
452
|
+
if (!module.unavailable) {
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
for (const row of await listForAvailability(module, name, context)) {
|
|
457
|
+
if (!isSubjectId(name, row.id)) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (id !== null && row.id !== id) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const reasons = await askUnavailable(name, guarded, () => module.unavailable?.(context, row));
|
|
466
|
+
|
|
467
|
+
if (Object.keys(reasons).length > 0) {
|
|
468
|
+
availability.subjects[row.id] = reasons;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
case BridgeKind.Catalog: {
|
|
476
|
+
if (!module.unavailable) {
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
for (const entry of await module.installed(context)) {
|
|
481
|
+
if (!isSubjectId(name, entry.id)) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (id !== null && entry.id !== id) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const reasons = await askUnavailable(name, guarded, () => module.unavailable?.(context, entry));
|
|
490
|
+
|
|
491
|
+
if (Object.keys(reasons).length > 0) {
|
|
492
|
+
availability.subjects[entry.id] = reasons;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
case BridgeKind.Actions:
|
|
500
|
+
case BridgeKind.Settings: {
|
|
501
|
+
availability.module = await askUnavailable(name, guarded, () => module.unavailable?.(context));
|
|
502
|
+
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
case BridgeKind.Detail: {
|
|
507
|
+
const detail = await module.read(context);
|
|
508
|
+
|
|
509
|
+
if (detail) {
|
|
510
|
+
availability.module = detailUnavailable(name, detail, guarded);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return availability;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const refuseEntry = async (
|
|
521
|
+
module: BridgeCatalogModule,
|
|
522
|
+
name: string,
|
|
523
|
+
action: string,
|
|
524
|
+
id: string,
|
|
525
|
+
context: BridgeContext,
|
|
526
|
+
) => {
|
|
527
|
+
if (!module.unavailable) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
refuse(
|
|
532
|
+
name,
|
|
533
|
+
action,
|
|
534
|
+
await askUnavailable(name, guardedActionsOf(module), async () => {
|
|
535
|
+
const entry = (await module.installed(context)).find((installed) => installed.id === id);
|
|
536
|
+
|
|
537
|
+
return entry ? await module.unavailable?.(context, entry) : undefined;
|
|
538
|
+
}),
|
|
539
|
+
);
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const readBefore = async (module: BridgeSettingsModule, scoped: BridgeContext, name: string) => {
|
|
543
|
+
try {
|
|
544
|
+
return {
|
|
545
|
+
...(await module.read(scoped)),
|
|
546
|
+
};
|
|
547
|
+
} catch (error) {
|
|
548
|
+
activity.warn("settings could not be read before the write", {
|
|
549
|
+
module: name,
|
|
550
|
+
error: errorText(error),
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const reportSettingsDefault = (
|
|
558
|
+
scope: BridgeActivityScope,
|
|
559
|
+
before: BridgeValues | null,
|
|
560
|
+
written: BridgeValues,
|
|
561
|
+
name: string,
|
|
562
|
+
) => {
|
|
563
|
+
try {
|
|
564
|
+
const keys = changedSettingKeys(before, written);
|
|
565
|
+
|
|
566
|
+
if (scope.reported === 0 && keys.length > 0) {
|
|
567
|
+
scope.reporter.report(settingsChanged(keys));
|
|
568
|
+
}
|
|
569
|
+
} catch (error) {
|
|
570
|
+
activity.warn("settings change could not be reported", {
|
|
571
|
+
module: name,
|
|
572
|
+
error: errorText(error),
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const dispatch = async (
|
|
578
|
+
module: BridgePanelModule,
|
|
579
|
+
name: string,
|
|
580
|
+
action: string,
|
|
581
|
+
args: BridgeValues,
|
|
582
|
+
scope: BridgeActivityScope,
|
|
583
|
+
) => {
|
|
584
|
+
const context = scopedContext(base, scope.reporter);
|
|
585
|
+
|
|
586
|
+
if (action === BridgeAction.Availability && actionsOf(module).includes(BridgeAction.Availability)) {
|
|
587
|
+
return await availabilityOf(module, name, args, context);
|
|
588
|
+
}
|
|
589
|
+
|
|
251
590
|
switch (module.kind) {
|
|
252
591
|
case BridgeKind.Actions: {
|
|
253
592
|
const serverAction = module.actions[action];
|
|
254
593
|
|
|
255
594
|
if (serverAction) {
|
|
595
|
+
if (module.unavailable) {
|
|
596
|
+
refuse(
|
|
597
|
+
name,
|
|
598
|
+
action,
|
|
599
|
+
await askUnavailable(name, guardedActionsOf(module), () => module.unavailable?.(context)),
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
256
603
|
await serverAction(context, args);
|
|
257
604
|
|
|
258
605
|
return null;
|
|
@@ -273,7 +620,7 @@ export const createRegistry = (
|
|
|
273
620
|
}
|
|
274
621
|
}
|
|
275
622
|
|
|
276
|
-
const scoped =
|
|
623
|
+
const scoped = scopedContext(base, scope.reporter, overlay);
|
|
277
624
|
|
|
278
625
|
const key = [
|
|
279
626
|
name,
|
|
@@ -296,7 +643,7 @@ export const createRegistry = (
|
|
|
296
643
|
}
|
|
297
644
|
}
|
|
298
645
|
|
|
299
|
-
const scoped =
|
|
646
|
+
const scoped = scopedContext(base, scope.reporter, overlay);
|
|
300
647
|
|
|
301
648
|
return await module.preview(scoped, args);
|
|
302
649
|
}
|
|
@@ -310,8 +657,20 @@ export const createRegistry = (
|
|
|
310
657
|
}
|
|
311
658
|
|
|
312
659
|
if (action === BridgeAction.Write && module.write) {
|
|
660
|
+
if (module.unavailable) {
|
|
661
|
+
refuse(
|
|
662
|
+
name,
|
|
663
|
+
action,
|
|
664
|
+
await askUnavailable(name, guardedActionsOf(module), () => module.unavailable?.(context)),
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const before = await readBefore(module, context, name);
|
|
669
|
+
|
|
313
670
|
await module.write(context, args);
|
|
314
671
|
|
|
672
|
+
reportSettingsDefault(scope, before, args, name);
|
|
673
|
+
|
|
315
674
|
return null;
|
|
316
675
|
}
|
|
317
676
|
|
|
@@ -320,7 +679,7 @@ export const createRegistry = (
|
|
|
320
679
|
|
|
321
680
|
case BridgeKind.Collection: {
|
|
322
681
|
if (action === BridgeAction.List) {
|
|
323
|
-
return await module
|
|
682
|
+
return await listFresh(module, name, context);
|
|
324
683
|
}
|
|
325
684
|
|
|
326
685
|
if (action === BridgeAction.Add && module.add) {
|
|
@@ -342,7 +701,17 @@ export const createRegistry = (
|
|
|
342
701
|
: undefined);
|
|
343
702
|
|
|
344
703
|
if (!row) {
|
|
345
|
-
throw new
|
|
704
|
+
throw new BridgeRefusalError(`module "${name}" has no row "${id}"`);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (module.unavailable) {
|
|
708
|
+
refuse(
|
|
709
|
+
name,
|
|
710
|
+
action,
|
|
711
|
+
await askUnavailable(name, guardedActionsOf(module), () =>
|
|
712
|
+
module.unavailable?.(context, row as BridgeRow),
|
|
713
|
+
),
|
|
714
|
+
);
|
|
346
715
|
}
|
|
347
716
|
|
|
348
717
|
await rowAction(context, row as BridgeRow, withoutId(args));
|
|
@@ -361,6 +730,18 @@ export const createRegistry = (
|
|
|
361
730
|
const detailAction = module.actions?.[action];
|
|
362
731
|
|
|
363
732
|
if (detailAction) {
|
|
733
|
+
const guarded = guardedActionsOf(module);
|
|
734
|
+
|
|
735
|
+
refuse(
|
|
736
|
+
name,
|
|
737
|
+
action,
|
|
738
|
+
await askUnavailable(name, guarded, async () => {
|
|
739
|
+
const detail = await module.read(context);
|
|
740
|
+
|
|
741
|
+
return detail ? detailUnavailable(name, detail, guarded) : undefined;
|
|
742
|
+
}),
|
|
743
|
+
);
|
|
744
|
+
|
|
364
745
|
return await detailAction(context, args);
|
|
365
746
|
}
|
|
366
747
|
|
|
@@ -393,13 +774,19 @@ export const createRegistry = (
|
|
|
393
774
|
}
|
|
394
775
|
|
|
395
776
|
if (action === BridgeAction.Remove) {
|
|
396
|
-
|
|
777
|
+
const id = asString(args, "id");
|
|
778
|
+
|
|
779
|
+
await refuseEntry(module, name, action, id, context);
|
|
780
|
+
await module.remove(context, id);
|
|
397
781
|
|
|
398
782
|
return null;
|
|
399
783
|
}
|
|
400
784
|
|
|
401
785
|
if (action === BridgeAction.Toggle && module.toggle) {
|
|
402
|
-
|
|
786
|
+
const id = asString(args, "id");
|
|
787
|
+
|
|
788
|
+
await refuseEntry(module, name, action, id, context);
|
|
789
|
+
await module.toggle(context, id, args.enabled !== false);
|
|
403
790
|
|
|
404
791
|
return null;
|
|
405
792
|
}
|
|
@@ -408,7 +795,7 @@ export const createRegistry = (
|
|
|
408
795
|
}
|
|
409
796
|
}
|
|
410
797
|
|
|
411
|
-
throw new
|
|
798
|
+
throw new BridgeRefusalError(`module "${name}" does not support "${action}"`);
|
|
412
799
|
};
|
|
413
800
|
|
|
414
801
|
return {
|
|
@@ -441,22 +828,49 @@ export const createRegistry = (
|
|
|
441
828
|
};
|
|
442
829
|
},
|
|
443
830
|
|
|
444
|
-
async invoke(
|
|
831
|
+
async invoke(
|
|
832
|
+
name: string,
|
|
833
|
+
action: string,
|
|
834
|
+
args: BridgeValues = {},
|
|
835
|
+
recoveryOperationId?: string,
|
|
836
|
+
): Promise<BridgeActivityAnswer<unknown>> {
|
|
445
837
|
const module = modules[name];
|
|
446
838
|
|
|
447
839
|
if (!module) {
|
|
448
|
-
throw new
|
|
840
|
+
throw new BridgeRefusalError(`no module named "${name}"`);
|
|
449
841
|
}
|
|
450
842
|
|
|
451
843
|
if (protectedActionsOf(module).includes(action) && (!recoveryOperationId || supervisor.running)) {
|
|
452
|
-
throw new
|
|
844
|
+
throw new BridgeRefusalError("this action needs a recovery operation with the game stopped");
|
|
453
845
|
}
|
|
454
846
|
|
|
455
847
|
if (requiresRunning(module) && !supervisor.running) {
|
|
456
|
-
throw new
|
|
848
|
+
throw new BridgeRefusalError(`module "${name}" needs the game to be running`);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const scope = createActivityScope(base.activity, activity);
|
|
852
|
+
const mutation = !isReadAction(action);
|
|
853
|
+
|
|
854
|
+
if (mutation) {
|
|
855
|
+
listings.clear();
|
|
457
856
|
}
|
|
458
857
|
|
|
459
|
-
|
|
858
|
+
try {
|
|
859
|
+
const result = await dispatch(module, name, action, args, scope);
|
|
860
|
+
|
|
861
|
+
return {
|
|
862
|
+
result,
|
|
863
|
+
activity: scope.close(),
|
|
864
|
+
};
|
|
865
|
+
} catch (error) {
|
|
866
|
+
scope.drain();
|
|
867
|
+
|
|
868
|
+
throw error;
|
|
869
|
+
} finally {
|
|
870
|
+
if (mutation) {
|
|
871
|
+
listings.clear();
|
|
872
|
+
}
|
|
873
|
+
}
|
|
460
874
|
},
|
|
461
875
|
};
|
|
462
876
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { BridgeActivityReporter, BridgeContext } from "../context";
|
|
2
|
+
|
|
3
|
+
export const scopedContext = (
|
|
4
|
+
context: BridgeContext,
|
|
5
|
+
activity: BridgeActivityReporter,
|
|
6
|
+
overlay: Record<string, string> = {},
|
|
7
|
+
): BridgeContext => {
|
|
8
|
+
return {
|
|
9
|
+
get server() {
|
|
10
|
+
return context.server;
|
|
11
|
+
},
|
|
12
|
+
variable(key) {
|
|
13
|
+
return Object.hasOwn(overlay, key) ? (overlay[key] ?? null) : context.variable(key);
|
|
14
|
+
},
|
|
15
|
+
variables() {
|
|
16
|
+
return {
|
|
17
|
+
...context.variables(),
|
|
18
|
+
...overlay,
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
secret(key) {
|
|
22
|
+
return context.secret(key);
|
|
23
|
+
},
|
|
24
|
+
secrets() {
|
|
25
|
+
return context.secrets();
|
|
26
|
+
},
|
|
27
|
+
port(key) {
|
|
28
|
+
return context.port(key);
|
|
29
|
+
},
|
|
30
|
+
portBlock(key) {
|
|
31
|
+
return context.portBlock(key);
|
|
32
|
+
},
|
|
33
|
+
files: context.files,
|
|
34
|
+
codec: context.codec,
|
|
35
|
+
net: context.net,
|
|
36
|
+
probe: context.probe,
|
|
37
|
+
rcon: context.rcon,
|
|
38
|
+
command(input, options) {
|
|
39
|
+
return context.command(input, options);
|
|
40
|
+
},
|
|
41
|
+
exec(command, options) {
|
|
42
|
+
return context.exec(command, options);
|
|
43
|
+
},
|
|
44
|
+
logs: context.logs,
|
|
45
|
+
setup: context.setup,
|
|
46
|
+
activity,
|
|
47
|
+
emit(event, payload) {
|
|
48
|
+
context.emit(event, payload);
|
|
49
|
+
},
|
|
50
|
+
restart() {
|
|
51
|
+
return context.restart();
|
|
52
|
+
},
|
|
53
|
+
log: context.log,
|
|
54
|
+
};
|
|
55
|
+
};
|