@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,5 +1,12 @@
|
|
|
1
1
|
import { timingSafeEqual } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import type { Socket } from "bun";
|
|
3
|
+
import {
|
|
4
|
+
type BridgeActivity,
|
|
5
|
+
BridgeFailureError,
|
|
6
|
+
BridgeSecretError,
|
|
7
|
+
BridgeUserError,
|
|
8
|
+
type BridgeValues,
|
|
9
|
+
} from "../core";
|
|
3
10
|
import {
|
|
4
11
|
BRIDGE_FRAME_LIMIT,
|
|
5
12
|
type BridgeAnnounceResult,
|
|
@@ -15,7 +22,10 @@ import {
|
|
|
15
22
|
type BridgeSetupResult,
|
|
16
23
|
type BridgeStatus,
|
|
17
24
|
} from "../protocol";
|
|
18
|
-
import
|
|
25
|
+
import type { BridgeActivityAnswer } from "./bridgeActivity";
|
|
26
|
+
import { type BridgeLogger, errorDetail } from "./bridgeLog";
|
|
27
|
+
import { redactLocalized } from "./bridgeRedaction";
|
|
28
|
+
import { BridgeRefusalError } from "./bridgeRefusalError";
|
|
19
29
|
|
|
20
30
|
export interface ServerOptions {
|
|
21
31
|
socket: string;
|
|
@@ -25,7 +35,12 @@ export interface ServerOptions {
|
|
|
25
35
|
onStart: () => Promise<void>;
|
|
26
36
|
onStop: () => Promise<void>;
|
|
27
37
|
onCommand: (input: string, expect: string | undefined, timeoutMs: number | undefined) => Promise<BridgeCommandResult>;
|
|
28
|
-
onInvoke: (
|
|
38
|
+
onInvoke: (
|
|
39
|
+
module: string,
|
|
40
|
+
action: string,
|
|
41
|
+
args: BridgeValues,
|
|
42
|
+
recoveryOperationId?: string,
|
|
43
|
+
) => Promise<BridgeActivityAnswer<unknown>>;
|
|
29
44
|
onEvents: (cursor: number) => BridgeEventsResult;
|
|
30
45
|
onSample: () => Promise<BridgeSampleResult>;
|
|
31
46
|
onQuiesce: (supportsSnapshot: boolean, leaseId?: string) => Promise<BridgeQuiesceResult>;
|
|
@@ -35,8 +50,15 @@ export interface ServerOptions {
|
|
|
35
50
|
onPendingList: () => Promise<BridgePendingResult>;
|
|
36
51
|
onPendingAdd: (input: string) => Promise<BridgePendingResult>;
|
|
37
52
|
onSetupStatus: () => BridgeSetupResult;
|
|
38
|
-
onSetupSubmit: (step: string, action: string, args: BridgeValues) => Promise<BridgeSetupResult
|
|
53
|
+
onSetupSubmit: (step: string, action: string, args: BridgeValues) => Promise<BridgeActivityAnswer<BridgeSetupResult>>;
|
|
54
|
+
onUndelivered: (activity: BridgeActivity[]) => void;
|
|
39
55
|
onLog: BridgeLogger;
|
|
56
|
+
redact?: (text: string) => string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SocketState {
|
|
60
|
+
buffer: string;
|
|
61
|
+
closed: boolean;
|
|
40
62
|
}
|
|
41
63
|
|
|
42
64
|
interface IncomingFrame {
|
|
@@ -71,6 +93,23 @@ const encode = (response: BridgeResponse) => {
|
|
|
71
93
|
return `${JSON.stringify(response)}\n`;
|
|
72
94
|
};
|
|
73
95
|
|
|
96
|
+
interface PlainAnswer {
|
|
97
|
+
result: unknown;
|
|
98
|
+
activity: null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const deliver = (socket: Socket<SocketState>, frame: string): boolean => {
|
|
102
|
+
if (socket.data.closed) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
return socket.write(frame) === Buffer.byteLength(frame);
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
74
113
|
const asArgs = (value: unknown): BridgeValues => {
|
|
75
114
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
76
115
|
return {};
|
|
@@ -87,6 +126,23 @@ const asArgs = (value: unknown): BridgeValues => {
|
|
|
87
126
|
return out;
|
|
88
127
|
};
|
|
89
128
|
|
|
129
|
+
const logFailure = (onLog: BridgeLogger, method: string, error: unknown) => {
|
|
130
|
+
if (error instanceof BridgeUserError || error instanceof BridgeRefusalError) {
|
|
131
|
+
onLog("bridge call refused", {
|
|
132
|
+
method,
|
|
133
|
+
error: errorDetail(error),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onLog("bridge call failed", {
|
|
140
|
+
method,
|
|
141
|
+
error: errorDetail(error),
|
|
142
|
+
stack: error instanceof Error ? (error.stack ?? null) : null,
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
90
146
|
export const createServer = (options: ServerOptions) => {
|
|
91
147
|
const handle = async (method: string, request: IncomingFrame): Promise<unknown> => {
|
|
92
148
|
switch (method) {
|
|
@@ -118,15 +174,6 @@ export const createServer = (options: ServerOptions) => {
|
|
|
118
174
|
);
|
|
119
175
|
}
|
|
120
176
|
|
|
121
|
-
case BridgeMethod.Invoke: {
|
|
122
|
-
return await options.onInvoke(
|
|
123
|
-
String(request.module ?? ""),
|
|
124
|
-
String(request.action ?? ""),
|
|
125
|
-
asArgs(request.args),
|
|
126
|
-
asText(request.recoveryOperationId) ?? undefined,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
177
|
case BridgeMethod.Events: {
|
|
131
178
|
return options.onEvents(typeof request.cursor === "number" ? request.cursor : 0);
|
|
132
179
|
}
|
|
@@ -177,33 +224,68 @@ export const createServer = (options: ServerOptions) => {
|
|
|
177
224
|
return options.onSetupStatus();
|
|
178
225
|
}
|
|
179
226
|
|
|
180
|
-
|
|
181
|
-
|
|
227
|
+
default: {
|
|
228
|
+
throw new Error(`unknown method "${method}"`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const answer = async (
|
|
234
|
+
method: string,
|
|
235
|
+
request: IncomingFrame,
|
|
236
|
+
): Promise<BridgeActivityAnswer<unknown> | PlainAnswer> => {
|
|
237
|
+
if (method === BridgeMethod.Invoke) {
|
|
238
|
+
return await options.onInvoke(
|
|
239
|
+
String(request.module ?? ""),
|
|
240
|
+
String(request.action ?? ""),
|
|
241
|
+
asArgs(request.args),
|
|
242
|
+
asText(request.recoveryOperationId) ?? undefined,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
182
245
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
246
|
+
if (method === BridgeMethod.SetupSubmit) {
|
|
247
|
+
const step = asText(request.module);
|
|
186
248
|
|
|
187
|
-
|
|
249
|
+
if (!step) {
|
|
250
|
+
throw new Error("setupSubmit needs a step id");
|
|
188
251
|
}
|
|
189
252
|
|
|
190
|
-
|
|
191
|
-
throw new Error(`unknown method "${method}"`);
|
|
192
|
-
}
|
|
253
|
+
return await options.onSetupSubmit(step, String(request.action ?? ""), asArgs(request.args));
|
|
193
254
|
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
result: await handle(method, request),
|
|
258
|
+
activity: null,
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const undelivered = (method: string, activity: BridgeActivity[]) => {
|
|
263
|
+
if (activity.length === 0) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
options.onLog("answer not delivered, its activity goes to the events ring", {
|
|
268
|
+
method,
|
|
269
|
+
entries: activity.length,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
options.onUndelivered(activity);
|
|
194
273
|
};
|
|
195
274
|
|
|
196
|
-
return Bun.listen<{
|
|
197
|
-
buffer: string;
|
|
198
|
-
}>({
|
|
275
|
+
return Bun.listen<SocketState>({
|
|
199
276
|
unix: options.socket,
|
|
200
277
|
socket: {
|
|
201
278
|
open(socket) {
|
|
202
279
|
socket.data = {
|
|
203
280
|
buffer: "",
|
|
281
|
+
closed: false,
|
|
204
282
|
};
|
|
205
283
|
},
|
|
206
284
|
|
|
285
|
+
close(socket) {
|
|
286
|
+
socket.data.closed = true;
|
|
287
|
+
},
|
|
288
|
+
|
|
207
289
|
data(socket, chunk) {
|
|
208
290
|
socket.data.buffer += chunk.toString();
|
|
209
291
|
|
|
@@ -259,28 +341,43 @@ export const createServer = (options: ServerOptions) => {
|
|
|
259
341
|
continue;
|
|
260
342
|
}
|
|
261
343
|
|
|
262
|
-
void
|
|
263
|
-
.then((result) => {
|
|
264
|
-
|
|
265
|
-
|
|
344
|
+
void answer(String(request.method ?? ""), request)
|
|
345
|
+
.then(({ result, activity }) => {
|
|
346
|
+
const method = String(request.method ?? "");
|
|
347
|
+
|
|
348
|
+
let frame: string;
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
frame = encode({
|
|
266
352
|
id,
|
|
267
353
|
ok: true,
|
|
268
354
|
result,
|
|
269
|
-
|
|
270
|
-
|
|
355
|
+
...(activity === null
|
|
356
|
+
? {}
|
|
357
|
+
: {
|
|
358
|
+
activity,
|
|
359
|
+
}),
|
|
360
|
+
});
|
|
361
|
+
} catch (error) {
|
|
362
|
+
undelivered(method, activity ?? []);
|
|
363
|
+
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!deliver(socket, frame)) {
|
|
368
|
+
undelivered(method, activity ?? []);
|
|
369
|
+
}
|
|
271
370
|
})
|
|
272
371
|
.catch((error: unknown) => {
|
|
273
|
-
options.onLog(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
stack: error instanceof Error ? (error.stack ?? null) : null,
|
|
277
|
-
});
|
|
372
|
+
logFailure(options.onLog, String(request.method ?? ""), error);
|
|
373
|
+
|
|
374
|
+
const redact = options.redact ?? ((text: string) => text);
|
|
278
375
|
|
|
279
376
|
socket.write(
|
|
280
377
|
encode({
|
|
281
378
|
id,
|
|
282
379
|
ok: false,
|
|
283
|
-
error:
|
|
380
|
+
error: redact(errorDetail(error)),
|
|
284
381
|
...(error instanceof BridgeSecretError
|
|
285
382
|
? {
|
|
286
383
|
secret: error.key,
|
|
@@ -293,7 +390,17 @@ export const createServer = (options: ServerOptions) => {
|
|
|
293
390
|
: {}),
|
|
294
391
|
...(error instanceof BridgeUserError
|
|
295
392
|
? {
|
|
296
|
-
text: error.text,
|
|
393
|
+
text: redactLocalized(error.text, redact),
|
|
394
|
+
}
|
|
395
|
+
: {}),
|
|
396
|
+
...(error instanceof BridgeRefusalError
|
|
397
|
+
? {
|
|
398
|
+
unchanged: true,
|
|
399
|
+
...(error.text
|
|
400
|
+
? {
|
|
401
|
+
text: redactLocalized(error.text, redact),
|
|
402
|
+
}
|
|
403
|
+
: {}),
|
|
297
404
|
}
|
|
298
405
|
: {}),
|
|
299
406
|
}),
|
|
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import type { BridgeContext } from "../context";
|
|
4
4
|
import { BridgeKind, BridgeSetupPromptKind, BridgeSetupStepKind, BridgeSetupStepState, BridgeUserError } from "../core";
|
|
5
5
|
import type { BridgeDriver, BridgeSetupModule } from "../modules";
|
|
6
|
-
import type
|
|
6
|
+
import { type BridgeConfig, BridgeEventName, type BridgeSetupRuntime } from "../protocol";
|
|
7
7
|
import { createContext } from "./bridgeContext";
|
|
8
8
|
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
9
9
|
import { log } from "./bridgeLog";
|
|
@@ -51,6 +51,12 @@ const contextOf = (): BridgeContext => {
|
|
|
51
51
|
onSetup() {
|
|
52
52
|
return;
|
|
53
53
|
},
|
|
54
|
+
onActivity() {
|
|
55
|
+
return;
|
|
56
|
+
},
|
|
57
|
+
async onRestart() {
|
|
58
|
+
return;
|
|
59
|
+
},
|
|
54
60
|
});
|
|
55
61
|
};
|
|
56
62
|
|
|
@@ -143,7 +149,7 @@ describe("the setup handlers", () => {
|
|
|
143
149
|
runtime: null,
|
|
144
150
|
});
|
|
145
151
|
|
|
146
|
-
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
152
|
+
expect((await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).result).toEqual({
|
|
147
153
|
supported: false,
|
|
148
154
|
runtime: null,
|
|
149
155
|
});
|
|
@@ -204,7 +210,7 @@ describe("the setup handlers", () => {
|
|
|
204
210
|
const { logger } = recorder();
|
|
205
211
|
const handlers = createSetupHandlers(driverWith(setupWith()), logger);
|
|
206
212
|
|
|
207
|
-
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
213
|
+
expect((await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).result).toEqual({
|
|
208
214
|
supported: false,
|
|
209
215
|
runtime: null,
|
|
210
216
|
});
|
|
@@ -224,7 +230,7 @@ describe("the setup handlers", () => {
|
|
|
224
230
|
|
|
225
231
|
handlers.onSetup(signIn());
|
|
226
232
|
|
|
227
|
-
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
233
|
+
expect((await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).result).toEqual({
|
|
228
234
|
supported: true,
|
|
229
235
|
runtime: {
|
|
230
236
|
steps: [
|
|
@@ -248,6 +254,65 @@ describe("the setup handlers", () => {
|
|
|
248
254
|
expect(warnings.at(-1)?.message).toBe("setup submit rejected");
|
|
249
255
|
});
|
|
250
256
|
|
|
257
|
+
test("add one rejected entry built from the user error", async () => {
|
|
258
|
+
const { logger } = recorder();
|
|
259
|
+
|
|
260
|
+
const handlers = createSetupHandlers(
|
|
261
|
+
driverWith(
|
|
262
|
+
setupWith(async () => {
|
|
263
|
+
throw new BridgeUserError({
|
|
264
|
+
ar: "الكود ما عاد ينفع",
|
|
265
|
+
en: "this code cannot be renewed any more",
|
|
266
|
+
});
|
|
267
|
+
}),
|
|
268
|
+
),
|
|
269
|
+
logger,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
handlers.onSetup(signIn());
|
|
273
|
+
|
|
274
|
+
const { activity } = await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {});
|
|
275
|
+
|
|
276
|
+
expect(activity).toEqual([
|
|
277
|
+
{
|
|
278
|
+
name: BridgeEventName.SetupRejected,
|
|
279
|
+
text: {
|
|
280
|
+
ar: "الكود ما عاد ينفع",
|
|
281
|
+
en: "this code cannot be renewed any more",
|
|
282
|
+
},
|
|
283
|
+
details: {
|
|
284
|
+
step: "sign-in",
|
|
285
|
+
action: "renew",
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("answer what the driver reported while submitting", async () => {
|
|
292
|
+
const { logger } = recorder();
|
|
293
|
+
|
|
294
|
+
const handlers = createSetupHandlers(
|
|
295
|
+
driverWith(
|
|
296
|
+
setupWith(async (context) => {
|
|
297
|
+
context.activity.report({
|
|
298
|
+
name: BridgeEventName.SetupCompleted,
|
|
299
|
+
text: text("signed in"),
|
|
300
|
+
});
|
|
301
|
+
}),
|
|
302
|
+
),
|
|
303
|
+
logger,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const { activity } = await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {});
|
|
307
|
+
|
|
308
|
+
expect(activity).toEqual([
|
|
309
|
+
{
|
|
310
|
+
name: BridgeEventName.SetupCompleted,
|
|
311
|
+
text: text("signed in"),
|
|
312
|
+
},
|
|
313
|
+
]);
|
|
314
|
+
});
|
|
315
|
+
|
|
251
316
|
test("answer the runtime the driver reported while submitting", async () => {
|
|
252
317
|
const { logger } = recorder();
|
|
253
318
|
|
|
@@ -270,7 +335,7 @@ describe("the setup handlers", () => {
|
|
|
270
335
|
|
|
271
336
|
handlers.onSetup(signIn());
|
|
272
337
|
|
|
273
|
-
expect(await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).toEqual({
|
|
338
|
+
expect((await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {})).result).toEqual({
|
|
274
339
|
supported: true,
|
|
275
340
|
runtime: {
|
|
276
341
|
steps: [
|
|
@@ -301,3 +366,89 @@ describe("the setup handlers", () => {
|
|
|
301
366
|
);
|
|
302
367
|
});
|
|
303
368
|
});
|
|
369
|
+
|
|
370
|
+
describe("the setup handlers redaction", () => {
|
|
371
|
+
const SECRET = "123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw";
|
|
372
|
+
|
|
373
|
+
const redact = (value: string) => value.split(SECRET).join("••••");
|
|
374
|
+
|
|
375
|
+
test("blanks a secret value in the prompt a driver reports", () => {
|
|
376
|
+
const { logger } = recorder();
|
|
377
|
+
const handlers = createSetupHandlers(driverWith(setupWith()), logger, redact);
|
|
378
|
+
|
|
379
|
+
handlers.onSetup({
|
|
380
|
+
steps: [
|
|
381
|
+
{
|
|
382
|
+
id: "sign-in",
|
|
383
|
+
state: BridgeSetupStepState.Active,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
prompt: {
|
|
387
|
+
kind: BridgeSetupPromptKind.Progress,
|
|
388
|
+
message: text(`checking ${SECRET}`),
|
|
389
|
+
percent: 50,
|
|
390
|
+
detail: `token ${SECRET}`,
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(handlers.onSetupStatus().runtime?.prompt).toEqual({
|
|
395
|
+
kind: BridgeSetupPromptKind.Progress,
|
|
396
|
+
message: text("checking ••••"),
|
|
397
|
+
percent: 50,
|
|
398
|
+
detail: "token ••••",
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("blanks a secret value in every text of a link prompt", () => {
|
|
403
|
+
const { logger } = recorder();
|
|
404
|
+
const handlers = createSetupHandlers(driverWith(setupWith()), logger, redact);
|
|
405
|
+
const reported = signIn();
|
|
406
|
+
|
|
407
|
+
handlers.onSetup({
|
|
408
|
+
...reported,
|
|
409
|
+
prompt: {
|
|
410
|
+
kind: BridgeSetupPromptKind.Link,
|
|
411
|
+
message: text(`sign in ${SECRET}`),
|
|
412
|
+
url: "https://hytale.com/device",
|
|
413
|
+
code: "ABCD-EFGH",
|
|
414
|
+
expiresAt: null,
|
|
415
|
+
actions: [
|
|
416
|
+
{
|
|
417
|
+
id: "renew",
|
|
418
|
+
label: text(`renew ${SECRET}`),
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
expect(JSON.stringify(handlers.current())).not.toContain(SECRET);
|
|
425
|
+
expect(handlers.current()?.prompt).toMatchObject({
|
|
426
|
+
message: text("sign in ••••"),
|
|
427
|
+
url: "https://hytale.com/device",
|
|
428
|
+
code: "ABCD-EFGH",
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test("blanks a secret value in the text of a rejected submit", async () => {
|
|
433
|
+
const { logger } = recorder();
|
|
434
|
+
const handlers = createSetupHandlers(
|
|
435
|
+
driverWith(
|
|
436
|
+
setupWith(async () => {
|
|
437
|
+
throw new BridgeUserError(text(`telegram refused ${SECRET}`));
|
|
438
|
+
}),
|
|
439
|
+
),
|
|
440
|
+
logger,
|
|
441
|
+
redact,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
handlers.onSetup(signIn());
|
|
445
|
+
|
|
446
|
+
const { result, activity } = await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {});
|
|
447
|
+
|
|
448
|
+
expect(result.runtime?.prompt).toMatchObject({
|
|
449
|
+
kind: BridgeSetupPromptKind.Failed,
|
|
450
|
+
message: text("telegram refused ••••"),
|
|
451
|
+
});
|
|
452
|
+
expect(JSON.stringify(activity)).not.toContain(SECRET);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
@@ -1,29 +1,52 @@
|
|
|
1
1
|
import type { BridgeContext } from "../context";
|
|
2
2
|
import { BridgeUserError, type BridgeValues } from "../core";
|
|
3
3
|
import type { BridgeDriver } from "../modules";
|
|
4
|
-
import type
|
|
5
|
-
import type
|
|
4
|
+
import { BridgeEventName, type BridgeSetupResult, type BridgeSetupRuntime } from "../protocol";
|
|
5
|
+
import { type ActivityNormalizeOptions, type BridgeActivityAnswer, createActivityScope } from "./bridgeActivity";
|
|
6
|
+
import { type BridgeLeveledLogger, errorDetail } from "./bridgeLog";
|
|
7
|
+
import { type BridgeRedact, redactLocalized, redactSetupRuntime, secretFieldKeys } from "./bridgeRedaction";
|
|
8
|
+
import { scopedContext } from "./bridgeScopedContext";
|
|
6
9
|
import { failedSetup, normalizeSetupRuntime, unsupportedSetup } from "./bridgeSetup";
|
|
7
10
|
|
|
8
11
|
export interface BridgeSetupHandlers {
|
|
9
12
|
onSetup(runtime: BridgeSetupRuntime | null): void;
|
|
10
13
|
onSetupStatus(): BridgeSetupResult;
|
|
11
|
-
onSetupSubmit(
|
|
14
|
+
onSetupSubmit(
|
|
15
|
+
context: BridgeContext,
|
|
16
|
+
step: string,
|
|
17
|
+
action: string,
|
|
18
|
+
args: BridgeValues,
|
|
19
|
+
): Promise<BridgeActivityAnswer<BridgeSetupResult>>;
|
|
12
20
|
current(): BridgeSetupRuntime | null;
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
const unredacted: BridgeRedact = (text) => text;
|
|
24
|
+
|
|
25
|
+
export const createSetupHandlers = (
|
|
26
|
+
driver: BridgeDriver,
|
|
27
|
+
log: BridgeLeveledLogger,
|
|
28
|
+
redact: BridgeRedact = unredacted,
|
|
29
|
+
): BridgeSetupHandlers => {
|
|
16
30
|
const declared: ReadonlySet<string> = new Set((driver.setup?.steps ?? []).map((step) => step.id));
|
|
17
31
|
|
|
32
|
+
const activity: ActivityNormalizeOptions = {
|
|
33
|
+
secretKeys: new Set(secretFieldKeys(driver)),
|
|
34
|
+
redact,
|
|
35
|
+
warn: log.warn,
|
|
36
|
+
};
|
|
37
|
+
|
|
18
38
|
let current: BridgeSetupRuntime | null = null;
|
|
19
39
|
|
|
20
40
|
return {
|
|
21
41
|
onSetup(runtime) {
|
|
22
42
|
current = driver.setup
|
|
23
|
-
?
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
? redactSetupRuntime(
|
|
44
|
+
normalizeSetupRuntime(runtime, {
|
|
45
|
+
declared,
|
|
46
|
+
warn: log.warn,
|
|
47
|
+
}),
|
|
48
|
+
redact,
|
|
49
|
+
)
|
|
27
50
|
: null;
|
|
28
51
|
},
|
|
29
52
|
current() {
|
|
@@ -43,28 +66,50 @@ export const createSetupHandlers = (driver: BridgeDriver, log: BridgeLeveledLogg
|
|
|
43
66
|
const module = driver.setup;
|
|
44
67
|
|
|
45
68
|
if (!module?.submit) {
|
|
46
|
-
return
|
|
69
|
+
return {
|
|
70
|
+
result: unsupportedSetup(),
|
|
71
|
+
activity: [],
|
|
72
|
+
};
|
|
47
73
|
}
|
|
48
74
|
|
|
75
|
+
const scope = createActivityScope(context.activity, activity);
|
|
76
|
+
|
|
49
77
|
try {
|
|
50
|
-
await module.submit(context, step, action, args);
|
|
78
|
+
await module.submit(scopedContext(context, scope.reporter), step, action, args);
|
|
51
79
|
} catch (error) {
|
|
52
80
|
if (!(error instanceof BridgeUserError)) {
|
|
81
|
+
scope.drain();
|
|
82
|
+
|
|
53
83
|
throw error;
|
|
54
84
|
}
|
|
55
85
|
|
|
56
86
|
log.warn("setup submit rejected", {
|
|
57
87
|
step,
|
|
58
88
|
action,
|
|
59
|
-
error: error
|
|
89
|
+
error: errorDetail(error),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
scope.reporter.report({
|
|
93
|
+
name: BridgeEventName.SetupRejected,
|
|
94
|
+
text: error.text,
|
|
95
|
+
details: {
|
|
96
|
+
step,
|
|
97
|
+
action,
|
|
98
|
+
},
|
|
60
99
|
});
|
|
61
100
|
|
|
62
|
-
return
|
|
101
|
+
return {
|
|
102
|
+
result: failedSetup(current, redactLocalized(error.text, redact)),
|
|
103
|
+
activity: scope.close(),
|
|
104
|
+
};
|
|
63
105
|
}
|
|
64
106
|
|
|
65
107
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
108
|
+
result: {
|
|
109
|
+
supported: true,
|
|
110
|
+
runtime: current,
|
|
111
|
+
},
|
|
112
|
+
activity: scope.close(),
|
|
68
113
|
};
|
|
69
114
|
},
|
|
70
115
|
};
|