@serverkgg/bridge 0.0.8 → 0.0.10
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 -3
- package/src/cli/validate.ts +2 -0
- package/src/context/bridgeContext.ts +17 -0
- package/src/context/index.ts +1 -0
- package/src/core/bridgeDownloadError.ts +10 -0
- package/src/core/bridgeHold.ts +12 -0
- package/src/core/bridgeKind.ts +3 -0
- package/src/core/bridgeProbeError.ts +16 -0
- package/src/core/bridgeSetup.ts +20 -0
- package/src/core/bridgeUserError.test.ts +23 -0
- package/src/core/bridgeUserError.ts +12 -0
- package/src/core/bridgeValue.test.ts +29 -0
- package/src/core/bridgeValue.ts +12 -0
- package/src/core/index.ts +5 -0
- package/src/guides/blocks.ts +8 -0
- package/src/guides/markdown.test.ts +18 -0
- package/src/guides/markdown.ts +7 -1
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +132 -0
- package/src/manifest/manifest.ts +21 -0
- package/src/manifest/presence.ts +56 -0
- package/src/manifest/primitives.ts +6 -0
- package/src/modules/bridge.ts +65 -2
- package/src/modules/bridgeDriver.ts +4 -0
- package/src/modules/bridgeModule.ts +32 -3
- package/src/panel/bridgeDetail.ts +49 -0
- package/src/panel/bridgeLayout.ts +1 -0
- package/src/panel/bridgePanel.ts +30 -0
- package/src/panel/bridgeUploadMode.ts +4 -0
- package/src/panel/index.ts +2 -0
- package/src/probes/a2s.test.ts +71 -0
- package/src/probes/a2s.ts +38 -11
- package/src/protocol/bridgeEgress.ts +8 -1
- package/src/protocol/bridgeEvents.ts +4 -0
- package/src/protocol/bridgeFrame.ts +33 -1
- package/src/protocol/bridgeLimits.ts +12 -0
- package/src/protocol/bridgeManifest.ts +2 -0
- package/src/protocol/bridgeSetup.ts +97 -0
- package/src/protocol/index.ts +1 -0
- package/src/runtime/bridgeActions.ts +7 -0
- package/src/runtime/bridgeConfigReader.ts +12 -8
- package/src/runtime/bridgeContext.ts +28 -2
- package/src/runtime/bridgeDriver.test.ts +748 -3
- package/src/runtime/bridgeDriver.ts +198 -4
- package/src/runtime/bridgeEgress.ts +3 -0
- package/src/runtime/bridgeEvents.test.ts +96 -0
- package/src/runtime/bridgeEvents.ts +11 -6
- package/src/runtime/bridgeFiles.test.ts +625 -15
- package/src/runtime/bridgeFiles.ts +152 -45
- package/src/runtime/bridgeInstall.test.ts +118 -0
- package/src/runtime/bridgeInstall.ts +55 -0
- package/src/runtime/bridgeNet.ts +32 -3
- package/src/runtime/bridgePending.test.ts +148 -0
- package/src/runtime/bridgePending.ts +43 -0
- package/src/runtime/bridgeRegistry.test.ts +172 -1
- package/src/runtime/bridgeRegistry.ts +40 -5
- package/src/runtime/bridgeSecrets.test.ts +121 -0
- package/src/runtime/bridgeSecrets.ts +43 -0
- package/src/runtime/bridgeServer.test.ts +598 -0
- package/src/runtime/bridgeServer.ts +42 -3
- package/src/runtime/bridgeSetup.test.ts +189 -0
- package/src/runtime/bridgeSetup.ts +63 -0
- package/src/runtime/bridgeSignal.test.ts +114 -0
- package/src/runtime/bridgeSignal.ts +31 -0
- package/src/runtime/bridgeSupervisor.test.ts +111 -0
- package/src/runtime/bridgeSupervisor.ts +55 -5
- package/src/runtime/bridgeZip.ts +179 -58
- package/src/runtime/serve.ts +115 -4
- package/src/utils/index.ts +1 -0
- package/src/utils/stripAnsi.ts +12 -0
- package/src/wire/panel.test.ts +259 -0
- package/src/wire/panel.ts +72 -0
package/src/modules/bridge.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { BridgeContext, BridgeExecResult, BridgeFileEntry, BridgeReply } from "../context";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
BridgeCopy,
|
|
4
|
+
BridgeInstallHold,
|
|
5
|
+
BridgeInstallOutcome,
|
|
6
|
+
BridgeOption,
|
|
7
|
+
BridgeRow,
|
|
8
|
+
BridgeValue,
|
|
9
|
+
BridgeValues,
|
|
10
|
+
LocalizedText,
|
|
11
|
+
} from "../core";
|
|
12
|
+
import { BridgeUserError } from "../core";
|
|
13
|
+
import type {
|
|
14
|
+
BridgeDetail,
|
|
15
|
+
BridgeDetailBadge,
|
|
16
|
+
BridgeDetailLink,
|
|
17
|
+
BridgeDetailOutcome,
|
|
18
|
+
BridgeDetailStat,
|
|
19
|
+
BridgeField,
|
|
20
|
+
BridgeFormClear,
|
|
21
|
+
BridgePanel,
|
|
22
|
+
BridgeSection,
|
|
23
|
+
BridgeSectionAction,
|
|
24
|
+
BridgeTab,
|
|
25
|
+
BridgeTabRef,
|
|
26
|
+
BridgeUploadMode,
|
|
27
|
+
} from "../panel";
|
|
4
28
|
import type {
|
|
5
29
|
BridgeCatalogEntry,
|
|
6
30
|
BridgeCatalogFacet,
|
|
@@ -9,7 +33,17 @@ import type {
|
|
|
9
33
|
BridgeCatalogProvider,
|
|
10
34
|
BridgeCatalogQuery,
|
|
11
35
|
} from "../protocol/bridgeCatalog";
|
|
36
|
+
import type { BridgePendingFile } from "../protocol/bridgeFrame";
|
|
12
37
|
import type { BridgePreview } from "../protocol/bridgePreview";
|
|
38
|
+
import type {
|
|
39
|
+
BridgeSetupAction,
|
|
40
|
+
BridgeSetupManifest,
|
|
41
|
+
BridgeSetupPrompt,
|
|
42
|
+
BridgeSetupRuntime,
|
|
43
|
+
BridgeSetupStep,
|
|
44
|
+
BridgeSetupStepStatus,
|
|
45
|
+
BridgeSetupSummary,
|
|
46
|
+
} from "../protocol/bridgeSetup";
|
|
13
47
|
import type { BridgeTerminalArg, BridgeTerminalCommand, BridgeTerminalRule } from "../terminal";
|
|
14
48
|
import type { BridgeDriver } from "./bridgeDriver";
|
|
15
49
|
import type {
|
|
@@ -18,17 +52,21 @@ import type {
|
|
|
18
52
|
BridgeBackupModule,
|
|
19
53
|
BridgeCatalogModule,
|
|
20
54
|
BridgeCollectionModule,
|
|
55
|
+
BridgeDetailAction,
|
|
56
|
+
BridgeDetailModule,
|
|
21
57
|
BridgeEventPattern,
|
|
22
58
|
BridgeEventsModule,
|
|
23
59
|
BridgeInstallModule,
|
|
24
60
|
BridgeLifecycleModule,
|
|
25
61
|
BridgeOptionsModule,
|
|
62
|
+
BridgePendingModule,
|
|
26
63
|
BridgePreviewModule,
|
|
27
64
|
BridgeQueryModule,
|
|
28
65
|
BridgeRowAction,
|
|
29
66
|
BridgeSample,
|
|
30
67
|
BridgeServerAction,
|
|
31
68
|
BridgeSettingsModule,
|
|
69
|
+
BridgeSetupModule,
|
|
32
70
|
BridgeTerminalModule,
|
|
33
71
|
} from "./bridgeModule";
|
|
34
72
|
|
|
@@ -45,17 +83,30 @@ export namespace Bridge {
|
|
|
45
83
|
export type CatalogQuery = BridgeCatalogQuery;
|
|
46
84
|
export type Collection<Row extends BridgeRow = BridgeRow> = BridgeCollectionModule<Row>;
|
|
47
85
|
export type Context = BridgeContext;
|
|
86
|
+
export type Copy = BridgeCopy;
|
|
87
|
+
export type Detail = BridgeDetailModule;
|
|
88
|
+
export type DetailAction = BridgeDetailAction;
|
|
89
|
+
export type DetailBadge = BridgeDetailBadge;
|
|
90
|
+
export type DetailLink = BridgeDetailLink;
|
|
91
|
+
export type DetailOutcome = BridgeDetailOutcome;
|
|
92
|
+
export type DetailResult = BridgeDetail;
|
|
93
|
+
export type DetailStat = BridgeDetailStat;
|
|
48
94
|
export type Driver = BridgeDriver;
|
|
49
95
|
export type EventPattern = BridgeEventPattern;
|
|
50
96
|
export type Events = BridgeEventsModule;
|
|
51
97
|
export type ExecResult = BridgeExecResult;
|
|
52
98
|
export type Field = BridgeField;
|
|
53
99
|
export type FileEntry = BridgeFileEntry;
|
|
100
|
+
export type FormClear = BridgeFormClear;
|
|
54
101
|
export type Install = BridgeInstallModule;
|
|
102
|
+
export type InstallHold = BridgeInstallHold;
|
|
103
|
+
export type InstallOutcome = BridgeInstallOutcome;
|
|
55
104
|
export type Lifecycle = BridgeLifecycleModule;
|
|
56
105
|
export type Option = BridgeOption;
|
|
57
106
|
export type Options = BridgeOptionsModule;
|
|
58
107
|
export type Panel = BridgePanel;
|
|
108
|
+
export type Pending = BridgePendingModule;
|
|
109
|
+
export type PendingFile = BridgePendingFile;
|
|
59
110
|
export type Preview = BridgePreviewModule;
|
|
60
111
|
export type PreviewResult = BridgePreview;
|
|
61
112
|
export type Query = BridgeQueryModule;
|
|
@@ -67,11 +118,23 @@ export namespace Bridge {
|
|
|
67
118
|
export type SectionAction = BridgeSectionAction;
|
|
68
119
|
export type ServerAction = BridgeServerAction;
|
|
69
120
|
export type Settings = BridgeSettingsModule;
|
|
121
|
+
export type Setup = BridgeSetupModule;
|
|
122
|
+
export type SetupAction = BridgeSetupAction;
|
|
123
|
+
export type SetupManifest = BridgeSetupManifest;
|
|
124
|
+
export type SetupPrompt = BridgeSetupPrompt;
|
|
125
|
+
export type SetupRuntime = BridgeSetupRuntime;
|
|
126
|
+
export type SetupStep = BridgeSetupStep;
|
|
127
|
+
export type SetupStepStatus = BridgeSetupStepStatus;
|
|
128
|
+
export type SetupSummary = BridgeSetupSummary;
|
|
70
129
|
export type Terminal = BridgeTerminalModule;
|
|
71
130
|
export type TerminalArg = BridgeTerminalArg;
|
|
72
131
|
export type TerminalCommand = BridgeTerminalCommand;
|
|
73
132
|
export type TerminalRule = BridgeTerminalRule;
|
|
74
133
|
export type Tab = BridgeTab;
|
|
134
|
+
export type TabRef = BridgeTabRef;
|
|
135
|
+
export type UploadMode = BridgeUploadMode;
|
|
136
|
+
export const UserError = BridgeUserError;
|
|
137
|
+
export type UserError = BridgeUserError;
|
|
75
138
|
export type Text = LocalizedText;
|
|
76
139
|
export type Value = BridgeValue;
|
|
77
140
|
export type Values = BridgeValues;
|
|
@@ -6,7 +6,9 @@ import type {
|
|
|
6
6
|
BridgeInstallModule,
|
|
7
7
|
BridgeLifecycleModule,
|
|
8
8
|
BridgePanelModule,
|
|
9
|
+
BridgePendingModule,
|
|
9
10
|
BridgeQueryModule,
|
|
11
|
+
BridgeSetupModule,
|
|
10
12
|
BridgeTerminalModule,
|
|
11
13
|
} from "./bridgeModule";
|
|
12
14
|
|
|
@@ -17,6 +19,8 @@ export interface BridgeDriver {
|
|
|
17
19
|
query?: BridgeQueryModule;
|
|
18
20
|
backup?: BridgeBackupModule;
|
|
19
21
|
announce?: BridgeAnnounceModule;
|
|
22
|
+
pending?: BridgePendingModule;
|
|
23
|
+
setup?: BridgeSetupModule;
|
|
20
24
|
terminal?: BridgeTerminalModule;
|
|
21
25
|
panel?: BridgePanel;
|
|
22
26
|
modules?: Record<string, BridgePanelModule>;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { BridgeContext } from "../context";
|
|
2
|
-
import type { BridgeKind, BridgeOption, BridgeRow, BridgeStream, BridgeValues } from "../core";
|
|
2
|
+
import type { BridgeInstallOutcome, BridgeKind, BridgeOption, BridgeRow, BridgeStream, BridgeValues } from "../core";
|
|
3
|
+
import type { BridgeDetail, BridgeDetailOutcome } from "../panel";
|
|
3
4
|
import type { BridgeCatalogEntry, BridgeCatalogPage, BridgeCatalogQuery } from "../protocol/bridgeCatalog";
|
|
4
|
-
import type { BridgeDescribeResult } from "../protocol/bridgeFrame";
|
|
5
|
+
import type { BridgeDescribeResult, BridgePendingFile } from "../protocol/bridgeFrame";
|
|
5
6
|
import type { BridgePreview } from "../protocol/bridgePreview";
|
|
7
|
+
import type { BridgeSetupStep } from "../protocol/bridgeSetup";
|
|
6
8
|
import type { BridgeTerminalCommand, BridgeTerminalRule } from "../terminal";
|
|
7
9
|
|
|
8
10
|
export type BridgeDescription = Omit<BridgeDescribeResult, "supported">;
|
|
9
11
|
|
|
10
12
|
export interface BridgeInstallModule {
|
|
11
13
|
kind: BridgeKind.Install;
|
|
12
|
-
run(context: BridgeContext): Promise<
|
|
14
|
+
run(context: BridgeContext): Promise<BridgeInstallOutcome | undefined>;
|
|
13
15
|
describe?(context: BridgeContext): Promise<BridgeDescription>;
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -34,6 +36,20 @@ export interface BridgeBackupModule {
|
|
|
34
36
|
release?(context: BridgeContext): Promise<void>;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
export interface BridgePendingModule {
|
|
40
|
+
kind: BridgeKind.Pending;
|
|
41
|
+
staging: string;
|
|
42
|
+
extensions: string[];
|
|
43
|
+
list(context: BridgeContext): Promise<BridgePendingFile[]>;
|
|
44
|
+
add(context: BridgeContext, input: string): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BridgeSetupModule {
|
|
48
|
+
kind: BridgeKind.Setup;
|
|
49
|
+
steps: BridgeSetupStep[];
|
|
50
|
+
submit?(context: BridgeContext, step: string, action: string, args: BridgeValues): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
export interface BridgeSample {
|
|
38
54
|
online: number | null;
|
|
39
55
|
max: number | null;
|
|
@@ -49,6 +65,7 @@ export interface BridgeEventPattern {
|
|
|
49
65
|
match: RegExp;
|
|
50
66
|
emit: string;
|
|
51
67
|
stream?: BridgeStream;
|
|
68
|
+
payload?: BridgeValues;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
export interface BridgeEventsModule {
|
|
@@ -104,6 +121,16 @@ export interface BridgeCollectionModule<Row extends BridgeRow = BridgeRow> {
|
|
|
104
121
|
actions?: Record<string, BridgeRowAction<Row>>;
|
|
105
122
|
}
|
|
106
123
|
|
|
124
|
+
export type BridgeDetailAction = (context: BridgeContext, args: BridgeValues) => Promise<BridgeDetailOutcome | null>;
|
|
125
|
+
|
|
126
|
+
export interface BridgeDetailModule {
|
|
127
|
+
kind: BridgeKind.Detail;
|
|
128
|
+
requiresRunning?: boolean;
|
|
129
|
+
refreshSeconds?: number;
|
|
130
|
+
read(context: BridgeContext): Promise<BridgeDetail | null>;
|
|
131
|
+
actions?: Record<string, BridgeDetailAction>;
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
export interface BridgeCatalogModule {
|
|
108
135
|
kind: BridgeKind.Catalog;
|
|
109
136
|
pageSize?: number;
|
|
@@ -118,6 +145,7 @@ export type BridgePanelModule =
|
|
|
118
145
|
| BridgeActionsModule
|
|
119
146
|
| BridgeCatalogModule
|
|
120
147
|
| BridgeCollectionModule
|
|
148
|
+
| BridgeDetailModule
|
|
121
149
|
| BridgeOptionsModule
|
|
122
150
|
| BridgePreviewModule
|
|
123
151
|
| BridgeSettingsModule;
|
|
@@ -130,4 +158,5 @@ export type BridgeModule =
|
|
|
130
158
|
| BridgeInstallModule
|
|
131
159
|
| BridgeLifecycleModule
|
|
132
160
|
| BridgePanelModule
|
|
161
|
+
| BridgePendingModule
|
|
133
162
|
| BridgeQueryModule;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BridgeCopy, BridgeValue, LocalizedText } from "../core";
|
|
2
|
+
|
|
3
|
+
export enum BridgeDetailTone {
|
|
4
|
+
Neutral = "neutral",
|
|
5
|
+
Success = "success",
|
|
6
|
+
Warning = "warning",
|
|
7
|
+
Danger = "danger",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export enum BridgeDetailFormat {
|
|
11
|
+
Bytes = "bytes",
|
|
12
|
+
Date = "date",
|
|
13
|
+
Number = "number",
|
|
14
|
+
Text = "text",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BridgeDetailBadge {
|
|
18
|
+
label: LocalizedText;
|
|
19
|
+
tone: BridgeDetailTone;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BridgeDetailStat {
|
|
23
|
+
key: string;
|
|
24
|
+
label: LocalizedText;
|
|
25
|
+
value: BridgeValue | LocalizedText;
|
|
26
|
+
format: BridgeDetailFormat;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BridgeDetailLink {
|
|
30
|
+
label: LocalizedText;
|
|
31
|
+
url: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BridgeDetail {
|
|
35
|
+
id: string;
|
|
36
|
+
title: BridgeCopy;
|
|
37
|
+
subtitle: BridgeCopy | null;
|
|
38
|
+
description: BridgeCopy | null;
|
|
39
|
+
image: string | null;
|
|
40
|
+
badges: BridgeDetailBadge[];
|
|
41
|
+
stats: BridgeDetailStat[];
|
|
42
|
+
links: BridgeDetailLink[];
|
|
43
|
+
stale: boolean;
|
|
44
|
+
actions: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BridgeDetailOutcome {
|
|
48
|
+
variables: Record<string, string> | null;
|
|
49
|
+
}
|
package/src/panel/bridgePanel.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { BridgeControl } from "./bridgeControl";
|
|
|
5
5
|
import type { BridgeFormTarget } from "./bridgeFormTarget";
|
|
6
6
|
import type { BridgeIcon } from "./bridgeIcon";
|
|
7
7
|
import type { BridgeLayout } from "./bridgeLayout";
|
|
8
|
+
import type { BridgeUploadMode } from "./bridgeUploadMode";
|
|
8
9
|
|
|
9
10
|
export interface BridgeVisibleWhen {
|
|
10
11
|
variable: string;
|
|
@@ -44,6 +45,16 @@ export interface BridgeSectionAction {
|
|
|
44
45
|
fields?: BridgeField[];
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
export interface BridgeTabRef {
|
|
49
|
+
tab: string;
|
|
50
|
+
label: LocalizedText;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface BridgeFormClear {
|
|
54
|
+
variable: string;
|
|
55
|
+
warning: LocalizedText;
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
export interface BridgeSectionAdd {
|
|
48
59
|
label: LocalizedText;
|
|
49
60
|
placeholder?: string;
|
|
@@ -53,6 +64,7 @@ export interface BridgeSectionUpload {
|
|
|
53
64
|
label: LocalizedText;
|
|
54
65
|
extensions: string[];
|
|
55
66
|
staging: string;
|
|
67
|
+
mode?: BridgeUploadMode;
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
export interface BridgeActionsSection {
|
|
@@ -78,6 +90,7 @@ export interface BridgeFormSection {
|
|
|
78
90
|
confirm?: BridgeConfirm;
|
|
79
91
|
confirmText?: LocalizedText;
|
|
80
92
|
fields: BridgeField[];
|
|
93
|
+
clears?: BridgeFormClear[];
|
|
81
94
|
visibleWhen?: BridgeVisibleWhen;
|
|
82
95
|
access?: BridgeAccess;
|
|
83
96
|
}
|
|
@@ -130,10 +143,27 @@ export interface BridgeCatalogSection {
|
|
|
130
143
|
access?: BridgeAccess;
|
|
131
144
|
}
|
|
132
145
|
|
|
146
|
+
export interface BridgeDetailSection {
|
|
147
|
+
layout: BridgeLayout.Detail;
|
|
148
|
+
id: string;
|
|
149
|
+
title?: LocalizedText;
|
|
150
|
+
module: string;
|
|
151
|
+
actions?: BridgeSectionAction[];
|
|
152
|
+
reinstall?: boolean;
|
|
153
|
+
variable?: string;
|
|
154
|
+
confirm?: BridgeConfirm;
|
|
155
|
+
confirmText?: LocalizedText;
|
|
156
|
+
related?: BridgeTabRef;
|
|
157
|
+
empty?: LocalizedText;
|
|
158
|
+
visibleWhen?: BridgeVisibleWhen;
|
|
159
|
+
access?: BridgeAccess;
|
|
160
|
+
}
|
|
161
|
+
|
|
133
162
|
export type BridgeSection =
|
|
134
163
|
| BridgeActionsSection
|
|
135
164
|
| BridgeCardsSection
|
|
136
165
|
| BridgeCatalogSection
|
|
166
|
+
| BridgeDetailSection
|
|
137
167
|
| BridgeFormSection
|
|
138
168
|
| BridgeTableSection;
|
|
139
169
|
|
package/src/panel/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from "./bridgeAccess";
|
|
2
2
|
export * from "./bridgeConfirm";
|
|
3
3
|
export * from "./bridgeControl";
|
|
4
|
+
export * from "./bridgeDetail";
|
|
4
5
|
export * from "./bridgeFormTarget";
|
|
5
6
|
export * from "./bridgeIcon";
|
|
6
7
|
export * from "./bridgeLayout";
|
|
7
8
|
export * from "./bridgePanel";
|
|
9
|
+
export * from "./bridgeUploadMode";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { BridgeProbeCode, BridgeProbeError } from "../core";
|
|
3
|
+
import { probeA2s } from "./a2s";
|
|
4
|
+
|
|
5
|
+
const HOST = "127.0.0.1";
|
|
6
|
+
|
|
7
|
+
const CLOSED_PORT = 59_437;
|
|
8
|
+
|
|
9
|
+
const listen = async (reply: Uint8Array | null) => {
|
|
10
|
+
return await Bun.udpSocket({
|
|
11
|
+
hostname: HOST,
|
|
12
|
+
port: 0,
|
|
13
|
+
socket: {
|
|
14
|
+
data(socket, _data, port, address) {
|
|
15
|
+
if (reply) {
|
|
16
|
+
socket.send(reply, port, address);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe("probeA2s", () => {
|
|
24
|
+
test("rejects with an unreachable probe error when nothing listens on the port", async () => {
|
|
25
|
+
const probe = probeA2s(HOST, CLOSED_PORT, 2000);
|
|
26
|
+
|
|
27
|
+
await expect(probe).rejects.toBeInstanceOf(BridgeProbeError);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("survives the port-unreachable the game leaves behind before it binds", async () => {
|
|
31
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
32
|
+
await probeA2s(HOST, CLOSED_PORT, 300).catch(() => null);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
expect(process.exitCode ?? 0).toBe(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("times out against a port that never answers", async () => {
|
|
39
|
+
const silent = await listen(null);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const error = await probeA2s(HOST, silent.port, 250).catch((reason: unknown) => reason);
|
|
43
|
+
|
|
44
|
+
expect(error).toBeInstanceOf(BridgeProbeError);
|
|
45
|
+
expect((error as BridgeProbeError).code).toBe(BridgeProbeCode.TimedOut);
|
|
46
|
+
} finally {
|
|
47
|
+
silent.close();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("rejects a reply that is not an a2s frame", async () => {
|
|
52
|
+
const noisy = await listen(
|
|
53
|
+
new Uint8Array([
|
|
54
|
+
1,
|
|
55
|
+
2,
|
|
56
|
+
3,
|
|
57
|
+
4,
|
|
58
|
+
5,
|
|
59
|
+
]),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const error = await probeA2s(HOST, noisy.port, 2000).catch((reason: unknown) => reason);
|
|
64
|
+
|
|
65
|
+
expect(error).toBeInstanceOf(BridgeProbeError);
|
|
66
|
+
expect((error as BridgeProbeError).code).toBe(BridgeProbeCode.Malformed);
|
|
67
|
+
} finally {
|
|
68
|
+
noisy.close();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
package/src/probes/a2s.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BridgeA2sInfo } from "../context";
|
|
2
|
+
import { BridgeProbeCode, BridgeProbeError } from "../core";
|
|
2
3
|
|
|
3
4
|
const SIMPLE_PREFIX = 0xff;
|
|
4
5
|
|
|
@@ -67,7 +68,19 @@ const parseInfo = (view: Uint8Array) => {
|
|
|
67
68
|
};
|
|
68
69
|
};
|
|
69
70
|
|
|
71
|
+
const probeError = (code: BridgeProbeCode, message: string, cause: unknown) => {
|
|
72
|
+
if (cause instanceof BridgeProbeError) {
|
|
73
|
+
return cause;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const detail = cause instanceof Error && cause.message.length > 0 ? `: ${cause.message}` : "";
|
|
77
|
+
|
|
78
|
+
return new BridgeProbeError(code, `${message}${detail}`);
|
|
79
|
+
};
|
|
80
|
+
|
|
70
81
|
export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
82
|
+
const target = `${host}:${hostPort}`;
|
|
83
|
+
|
|
71
84
|
return new Promise<BridgeA2sInfo>((resolve, reject) => {
|
|
72
85
|
const startedAt = performance.now();
|
|
73
86
|
|
|
@@ -75,7 +88,7 @@ export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
|
75
88
|
let challenged = false;
|
|
76
89
|
let settled = false;
|
|
77
90
|
|
|
78
|
-
const finish = (outcome: Omit<BridgeA2sInfo, "latencyMs"> |
|
|
91
|
+
const finish = (outcome: Omit<BridgeA2sInfo, "latencyMs"> | BridgeProbeError) => {
|
|
79
92
|
if (settled) {
|
|
80
93
|
return;
|
|
81
94
|
}
|
|
@@ -83,9 +96,12 @@ export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
|
83
96
|
settled = true;
|
|
84
97
|
|
|
85
98
|
clearTimeout(timer);
|
|
86
|
-
socket?.close();
|
|
87
99
|
|
|
88
|
-
|
|
100
|
+
try {
|
|
101
|
+
socket?.close();
|
|
102
|
+
} catch {}
|
|
103
|
+
|
|
104
|
+
if (outcome instanceof BridgeProbeError) {
|
|
89
105
|
reject(outcome);
|
|
90
106
|
|
|
91
107
|
return;
|
|
@@ -98,7 +114,7 @@ export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
|
98
114
|
};
|
|
99
115
|
|
|
100
116
|
const timer = setTimeout(() => {
|
|
101
|
-
finish(new
|
|
117
|
+
finish(new BridgeProbeError(BridgeProbeCode.TimedOut, `a2s query to ${target} timed out`));
|
|
102
118
|
}, timeoutMs);
|
|
103
119
|
|
|
104
120
|
const handle = (view: Uint8Array) => {
|
|
@@ -109,27 +125,31 @@ export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
|
109
125
|
|| view[2] !== SIMPLE_PREFIX
|
|
110
126
|
|| view[3] !== SIMPLE_PREFIX
|
|
111
127
|
) {
|
|
112
|
-
finish(new
|
|
128
|
+
finish(new BridgeProbeError(BridgeProbeCode.Malformed, "a2s response is malformed"));
|
|
113
129
|
|
|
114
130
|
return;
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
if (view[4] === CHALLENGE_HEADER) {
|
|
118
134
|
if (challenged || view.length < 9) {
|
|
119
|
-
finish(new
|
|
135
|
+
finish(new BridgeProbeError(BridgeProbeCode.Malformed, "a2s challenge is malformed"));
|
|
120
136
|
|
|
121
137
|
return;
|
|
122
138
|
}
|
|
123
139
|
|
|
124
140
|
challenged = true;
|
|
125
141
|
|
|
126
|
-
|
|
142
|
+
try {
|
|
143
|
+
socket?.send(infoRequest(view.slice(5, 9)));
|
|
144
|
+
} catch (error) {
|
|
145
|
+
finish(probeError(BridgeProbeCode.Unreachable, `a2s challenge to ${target} could not be sent`, error));
|
|
146
|
+
}
|
|
127
147
|
|
|
128
148
|
return;
|
|
129
149
|
}
|
|
130
150
|
|
|
131
151
|
if (view[4] !== INFO_HEADER) {
|
|
132
|
-
finish(new
|
|
152
|
+
finish(new BridgeProbeError(BridgeProbeCode.Malformed, "a2s response has an unexpected header"));
|
|
133
153
|
|
|
134
154
|
return;
|
|
135
155
|
}
|
|
@@ -137,7 +157,7 @@ export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
|
137
157
|
try {
|
|
138
158
|
finish(parseInfo(view));
|
|
139
159
|
} catch (error) {
|
|
140
|
-
finish(
|
|
160
|
+
finish(probeError(BridgeProbeCode.Malformed, `a2s response from ${target} could not be read`, error));
|
|
141
161
|
}
|
|
142
162
|
};
|
|
143
163
|
|
|
@@ -150,6 +170,9 @@ export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
|
150
170
|
data(_socket, data) {
|
|
151
171
|
handle(data);
|
|
152
172
|
},
|
|
173
|
+
error(_socket, error) {
|
|
174
|
+
finish(probeError(BridgeProbeCode.Unreachable, `a2s query to ${target} failed`, error));
|
|
175
|
+
},
|
|
153
176
|
},
|
|
154
177
|
})
|
|
155
178
|
.then((created) => {
|
|
@@ -161,10 +184,14 @@ export const probeA2s = (host: string, hostPort: number, timeoutMs: number) => {
|
|
|
161
184
|
|
|
162
185
|
socket = created;
|
|
163
186
|
|
|
164
|
-
|
|
187
|
+
try {
|
|
188
|
+
created.send(infoRequest(null));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
finish(probeError(BridgeProbeCode.Unreachable, `a2s query to ${target} could not be sent`, error));
|
|
191
|
+
}
|
|
165
192
|
})
|
|
166
193
|
.catch((error) => {
|
|
167
|
-
finish(
|
|
194
|
+
finish(probeError(BridgeProbeCode.Unreachable, `a2s socket for ${target} could not be opened`, error));
|
|
168
195
|
});
|
|
169
196
|
});
|
|
170
197
|
};
|
|
@@ -4,18 +4,25 @@ export const BRIDGE_EGRESS_FRAME_LIMIT = 16 * 1024 * 1024;
|
|
|
4
4
|
|
|
5
5
|
export const BRIDGE_EGRESS_BODY_LIMIT = 8 * 1024 * 1024;
|
|
6
6
|
|
|
7
|
-
export const BRIDGE_EGRESS_DOWNLOAD_LIMIT =
|
|
7
|
+
export const BRIDGE_EGRESS_DOWNLOAD_LIMIT = 2 * 1024 * 1024 * 1024;
|
|
8
8
|
|
|
9
9
|
export enum BridgeEgressMethod {
|
|
10
10
|
Fetch = "fetch",
|
|
11
11
|
Download = "download",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export enum BridgeHttpMethod {
|
|
15
|
+
Get = "GET",
|
|
16
|
+
Post = "POST",
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export interface BridgeEgressRequest {
|
|
15
20
|
id: number;
|
|
16
21
|
token: string;
|
|
17
22
|
method: BridgeEgressMethod;
|
|
18
23
|
url: string;
|
|
24
|
+
httpMethod?: BridgeHttpMethod;
|
|
25
|
+
body?: string;
|
|
19
26
|
headers?: Record<string, string>;
|
|
20
27
|
timeoutMs?: number;
|
|
21
28
|
cache?: boolean;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export enum BridgeEventName {
|
|
2
2
|
GameModeChanged = "GameModeChanged",
|
|
3
|
+
MemoryUndersized = "MemoryUndersized",
|
|
3
4
|
MissingDependency = "MissingDependency",
|
|
5
|
+
ModCrashed = "ModCrashed",
|
|
6
|
+
PlayerAdvanced = "PlayerAdvanced",
|
|
7
|
+
PlayerDied = "PlayerDied",
|
|
4
8
|
PlayerJoined = "PlayerJoined",
|
|
5
9
|
PlayerLeft = "PlayerLeft",
|
|
6
10
|
PortBindFailed = "PortBindFailed",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { BridgeDesired, BridgeRuntime, BridgeValues } from "../core";
|
|
1
|
+
import type { BridgeDesired, BridgeInstallHold, BridgeRuntime, BridgeValues, LocalizedText } from "../core";
|
|
2
|
+
import type { BridgeSetupSummary } from "./bridgeSetup";
|
|
2
3
|
|
|
3
4
|
export const BRIDGE_SOCKET_NAME = "bridge.sock";
|
|
4
5
|
|
|
@@ -19,9 +20,13 @@ export enum BridgeMethod {
|
|
|
19
20
|
Events = "events",
|
|
20
21
|
Invoke = "invoke",
|
|
21
22
|
Manifest = "manifest",
|
|
23
|
+
PendingAdd = "pendingAdd",
|
|
24
|
+
PendingList = "pendingList",
|
|
22
25
|
Quiesce = "quiesce",
|
|
23
26
|
Release = "release",
|
|
24
27
|
Sample = "sample",
|
|
28
|
+
SetupStatus = "setupStatus",
|
|
29
|
+
SetupSubmit = "setupSubmit",
|
|
25
30
|
Start = "start",
|
|
26
31
|
Status = "status",
|
|
27
32
|
Stop = "stop",
|
|
@@ -59,6 +64,7 @@ export interface BridgeFailure {
|
|
|
59
64
|
error: string;
|
|
60
65
|
secret?: string;
|
|
61
66
|
failure?: string;
|
|
67
|
+
text?: LocalizedText;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
export type BridgeResponse = BridgeFailure | BridgeSuccess;
|
|
@@ -68,8 +74,12 @@ export interface BridgeStatus {
|
|
|
68
74
|
runtime: BridgeRuntime;
|
|
69
75
|
desired: BridgeDesired;
|
|
70
76
|
exitCode: number | null;
|
|
77
|
+
outOfMemory: boolean;
|
|
71
78
|
restarts: number;
|
|
72
79
|
installError: string | null;
|
|
80
|
+
installErrorText: LocalizedText | null;
|
|
81
|
+
installHold: BridgeInstallHold | null;
|
|
82
|
+
setup: BridgeSetupSummary | null;
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
export interface BridgeSampleResult {
|
|
@@ -95,6 +105,28 @@ export interface BridgeDescribeResult {
|
|
|
95
105
|
build: string | null;
|
|
96
106
|
}
|
|
97
107
|
|
|
108
|
+
export interface BridgePendingFile {
|
|
109
|
+
id: string;
|
|
110
|
+
title: LocalizedText;
|
|
111
|
+
fileName: string;
|
|
112
|
+
sizeBytes: number | null;
|
|
113
|
+
digest: string | null;
|
|
114
|
+
sourceUrl: string | null;
|
|
115
|
+
note: LocalizedText | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface BridgePendingRejection {
|
|
119
|
+
expected: string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface BridgePendingResult {
|
|
123
|
+
supported: boolean;
|
|
124
|
+
staging: string;
|
|
125
|
+
extensions: string[];
|
|
126
|
+
files: BridgePendingFile[];
|
|
127
|
+
rejected: BridgePendingRejection | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
98
130
|
export interface BridgeEvent {
|
|
99
131
|
seq: number;
|
|
100
132
|
event: string;
|