@serverkgg/bridge 0.1.1 → 0.1.3

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 (121) hide show
  1. package/package.json +3 -2
  2. package/rules/package.md +60 -0
  3. package/src/blog/articleFrontmatter.test.ts +10 -10
  4. package/src/blog/compile.test.ts +9 -9
  5. package/src/bot/botDeploy.test.ts +16 -16
  6. package/src/bot/botEnvironment.test.ts +8 -8
  7. package/src/bot/botIdentity.test.ts +12 -12
  8. package/src/bot/botPanel.test.ts +41 -11
  9. package/src/bot/botPanel.ts +13 -5
  10. package/src/bot/botState.test.ts +3 -3
  11. package/src/bot/botTerminal.test.ts +5 -5
  12. package/src/bot/botVariables.test.ts +8 -8
  13. package/src/bot/detect/botToken.test.ts +6 -6
  14. package/src/bot/detect/detect.test.ts +21 -21
  15. package/src/bot/detect/detectTable.test.ts +5 -5
  16. package/src/bot/detect/envExample.test.ts +2 -2
  17. package/src/bot/detect/envScan.test.ts +4 -4
  18. package/src/bot/detect/inferBotPlatform.test.ts +4 -4
  19. package/src/bot/detect/keys.test.ts +3 -3
  20. package/src/bot/detect/sensitiveKey.test.ts +5 -5
  21. package/src/bot/detect/serverkJson.test.ts +5 -5
  22. package/src/catalogs/curseforge.test.ts +35 -35
  23. package/src/catalogs/thunderstore.test.ts +9 -9
  24. package/src/cli/detectTable.test.ts +3 -3
  25. package/src/cli/link.test.ts +11 -11
  26. package/src/cli/main.ts +8 -0
  27. package/src/cli/rules.test.ts +198 -0
  28. package/src/cli/rules.ts +188 -0
  29. package/src/cli/validate.test.ts +123 -29
  30. package/src/cli/validate.ts +37 -1
  31. package/src/codecs/ini.test.ts +13 -13
  32. package/src/codecs/javaProperties.test.ts +11 -11
  33. package/src/codecs/sourceCfg.test.ts +14 -14
  34. package/src/codecs/xmlProperties.test.ts +10 -10
  35. package/src/context/bridgeContext.ts +6 -1
  36. package/src/core/bridgeAction.ts +2 -0
  37. package/src/core/bridgeActivity.ts +9 -0
  38. package/src/core/bridgeAvailability.ts +10 -0
  39. package/src/core/bridgePort.test.ts +6 -6
  40. package/src/core/bridgeUserError.test.ts +13 -3
  41. package/src/core/bridgeUserError.ts +6 -2
  42. package/src/core/bridgeValue.test.ts +5 -5
  43. package/src/core/describeDownloadFailure.test.ts +58 -0
  44. package/src/core/describeDownloadFailure.ts +69 -0
  45. package/src/core/index.ts +3 -0
  46. package/src/guides/markdown.test.ts +31 -31
  47. package/src/install/installStamp.test.ts +9 -9
  48. package/src/manifest/extension.ts +36 -0
  49. package/src/manifest/index.ts +1 -0
  50. package/src/manifest/manifest.test.ts +208 -90
  51. package/src/manifest/manifest.ts +34 -0
  52. package/src/modules/bridge.ts +8 -1
  53. package/src/modules/bridgeModule.ts +13 -1
  54. package/src/net/loopback.test.ts +10 -10
  55. package/src/panel/bridgeDetail.ts +2 -1
  56. package/src/presence/rosterSync.test.ts +7 -7
  57. package/src/probes/a2s.test.ts +5 -5
  58. package/src/probes/minecraftPing.test.ts +17 -17
  59. package/src/probes/raknetPing.test.ts +13 -13
  60. package/src/protocol/bridgeActivity.ts +54 -0
  61. package/src/protocol/bridgeEvents.ts +30 -0
  62. package/src/protocol/bridgeFrame.ts +10 -1
  63. package/src/protocol/index.ts +1 -0
  64. package/src/rcon/rconAccess.test.ts +20 -20
  65. package/src/rcon/rconAccess.ts +13 -13
  66. package/src/rcon/rconClient.test.ts +49 -49
  67. package/src/rcon/rconFrame.test.ts +14 -14
  68. package/src/rcon/rconIds.test.ts +4 -4
  69. package/src/rcon/rconWriter.test.ts +6 -6
  70. package/src/runtime/bridgeActions.ts +19 -2
  71. package/src/runtime/bridgeActivity.test.ts +318 -0
  72. package/src/runtime/bridgeActivity.ts +268 -0
  73. package/src/runtime/bridgeBackupLease.test.ts +9 -9
  74. package/src/runtime/bridgeConfigReader.test.ts +5 -5
  75. package/src/runtime/bridgeConsoleHandler.test.ts +8 -8
  76. package/src/runtime/bridgeContext.test.ts +11 -5
  77. package/src/runtime/bridgeContext.ts +8 -1
  78. package/src/runtime/bridgeDriver.test.ts +114 -93
  79. package/src/runtime/bridgeDriver.ts +10 -7
  80. package/src/runtime/bridgeEvents.test.ts +15 -15
  81. package/src/runtime/bridgeFiles.test.ts +54 -54
  82. package/src/runtime/bridgeInstall.test.ts +10 -10
  83. package/src/runtime/bridgeLiveConfig.test.ts +6 -6
  84. package/src/runtime/bridgeLog.ts +10 -0
  85. package/src/runtime/bridgePending.test.ts +6 -6
  86. package/src/runtime/bridgeRedaction.test.ts +16 -16
  87. package/src/runtime/bridgeRefusalError.ts +13 -0
  88. package/src/runtime/bridgeRegistry.test.ts +1077 -34
  89. package/src/runtime/bridgeRegistry.ts +464 -65
  90. package/src/runtime/bridgeScopedContext.ts +55 -0
  91. package/src/runtime/bridgeServer.test.ts +424 -24
  92. package/src/runtime/bridgeServer.ts +125 -35
  93. package/src/runtime/bridgeSetup.test.ts +27 -27
  94. package/src/runtime/bridgeSetup.ts +1 -1
  95. package/src/runtime/bridgeSetupHandlers.test.ts +80 -17
  96. package/src/runtime/bridgeSetupHandlers.ts +45 -10
  97. package/src/runtime/bridgeSignal.test.ts +7 -7
  98. package/src/runtime/bridgeSupervisor.test.ts +42 -42
  99. package/src/runtime/serve.ts +45 -3
  100. package/src/steam/avatars.test.ts +13 -13
  101. package/src/steam/steamApp.test.ts +8 -8
  102. package/src/steam/steamcmd.test.ts +44 -13
  103. package/src/steam/steamcmd.ts +27 -4
  104. package/src/steam/steamcmdNarrator.test.ts +4 -4
  105. package/src/steam/workshop.test.ts +5 -5
  106. package/src/utils/changedSettingKeys.test.ts +65 -0
  107. package/src/utils/changedSettingKeys.ts +6 -0
  108. package/src/utils/describeExecFailure.test.ts +34 -0
  109. package/src/utils/describeExecFailure.ts +9 -0
  110. package/src/utils/index.ts +3 -0
  111. package/src/utils/sameSettingValue.test.ts +35 -0
  112. package/src/utils/sameSettingValue.ts +40 -0
  113. package/src/utils/token.test.ts +7 -7
  114. package/src/wine/protonCommand.test.ts +5 -5
  115. package/src/wine/wineCommand.test.ts +6 -6
  116. package/src/wine/winePrefix.test.ts +6 -6
  117. package/src/wire/activity.test.ts +95 -0
  118. package/src/wire/activity.ts +41 -0
  119. package/src/wire/index.ts +1 -0
  120. package/src/wire/panel.test.ts +91 -25
  121. package/src/wire/panel.ts +11 -0
@@ -1,8 +1,9 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
1
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
2
  import { mkdtemp, rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import {
6
+ type BridgeActivity,
6
7
  BridgeDesired,
7
8
  BridgeFailureCode,
8
9
  BridgeFailureError,
@@ -12,6 +13,7 @@ import {
12
13
  BridgeUserError,
13
14
  } from "../core";
14
15
  import {
16
+ BridgeEventName,
15
17
  BridgeMethod,
16
18
  type BridgePendingResult,
17
19
  type BridgeResponse,
@@ -19,6 +21,7 @@ import {
19
21
  type BridgeSetupRuntime,
20
22
  type BridgeValues,
21
23
  } from "../protocol";
24
+ import { BridgeRefusalError } from "./bridgeRefusalError";
22
25
  import { createServer, type ServerOptions } from "./bridgeServer";
23
26
  import { failedSetup, unsupportedSetup } from "./bridgeSetup";
24
27
 
@@ -61,11 +64,41 @@ const baseOptions = (socket: string): ServerOptions => ({
61
64
  onPendingAdd: unreachable("pendingAdd"),
62
65
  onSetupStatus: unreachable("setupStatus"),
63
66
  onSetupSubmit: unreachable("setupSubmit"),
67
+ onUndelivered: unreachable("undelivered"),
64
68
  onLog: () => {
65
69
  return;
66
70
  },
67
71
  });
68
72
 
73
+ const abandon = async (socket: string, frame: Record<string, unknown>) => {
74
+ await new Promise<void>((resolve, reject) => {
75
+ void Bun.connect({
76
+ unix: socket,
77
+ socket: {
78
+ open(connection) {
79
+ connection.write(`${JSON.stringify(frame)}\n`);
80
+ connection.end();
81
+ },
82
+ close() {
83
+ resolve();
84
+ },
85
+ data() {
86
+ return;
87
+ },
88
+ error(_connection, error) {
89
+ reject(error);
90
+ },
91
+ },
92
+ }).catch(reject);
93
+ });
94
+ };
95
+
96
+ const until = async (check: () => boolean) => {
97
+ for (let attempt = 0; attempt < 100 && !check(); attempt++) {
98
+ await Bun.sleep(10);
99
+ }
100
+ };
101
+
69
102
  const call = async (socket: string, frame: Record<string, unknown>): Promise<BridgeResponse> => {
70
103
  return await new Promise<BridgeResponse>((resolve, reject) => {
71
104
  void Bun.connect<{
@@ -129,7 +162,7 @@ describe("pending dispatch", () => {
129
162
  rejected: null,
130
163
  };
131
164
 
132
- test("answers pendingList with what the driver lists", async () => {
165
+ it("answers pendingList with what the driver lists", async () => {
133
166
  const server = createServer({
134
167
  ...baseOptions(socket),
135
168
  onPendingList: async () => listed,
@@ -150,7 +183,7 @@ describe("pending dispatch", () => {
150
183
  });
151
184
  });
152
185
 
153
- test("answers pendingList with the unsupported sentinel when the driver declares no pending module", async () => {
186
+ it("answers pendingList with the unsupported sentinel when the driver declares no pending module", async () => {
154
187
  const server = createServer({
155
188
  ...baseOptions(socket),
156
189
  onPendingList: async () => ({
@@ -183,7 +216,7 @@ describe("pending dispatch", () => {
183
216
  });
184
217
  });
185
218
 
186
- test("passes the uploaded path to pendingAdd and answers with the refreshed list", async () => {
219
+ it("passes the uploaded path to pendingAdd and answers with the refreshed list", async () => {
187
220
  const received: string[] = [];
188
221
 
189
222
  const server = createServer({
@@ -214,7 +247,7 @@ describe("pending dispatch", () => {
214
247
  });
215
248
  });
216
249
 
217
- test("refuses a pendingAdd carrying no path", async () => {
250
+ it("refuses a pendingAdd carrying no path", async () => {
218
251
  const server = createServer({
219
252
  ...baseOptions(socket),
220
253
  onPendingAdd: unreachable("pendingAdd"),
@@ -235,7 +268,7 @@ describe("pending dispatch", () => {
235
268
  });
236
269
  });
237
270
 
238
- test("answers pendingAdd with the rejection the driver reported beside the refreshed list", async () => {
271
+ it("answers pendingAdd with the rejection the driver reported beside the refreshed list", async () => {
239
272
  const rejected: BridgePendingResult = {
240
273
  ...listed,
241
274
  rejected: {
@@ -266,7 +299,7 @@ describe("pending dispatch", () => {
266
299
  });
267
300
  });
268
301
 
269
- test("names an unknown method so an older agent can degrade", async () => {
302
+ it("names an unknown method so an older agent can degrade", async () => {
270
303
  const server = createServer(baseOptions(socket));
271
304
 
272
305
  const response = await call(socket, {
@@ -334,7 +367,7 @@ describe("setup dispatch", () => {
334
367
  runtime,
335
368
  };
336
369
 
337
- test("answers setupStatus with what the driver reported", async () => {
370
+ it("answers setupStatus with what the driver reported", async () => {
338
371
  const server = createServer({
339
372
  ...baseOptions(socket),
340
373
  onSetupStatus: () => reported,
@@ -355,7 +388,7 @@ describe("setup dispatch", () => {
355
388
  });
356
389
  });
357
390
 
358
- test("answers setupStatus with the unsupported sentinel when the driver declares no setup", async () => {
391
+ it("answers setupStatus with the unsupported sentinel when the driver declares no setup", async () => {
359
392
  const server = createServer({
360
393
  ...baseOptions(socket),
361
394
  onSetupStatus: () => unsupportedSetup(),
@@ -379,7 +412,7 @@ describe("setup dispatch", () => {
379
412
  });
380
413
  });
381
414
 
382
- test("passes the step, action and args of a setupSubmit to the driver", async () => {
415
+ it("passes the step, action and args of a setupSubmit to the driver", async () => {
383
416
  const received: {
384
417
  step: string;
385
418
  action: string;
@@ -395,7 +428,10 @@ describe("setup dispatch", () => {
395
428
  args,
396
429
  });
397
430
 
398
- return reported;
431
+ return {
432
+ result: reported,
433
+ activity: [],
434
+ };
399
435
  },
400
436
  });
401
437
 
@@ -425,10 +461,11 @@ describe("setup dispatch", () => {
425
461
  id: 3,
426
462
  ok: true,
427
463
  result: reported,
464
+ activity: [],
428
465
  });
429
466
  });
430
467
 
431
- test("refuses a setupSubmit carrying no step id", async () => {
468
+ it("refuses a setupSubmit carrying no step id", async () => {
432
469
  const server = createServer({
433
470
  ...baseOptions(socket),
434
471
  onSetupSubmit: unreachable("setupSubmit"),
@@ -450,14 +487,16 @@ describe("setup dispatch", () => {
450
487
  });
451
488
  });
452
489
 
453
- test("answers a rejected setupSubmit with a failed prompt instead of a failed call", async () => {
490
+ it("answers a rejected setupSubmit with a failed prompt instead of a failed call", async () => {
454
491
  const server = createServer({
455
492
  ...baseOptions(socket),
456
- onSetupSubmit: async () =>
457
- failedSetup(runtime, {
493
+ onSetupSubmit: async () => ({
494
+ result: failedSetup(runtime, {
458
495
  ar: "الكود ما عاد ينفع",
459
496
  en: "this code cannot be renewed any more",
460
497
  }),
498
+ activity: [],
499
+ }),
461
500
  });
462
501
 
463
502
  const response = await call(socket, {
@@ -495,13 +534,17 @@ describe("setup dispatch", () => {
495
534
  },
496
535
  },
497
536
  },
537
+ activity: [],
498
538
  });
499
539
  });
500
540
 
501
- test("answers setupSubmit with the unsupported sentinel when the driver implements no submit", async () => {
541
+ it("answers setupSubmit with the unsupported sentinel when the driver implements no submit", async () => {
502
542
  const server = createServer({
503
543
  ...baseOptions(socket),
504
- onSetupSubmit: async () => unsupportedSetup(),
544
+ onSetupSubmit: async () => ({
545
+ result: unsupportedSetup(),
546
+ activity: [],
547
+ }),
505
548
  });
506
549
 
507
550
  const response = await call(socket, {
@@ -521,6 +564,7 @@ describe("setup dispatch", () => {
521
564
  supported: false,
522
565
  runtime: null,
523
566
  },
567
+ activity: [],
524
568
  });
525
569
  });
526
570
  });
@@ -561,7 +605,7 @@ describe("error serialisation", () => {
561
605
  return response;
562
606
  };
563
607
 
564
- test("carries the localized text of a user error beside the message", async () => {
608
+ it("carries the localized text of a user error beside the message", async () => {
565
609
  expect(
566
610
  await thrown(
567
611
  new BridgeUserError({
@@ -580,7 +624,7 @@ describe("error serialisation", () => {
580
624
  });
581
625
  });
582
626
 
583
- test("carries the failure code of a failure error and no text", async () => {
627
+ it("carries the failure code of a failure error and no text", async () => {
584
628
  expect(await thrown(new BridgeFailureError(BridgeFailureCode.CatalogRestricted, "the author blocked it"))).toEqual({
585
629
  id: 1,
586
630
  ok: false,
@@ -589,7 +633,36 @@ describe("error serialisation", () => {
589
633
  });
590
634
  });
591
635
 
592
- test("carries the message alone for an ordinary error", async () => {
636
+ it("marks a runtime refusal unchanged and carries its text", async () => {
637
+ expect(
638
+ await thrown(
639
+ new BridgeRefusalError('module "worlds" cannot run "delete" right now', {
640
+ ar: "ما تقدر تحذف العالم الشغّال",
641
+ en: "You cannot delete the active world",
642
+ }),
643
+ ),
644
+ ).toEqual({
645
+ id: 1,
646
+ ok: false,
647
+ error: 'module "worlds" cannot run "delete" right now',
648
+ unchanged: true,
649
+ text: {
650
+ ar: "ما تقدر تحذف العالم الشغّال",
651
+ en: "You cannot delete the active world",
652
+ },
653
+ });
654
+ });
655
+
656
+ it("marks a runtime refusal without text unchanged and sends no text", async () => {
657
+ expect(await thrown(new BridgeRefusalError('no module named "worlds"'))).toEqual({
658
+ id: 1,
659
+ ok: false,
660
+ error: 'no module named "worlds"',
661
+ unchanged: true,
662
+ });
663
+ });
664
+
665
+ it("carries the message alone for an ordinary error", async () => {
593
666
  expect(await thrown(new Error("the file could not be read"))).toEqual({
594
667
  id: 1,
595
668
  ok: false,
@@ -609,7 +682,7 @@ describe("backup ownership dispatch", () => {
609
682
  recursive: true,
610
683
  });
611
684
  });
612
- test("carries the lease token through renew and release frames", async () => {
685
+ it("carries the lease token through renew and release frames", async () => {
613
686
  const socket = join(dir, "bridge.sock");
614
687
  const renewals: (string | undefined)[] = [];
615
688
  const releases: (string | undefined)[] = [];
@@ -673,7 +746,7 @@ describe("error replies", () => {
673
746
  });
674
747
  });
675
748
 
676
- test("hide a secret value from the error and the customer text", async () => {
749
+ it("hide a secret value from the error and the customer text", async () => {
677
750
  const server = createServer({
678
751
  ...baseOptions(socket),
679
752
  redact: (value) => value.split(SECRET).join("••••"),
@@ -757,7 +830,7 @@ describe("failure logging", () => {
757
830
  return lines;
758
831
  };
759
832
 
760
- test("logs a refused action as a refusal with its message and no stack", async () => {
833
+ it("logs a refused action as a refusal with its message and no stack", async () => {
761
834
  expect(
762
835
  await logged(
763
836
  new BridgeUserError({
@@ -776,11 +849,338 @@ describe("failure logging", () => {
776
849
  ]);
777
850
  });
778
851
 
779
- test("logs an ordinary error as a failure with its stack", async () => {
852
+ it("logs an ordinary error as a failure with its stack", async () => {
780
853
  const [line] = await logged(new Error("the marker could not be written"));
781
854
 
782
855
  expect(line?.message).toBe("bridge call failed");
783
856
  expect(line?.fields.error).toBe("the marker could not be written");
784
857
  expect(String(line?.fields.stack)).toContain("the marker could not be written");
785
858
  });
859
+
860
+ it("logs a refusal with the cause the driver attached", async () => {
861
+ expect(
862
+ await logged(
863
+ new BridgeUserError(
864
+ {
865
+ ar: "المصدر رفض التنزيل",
866
+ en: "the source refused the download",
867
+ },
868
+ {
869
+ cause: new Error("cdn.modrinth.com answered 403"),
870
+ },
871
+ ),
872
+ ),
873
+ ).toEqual([
874
+ {
875
+ message: "bridge call refused",
876
+ fields: {
877
+ method: BridgeMethod.Invoke,
878
+ error: "the source refused the download — cdn.modrinth.com answered 403",
879
+ },
880
+ },
881
+ ]);
882
+ });
883
+ });
884
+
885
+ describe("activity on the answer frame", () => {
886
+ let dir = "";
887
+ let socket = "";
888
+
889
+ beforeEach(async () => {
890
+ dir = await mkdtemp(join(tmpdir(), "bridge-server-"));
891
+ socket = join(dir, "bridge.sock");
892
+ });
893
+
894
+ afterEach(async () => {
895
+ await rm(dir, {
896
+ force: true,
897
+ recursive: true,
898
+ });
899
+ });
900
+
901
+ const entry: BridgeActivity = {
902
+ name: BridgeEventName.AccessGranted,
903
+ text: {
904
+ ar: "أضفت Steve للقائمة البيضاء",
905
+ en: "Added Steve to the whitelist",
906
+ },
907
+ subject: "Steve",
908
+ };
909
+
910
+ it("carries the entries an invoke reported beside its result", async () => {
911
+ const server = createServer({
912
+ ...baseOptions(socket),
913
+ onInvoke: async () => ({
914
+ result: null,
915
+ activity: [
916
+ entry,
917
+ ],
918
+ }),
919
+ });
920
+
921
+ const response = await call(socket, {
922
+ id: 11,
923
+ token: TOKEN,
924
+ method: BridgeMethod.Invoke,
925
+ module: "whitelist",
926
+ action: "add",
927
+ args: {
928
+ input: "Steve",
929
+ },
930
+ });
931
+
932
+ server.stop(true);
933
+
934
+ expect(response).toEqual({
935
+ id: 11,
936
+ ok: true,
937
+ result: null,
938
+ activity: [
939
+ entry,
940
+ ],
941
+ });
942
+ });
943
+
944
+ it("carries the entries a setup submit reported beside its result", async () => {
945
+ const server = createServer({
946
+ ...baseOptions(socket),
947
+ onSetupSubmit: async () => ({
948
+ result: unsupportedSetup(),
949
+ activity: [
950
+ entry,
951
+ ],
952
+ }),
953
+ });
954
+
955
+ const response = await call(socket, {
956
+ id: 12,
957
+ token: TOKEN,
958
+ method: BridgeMethod.SetupSubmit,
959
+ module: "sign-in",
960
+ action: "renew",
961
+ });
962
+
963
+ server.stop(true);
964
+
965
+ expect(response).toMatchObject({
966
+ ok: true,
967
+ activity: [
968
+ entry,
969
+ ],
970
+ });
971
+ });
972
+
973
+ it("carries an empty list when nothing was reported, so the platform knows the runtime reports", async () => {
974
+ const server = createServer({
975
+ ...baseOptions(socket),
976
+ onInvoke: async () => ({
977
+ result: "done",
978
+ activity: [],
979
+ }),
980
+ });
981
+
982
+ const response = await call(socket, {
983
+ id: 13,
984
+ token: TOKEN,
985
+ method: BridgeMethod.Invoke,
986
+ module: "whitelist",
987
+ action: "add",
988
+ });
989
+
990
+ server.stop(true);
991
+
992
+ expect(response).toEqual({
993
+ id: 13,
994
+ ok: true,
995
+ result: "done",
996
+ activity: [],
997
+ });
998
+ });
999
+
1000
+ it("leaves the field off the answers of methods that report no activity", async () => {
1001
+ const server = createServer(baseOptions(socket));
1002
+
1003
+ const response = await call(socket, {
1004
+ id: 14,
1005
+ token: TOKEN,
1006
+ method: BridgeMethod.Status,
1007
+ });
1008
+
1009
+ server.stop(true);
1010
+
1011
+ expect(response.ok).toBe(true);
1012
+ expect("activity" in response).toBe(false);
1013
+ });
1014
+
1015
+ const invokeFrame = {
1016
+ id: 21,
1017
+ token: TOKEN,
1018
+ method: BridgeMethod.Invoke,
1019
+ module: "whitelist",
1020
+ action: "add",
1021
+ };
1022
+
1023
+ it("sends the entries to the events ring once when the caller left before the answer", async () => {
1024
+ const ring: BridgeActivity[][] = [];
1025
+
1026
+ let finish: () => void = () => undefined;
1027
+ let started = false;
1028
+ let answered = false;
1029
+
1030
+ const server = createServer({
1031
+ ...baseOptions(socket),
1032
+ onInvoke: async () => {
1033
+ started = true;
1034
+
1035
+ await new Promise<void>((resolve) => {
1036
+ finish = resolve;
1037
+ });
1038
+
1039
+ answered = true;
1040
+
1041
+ return {
1042
+ result: null,
1043
+ activity: [
1044
+ entry,
1045
+ ],
1046
+ };
1047
+ },
1048
+ onUndelivered: (activity) => {
1049
+ ring.push(activity);
1050
+ },
1051
+ });
1052
+
1053
+ await abandon(socket, invokeFrame);
1054
+ await until(() => started);
1055
+ await Bun.sleep(20);
1056
+
1057
+ finish();
1058
+
1059
+ await until(() => answered && ring.length > 0);
1060
+ await Bun.sleep(20);
1061
+
1062
+ server.stop(true);
1063
+
1064
+ expect(ring).toEqual([
1065
+ [
1066
+ entry,
1067
+ ],
1068
+ ]);
1069
+ });
1070
+
1071
+ it("sends nothing to the ring when the answer reached the caller", async () => {
1072
+ const ring: BridgeActivity[][] = [];
1073
+
1074
+ const server = createServer({
1075
+ ...baseOptions(socket),
1076
+ onInvoke: async () => ({
1077
+ result: null,
1078
+ activity: [
1079
+ entry,
1080
+ ],
1081
+ }),
1082
+ onUndelivered: (activity) => {
1083
+ ring.push(activity);
1084
+ },
1085
+ });
1086
+
1087
+ const response = await call(socket, invokeFrame);
1088
+
1089
+ await Bun.sleep(20);
1090
+
1091
+ server.stop(true);
1092
+
1093
+ expect(response).toMatchObject({
1094
+ ok: true,
1095
+ activity: [
1096
+ entry,
1097
+ ],
1098
+ });
1099
+ expect(ring).toEqual([]);
1100
+ });
1101
+
1102
+ it("sends the entries to the ring when the answer cannot be written", async () => {
1103
+ const ring: BridgeActivity[][] = [];
1104
+
1105
+ const server = createServer({
1106
+ ...baseOptions(socket),
1107
+ onInvoke: async () => ({
1108
+ result: BigInt(1),
1109
+ activity: [
1110
+ entry,
1111
+ ],
1112
+ }),
1113
+ onUndelivered: (activity) => {
1114
+ ring.push(activity);
1115
+ },
1116
+ });
1117
+
1118
+ const response = await call(socket, invokeFrame);
1119
+
1120
+ server.stop(true);
1121
+
1122
+ expect(response.ok).toBe(false);
1123
+ expect(ring).toEqual([
1124
+ [
1125
+ entry,
1126
+ ],
1127
+ ]);
1128
+ });
1129
+ });
1130
+
1131
+ describe("failure frames", () => {
1132
+ let dir = "";
1133
+ let socket = "";
1134
+
1135
+ beforeEach(async () => {
1136
+ dir = await mkdtemp(join(tmpdir(), "bridge-server-"));
1137
+ socket = join(dir, "bridge.sock");
1138
+ });
1139
+
1140
+ afterEach(async () => {
1141
+ await rm(dir, {
1142
+ force: true,
1143
+ recursive: true,
1144
+ });
1145
+ });
1146
+
1147
+ it("carry the cause in the staff error and keep the customer text friendly", async () => {
1148
+ const SECRET = "tok-1234567890-secret";
1149
+
1150
+ const server = createServer({
1151
+ ...baseOptions(socket),
1152
+ redact: (value) => value.split(SECRET).join("••••"),
1153
+ onInvoke: async () => {
1154
+ throw new BridgeUserError(
1155
+ {
1156
+ ar: "المصدر رفض التنزيل",
1157
+ en: "the source refused the download",
1158
+ },
1159
+ {
1160
+ cause: new Error(`cdn.modrinth.com answered 403 for ${SECRET}`),
1161
+ },
1162
+ );
1163
+ },
1164
+ });
1165
+
1166
+ const response = await call(socket, {
1167
+ id: 31,
1168
+ token: TOKEN,
1169
+ method: BridgeMethod.Invoke,
1170
+ module: "mods",
1171
+ action: "install",
1172
+ });
1173
+
1174
+ server.stop(true);
1175
+
1176
+ expect(response).toEqual({
1177
+ id: 31,
1178
+ ok: false,
1179
+ error: "the source refused the download — cdn.modrinth.com answered 403 for ••••",
1180
+ text: {
1181
+ ar: "المصدر رفض التنزيل",
1182
+ en: "the source refused the download",
1183
+ },
1184
+ });
1185
+ });
786
1186
  });