@serverkgg/bridge 0.0.21 → 0.1.1

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 (86) 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 +216 -0
  9. package/src/bot/botPanel.ts +121 -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 +210 -2
  50. package/src/cli/validate.ts +71 -7
  51. package/src/context/bridgeContext.ts +11 -0
  52. package/src/core/bridgePort.test.ts +45 -0
  53. package/src/core/bridgePort.ts +26 -0
  54. package/src/guides/compile.ts +2 -2
  55. package/src/manifest/index.ts +1 -0
  56. package/src/manifest/kind.ts +5 -0
  57. package/src/manifest/manifest.test.ts +630 -3
  58. package/src/manifest/manifest.ts +391 -87
  59. package/src/manifest/runtime.ts +2 -2
  60. package/src/manifest/source.ts +4 -4
  61. package/src/modules/bridgeLifecycleSubject.ts +8 -0
  62. package/src/modules/bridgeModule.ts +6 -0
  63. package/src/modules/bridgeStopSignal.ts +13 -0
  64. package/src/modules/index.ts +2 -0
  65. package/src/panel/bridgeDetail.ts +2 -0
  66. package/src/protocol/bridgeConfig.ts +2 -0
  67. package/src/runtime/bridgeConfigReader.test.ts +80 -0
  68. package/src/runtime/bridgeConfigReader.ts +20 -0
  69. package/src/runtime/bridgeContext.test.ts +241 -0
  70. package/src/runtime/bridgeContext.ts +35 -9
  71. package/src/runtime/bridgeDriver.test.ts +132 -1
  72. package/src/runtime/bridgeDriver.ts +54 -1
  73. package/src/runtime/bridgeFiles.test.ts +62 -0
  74. package/src/runtime/bridgeFiles.ts +12 -2
  75. package/src/runtime/bridgeLiveConfig.ts +18 -0
  76. package/src/runtime/bridgeRedaction.test.ts +410 -0
  77. package/src/runtime/bridgeRedaction.ts +214 -0
  78. package/src/runtime/bridgeRegistry.test.ts +3 -0
  79. package/src/runtime/bridgeRegistry.ts +15 -0
  80. package/src/runtime/bridgeServer.test.ts +130 -0
  81. package/src/runtime/bridgeServer.ts +24 -7
  82. package/src/runtime/bridgeSetupHandlers.test.ts +88 -0
  83. package/src/runtime/bridgeSetupHandlers.ts +16 -6
  84. package/src/runtime/bridgeSupervisor.test.ts +670 -2
  85. package/src/runtime/bridgeSupervisor.ts +219 -67
  86. package/src/runtime/serve.ts +47 -13
@@ -0,0 +1,214 @@
1
+ import { BridgeSetupPromptKind, type LocalizedText } from "../core";
2
+ import type { BridgeDriver } from "../modules";
3
+ import { BridgeControl, BridgeLayout } from "../panel";
4
+ import type { BridgeSetupAction, BridgeSetupPrompt, BridgeSetupRuntime } from "../protocol";
5
+ import type { BridgeLeveledLogger, BridgeLogger } from "./bridgeLog";
6
+
7
+ export const REDACTED = "••••";
8
+
9
+ export const REDACTION_MIN_LENGTH = 8;
10
+
11
+ const REDACTION_DEPTH_LIMIT = 4;
12
+
13
+ const PATTERN_SPECIALS = /[.*+?^${}()|[\]\\]/g;
14
+
15
+ export type BridgeRedact = (text: string) => string;
16
+
17
+ export interface BridgeRedactionSource {
18
+ variables(): Readonly<Record<string, string>>;
19
+ secrets(): Readonly<Record<string, string>>;
20
+ }
21
+
22
+ export const secretFieldKeys = (driver: BridgeDriver): string[] => {
23
+ const keys = new Set<string>();
24
+
25
+ for (const tab of driver.panel?.tabs ?? []) {
26
+ for (const section of tab.sections) {
27
+ if (section.layout !== BridgeLayout.Form) {
28
+ continue;
29
+ }
30
+
31
+ for (const field of section.fields) {
32
+ if (field.control === BridgeControl.Secret) {
33
+ keys.add(field.key);
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ return [
40
+ ...keys,
41
+ ];
42
+ };
43
+
44
+ const LINE_BREAK = /\r\n|\r|\n/;
45
+
46
+ const redactableParts = (value: string): string[] => {
47
+ if (!LINE_BREAK.test(value)) {
48
+ return [
49
+ value,
50
+ ];
51
+ }
52
+
53
+ return [
54
+ value,
55
+ ...value.split(LINE_BREAK).map((line) => line.trim()),
56
+ ];
57
+ };
58
+
59
+ const matcherFor = (values: string[]) => {
60
+ if (values.length === 0) {
61
+ return null;
62
+ }
63
+
64
+ return new RegExp(values.map((value) => value.replace(PATTERN_SPECIALS, "\\$&")).join("|"), "g");
65
+ };
66
+
67
+ export const createBridgeRedaction = (keys: string[], source: BridgeRedactionSource): BridgeRedact => {
68
+ let seenVariables: Readonly<Record<string, string>> | null = null;
69
+ let seenSecrets: Readonly<Record<string, string>> | null = null;
70
+ let signature = "";
71
+ let matcher: RegExp | null = null;
72
+
73
+ const current = () => {
74
+ const variables = source.variables();
75
+ const secrets = source.secrets();
76
+
77
+ if (variables === seenVariables && secrets === seenSecrets) {
78
+ return matcher;
79
+ }
80
+
81
+ seenVariables = variables;
82
+ seenSecrets = secrets;
83
+
84
+ const values = [
85
+ ...new Set(
86
+ [
87
+ ...keys.map((key) => variables[key] ?? ""),
88
+ ...Object.values(secrets),
89
+ ].flatMap(redactableParts),
90
+ ),
91
+ ]
92
+ .filter((value) => value.length >= REDACTION_MIN_LENGTH)
93
+ .sort((a, b) => b.length - a.length);
94
+ const next = values.join("\0");
95
+
96
+ if (next !== signature) {
97
+ signature = next;
98
+ matcher = matcherFor(values);
99
+ }
100
+
101
+ return matcher;
102
+ };
103
+
104
+ return (text) => {
105
+ const pattern = current();
106
+
107
+ return pattern ? text.replace(pattern, REDACTED) : text;
108
+ };
109
+ };
110
+
111
+ export const redactLocalized = (text: LocalizedText, redact: BridgeRedact): LocalizedText => {
112
+ return {
113
+ ar: redact(text.ar),
114
+ en: redact(text.en),
115
+ };
116
+ };
117
+
118
+ const redactActions = (actions: BridgeSetupAction[], redact: BridgeRedact): BridgeSetupAction[] => {
119
+ return actions.map((action) => ({
120
+ id: action.id,
121
+ label: redactLocalized(action.label, redact),
122
+ }));
123
+ };
124
+
125
+ const redactPrompt = (prompt: BridgeSetupPrompt, redact: BridgeRedact): BridgeSetupPrompt => {
126
+ const message = redactLocalized(prompt.message, redact);
127
+
128
+ switch (prompt.kind) {
129
+ case BridgeSetupPromptKind.Wait: {
130
+ return {
131
+ ...prompt,
132
+ message,
133
+ };
134
+ }
135
+
136
+ case BridgeSetupPromptKind.Progress: {
137
+ return {
138
+ ...prompt,
139
+ message,
140
+ detail: prompt.detail === null ? null : redact(prompt.detail),
141
+ };
142
+ }
143
+
144
+ case BridgeSetupPromptKind.Link: {
145
+ return {
146
+ ...prompt,
147
+ message,
148
+ url: redact(prompt.url),
149
+ code: prompt.code === null ? null : redact(prompt.code),
150
+ actions: redactActions(prompt.actions, redact),
151
+ };
152
+ }
153
+
154
+ case BridgeSetupPromptKind.Action:
155
+ case BridgeSetupPromptKind.Failed: {
156
+ return {
157
+ ...prompt,
158
+ message,
159
+ actions: redactActions(prompt.actions, redact),
160
+ };
161
+ }
162
+ }
163
+ };
164
+
165
+ export const redactSetupRuntime = (
166
+ runtime: BridgeSetupRuntime | null,
167
+ redact: BridgeRedact,
168
+ ): BridgeSetupRuntime | null => {
169
+ if (!runtime) {
170
+ return null;
171
+ }
172
+
173
+ return {
174
+ steps: runtime.steps,
175
+ prompt: runtime.prompt ? redactPrompt(runtime.prompt, redact) : null,
176
+ };
177
+ };
178
+
179
+ const redactValue = (value: unknown, redact: BridgeRedact, depth: number): unknown => {
180
+ if (typeof value === "string") {
181
+ return redact(value);
182
+ }
183
+
184
+ if (depth >= REDACTION_DEPTH_LIMIT || value === null || typeof value !== "object") {
185
+ return value;
186
+ }
187
+
188
+ if (Array.isArray(value)) {
189
+ return value.map((entry) => redactValue(entry, redact, depth + 1));
190
+ }
191
+
192
+ return Object.fromEntries(
193
+ Object.entries(value).map(([key, entry]) => [
194
+ key,
195
+ redactValue(entry, redact, depth + 1),
196
+ ]),
197
+ );
198
+ };
199
+
200
+ const redactingWriter = (write: BridgeLogger, redact: BridgeRedact): BridgeLogger => {
201
+ return (message, fields) => {
202
+ write(
203
+ redact(message),
204
+ fields === undefined ? undefined : (redactValue(fields, redact, 0) as Record<string, unknown>),
205
+ );
206
+ };
207
+ };
208
+
209
+ export const redactLogger = (logger: BridgeLeveledLogger, redact: BridgeRedact): BridgeLeveledLogger => {
210
+ return Object.assign(redactingWriter(logger, redact), {
211
+ warn: redactingWriter(logger.warn, redact),
212
+ error: redactingWriter(logger.error, redact),
213
+ });
214
+ };
@@ -66,6 +66,9 @@ const registryFor = (driver: BridgeDriver, variables: Record<string, string> = {
66
66
  onSetup() {
67
67
  return;
68
68
  },
69
+ async onRestart() {
70
+ return;
71
+ },
69
72
  });
70
73
 
71
74
  return createRegistry(driver, context, supervisor, ring);
@@ -95,12 +95,24 @@ const withVariables = (context: BridgeContext, overlay: Record<string, string>):
95
95
  variable(key) {
96
96
  return Object.hasOwn(overlay, key) ? (overlay[key] ?? null) : context.variable(key);
97
97
  },
98
+ variables() {
99
+ return {
100
+ ...context.variables(),
101
+ ...overlay,
102
+ };
103
+ },
98
104
  secret(key) {
99
105
  return context.secret(key);
100
106
  },
107
+ secrets() {
108
+ return context.secrets();
109
+ },
101
110
  port(key) {
102
111
  return context.port(key);
103
112
  },
113
+ portBlock(key) {
114
+ return context.portBlock(key);
115
+ },
104
116
  files: context.files,
105
117
  codec: context.codec,
106
118
  net: context.net,
@@ -117,6 +129,9 @@ const withVariables = (context: BridgeContext, overlay: Record<string, string>):
117
129
  emit(event, payload) {
118
130
  context.emit(event, payload);
119
131
  },
132
+ restart() {
133
+ return context.restart();
134
+ },
120
135
  log: context.log,
121
136
  };
122
137
  };
@@ -654,3 +654,133 @@ describe("backup ownership dispatch", () => {
654
654
  }
655
655
  });
656
656
  });
657
+
658
+ describe("error replies", () => {
659
+ const SECRET = "tok-1234567890-secret";
660
+
661
+ let dir = "";
662
+ let socket = "";
663
+
664
+ beforeEach(async () => {
665
+ dir = await mkdtemp(join(tmpdir(), "bridge-server-"));
666
+ socket = join(dir, "bridge.sock");
667
+ });
668
+
669
+ afterEach(async () => {
670
+ await rm(dir, {
671
+ force: true,
672
+ recursive: true,
673
+ });
674
+ });
675
+
676
+ test("hide a secret value from the error and the customer text", async () => {
677
+ const server = createServer({
678
+ ...baseOptions(socket),
679
+ redact: (value) => value.split(SECRET).join("••••"),
680
+ onInvoke: async () => {
681
+ throw new BridgeUserError({
682
+ ar: `رفض ${SECRET}`,
683
+ en: `refused ${SECRET}`,
684
+ });
685
+ },
686
+ });
687
+
688
+ const response = await call(socket, {
689
+ id: 9,
690
+ token: TOKEN,
691
+ method: BridgeMethod.Invoke,
692
+ module: "botDeploy",
693
+ action: "redeploy",
694
+ args: {},
695
+ });
696
+
697
+ server.stop(true);
698
+
699
+ expect(response).toEqual({
700
+ id: 9,
701
+ ok: false,
702
+ error: "refused ••••",
703
+ text: {
704
+ ar: "رفض ••••",
705
+ en: "refused ••••",
706
+ },
707
+ });
708
+ });
709
+ });
710
+
711
+ describe("failure logging", () => {
712
+ let dir = "";
713
+ let socket = "";
714
+
715
+ beforeEach(async () => {
716
+ dir = await mkdtemp(join(tmpdir(), "bridge-server-"));
717
+ socket = join(dir, "bridge.sock");
718
+ });
719
+
720
+ afterEach(async () => {
721
+ await rm(dir, {
722
+ force: true,
723
+ recursive: true,
724
+ });
725
+ });
726
+
727
+ const logged = async (error: unknown) => {
728
+ const lines: {
729
+ message: string;
730
+ fields: Record<string, unknown>;
731
+ }[] = [];
732
+
733
+ const server = createServer({
734
+ ...baseOptions(socket),
735
+ onLog: (message, fields = {}) => {
736
+ lines.push({
737
+ message,
738
+ fields,
739
+ });
740
+ },
741
+ onInvoke: async () => {
742
+ throw error;
743
+ },
744
+ });
745
+
746
+ await call(socket, {
747
+ id: 3,
748
+ token: TOKEN,
749
+ method: BridgeMethod.Invoke,
750
+ module: "botDeploy",
751
+ action: "redeploy",
752
+ args: {},
753
+ });
754
+
755
+ server.stop(true);
756
+
757
+ return lines;
758
+ };
759
+
760
+ test("logs a refused action as a refusal with its message and no stack", async () => {
761
+ expect(
762
+ await logged(
763
+ new BridgeUserError({
764
+ ar: "فيه نشر شغّال الحين",
765
+ en: "a deploy is already running",
766
+ }),
767
+ ),
768
+ ).toEqual([
769
+ {
770
+ message: "bridge call refused",
771
+ fields: {
772
+ method: BridgeMethod.Invoke,
773
+ error: "a deploy is already running",
774
+ },
775
+ },
776
+ ]);
777
+ });
778
+
779
+ test("logs an ordinary error as a failure with its stack", async () => {
780
+ const [line] = await logged(new Error("the marker could not be written"));
781
+
782
+ expect(line?.message).toBe("bridge call failed");
783
+ expect(line?.fields.error).toBe("the marker could not be written");
784
+ expect(String(line?.fields.stack)).toContain("the marker could not be written");
785
+ });
786
+ });
@@ -16,6 +16,7 @@ import {
16
16
  type BridgeStatus,
17
17
  } from "../protocol";
18
18
  import { type BridgeLogger, errorText } from "./bridgeLog";
19
+ import { redactLocalized } from "./bridgeRedaction";
19
20
 
20
21
  export interface ServerOptions {
21
22
  socket: string;
@@ -37,6 +38,7 @@ export interface ServerOptions {
37
38
  onSetupStatus: () => BridgeSetupResult;
38
39
  onSetupSubmit: (step: string, action: string, args: BridgeValues) => Promise<BridgeSetupResult>;
39
40
  onLog: BridgeLogger;
41
+ redact?: (text: string) => string;
40
42
  }
41
43
 
42
44
  interface IncomingFrame {
@@ -87,6 +89,23 @@ const asArgs = (value: unknown): BridgeValues => {
87
89
  return out;
88
90
  };
89
91
 
92
+ const logFailure = (onLog: BridgeLogger, method: string, error: unknown) => {
93
+ if (error instanceof BridgeUserError) {
94
+ onLog("bridge call refused", {
95
+ method,
96
+ error: error.message,
97
+ });
98
+
99
+ return;
100
+ }
101
+
102
+ onLog("bridge call failed", {
103
+ method,
104
+ error: errorText(error),
105
+ stack: error instanceof Error ? (error.stack ?? null) : null,
106
+ });
107
+ };
108
+
90
109
  export const createServer = (options: ServerOptions) => {
91
110
  const handle = async (method: string, request: IncomingFrame): Promise<unknown> => {
92
111
  switch (method) {
@@ -270,17 +289,15 @@ export const createServer = (options: ServerOptions) => {
270
289
  );
271
290
  })
272
291
  .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
- });
292
+ logFailure(options.onLog, String(request.method ?? ""), error);
293
+
294
+ const redact = options.redact ?? ((text: string) => text);
278
295
 
279
296
  socket.write(
280
297
  encode({
281
298
  id,
282
299
  ok: false,
283
- error: errorText(error),
300
+ error: redact(errorText(error)),
284
301
  ...(error instanceof BridgeSecretError
285
302
  ? {
286
303
  secret: error.key,
@@ -293,7 +310,7 @@ export const createServer = (options: ServerOptions) => {
293
310
  : {}),
294
311
  ...(error instanceof BridgeUserError
295
312
  ? {
296
- text: error.text,
313
+ text: redactLocalized(error.text, redact),
297
314
  }
298
315
  : {}),
299
316
  }),
@@ -51,6 +51,9 @@ const contextOf = (): BridgeContext => {
51
51
  onSetup() {
52
52
  return;
53
53
  },
54
+ async onRestart() {
55
+ return;
56
+ },
54
57
  });
55
58
  };
56
59
 
@@ -301,3 +304,88 @@ describe("the setup handlers", () => {
301
304
  );
302
305
  });
303
306
  });
307
+
308
+ describe("the setup handlers redaction", () => {
309
+ const SECRET = "123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw";
310
+
311
+ const redact = (value: string) => value.split(SECRET).join("••••");
312
+
313
+ test("blanks a secret value in the prompt a driver reports", () => {
314
+ const { logger } = recorder();
315
+ const handlers = createSetupHandlers(driverWith(setupWith()), logger, redact);
316
+
317
+ handlers.onSetup({
318
+ steps: [
319
+ {
320
+ id: "sign-in",
321
+ state: BridgeSetupStepState.Active,
322
+ },
323
+ ],
324
+ prompt: {
325
+ kind: BridgeSetupPromptKind.Progress,
326
+ message: text(`checking ${SECRET}`),
327
+ percent: 50,
328
+ detail: `token ${SECRET}`,
329
+ },
330
+ });
331
+
332
+ expect(handlers.onSetupStatus().runtime?.prompt).toEqual({
333
+ kind: BridgeSetupPromptKind.Progress,
334
+ message: text("checking ••••"),
335
+ percent: 50,
336
+ detail: "token ••••",
337
+ });
338
+ });
339
+
340
+ test("blanks a secret value in every text of a link prompt", () => {
341
+ const { logger } = recorder();
342
+ const handlers = createSetupHandlers(driverWith(setupWith()), logger, redact);
343
+ const reported = signIn();
344
+
345
+ handlers.onSetup({
346
+ ...reported,
347
+ prompt: {
348
+ kind: BridgeSetupPromptKind.Link,
349
+ message: text(`sign in ${SECRET}`),
350
+ url: "https://hytale.com/device",
351
+ code: "ABCD-EFGH",
352
+ expiresAt: null,
353
+ actions: [
354
+ {
355
+ id: "renew",
356
+ label: text(`renew ${SECRET}`),
357
+ },
358
+ ],
359
+ },
360
+ });
361
+
362
+ expect(JSON.stringify(handlers.current())).not.toContain(SECRET);
363
+ expect(handlers.current()?.prompt).toMatchObject({
364
+ message: text("sign in ••••"),
365
+ url: "https://hytale.com/device",
366
+ code: "ABCD-EFGH",
367
+ });
368
+ });
369
+
370
+ test("blanks a secret value in the text of a rejected submit", async () => {
371
+ const { logger } = recorder();
372
+ const handlers = createSetupHandlers(
373
+ driverWith(
374
+ setupWith(async () => {
375
+ throw new BridgeUserError(text(`telegram refused ${SECRET}`));
376
+ }),
377
+ ),
378
+ logger,
379
+ redact,
380
+ );
381
+
382
+ handlers.onSetup(signIn());
383
+
384
+ const result = await handlers.onSetupSubmit(contextOf(), "sign-in", "renew", {});
385
+
386
+ expect(result.runtime?.prompt).toMatchObject({
387
+ kind: BridgeSetupPromptKind.Failed,
388
+ message: text("telegram refused ••••"),
389
+ });
390
+ });
391
+ });
@@ -3,6 +3,7 @@ import { BridgeUserError, type BridgeValues } from "../core";
3
3
  import type { BridgeDriver } from "../modules";
4
4
  import type { BridgeSetupResult, BridgeSetupRuntime } from "../protocol";
5
5
  import type { BridgeLeveledLogger } from "./bridgeLog";
6
+ import { type BridgeRedact, redactLocalized, redactSetupRuntime } from "./bridgeRedaction";
6
7
  import { failedSetup, normalizeSetupRuntime, unsupportedSetup } from "./bridgeSetup";
7
8
 
8
9
  export interface BridgeSetupHandlers {
@@ -12,7 +13,13 @@ export interface BridgeSetupHandlers {
12
13
  current(): BridgeSetupRuntime | null;
13
14
  }
14
15
 
15
- export const createSetupHandlers = (driver: BridgeDriver, log: BridgeLeveledLogger): BridgeSetupHandlers => {
16
+ const unredacted: BridgeRedact = (text) => text;
17
+
18
+ export const createSetupHandlers = (
19
+ driver: BridgeDriver,
20
+ log: BridgeLeveledLogger,
21
+ redact: BridgeRedact = unredacted,
22
+ ): BridgeSetupHandlers => {
16
23
  const declared: ReadonlySet<string> = new Set((driver.setup?.steps ?? []).map((step) => step.id));
17
24
 
18
25
  let current: BridgeSetupRuntime | null = null;
@@ -20,10 +27,13 @@ export const createSetupHandlers = (driver: BridgeDriver, log: BridgeLeveledLogg
20
27
  return {
21
28
  onSetup(runtime) {
22
29
  current = driver.setup
23
- ? normalizeSetupRuntime(runtime, {
24
- declared,
25
- warn: log.warn,
26
- })
30
+ ? redactSetupRuntime(
31
+ normalizeSetupRuntime(runtime, {
32
+ declared,
33
+ warn: log.warn,
34
+ }),
35
+ redact,
36
+ )
27
37
  : null;
28
38
  },
29
39
  current() {
@@ -59,7 +69,7 @@ export const createSetupHandlers = (driver: BridgeDriver, log: BridgeLeveledLogg
59
69
  error: error.message,
60
70
  });
61
71
 
62
- return failedSetup(current, error.text);
72
+ return failedSetup(current, redactLocalized(error.text, redact));
63
73
  }
64
74
 
65
75
  return {