@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.
Files changed (119) hide show
  1. package/package.json +3 -1
  2. package/src/bot/botDeploy.test.ts +363 -0
  3. package/src/bot/botDeploy.ts +136 -0
  4. package/src/bot/botEnvironment.test.ts +189 -0
  5. package/src/bot/botEnvironment.ts +141 -0
  6. package/src/bot/botIdentity.test.ts +251 -0
  7. package/src/bot/botIdentity.ts +140 -0
  8. package/src/bot/botPanel.test.ts +246 -0
  9. package/src/bot/botPanel.ts +129 -0
  10. package/src/bot/botState.test.ts +45 -0
  11. package/src/bot/botState.ts +54 -0
  12. package/src/bot/botTerminal.test.ts +79 -0
  13. package/src/bot/botTerminal.ts +62 -0
  14. package/src/bot/botVariables.test.ts +193 -0
  15. package/src/bot/botVariables.ts +73 -0
  16. package/src/bot/detect/botFramework.ts +23 -0
  17. package/src/bot/detect/botFrameworks.ts +198 -0
  18. package/src/bot/detect/botInspectWarning.ts +15 -0
  19. package/src/bot/detect/botPackageManager.ts +6 -0
  20. package/src/bot/detect/botPlatform.ts +10 -0
  21. package/src/bot/detect/botRuntime.ts +15 -0
  22. package/src/bot/detect/botToken.test.ts +71 -0
  23. package/src/bot/detect/botToken.ts +126 -0
  24. package/src/bot/detect/detect.test.ts +543 -0
  25. package/src/bot/detect/detect.ts +842 -0
  26. package/src/bot/detect/detectResult.ts +40 -0
  27. package/src/bot/detect/detectTable.test.ts +72 -0
  28. package/src/bot/detect/detectTable.ts +166 -0
  29. package/src/bot/detect/envExample.test.ts +29 -0
  30. package/src/bot/detect/envExample.ts +48 -0
  31. package/src/bot/detect/envKey.ts +7 -0
  32. package/src/bot/detect/envScan.test.ts +39 -0
  33. package/src/bot/detect/envScan.ts +87 -0
  34. package/src/bot/detect/index.ts +17 -0
  35. package/src/bot/detect/inferBotPlatform.test.ts +60 -0
  36. package/src/bot/detect/inferBotPlatform.ts +28 -0
  37. package/src/bot/detect/keys.test.ts +28 -0
  38. package/src/bot/detect/keys.ts +64 -0
  39. package/src/bot/detect/sensitiveKey.test.ts +91 -0
  40. package/src/bot/detect/sensitiveKey.ts +44 -0
  41. package/src/bot/detect/serverkJson.test.ts +88 -0
  42. package/src/bot/detect/serverkJson.ts +174 -0
  43. package/src/bot/index.ts +8 -0
  44. package/src/cli/detectTable.test.ts +66 -0
  45. package/src/cli/detectTable.ts +43 -0
  46. package/src/cli/main.ts +12 -4
  47. package/src/cli/renderedFile.ts +35 -0
  48. package/src/cli/schema.ts +7 -26
  49. package/src/cli/validate.test.ts +283 -3
  50. package/src/cli/validate.ts +105 -7
  51. package/src/context/bridgeContext.ts +17 -1
  52. package/src/core/bridgeAction.ts +2 -0
  53. package/src/core/bridgeActivity.ts +9 -0
  54. package/src/core/bridgeAvailability.ts +10 -0
  55. package/src/core/bridgePort.test.ts +45 -0
  56. package/src/core/bridgePort.ts +26 -0
  57. package/src/core/bridgeUserError.test.ts +10 -0
  58. package/src/core/bridgeUserError.ts +6 -2
  59. package/src/core/describeDownloadFailure.test.ts +58 -0
  60. package/src/core/describeDownloadFailure.ts +69 -0
  61. package/src/core/index.ts +3 -0
  62. package/src/guides/compile.ts +2 -2
  63. package/src/manifest/index.ts +1 -0
  64. package/src/manifest/kind.ts +5 -0
  65. package/src/manifest/manifest.test.ts +630 -3
  66. package/src/manifest/manifest.ts +391 -87
  67. package/src/manifest/runtime.ts +2 -2
  68. package/src/manifest/source.ts +4 -4
  69. package/src/modules/bridge.ts +8 -1
  70. package/src/modules/bridgeLifecycleSubject.ts +8 -0
  71. package/src/modules/bridgeModule.ts +19 -1
  72. package/src/modules/bridgeStopSignal.ts +13 -0
  73. package/src/modules/index.ts +2 -0
  74. package/src/panel/bridgeDetail.ts +4 -1
  75. package/src/protocol/bridgeActivity.ts +54 -0
  76. package/src/protocol/bridgeConfig.ts +2 -0
  77. package/src/protocol/bridgeEvents.ts +30 -0
  78. package/src/protocol/bridgeFrame.ts +10 -1
  79. package/src/protocol/index.ts +1 -0
  80. package/src/runtime/bridgeActions.ts +19 -2
  81. package/src/runtime/bridgeActivity.test.ts +318 -0
  82. package/src/runtime/bridgeActivity.ts +268 -0
  83. package/src/runtime/bridgeConfigReader.test.ts +80 -0
  84. package/src/runtime/bridgeConfigReader.ts +20 -0
  85. package/src/runtime/bridgeContext.test.ts +247 -0
  86. package/src/runtime/bridgeContext.ts +43 -10
  87. package/src/runtime/bridgeDriver.test.ts +153 -1
  88. package/src/runtime/bridgeDriver.ts +64 -8
  89. package/src/runtime/bridgeFiles.test.ts +62 -0
  90. package/src/runtime/bridgeFiles.ts +12 -2
  91. package/src/runtime/bridgeLiveConfig.ts +18 -0
  92. package/src/runtime/bridgeLog.ts +10 -0
  93. package/src/runtime/bridgeRedaction.test.ts +410 -0
  94. package/src/runtime/bridgeRedaction.ts +214 -0
  95. package/src/runtime/bridgeRefusalError.ts +13 -0
  96. package/src/runtime/bridgeRegistry.test.ts +1056 -10
  97. package/src/runtime/bridgeRegistry.ts +464 -50
  98. package/src/runtime/bridgeScopedContext.ts +55 -0
  99. package/src/runtime/bridgeServer.test.ts +534 -4
  100. package/src/runtime/bridgeServer.ts +145 -38
  101. package/src/runtime/bridgeSetupHandlers.test.ts +156 -5
  102. package/src/runtime/bridgeSetupHandlers.ts +59 -14
  103. package/src/runtime/bridgeSupervisor.test.ts +670 -2
  104. package/src/runtime/bridgeSupervisor.ts +219 -67
  105. package/src/runtime/serve.ts +87 -11
  106. package/src/steam/steamcmd.test.ts +32 -1
  107. package/src/steam/steamcmd.ts +27 -4
  108. package/src/utils/changedSettingKeys.test.ts +65 -0
  109. package/src/utils/changedSettingKeys.ts +6 -0
  110. package/src/utils/describeExecFailure.test.ts +34 -0
  111. package/src/utils/describeExecFailure.ts +9 -0
  112. package/src/utils/index.ts +3 -0
  113. package/src/utils/sameSettingValue.test.ts +35 -0
  114. package/src/utils/sameSettingValue.ts +40 -0
  115. package/src/wire/activity.test.ts +95 -0
  116. package/src/wire/activity.ts +41 -0
  117. package/src/wire/index.ts +1 -0
  118. package/src/wire/panel.test.ts +67 -1
  119. package/src/wire/panel.ts +11 -0
@@ -1,5 +1,12 @@
1
1
  import { timingSafeEqual } from "node:crypto";
2
- import { BridgeFailureError, BridgeSecretError, BridgeUserError, type BridgeValues } from "../core";
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 { type BridgeLogger, errorText } from "./bridgeLog";
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: (module: string, action: string, args: BridgeValues, recoveryOperationId?: string) => Promise<unknown>;
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
- case BridgeMethod.SetupSubmit: {
181
- const step = asText(request.module);
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
- if (!step) {
184
- throw new Error("setupSubmit needs a step id");
185
- }
246
+ if (method === BridgeMethod.SetupSubmit) {
247
+ const step = asText(request.module);
186
248
 
187
- return await options.onSetupSubmit(step, String(request.action ?? ""), asArgs(request.args));
249
+ if (!step) {
250
+ throw new Error("setupSubmit needs a step id");
188
251
  }
189
252
 
190
- default: {
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 handle(String(request.method ?? ""), request)
263
- .then((result) => {
264
- socket.write(
265
- encode({
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("bridge call failed", {
274
- method: String(request.method ?? ""),
275
- error: errorText(error),
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: errorText(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 { BridgeConfig, BridgeSetupRuntime } from "../protocol";
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 { BridgeSetupResult, BridgeSetupRuntime } from "../protocol";
5
- import type { BridgeLeveledLogger } from "./bridgeLog";
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(context: BridgeContext, step: string, action: string, args: BridgeValues): Promise<BridgeSetupResult>;
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
- export const createSetupHandlers = (driver: BridgeDriver, log: BridgeLeveledLogger): BridgeSetupHandlers => {
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
- ? normalizeSetupRuntime(runtime, {
24
- declared,
25
- warn: log.warn,
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 unsupportedSetup();
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.message,
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 failedSetup(current, error.text);
101
+ return {
102
+ result: failedSetup(current, redactLocalized(error.text, redact)),
103
+ activity: scope.close(),
104
+ };
63
105
  }
64
106
 
65
107
  return {
66
- supported: true,
67
- runtime: current,
108
+ result: {
109
+ supported: true,
110
+ runtime: current,
111
+ },
112
+ activity: scope.close(),
68
113
  };
69
114
  },
70
115
  };