@serverkgg/bridge 0.0.10 → 0.0.11
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/src/cli/validate.test.ts +116 -1
- package/src/cli/validate.ts +50 -4
- package/src/manifest/guide.ts +1 -0
- package/src/panel/bridgeControl.ts +1 -0
- package/src/panel/bridgeDetail.ts +1 -0
- package/src/panel/bridgePanel.ts +2 -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/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
|
@@ -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
|
|
package/src/wire/panel.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
BridgeAction,
|
|
6
6
|
BridgeConfirm,
|
|
7
7
|
BridgeControl,
|
|
8
|
+
type BridgeField,
|
|
8
9
|
BridgeFormTarget,
|
|
9
10
|
BridgeKind,
|
|
10
11
|
BridgeLayout,
|
|
@@ -457,6 +458,67 @@ describe("the upload mirror", () => {
|
|
|
457
458
|
});
|
|
458
459
|
});
|
|
459
460
|
|
|
461
|
+
const fieldManifest = (field: BridgeField): BridgeManifest => ({
|
|
462
|
+
version: "1.0.0",
|
|
463
|
+
panel: {
|
|
464
|
+
tabs: [
|
|
465
|
+
{
|
|
466
|
+
id: "license",
|
|
467
|
+
title: text("Licence"),
|
|
468
|
+
sections: [
|
|
469
|
+
{
|
|
470
|
+
layout: BridgeLayout.Form,
|
|
471
|
+
id: "license",
|
|
472
|
+
target: BridgeFormTarget.Variables,
|
|
473
|
+
fields: [
|
|
474
|
+
field,
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
terminal: null,
|
|
482
|
+
setup: null,
|
|
483
|
+
modules: [],
|
|
484
|
+
events: [],
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
describe("secret fields", () => {
|
|
488
|
+
test("carries a secret control with its pattern and hint through unchanged", () => {
|
|
489
|
+
const source = fieldManifest({
|
|
490
|
+
key: "LICENSE_KEY",
|
|
491
|
+
control: BridgeControl.Secret,
|
|
492
|
+
label: text("Licence key"),
|
|
493
|
+
pattern: "^cfxk_\\w{1,60}_\\w{1,20}$",
|
|
494
|
+
patternHint: text("A licence key starts with cfxk_"),
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
expect(bridgeManifestSchema.parse(source)).toEqual(source);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("refuses a control the platform does not know", () => {
|
|
501
|
+
const source = fieldManifest({
|
|
502
|
+
key: "LICENSE_KEY",
|
|
503
|
+
control: "password" as BridgeControl,
|
|
504
|
+
label: text("Licence key"),
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("refuses a pattern longer than the platform takes", () => {
|
|
511
|
+
const source = fieldManifest({
|
|
512
|
+
key: "LICENSE_KEY",
|
|
513
|
+
control: BridgeControl.Secret,
|
|
514
|
+
label: text("Licence key"),
|
|
515
|
+
pattern: `^${"a".repeat(256)}$`,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
expect(bridgeManifestSchema.safeParse(source).success).toBe(false);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
460
522
|
const setupManifest = (setup: BridgeSetupManifest | null): BridgeManifest => ({
|
|
461
523
|
version: "1.0.0",
|
|
462
524
|
panel: null,
|
package/src/wire/panel.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { guideOpenTargetSchema } from "@serverkgg/bridge/guides";
|
|
|
2
2
|
import {
|
|
3
3
|
BRIDGE_ID_PATTERN,
|
|
4
4
|
BRIDGE_MODULE_ID_PATTERN,
|
|
5
|
+
BRIDGE_PATTERN_LENGTH_LIMIT,
|
|
5
6
|
BridgeAccess,
|
|
6
7
|
BridgeConfirm,
|
|
7
8
|
BridgeControl,
|
|
@@ -100,6 +101,8 @@ export const bridgeFieldSchema = z.object({
|
|
|
100
101
|
max: z.number().optional(),
|
|
101
102
|
step: z.number().optional(),
|
|
102
103
|
maxLength: z.int().positive().optional(),
|
|
104
|
+
pattern: z.string().max(BRIDGE_PATTERN_LENGTH_LIMIT).optional(),
|
|
105
|
+
patternHint: panelTextSchema.optional(),
|
|
103
106
|
options: z
|
|
104
107
|
.union([
|
|
105
108
|
z.array(bridgeOptionSchema).max(64),
|