@ghostlytools/cli 1.3.0 → 1.3.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 (81) hide show
  1. package/README.md +9 -3
  2. package/dist/assets/{api-BiaX6l6f.js → api-SYPJgFRU.js} +2 -2
  3. package/dist/assets/{api-CrxVxtY1.js → api-aup3Amud.js} +141 -40
  4. package/dist/assets/api-aup3Amud.js.map +1 -0
  5. package/dist/assets/{apiKit-CajiDxdK.js → apiKit-DYkVEs6r.js} +30 -30
  6. package/dist/assets/apiKit-DYkVEs6r.js.map +1 -0
  7. package/dist/assets/{apps-t23gkc28.js → apps-DcZKu29s.js} +1 -1
  8. package/dist/assets/{apps-CbfiCSIw.js → apps-Rbk2HHXb.js} +3 -4
  9. package/dist/assets/{apps-CbfiCSIw.js.map → apps-Rbk2HHXb.js.map} +1 -1
  10. package/dist/assets/{arkade-D4BVJFEQ.js → arkade-CuWlNnCz.js} +4 -4
  11. package/dist/assets/arkade-CuWlNnCz.js.map +1 -0
  12. package/dist/assets/{backup-BAMi2lAq.js → backup-CJWLDHuS.js} +8 -8
  13. package/dist/assets/backup-CJWLDHuS.js.map +1 -0
  14. package/dist/assets/{backup-Cg52WeQi.js → backup-CSMw2GHv.js} +1 -1
  15. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  16. package/dist/assets/bitcoind-CZchoh43.js +349 -0
  17. package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
  18. package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
  19. package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
  20. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  21. package/dist/assets/buttons-CGRqZ7z0.js +2 -0
  22. package/dist/assets/{buttons-CX1pKB0j.js → buttons-QEnAGWoC.js} +4 -5
  23. package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-QEnAGWoC.js.map} +1 -1
  24. package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
  25. package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
  26. package/dist/assets/{envelope-BvU0fLEt.js → envelope-K27mzoyu.js} +2 -2
  27. package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-K27mzoyu.js.map} +1 -1
  28. package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
  29. package/dist/assets/errors-DO5z6h_l.js.map +1 -0
  30. package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  31. package/dist/assets/groupFiles-DHTrTA4I.js +3229 -0
  32. package/dist/assets/groupFiles-DHTrTA4I.js.map +1 -0
  33. package/dist/assets/{host-Cmd205Dc.js → host-C7hA-Za7.js} +135 -42
  34. package/dist/assets/host-C7hA-Za7.js.map +1 -0
  35. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-BVoWY2ua.js} +2 -3
  36. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-BVoWY2ua.js.map} +1 -1
  37. package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-BJPk9bMn.js} +2 -3
  38. package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-BJPk9bMn.js.map} +1 -1
  39. package/dist/assets/{main-BzNN6bkx.js → main-DC8cUkDx.js} +142 -35
  40. package/dist/assets/main-DC8cUkDx.js.map +1 -0
  41. package/dist/assets/{mainline-D5D5C-jT.js → mainline-CxbxpoU4.js} +2 -2
  42. package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-CxbxpoU4.js.map} +1 -1
  43. package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-D42iNuUB.js} +2 -2
  44. package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-D42iNuUB.js.map} +1 -1
  45. package/dist/assets/nwc-Cm4sYgLv.js +612 -0
  46. package/dist/assets/nwc-Cm4sYgLv.js.map +1 -0
  47. package/dist/assets/nwcProvider-0D7vdPDd.js +77 -0
  48. package/dist/assets/nwcProvider-0D7vdPDd.js.map +1 -0
  49. package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
  50. package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
  51. package/dist/assets/registry-CLolR0kM.js +2 -0
  52. package/dist/assets/{groupFiles-BNTSDaoq.js → registry-CVdSZW5l.js} +19 -3911
  53. package/dist/assets/registry-CVdSZW5l.js.map +1 -0
  54. package/dist/assets/resolve-BnlEajYz.js +591 -0
  55. package/dist/assets/resolve-BnlEajYz.js.map +1 -0
  56. package/dist/assets/{server-CBCEYKMN.js → server-CJNEKxzE.js} +284 -1202
  57. package/dist/assets/server-CJNEKxzE.js.map +1 -0
  58. package/dist/assets/{src-DhfGgAUu.js → src-yMUQvdyL.js} +2264 -71
  59. package/dist/assets/src-yMUQvdyL.js.map +1 -0
  60. package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B2a8to9q.js} +2 -2
  61. package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B2a8to9q.js.map} +1 -1
  62. package/dist/assets/types-C4_KafL_.js +122 -0
  63. package/dist/assets/types-C4_KafL_.js.map +1 -0
  64. package/dist/assets/{usdt-C1Xao2xH.js → usdt-DesFeDiG.js} +3 -3
  65. package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-DesFeDiG.js.map} +1 -1
  66. package/dist/ghostly.mjs +1 -1
  67. package/package.json +1 -1
  68. package/dist/assets/api-CrxVxtY1.js.map +0 -1
  69. package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
  70. package/dist/assets/arkade-D4BVJFEQ.js.map +0 -1
  71. package/dist/assets/backup-BAMi2lAq.js.map +0 -1
  72. package/dist/assets/buttons-CWl2yNJu.js +0 -2
  73. package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
  74. package/dist/assets/errors-my6VJR_0.js.map +0 -1
  75. package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
  76. package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
  77. package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
  78. package/dist/assets/host-Cmd205Dc.js.map +0 -1
  79. package/dist/assets/main-BzNN6bkx.js.map +0 -1
  80. package/dist/assets/server-CBCEYKMN.js.map +0 -1
  81. package/dist/assets/src-DhfGgAUu.js.map +0 -1
@@ -1,13 +1,12 @@
1
- import { jt as fromBase64Url } from "./groupFiles-DBbjOLuD.js";
2
- import { Ba as callRtcConfig, Gr as checkPushEndpoint, Ha as extractParamsFromSdp, Ia as CALL_SIGNAL_MAX_AGE_MS, La as RTC_CONFIG, Ra as answersOffer, Ua as parseCallSignal, Us as setLinkTraceSink, Va as callSignalHeardAt, Wa as signalHasVideo, hi as isPublicAddress, za as buildSdpFromSignal } from "./src-DhfGgAUu.js";
3
- import { a as fedimintSdkOn, r as irohRelayProblem } from "./irohWeb--YyEA-hX.js";
4
- import { r as asCliError, t as CliError } from "./errors-my6VJR_0.js";
5
- import { a as findChat } from "./apiKit-CajiDxdK.js";
6
- import { _ as flushCardUpdates, c as paymentJson, d as nodeLocalFetch, f as chatJson, g as resumeHolds, h as groupMessageJson, l as reportAppFrames, m as groupJson, p as chatMessageJson, r as callApi, u as servedApps } from "./api-CrxVxtY1.js";
7
- import { n as RESTORED_MARK, r as acquireLock } from "./profiles-B6F4oa9K.js";
1
+ import { $o as parseCallSignal, Jo as answersOffer, Ko as CALL_SIGNAL_MAX_AGE_MS, Qo as extractParamsFromSdp, Xo as callRtcConfig, Yo as buildSdpFromSignal, Zo as callSignalHeardAt, ai as checkPushEndpoint, el as setLinkTraceSink, es as signalHasVideo, ki as isPublicAddress, qo as RTC_CONFIG, yl as fromBase64Url } from "./src-yMUQvdyL.js";
2
+ import { a as fedimintSdkOn, r as irohRelayProblem } from "./irohWeb-BJPk9bMn.js";
3
+ import { r as asCliError, t as CliError } from "./errors-DO5z6h_l.js";
4
+ import { a as findChat } from "./apiKit-DYkVEs6r.js";
5
+ import { _ as flushCardUpdates, c as paymentJson, d as nodeLocalFetch, f as chatJson, g as resumeHolds, h as groupMessageJson, l as reportAppFrames, m as groupJson, p as chatMessageJson, r as callApi, u as servedApps } from "./api-aup3Amud.js";
6
+ import { n as RESTORED_MARK, r as acquireLock } from "./profiles-BHc9pEYg.js";
8
7
  import { r as privateFolder, t as ownFolder } from "./privateFolder-Bk9C9oFv.js";
9
8
  import { createRequire } from "node:module";
10
- import { appendFileSync, chmodSync, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
9
+ import { appendFileSync, chmodSync, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
11
10
  import { readFile } from "node:fs/promises";
12
11
  import { dirname, join } from "node:path";
13
12
  import { createHash, randomBytes } from "node:crypto";
@@ -910,8 +909,14 @@ var CallManager = class {
910
909
  calls = /* @__PURE__ */ new Map();
911
910
  /** Per chat: the timestamp of the last signal handled. */
912
911
  lastSignal = /* @__PURE__ */ new Map();
912
+ /** Per chat: the timestamp of the last signal sent (`stamp`). */
913
+ lastSent = /* @__PURE__ */ new Map();
913
914
  /** Per chat: the timer that clears the hang-up signal. */
914
915
  clearing = /* @__PURE__ */ new Map();
916
+ /** Per chat: when this side's last call there ended before it was answered (`CALL_PACE_MS`). */
917
+ unansweredAt = /* @__PURE__ */ new Map();
918
+ /** The early answers being made, those of ended calls too (MAX_EARLY_ANSWERS). */
919
+ makings = /* @__PURE__ */ new Set();
915
920
  auto;
916
921
  configPath;
917
922
  stopping = false;
@@ -924,6 +929,15 @@ var CallManager = class {
924
929
  get now() {
925
930
  return (this.host.now ?? Date.now)();
926
931
  }
932
+ /**
933
+ * A new signal's timestamp in a chat: now, or 1 ms after the last one sent there. The contact drops a signal no
934
+ * newer than the last it heard, so a redial or a hang-up in the same millisecond as the signal before it would be lost.
935
+ */
936
+ stamp(chat) {
937
+ const ts = Math.max(this.now, (this.lastSent.get(chat) ?? 0) + 1);
938
+ this.lastSent.set(chat, ts);
939
+ return ts;
940
+ }
927
941
  /** A signal the contact's app sent in a chat (the engine's `call-signal`). */
928
942
  onSignal(chat, json) {
929
943
  if (this.stopping) return;
@@ -1035,7 +1049,7 @@ var CallManager = class {
1035
1049
  try {
1036
1050
  const stack = await this.stack();
1037
1051
  if (!await this.connect(call, (media) => CallMedia.offer(stack, media))) return;
1038
- call.offerTs = this.now;
1052
+ call.offerTs = this.stamp(call.chat);
1039
1053
  call.offering = true;
1040
1054
  await this.send(call.chat, {
1041
1055
  t: "o",
@@ -1059,7 +1073,7 @@ var CallManager = class {
1059
1073
  if (!await this.connect(call, (media) => CallMedia.answer(stack, offer, media))) return;
1060
1074
  await this.send(call.chat, {
1061
1075
  t: "a",
1062
- ts: this.now,
1076
+ ts: this.stamp(call.chat),
1063
1077
  o: offer.ts,
1064
1078
  ...call.media.local,
1065
1079
  v: 0
@@ -1075,11 +1089,15 @@ var CallManager = class {
1075
1089
  */
1076
1090
  prepare(call) {
1077
1091
  if (this.host.answers === false || call.ended || call.state !== "ringing") return;
1092
+ this.stopEarly(call);
1078
1093
  call.earlyTimer = setTimeout(() => this.prepare(call), this.host.earlyAnswerMs ?? 3e4);
1079
1094
  call.earlyTimer.unref?.();
1080
1095
  if (call.making) return;
1096
+ const held = [...this.calls.values()].filter((c) => c !== call && c.early).length;
1097
+ if (this.makings.size + held >= 4 || [...this.makings].some((m) => m.chat === call.chat)) return;
1081
1098
  const offer = call.offer;
1082
1099
  const making = {
1100
+ chat: call.chat,
1083
1101
  done: this.stack().then((stack) => EarlyAnswer.open(stack, offer, {
1084
1102
  iceServers: this.host.engine.getState().settings?.iceServers ?? [],
1085
1103
  log: (line) => process.stderr.write(`ghostly: call ${call.id}: ${line}\n`)
@@ -1091,15 +1109,19 @@ var CallManager = class {
1091
1109
  claimed: false
1092
1110
  };
1093
1111
  call.making = making;
1112
+ this.makings.add(making);
1094
1113
  making.done.then((early) => {
1114
+ this.makings.delete(making);
1095
1115
  if (call.making === making) call.making = null;
1096
- if (making.claimed || !early) return;
1097
- if (call.ended || call.state !== "ringing") {
1098
- early.close();
1099
- return;
1116
+ if (!making.claimed && early) {
1117
+ if (call.ended || call.state !== "ringing") early.close();
1118
+ else {
1119
+ call.early?.close();
1120
+ call.early = early;
1121
+ }
1100
1122
  }
1101
- call.early?.close();
1102
- call.early = early;
1123
+ const next = this.byChat(making.chat);
1124
+ if (next && next !== call && next.direction === "in" && !next.making && !next.early) this.prepare(next);
1103
1125
  });
1104
1126
  }
1105
1127
  /** The answered call's early answer: the one made, if still good, or the one being made; none from then on. */
@@ -1122,6 +1144,7 @@ var CallManager = class {
1122
1144
  async start(chat, options) {
1123
1145
  const link = this.callable(chat);
1124
1146
  if (this.byChat(link.id)) throw new CliError("busy", "A call is already on in this chat");
1147
+ this.paced(link.id);
1125
1148
  const rate = rateOf(options.rate, this.auto.rate);
1126
1149
  const stack = await this.stack();
1127
1150
  if (this.byChat(link.id)) throw new CliError("busy", "A call is already on in this chat");
@@ -1130,7 +1153,7 @@ var CallManager = class {
1130
1153
  await this.attach(call, (media) => CallMedia.offer(stack, media));
1131
1154
  if (call.reason === "crossed") throw new CliError("busy", "The contact called at the same time: their call rings here");
1132
1155
  if (call.ended) throw new CliError("unavailable", "The call ended before it was placed");
1133
- call.offerTs = this.now;
1156
+ call.offerTs = this.stamp(link.id);
1134
1157
  call.offering = true;
1135
1158
  await this.send(link.id, {
1136
1159
  t: "o",
@@ -1168,7 +1191,7 @@ var CallManager = class {
1168
1191
  if (call.ended) throw new CliError("unavailable", "The call ended before it was answered");
1169
1192
  await this.send(call.chat, {
1170
1193
  t: "a",
1171
- ts: this.now,
1194
+ ts: this.stamp(call.chat),
1172
1195
  o: call.offer.ts,
1173
1196
  ...call.media.local,
1174
1197
  v: 0
@@ -1388,6 +1411,7 @@ var CallManager = class {
1388
1411
  this.disarm(call);
1389
1412
  if (call.watcher) clearInterval(call.watcher);
1390
1413
  this.calls.delete(call.id);
1414
+ if (call.direction === "out" && call.connectedAt === null && call.state === "ringing" && (reason === "hangup" || reason === "unanswered")) this.unansweredAt.set(call.chat, this.now);
1391
1415
  if (report) process.stderr.write(`ghostly: call ${call.id}: ended (${reason})${call.media ? `, ice ${call.media.ice.state}` : ""}\n`);
1392
1416
  call.media?.close();
1393
1417
  this.stopEarly(call);
@@ -1397,7 +1421,7 @@ var CallManager = class {
1397
1421
  if (tell) {
1398
1422
  await this.send(call.chat, {
1399
1423
  t: "h",
1400
- ts: this.now
1424
+ ts: this.stamp(call.chat)
1401
1425
  }).catch(() => {});
1402
1426
  if (!this.stopping) {
1403
1427
  const timer = setTimeout(() => {
@@ -1416,6 +1440,17 @@ var CallManager = class {
1416
1440
  ...call.connectedAt !== null ? { duration: this.now - call.connectedAt } : {}
1417
1441
  });
1418
1442
  }
1443
+ /** Refuses a call placed too soon after this side's last unanswered one in the chat (`CALL_PACE_MS`). */
1444
+ paced(chat) {
1445
+ const pace = this.host.callPaceMs ?? 2e3, last = this.unansweredAt.get(chat);
1446
+ if (!pace || last === void 0) return;
1447
+ const left = last + pace - this.now;
1448
+ if (left <= 0) {
1449
+ this.unansweredAt.delete(chat);
1450
+ return;
1451
+ }
1452
+ throw new CliError("busy", `Your last call in this chat ended unanswered a moment ago: call again in ${Math.ceil(left / 100) / 10} s`, { retryAfterMs: left });
1453
+ }
1419
1454
  arm(call, ms, reason, tell) {
1420
1455
  this.disarm(call);
1421
1456
  call.timer = setTimeout(() => void this.end(call, reason, tell), ms);
@@ -1537,6 +1572,8 @@ function readAuto(path) {
1537
1572
  return off;
1538
1573
  }
1539
1574
  }
1575
+ /** A journal past this is trimmed, and only this much of its end is ever read. */
1576
+ var JOURNAL_MAX_BYTES = 33554432;
1540
1577
  var SEEN_DB = "ghostly-cli";
1541
1578
  var SEEN = "seen";
1542
1579
  /**
@@ -1562,6 +1599,7 @@ var EventHub = class {
1562
1599
  seenDb;
1563
1600
  seq = 0;
1564
1601
  journalLines = 0;
1602
+ journalBytes = 0;
1565
1603
  listeners = /* @__PURE__ */ new Set();
1566
1604
  stateListeners = /* @__PURE__ */ new Set();
1567
1605
  callListeners = /* @__PURE__ */ new Set();
@@ -1598,13 +1636,24 @@ var EventHub = class {
1598
1636
  }
1599
1637
  /** Loads the journal's last seq and the seen set; call before `sink` is attached. */
1600
1638
  async open() {
1601
- if (existsSync(this.journalPath)) {
1602
- const lines = readFileSync(this.journalPath, "utf8").split("\n").filter(Boolean);
1603
- this.journalLines = lines.length;
1604
- for (let i = lines.length - 1; i >= 0; i--) try {
1605
- this.seq = JSON.parse(lines[i]).seq;
1606
- break;
1639
+ try {
1640
+ if (existsSync(this.journalPath)) {
1641
+ const { lines, size } = journalTail(this.journalPath);
1642
+ this.journalLines = lines.length;
1643
+ this.journalBytes = size;
1644
+ for (let i = lines.length - 1; i >= 0; i--) try {
1645
+ this.seq = JSON.parse(lines[i]).seq;
1646
+ break;
1647
+ } catch {}
1648
+ if (size > 33554432 || lines.length > 2e4) this.trimJournal();
1649
+ }
1650
+ } catch (error) {
1651
+ process.stderr.write(`ghostly: the event journal could not be read, a new one begins: ${error instanceof Error ? error.message : String(error)}\n`);
1652
+ try {
1653
+ renameSync(this.journalPath, this.journalPath + ".unreadable");
1607
1654
  } catch {}
1655
+ this.journalLines = 0;
1656
+ this.journalBytes = 0;
1608
1657
  }
1609
1658
  this.db = await new Promise((resolve, reject) => {
1610
1659
  const request = indexedDB.open(this.seenDb, 1);
@@ -1729,13 +1778,10 @@ var EventHub = class {
1729
1778
  replay(since) {
1730
1779
  if (!existsSync(this.journalPath) || since >= this.seq) return [];
1731
1780
  const out = [];
1732
- for (const line of readFileSync(this.journalPath, "utf8").split("\n")) {
1733
- if (!line) continue;
1734
- try {
1735
- const event = JSON.parse(line);
1736
- if (event.seq > since) out.push(event);
1737
- } catch {}
1738
- }
1781
+ for (const line of journalTail(this.journalPath).lines) try {
1782
+ const event = JSON.parse(line);
1783
+ if (event.seq > since) out.push(event);
1784
+ } catch {}
1739
1785
  if (out.length && out[0].seq > since + 1) out.unshift({
1740
1786
  seq: since,
1741
1787
  id: `events.gap:${since}:${out[0].seq}`,
@@ -1758,18 +1804,29 @@ var EventHub = class {
1758
1804
  at: this.now(),
1759
1805
  ...fields
1760
1806
  };
1761
- appendFileSync(this.journalPath, JSON.stringify(event) + "\n", { mode: 384 });
1762
- if (++this.journalLines > 2e4) this.trimJournal();
1807
+ try {
1808
+ const line = JSON.stringify(event) + "\n";
1809
+ appendFileSync(this.journalPath, line, { mode: 384 });
1810
+ this.journalBytes += Buffer.byteLength(line);
1811
+ if (++this.journalLines > 2e4 || this.journalBytes > 33554432) this.trimJournal();
1812
+ } catch (error) {
1813
+ process.stderr.write(`ghostly: event journal error: ${error instanceof Error ? error.message : String(error)}\n`);
1814
+ }
1763
1815
  for (const listener of this.listeners) try {
1764
1816
  listener(event);
1765
1817
  } catch {}
1766
1818
  return event;
1767
1819
  }
1820
+ /** Keeps the last `JOURNAL_KEEP` events, and of them no more than `JOURNAL_KEEP_BYTES`. */
1768
1821
  trimJournal() {
1769
- const lines = readFileSync(this.journalPath, "utf8").split("\n").filter(Boolean).slice(-1e4);
1770
- writeFileSync(this.journalPath + ".tmp", lines.join("\n") + "\n", { mode: 384 });
1822
+ const lines = journalTail(this.journalPath).lines.slice(-1e4);
1823
+ let start = lines.length, bytes = 0;
1824
+ while (start > 0 && bytes + Buffer.byteLength(lines[start - 1]) + 1 <= 16777216) bytes += Buffer.byteLength(lines[--start]) + 1;
1825
+ const kept = lines.slice(start);
1826
+ writeFileSync(this.journalPath + ".tmp", kept.length ? kept.join("\n") + "\n" : "", { mode: 384 });
1771
1827
  renameSync(this.journalPath + ".tmp", this.journalPath);
1772
- this.journalLines = lines.length;
1828
+ this.journalLines = kept.length;
1829
+ this.journalBytes = bytes;
1773
1830
  }
1774
1831
  handle(message) {
1775
1832
  switch (message.kind) {
@@ -2327,6 +2384,32 @@ function groupShape(group) {
2327
2384
  pin: group.pin ?? null
2328
2385
  };
2329
2386
  }
2387
+ /**
2388
+ * The journal's whole lines within its last `JOURNAL_MAX_BYTES`, and its size: never the whole file as one string, so
2389
+ * a journal grown past what a string can hold (or a trim's bound) is still read, and in bounded time.
2390
+ */
2391
+ function journalTail(path) {
2392
+ const fd = openSync(path, "r");
2393
+ try {
2394
+ const size = fstatSync(fd).size;
2395
+ const from = Math.max(0, size - JOURNAL_MAX_BYTES - 1);
2396
+ const buffer = Buffer.alloc(size - from);
2397
+ let read = 0;
2398
+ while (read < buffer.length) {
2399
+ const n = readSync(fd, buffer, read, buffer.length - read, from + read);
2400
+ if (!n) break;
2401
+ read += n;
2402
+ }
2403
+ const lines = buffer.toString("utf8", 0, read).split("\n");
2404
+ if (from > 0) lines.shift();
2405
+ return {
2406
+ lines: lines.filter(Boolean),
2407
+ size
2408
+ };
2409
+ } finally {
2410
+ closeSync(fd);
2411
+ }
2412
+ }
2330
2413
  //#endregion
2331
2414
  //#region src/runtime/fedimint.ts
2332
2415
  /**
@@ -4358,14 +4441,14 @@ async function startRuntime(paths, options = {}) {
4358
4441
  (await import("./ghostly_iroh_web-Dvzh3Y6E.js")).initSync({ module: irohWasmBytes() });
4359
4442
  const [{ registerFileBytes }, { NodeFileBytes }] = await Promise.all([import("./fileBytes-CgbibpbV.js"), import("./fileBytes-CfSXjTFa.js")]);
4360
4443
  registerFileBytes("native", async () => new NodeFileBytes(paths.files), true);
4361
- const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./server-CBCEYKMN.js"), import("./endpoint-Dn7H8qE1.js")]);
4444
+ const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./server-CJNEKxzE.js"), import("./endpoint-BOTFMpfS.js")]);
4362
4445
  const network = hyperdhtNetwork();
4363
4446
  const dht = mainlineNetwork();
4364
- const mainline = dht.off ? null : new (await (import("./mainline-D5D5C-jT.js"))).Mainline({
4447
+ const mainline = dht.off ? null : new (await (import("./mainline-CxbxpoU4.js"))).Mainline({
4365
4448
  bootstrap: dht.bootstrap,
4366
4449
  host: dht.host
4367
4450
  });
4368
- const transport = mainline || pinned ? new (await (import("./mainline-D5D5C-jT.js"))).RelaysAndDht(mainline, pinned ? { relays: pinned } : {}, !!pinned) : null;
4451
+ const transport = mainline || pinned ? new (await (import("./mainline-CxbxpoU4.js"))).RelaysAndDht(mainline, pinned ? { relays: pinned } : {}, !!pinned) : null;
4369
4452
  const apps = servedApps(paths.dir).length > 0;
4370
4453
  const server = new EngineServer({
4371
4454
  ...transport ? { transport } : {},
@@ -4611,6 +4694,16 @@ function connection(ctx, socket) {
4611
4694
  if (request.method === "events.subscribe") {
4612
4695
  const params = request.params ?? {};
4613
4696
  const since = typeof params.since === "number" ? params.since : ctx.hub.lastSeq;
4697
+ let replay;
4698
+ try {
4699
+ replay = ctx.hub.replay(since);
4700
+ } catch (error) {
4701
+ write({
4702
+ id,
4703
+ error: asCliError(error).toJSON()
4704
+ });
4705
+ return;
4706
+ }
4614
4707
  const queued = [];
4615
4708
  let replaying = true;
4616
4709
  unsubscribes.push(ctx.hub.onEvent((event) => {
@@ -4625,7 +4718,7 @@ function connection(ctx, socket) {
4625
4718
  }
4626
4719
  });
4627
4720
  let last = since;
4628
- for (const event of ctx.hub.replay(since)) {
4721
+ for (const event of replay) {
4629
4722
  write({ event });
4630
4723
  last = event.seq;
4631
4724
  }
@@ -4645,4 +4738,4 @@ function connection(ctx, socket) {
4645
4738
  //#endregion
4646
4739
  export { BASELINE_NEWEST, MAX_LINE, keepServing, openHost, serve };
4647
4740
 
4648
- //# sourceMappingURL=host-Cmd205Dc.js.map
4741
+ //# sourceMappingURL=host-C7hA-Za7.js.map