@serverkgg/bridge 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/blog/articleFrontmatter.test.ts +95 -0
- package/src/blog/articleFrontmatter.ts +99 -0
- package/src/blog/compile.test.ts +148 -0
- package/src/blog/compile.ts +172 -0
- package/src/blog/compiled.ts +22 -0
- package/src/blog/index.ts +3 -0
- package/src/cli/validate.test.ts +116 -1
- package/src/cli/validate.ts +57 -4
- package/src/context/bridgeContext.ts +20 -0
- package/src/guides/compile.ts +70 -54
- package/src/manifest/guide.ts +1 -0
- package/src/manifest/manifest.test.ts +33 -0
- package/src/panel/bridgeControl.ts +1 -0
- package/src/panel/bridgeDetail.ts +1 -0
- package/src/panel/bridgePanel.ts +2 -0
- package/src/probes/index.ts +9 -0
- package/src/probes/minecraftPing.test.ts +344 -0
- package/src/probes/minecraftPing.ts +283 -0
- package/src/probes/raknetPing.test.ts +197 -0
- package/src/probes/raknetPing.ts +228 -0
- package/src/protocol/bridgeLimits.ts +8 -0
- package/src/runtime/bridgeDriver.test.ts +240 -0
- package/src/runtime/bridgeDriver.ts +137 -0
- package/src/runtime/bridgeProbe.ts +13 -1
- package/src/runtime/bridgeSetup.test.ts +387 -1
- package/src/runtime/bridgeSetup.ts +247 -7
- package/src/runtime/bridgeSetupHandlers.test.ts +303 -0
- package/src/runtime/bridgeSetupHandlers.ts +71 -0
- package/src/runtime/serve.ts +8 -41
- package/src/wire/panel.test.ts +62 -0
- package/src/wire/panel.ts +3 -0
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { BridgeSetupPromptKind, BridgeSetupStepState, type LocalizedText } from "../core";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { BRIDGE_ID_PATTERN, BridgeSetupPromptKind, BridgeSetupStepState, type LocalizedText } from "../core";
|
|
2
|
+
import {
|
|
3
|
+
BRIDGE_SETUP_ACTIONS_LIMIT,
|
|
4
|
+
BRIDGE_SETUP_CODE_LENGTH_LIMIT,
|
|
5
|
+
BRIDGE_SETUP_DETAIL_LENGTH_LIMIT,
|
|
6
|
+
type BridgeSetupAction,
|
|
7
|
+
type BridgeSetupPrompt,
|
|
8
|
+
type BridgeSetupResult,
|
|
9
|
+
type BridgeSetupRuntime,
|
|
10
|
+
type BridgeSetupStepStatus,
|
|
11
|
+
type BridgeSetupSummary,
|
|
8
12
|
} from "../protocol";
|
|
13
|
+
import type { BridgeLogger } from "./bridgeLog";
|
|
9
14
|
|
|
10
15
|
const ATTENTION_KINDS: ReadonlySet<BridgeSetupPromptKind> = new Set([
|
|
11
16
|
BridgeSetupPromptKind.Link,
|
|
@@ -61,3 +66,238 @@ export const failedSetup = (runtime: BridgeSetupRuntime | null, text: LocalizedT
|
|
|
61
66
|
},
|
|
62
67
|
};
|
|
63
68
|
};
|
|
69
|
+
|
|
70
|
+
const BROKEN_LINK_TEXT: LocalizedText = {
|
|
71
|
+
ar: "اللعبة رجّعت رابط دخول ما يشتغل. جرّب مرة ثانية.",
|
|
72
|
+
en: "The game reported a broken sign-in link. Try again.",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const LINK_PROTOCOLS: ReadonlySet<string> = new Set([
|
|
76
|
+
"http:",
|
|
77
|
+
"https:",
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
export interface SetupNormalizeOptions {
|
|
81
|
+
declared: ReadonlySet<string>;
|
|
82
|
+
warn: BridgeLogger;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const httpUrl = (url: string) => {
|
|
86
|
+
try {
|
|
87
|
+
return LINK_PROTOCOLS.has(new URL(url).protocol);
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const normalizeSetupRuntime = (
|
|
94
|
+
runtime: BridgeSetupRuntime | null,
|
|
95
|
+
options: SetupNormalizeOptions,
|
|
96
|
+
): BridgeSetupRuntime | null => {
|
|
97
|
+
if (!runtime) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const reported = new Set<string>();
|
|
102
|
+
|
|
103
|
+
const report = (reason: string) => {
|
|
104
|
+
if (reported.has(reason)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
reported.add(reason);
|
|
109
|
+
|
|
110
|
+
options.warn("setup runtime normalized", {
|
|
111
|
+
reason,
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const steps: BridgeSetupStepStatus[] = [];
|
|
116
|
+
const seen = new Set<string>();
|
|
117
|
+
|
|
118
|
+
let active = false;
|
|
119
|
+
|
|
120
|
+
for (const step of runtime.steps) {
|
|
121
|
+
if (!options.declared.has(step.id)) {
|
|
122
|
+
report("unknown-step");
|
|
123
|
+
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (seen.has(step.id)) {
|
|
128
|
+
report("duplicate-step");
|
|
129
|
+
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
seen.add(step.id);
|
|
134
|
+
|
|
135
|
+
if (step.state !== BridgeSetupStepState.Active) {
|
|
136
|
+
steps.push(step);
|
|
137
|
+
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (active) {
|
|
142
|
+
report("extra-active-step");
|
|
143
|
+
|
|
144
|
+
steps.push({
|
|
145
|
+
id: step.id,
|
|
146
|
+
state: BridgeSetupStepState.Pending,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
active = true;
|
|
153
|
+
|
|
154
|
+
steps.push(step);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
steps,
|
|
159
|
+
prompt: normalizePrompt(runtime.prompt, report),
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const normalizeActions = (actions: BridgeSetupAction[], report: (reason: string) => void): BridgeSetupAction[] => {
|
|
164
|
+
const kept: BridgeSetupAction[] = [];
|
|
165
|
+
const seen = new Set<string>();
|
|
166
|
+
|
|
167
|
+
for (const action of actions) {
|
|
168
|
+
if (!BRIDGE_ID_PATTERN.test(action.id)) {
|
|
169
|
+
report("unknown-action-id");
|
|
170
|
+
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (seen.has(action.id)) {
|
|
175
|
+
report("duplicate-action");
|
|
176
|
+
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
seen.add(action.id);
|
|
181
|
+
kept.push(action);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (kept.length <= BRIDGE_SETUP_ACTIONS_LIMIT) {
|
|
185
|
+
return kept;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
report("extra-actions");
|
|
189
|
+
|
|
190
|
+
return kept.slice(0, BRIDGE_SETUP_ACTIONS_LIMIT);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const normalizePercent = (percent: number | null, report: (reason: string) => void): number | null => {
|
|
194
|
+
if (percent === null) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!Number.isFinite(percent)) {
|
|
199
|
+
report("unusable-percent");
|
|
200
|
+
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (percent < 0 || percent > 100) {
|
|
205
|
+
report("percent-out-of-range");
|
|
206
|
+
|
|
207
|
+
return Math.min(Math.max(percent, 0), 100);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return percent;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const normalizeLine = (
|
|
214
|
+
value: string | null,
|
|
215
|
+
limit: number,
|
|
216
|
+
reason: string,
|
|
217
|
+
report: (reason: string) => void,
|
|
218
|
+
): string | null => {
|
|
219
|
+
if (value === null) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const sliced = value.slice(0, limit);
|
|
224
|
+
|
|
225
|
+
if (sliced.length === 0) {
|
|
226
|
+
report(`empty-${reason}`);
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (sliced.length !== value.length) {
|
|
232
|
+
report(`long-${reason}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return sliced;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const normalizeExpiry = (expiresAt: string | null, report: (reason: string) => void): string | null => {
|
|
239
|
+
if (expiresAt === null) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const stamp = Date.parse(expiresAt);
|
|
244
|
+
|
|
245
|
+
if (!Number.isFinite(stamp)) {
|
|
246
|
+
report("unusable-expiry");
|
|
247
|
+
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return new Date(stamp).toISOString();
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const normalizePrompt = (
|
|
255
|
+
prompt: BridgeSetupPrompt | null,
|
|
256
|
+
report: (reason: string) => void,
|
|
257
|
+
): BridgeSetupPrompt | null => {
|
|
258
|
+
if (!prompt) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (prompt.kind === BridgeSetupPromptKind.Wait) {
|
|
263
|
+
return prompt;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (prompt.kind === BridgeSetupPromptKind.Progress) {
|
|
267
|
+
return {
|
|
268
|
+
kind: BridgeSetupPromptKind.Progress,
|
|
269
|
+
message: prompt.message,
|
|
270
|
+
percent: normalizePercent(prompt.percent, report),
|
|
271
|
+
detail: normalizeLine(prompt.detail, BRIDGE_SETUP_DETAIL_LENGTH_LIMIT, "detail", report),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const actions = normalizeActions(prompt.actions, report);
|
|
276
|
+
|
|
277
|
+
if (prompt.kind !== BridgeSetupPromptKind.Link) {
|
|
278
|
+
return {
|
|
279
|
+
kind: prompt.kind,
|
|
280
|
+
message: prompt.message,
|
|
281
|
+
actions,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!httpUrl(prompt.url)) {
|
|
286
|
+
report("unusable-link");
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
kind: BridgeSetupPromptKind.Failed,
|
|
290
|
+
message: BROKEN_LINK_TEXT,
|
|
291
|
+
actions,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
kind: BridgeSetupPromptKind.Link,
|
|
297
|
+
message: prompt.message,
|
|
298
|
+
url: prompt.url,
|
|
299
|
+
code: normalizeLine(prompt.code, BRIDGE_SETUP_CODE_LENGTH_LIMIT, "code", report),
|
|
300
|
+
expiresAt: normalizeExpiry(prompt.expiresAt, report),
|
|
301
|
+
actions,
|
|
302
|
+
};
|
|
303
|
+
};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import type { BridgeContext } from "../context";
|
|
4
|
+
import { BridgeKind, BridgeSetupPromptKind, BridgeSetupStepKind, BridgeSetupStepState, BridgeUserError } from "../core";
|
|
5
|
+
import type { BridgeDriver, BridgeSetupModule } from "../modules";
|
|
6
|
+
import type { BridgeConfig, BridgeSetupRuntime } from "../protocol";
|
|
7
|
+
import { createContext } from "./bridgeContext";
|
|
8
|
+
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
9
|
+
import { log } from "./bridgeLog";
|
|
10
|
+
import { createSetupHandlers } from "./bridgeSetupHandlers";
|
|
11
|
+
import { createSupervisor } from "./bridgeSupervisor";
|
|
12
|
+
|
|
13
|
+
const text = (value: string) => ({
|
|
14
|
+
ar: value,
|
|
15
|
+
en: value,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const config = (): BridgeConfig => ({
|
|
19
|
+
code: "abcdef",
|
|
20
|
+
token: "token",
|
|
21
|
+
cwd: `${tmpdir()}/serverk-setup-handlers-test`,
|
|
22
|
+
run: `${tmpdir()}/serverk-setup-handlers-test/run`,
|
|
23
|
+
ports: {},
|
|
24
|
+
memoryMb: 1024,
|
|
25
|
+
cpuPercent: 100,
|
|
26
|
+
gameUid: 1000,
|
|
27
|
+
gameGid: 1000,
|
|
28
|
+
variables: {},
|
|
29
|
+
secrets: {},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const contextOf = (): BridgeContext => {
|
|
33
|
+
const settings = config();
|
|
34
|
+
|
|
35
|
+
const supervisor = createSupervisor({
|
|
36
|
+
cwd: settings.cwd,
|
|
37
|
+
uid: settings.gameUid,
|
|
38
|
+
gid: settings.gameGid,
|
|
39
|
+
stopTimeoutSeconds: 1,
|
|
40
|
+
onLog: log,
|
|
41
|
+
resolveCommand: async () => [
|
|
42
|
+
"run",
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return createContext(settings, supervisor, {
|
|
47
|
+
onLog: log,
|
|
48
|
+
onEmit() {
|
|
49
|
+
return;
|
|
50
|
+
},
|
|
51
|
+
onSetup() {
|
|
52
|
+
return;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const recorder = () => {
|
|
58
|
+
const warnings: {
|
|
59
|
+
message: string;
|
|
60
|
+
fields?: Record<string, unknown>;
|
|
61
|
+
}[] = [];
|
|
62
|
+
|
|
63
|
+
const logger: BridgeLeveledLogger = Object.assign(
|
|
64
|
+
() => {
|
|
65
|
+
return;
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
warn: (message: string, fields?: Record<string, unknown>) => {
|
|
69
|
+
warnings.push({
|
|
70
|
+
message,
|
|
71
|
+
fields,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
error: () => {
|
|
75
|
+
return;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
logger,
|
|
82
|
+
warnings,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const driverWith = (setup?: BridgeSetupModule): BridgeDriver => ({
|
|
87
|
+
install: {
|
|
88
|
+
kind: BridgeKind.Install,
|
|
89
|
+
run: async () => {
|
|
90
|
+
return;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
lifecycle: {
|
|
94
|
+
kind: BridgeKind.Lifecycle,
|
|
95
|
+
command: async () => [
|
|
96
|
+
"run",
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
setup,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const signIn = (): BridgeSetupRuntime => ({
|
|
103
|
+
steps: [
|
|
104
|
+
{
|
|
105
|
+
id: "sign-in",
|
|
106
|
+
state: BridgeSetupStepState.Active,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
prompt: {
|
|
110
|
+
kind: BridgeSetupPromptKind.Link,
|
|
111
|
+
message: text("sign in with your account"),
|
|
112
|
+
url: "https://hytale.com/device",
|
|
113
|
+
code: "ABCD-EFGH",
|
|
114
|
+
expiresAt: null,
|
|
115
|
+
actions: [
|
|
116
|
+
{
|
|
117
|
+
id: "renew",
|
|
118
|
+
label: text("Get a new code"),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const setupWith = (submit?: BridgeSetupModule["submit"]): BridgeSetupModule => ({
|
|
125
|
+
kind: BridgeKind.Setup,
|
|
126
|
+
steps: [
|
|
127
|
+
{
|
|
128
|
+
kind: BridgeSetupStepKind.Driver,
|
|
129
|
+
id: "sign-in",
|
|
130
|
+
title: text("Sign in"),
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
submit,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("the setup handlers", () => {
|
|
137
|
+
test("answer unsupported for a driver that declares no setup", async () => {
|
|
138
|
+
const { logger } = recorder();
|
|
139
|
+
const handlers = createSetupHandlers(driverWith(), logger);
|
|
140
|
+
|
|
141
|
+
expect(handlers.onSetupStatus()).toEqual({
|
|
142
|
+
supported: false,
|
|
143
|
+
runtime: null,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
147
|
+
supported: false,
|
|
148
|
+
runtime: null,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("keep nothing for a driver that declares no setup, whatever it reports", () => {
|
|
153
|
+
const { logger, warnings } = recorder();
|
|
154
|
+
const handlers = createSetupHandlers(driverWith(), logger);
|
|
155
|
+
|
|
156
|
+
handlers.onSetup(signIn());
|
|
157
|
+
|
|
158
|
+
expect(handlers.current()).toBeNull();
|
|
159
|
+
expect(warnings).toEqual([]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("answer the normalized runtime the driver reported", () => {
|
|
163
|
+
const { logger, warnings } = recorder();
|
|
164
|
+
const handlers = createSetupHandlers(driverWith(setupWith()), logger);
|
|
165
|
+
|
|
166
|
+
handlers.onSetup({
|
|
167
|
+
steps: [
|
|
168
|
+
{
|
|
169
|
+
id: "sign-in",
|
|
170
|
+
state: BridgeSetupStepState.Active,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: "invent",
|
|
174
|
+
state: BridgeSetupStepState.Pending,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
prompt: null,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(handlers.onSetupStatus()).toEqual({
|
|
181
|
+
supported: true,
|
|
182
|
+
runtime: {
|
|
183
|
+
steps: [
|
|
184
|
+
{
|
|
185
|
+
id: "sign-in",
|
|
186
|
+
state: BridgeSetupStepState.Active,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
prompt: null,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
expect(handlers.current()).toEqual(handlers.onSetupStatus().runtime);
|
|
193
|
+
expect(warnings).toEqual([
|
|
194
|
+
{
|
|
195
|
+
message: "setup runtime normalized",
|
|
196
|
+
fields: {
|
|
197
|
+
reason: "unknown-step",
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("answer unsupported for a setup that exports no submit", async () => {
|
|
204
|
+
const { logger } = recorder();
|
|
205
|
+
const handlers = createSetupHandlers(driverWith(setupWith()), logger);
|
|
206
|
+
|
|
207
|
+
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
208
|
+
supported: false,
|
|
209
|
+
runtime: null,
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("turn a user error into a failed prompt that keeps the previous actions", async () => {
|
|
214
|
+
const { logger, warnings } = recorder();
|
|
215
|
+
|
|
216
|
+
const handlers = createSetupHandlers(
|
|
217
|
+
driverWith(
|
|
218
|
+
setupWith(async () => {
|
|
219
|
+
throw new BridgeUserError(text("this code cannot be renewed any more"));
|
|
220
|
+
}),
|
|
221
|
+
),
|
|
222
|
+
logger,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
handlers.onSetup(signIn());
|
|
226
|
+
|
|
227
|
+
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
228
|
+
supported: true,
|
|
229
|
+
runtime: {
|
|
230
|
+
steps: [
|
|
231
|
+
{
|
|
232
|
+
id: "sign-in",
|
|
233
|
+
state: BridgeSetupStepState.Active,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
prompt: {
|
|
237
|
+
kind: BridgeSetupPromptKind.Failed,
|
|
238
|
+
message: text("this code cannot be renewed any more"),
|
|
239
|
+
actions: [
|
|
240
|
+
{
|
|
241
|
+
id: "renew",
|
|
242
|
+
label: text("Get a new code"),
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
expect(warnings.at(-1)?.message).toBe("setup submit rejected");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("answer the runtime the driver reported while submitting", async () => {
|
|
252
|
+
const { logger } = recorder();
|
|
253
|
+
|
|
254
|
+
const handlers = createSetupHandlers(
|
|
255
|
+
driverWith(
|
|
256
|
+
setupWith(async () => {
|
|
257
|
+
handlers.onSetup({
|
|
258
|
+
steps: [
|
|
259
|
+
{
|
|
260
|
+
id: "sign-in",
|
|
261
|
+
state: BridgeSetupStepState.Done,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
prompt: null,
|
|
265
|
+
});
|
|
266
|
+
}),
|
|
267
|
+
),
|
|
268
|
+
logger,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
handlers.onSetup(signIn());
|
|
272
|
+
|
|
273
|
+
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
274
|
+
supported: true,
|
|
275
|
+
runtime: {
|
|
276
|
+
steps: [
|
|
277
|
+
{
|
|
278
|
+
id: "sign-in",
|
|
279
|
+
state: BridgeSetupStepState.Done,
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
prompt: null,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("let an error the driver cannot explain out", async () => {
|
|
288
|
+
const { logger } = recorder();
|
|
289
|
+
|
|
290
|
+
const handlers = createSetupHandlers(
|
|
291
|
+
driverWith(
|
|
292
|
+
setupWith(async () => {
|
|
293
|
+
throw new Error("the device endpoint answered 503");
|
|
294
|
+
}),
|
|
295
|
+
),
|
|
296
|
+
logger,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
await expect(handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).rejects.toThrow(
|
|
300
|
+
"the device endpoint answered 503",
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { BridgeContext } from "../context";
|
|
2
|
+
import { BridgeUserError, type BridgeValues } from "../core";
|
|
3
|
+
import type { BridgeDriver } from "../modules";
|
|
4
|
+
import type { BridgeSetupResult, BridgeSetupRuntime } from "../protocol";
|
|
5
|
+
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
6
|
+
import { failedSetup, normalizeSetupRuntime, unsupportedSetup } from "./bridgeSetup";
|
|
7
|
+
|
|
8
|
+
export interface BridgeSetupHandlers {
|
|
9
|
+
onSetup(runtime: BridgeSetupRuntime | null): void;
|
|
10
|
+
onSetupStatus(): BridgeSetupResult;
|
|
11
|
+
onSetupSubmit(context: BridgeContext, step: string, action: string, args: BridgeValues): Promise<BridgeSetupResult>;
|
|
12
|
+
current(): BridgeSetupRuntime | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const createSetupHandlers = (driver: BridgeDriver, log: BridgeLeveledLogger): BridgeSetupHandlers => {
|
|
16
|
+
const declared: ReadonlySet<string> = new Set((driver.setup?.steps ?? []).map((step) => step.id));
|
|
17
|
+
|
|
18
|
+
let current: BridgeSetupRuntime | null = null;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
onSetup(runtime) {
|
|
22
|
+
current = driver.setup
|
|
23
|
+
? normalizeSetupRuntime(runtime, {
|
|
24
|
+
declared,
|
|
25
|
+
warn: log.warn,
|
|
26
|
+
})
|
|
27
|
+
: null;
|
|
28
|
+
},
|
|
29
|
+
current() {
|
|
30
|
+
return current;
|
|
31
|
+
},
|
|
32
|
+
onSetupStatus() {
|
|
33
|
+
if (!driver.setup) {
|
|
34
|
+
return unsupportedSetup();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
supported: true,
|
|
39
|
+
runtime: current,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
async onSetupSubmit(context, step, action, args) {
|
|
43
|
+
const module = driver.setup;
|
|
44
|
+
|
|
45
|
+
if (!module?.submit) {
|
|
46
|
+
return unsupportedSetup();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await module.submit(context, step, action, args);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (!(error instanceof BridgeUserError)) {
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
log.warn("setup submit rejected", {
|
|
57
|
+
step,
|
|
58
|
+
action,
|
|
59
|
+
error: error.message,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return failedSetup(current, error.text);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
supported: true,
|
|
67
|
+
runtime: current,
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
package/src/runtime/serve.ts
CHANGED
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
type BridgeConfig,
|
|
8
8
|
type BridgePendingResult,
|
|
9
9
|
type BridgeSampleResult,
|
|
10
|
-
type BridgeSetupResult,
|
|
11
|
-
type BridgeSetupRuntime,
|
|
12
10
|
type BridgeStatus,
|
|
13
11
|
} from "../protocol";
|
|
14
12
|
import { configPath, readConfig, socketPath, statePath } from "./bridgeConfigReader";
|
|
@@ -20,7 +18,8 @@ import { errorText, log } from "./bridgeLog";
|
|
|
20
18
|
import { pendingResult, rejectedPending, unsupportedPending } from "./bridgePending";
|
|
21
19
|
import { createRegistry } from "./bridgeRegistry";
|
|
22
20
|
import { createServer } from "./bridgeServer";
|
|
23
|
-
import {
|
|
21
|
+
import { setupSummaryOf } from "./bridgeSetup";
|
|
22
|
+
import { createSetupHandlers } from "./bridgeSetupHandlers";
|
|
24
23
|
import { readDesired, writeDesired } from "./bridgeState";
|
|
25
24
|
import { createSupervisor } from "./bridgeSupervisor";
|
|
26
25
|
|
|
@@ -74,7 +73,7 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
74
73
|
|
|
75
74
|
let installHold: BridgeInstallHold | null = null;
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
const setup = createSetupHandlers(driver, log);
|
|
78
77
|
|
|
79
78
|
const supervisor = createSupervisor({
|
|
80
79
|
cwd: config.cwd,
|
|
@@ -106,7 +105,7 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
106
105
|
ring.emit(event, payload);
|
|
107
106
|
},
|
|
108
107
|
onSetup(runtime) {
|
|
109
|
-
|
|
108
|
+
setup.onSetup(runtime);
|
|
110
109
|
},
|
|
111
110
|
});
|
|
112
111
|
|
|
@@ -125,7 +124,7 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
125
124
|
installError,
|
|
126
125
|
installErrorText,
|
|
127
126
|
installHold,
|
|
128
|
-
setup: setupSummaryOf(
|
|
127
|
+
setup: setupSummaryOf(setup.current()),
|
|
129
128
|
};
|
|
130
129
|
};
|
|
131
130
|
|
|
@@ -320,42 +319,10 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
320
319
|
return await pending();
|
|
321
320
|
},
|
|
322
321
|
onSetupStatus() {
|
|
323
|
-
|
|
324
|
-
return unsupportedSetup();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return {
|
|
328
|
-
supported: true,
|
|
329
|
-
runtime: setupRuntime,
|
|
330
|
-
};
|
|
322
|
+
return setup.onSetupStatus();
|
|
331
323
|
},
|
|
332
|
-
async onSetupSubmit(step, action, args)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (!module?.submit) {
|
|
336
|
-
return unsupportedSetup();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
await module.submit(context, step, action, args);
|
|
341
|
-
} catch (error) {
|
|
342
|
-
if (!(error instanceof BridgeUserError)) {
|
|
343
|
-
throw error;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
log.warn("setup submit rejected", {
|
|
347
|
-
step,
|
|
348
|
-
action,
|
|
349
|
-
error: error.message,
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
return failedSetup(setupRuntime, error.text);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
supported: true,
|
|
357
|
-
runtime: setupRuntime,
|
|
358
|
-
};
|
|
324
|
+
async onSetupSubmit(step, action, args) {
|
|
325
|
+
return await setup.onSetupSubmit(context, step, action, args);
|
|
359
326
|
},
|
|
360
327
|
});
|
|
361
328
|
|