@ghostlytools/cli 1.3.2 → 1.3.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.
- package/README.md +6 -6
- package/dist/assets/{api-aup3Amud.js → api-B1fADtqd.js} +65 -20
- package/dist/assets/api-B1fADtqd.js.map +1 -0
- package/dist/assets/{api-SYPJgFRU.js → api-Bg7bh_6E.js} +2 -2
- package/dist/assets/{apiKit-DYkVEs6r.js → apiKit-BsUZPHLn.js} +21 -6
- package/dist/assets/apiKit-BsUZPHLn.js.map +1 -0
- package/dist/assets/{apps-Rbk2HHXb.js → apps-BaUNt-5c.js} +91 -24
- package/dist/assets/apps-BaUNt-5c.js.map +1 -0
- package/dist/assets/{apps-DcZKu29s.js → apps-DepomCiX.js} +1 -1
- package/dist/assets/{arkade-CuWlNnCz.js → arkade-CNnMaoEQ.js} +2 -2
- package/dist/assets/{arkade-CuWlNnCz.js.map → arkade-CNnMaoEQ.js.map} +1 -1
- package/dist/assets/{backup-CSMw2GHv.js → backup-B5Q1W-Ey.js} +1 -1
- package/dist/assets/{backup-CJWLDHuS.js → backup-Bbz3lyC5.js} +6 -6
- package/dist/assets/{backup-CJWLDHuS.js.map → backup-Bbz3lyC5.js.map} +1 -1
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/buttons-CUd7j0ZA.js +2 -0
- package/dist/assets/{buttons-QEnAGWoC.js → buttons-DS1iURp8.js} +4 -4
- package/dist/assets/{buttons-QEnAGWoC.js.map → buttons-DS1iURp8.js.map} +1 -1
- package/dist/assets/{envelope-K27mzoyu.js → envelope-fNC941z4.js} +2 -2
- package/dist/assets/{envelope-K27mzoyu.js.map → envelope-fNC941z4.js.map} +1 -1
- package/dist/assets/{errors-DO5z6h_l.js → errors-B-zMxbcd.js} +6 -5
- package/dist/assets/{errors-DO5z6h_l.js.map → errors-B-zMxbcd.js.map} +1 -1
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
- package/dist/assets/{ghostly_iroh_web-Dvzh3Y6E.js → ghostly_iroh_web-BsAEFIjl.js} +30 -36
- package/dist/assets/ghostly_iroh_web-BsAEFIjl.js.map +1 -0
- package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
- package/dist/assets/{groupFiles-DHTrTA4I.js → groupFiles-6qnLCnOm.js} +47 -21
- package/dist/assets/{groupFiles-DHTrTA4I.js.map → groupFiles-6qnLCnOm.js.map} +1 -1
- package/dist/assets/{host-C7hA-Za7.js → host-Dj3C1fn3.js} +211 -42
- package/dist/assets/host-Dj3C1fn3.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-BVoWY2ua.js → hyperdhtRelay-CrgYQN-g.js} +2 -2
- package/dist/assets/{hyperdhtRelay-BVoWY2ua.js.map → hyperdhtRelay-CrgYQN-g.js.map} +1 -1
- package/dist/assets/{irohWeb-BJPk9bMn.js → irohWeb-DSyLp5Re.js} +3 -3
- package/dist/assets/{irohWeb-BJPk9bMn.js.map → irohWeb-DSyLp5Re.js.map} +1 -1
- package/dist/assets/{main-DC8cUkDx.js → main-BDd11Yd1.js} +159 -54
- package/dist/assets/main-BDd11Yd1.js.map +1 -0
- package/dist/assets/{mainline-CxbxpoU4.js → mainline-CLTo0nGa.js} +2 -2
- package/dist/assets/{mainline-CxbxpoU4.js.map → mainline-CLTo0nGa.js.map} +1 -1
- package/dist/assets/{networkReason-D42iNuUB.js → networkReason-DwcadZz5.js} +2 -2
- package/dist/assets/{networkReason-D42iNuUB.js.map → networkReason-DwcadZz5.js.map} +1 -1
- package/dist/assets/{nwc-Cm4sYgLv.js → nwc-BNFyyECH.js} +3 -3
- package/dist/assets/{nwc-Cm4sYgLv.js.map → nwc-BNFyyECH.js.map} +1 -1
- package/dist/assets/{nwcProvider-0D7vdPDd.js → nwcProvider-BbjJOfLD.js} +2 -2
- package/dist/assets/{nwcProvider-0D7vdPDd.js.map → nwcProvider-BbjJOfLD.js.map} +1 -1
- package/dist/assets/{profiles-BHc9pEYg.js → profiles-gPCjLOQq.js} +2 -2
- package/dist/assets/{profiles-BHc9pEYg.js.map → profiles-gPCjLOQq.js.map} +1 -1
- package/dist/assets/{registry-CVdSZW5l.js → registry-CoLHpxkb.js} +17 -32
- package/dist/assets/registry-CoLHpxkb.js.map +1 -0
- package/dist/assets/{registry-CLolR0kM.js → registry-DoTb6uEr.js} +1 -1
- package/dist/assets/{resolve-BnlEajYz.js → resolve-BIDJ-UUl.js} +18 -3
- package/dist/assets/resolve-BIDJ-UUl.js.map +1 -0
- package/dist/assets/{server-CJNEKxzE.js → server-Bx3RGkBC.js} +282 -97
- package/dist/assets/server-Bx3RGkBC.js.map +1 -0
- package/dist/assets/{src-yMUQvdyL.js → src-D7gcJElT.js} +316 -104
- package/dist/assets/src-D7gcJElT.js.map +1 -0
- package/dist/assets/{turnRefs-B2a8to9q.js → turnRefs-PN644M9o.js} +2 -2
- package/dist/assets/{turnRefs-B2a8to9q.js.map → turnRefs-PN644M9o.js.map} +1 -1
- package/dist/assets/{usdt-DesFeDiG.js → usdt-B2TLRlYa.js} +3 -3
- package/dist/assets/{usdt-DesFeDiG.js.map → usdt-B2TLRlYa.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +1 -1
- package/dist/assets/api-aup3Amud.js.map +0 -1
- package/dist/assets/apiKit-DYkVEs6r.js.map +0 -1
- package/dist/assets/apps-Rbk2HHXb.js.map +0 -1
- package/dist/assets/buttons-CGRqZ7z0.js +0 -2
- package/dist/assets/ghostly_iroh_web-Dvzh3Y6E.js.map +0 -1
- package/dist/assets/host-C7hA-Za7.js.map +0 -1
- package/dist/assets/main-DC8cUkDx.js.map +0 -1
- package/dist/assets/registry-CVdSZW5l.js.map +0 -1
- package/dist/assets/resolve-BnlEajYz.js.map +0 -1
- package/dist/assets/server-CJNEKxzE.js.map +0 -1
- package/dist/assets/src-yMUQvdyL.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { $o as
|
|
2
|
-
import { a as fedimintSdkOn, r as irohRelayProblem } from "./irohWeb-
|
|
3
|
-
import { r as asCliError, t as CliError } from "./errors-
|
|
4
|
-
import { a as findChat } from "./apiKit-
|
|
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-
|
|
6
|
-
import { n as RESTORED_MARK, r as acquireLock } from "./profiles-
|
|
1
|
+
import { $o as extractParamsFromSdp, Ai as isPublicAddress, Jo as RTC_CONFIG, Qo as callSignalHeardAt, Xo as buildSdpFromSignal, Yo as answersOffer, Zo as callRtcConfig, es as parseCallSignal, oi as checkPushEndpoint, qo as CALL_SIGNAL_MAX_AGE_MS, tl as setLinkTraceSink, ts as signalHasVideo, xl as fromBase64Url } from "./src-D7gcJElT.js";
|
|
2
|
+
import { a as fedimintSdkOn, r as irohRelayProblem } from "./irohWeb-DSyLp5Re.js";
|
|
3
|
+
import { r as asCliError, t as CliError } from "./errors-B-zMxbcd.js";
|
|
4
|
+
import { a as findChat } from "./apiKit-BsUZPHLn.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-B1fADtqd.js";
|
|
6
|
+
import { n as RESTORED_MARK, r as acquireLock } from "./profiles-gPCjLOQq.js";
|
|
7
7
|
import { r as privateFolder, t as ownFolder } from "./privateFolder-Bk9C9oFv.js";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import { appendFileSync, chmodSync, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
@@ -418,11 +418,13 @@ var CallAudio = class {
|
|
|
418
418
|
this.schedule();
|
|
419
419
|
}
|
|
420
420
|
stop() {
|
|
421
|
-
this.stopped = true;
|
|
422
421
|
if (this.timer) clearTimeout(this.timer);
|
|
423
422
|
this.timer = null;
|
|
424
|
-
this.
|
|
425
|
-
this.
|
|
423
|
+
if (this.stopped) return;
|
|
424
|
+
this.stopped = true;
|
|
425
|
+
for (const codec of [this.options.encoder, this.options.decoder]) try {
|
|
426
|
+
codec.delete?.();
|
|
427
|
+
} catch {}
|
|
426
428
|
}
|
|
427
429
|
/** Sends the frame that is due now. Exposed for tests, which drive the clock themselves. */
|
|
428
430
|
tick() {
|
|
@@ -551,6 +553,57 @@ var CallAudio = class {
|
|
|
551
553
|
//#endregion
|
|
552
554
|
//#region src/calls/media.ts
|
|
553
555
|
var loading = null;
|
|
556
|
+
var OPUS_APPLICATION_VOIP = 2048;
|
|
557
|
+
var OPUS_SET_BITRATE = 4002;
|
|
558
|
+
/** The largest packet a codec reads or writes (opusscript's MAX_PACKET_SIZE). */
|
|
559
|
+
var MAX_PACKET_BYTES = 3828;
|
|
560
|
+
/**
|
|
561
|
+
* Room for a frame of PCM as the module reads and writes it, one byte of PCM in each 16-bit element: a concealment (an
|
|
562
|
+
* empty packet) writes 120 ms at 48 kHz, 5,760 samples, 23,040 bytes, at any rate.
|
|
563
|
+
*/
|
|
564
|
+
var PCM_ROOM_BYTES = 23040;
|
|
565
|
+
/** The most PCM a decode hands back, as opusscript's: its largest frame, 60 ms at 48 kHz (CallAudio's `conceal`). */
|
|
566
|
+
var MAX_PCM_OUT = 5760;
|
|
567
|
+
/**
|
|
568
|
+
* An Opus encoder or decoder on opusscript's libopus, with buffers that hold what libopus writes. opusscript's own
|
|
569
|
+
* wrapper (0.1.1) keeps 5,760 bytes for its PCM and reaches it through views at twice the buffer's address, past it: a
|
|
570
|
+
* 20 ms frame at 48 kHz takes 3,840 bytes there and a concealment 23,040. With three calls or more, as calls ended and
|
|
571
|
+
* others started, one codec's audio landed on another's state: noise, then "memory access out of bounds" in every
|
|
572
|
+
* call until the daemon restarted. The bytes in and out are the same as opusscript's.
|
|
573
|
+
*/
|
|
574
|
+
function opusCodec(native, rate) {
|
|
575
|
+
const handler = new native.OpusScriptHandler(rate, 1, OPUS_APPLICATION_VOIP);
|
|
576
|
+
const pcm = native._malloc(PCM_ROOM_BYTES), packet = native._malloc(MAX_PACKET_BYTES);
|
|
577
|
+
let deleted = false;
|
|
578
|
+
const codec = {
|
|
579
|
+
encode(frame, frameSize) {
|
|
580
|
+
if (deleted || frame.length * 2 > PCM_ROOM_BYTES) throw new Error("Encode error: Bad argument");
|
|
581
|
+
native.HEAPU16.set(frame, pcm / 2);
|
|
582
|
+
const length = handler._encode(pcm, frame.length, packet, frameSize);
|
|
583
|
+
if (length < 0) throw new Error(`Encode error: ${length}`);
|
|
584
|
+
return Buffer.from(native.HEAPU8.subarray(packet, packet + length));
|
|
585
|
+
},
|
|
586
|
+
decode(data) {
|
|
587
|
+
if (deleted || data.length > MAX_PACKET_BYTES) throw new Error("Decode error: Bad argument");
|
|
588
|
+
native.HEAPU8.set(data, packet);
|
|
589
|
+
const samples = handler._decode(packet, data.length, pcm);
|
|
590
|
+
if (samples < 0) throw new Error(`Decode error: ${samples}`);
|
|
591
|
+
return Buffer.from(native.HEAPU16.subarray(pcm / 2, pcm / 2 + Math.min(samples * 2, MAX_PCM_OUT)));
|
|
592
|
+
},
|
|
593
|
+
delete() {
|
|
594
|
+
if (deleted) return;
|
|
595
|
+
deleted = true;
|
|
596
|
+
native.OpusScriptHandler.destroy_handler(handler);
|
|
597
|
+
native._free(pcm);
|
|
598
|
+
native._free(packet);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
if (handler._encoder_ctl(OPUS_SET_BITRATE, 32e3) < 0) {
|
|
602
|
+
codec.delete?.();
|
|
603
|
+
throw new Error("Encoder CTL error");
|
|
604
|
+
}
|
|
605
|
+
return codec;
|
|
606
|
+
}
|
|
554
607
|
/** The media stack, or why calls are not available here (the reason the call buttons of the contact's app show). */
|
|
555
608
|
function loadCallStack() {
|
|
556
609
|
return loading ??= (async () => {
|
|
@@ -558,12 +611,8 @@ function loadCallStack() {
|
|
|
558
611
|
try {
|
|
559
612
|
const ndc = await import("node-datachannel");
|
|
560
613
|
const lib = ndc.PeerConnection ? ndc : ndc.default;
|
|
561
|
-
const
|
|
562
|
-
const opus = (rate) =>
|
|
563
|
-
const codec = new OpusScript(rate, 1, OpusScript.Application.VOIP);
|
|
564
|
-
codec.setBitrate(32e3);
|
|
565
|
-
return codec;
|
|
566
|
-
};
|
|
614
|
+
const native = createRequire(import.meta.url)("opusscript/build/opusscript_native_wasm.js")();
|
|
615
|
+
const opus = (rate) => opusCodec(native, rate);
|
|
567
616
|
opus(48e3).delete?.();
|
|
568
617
|
return {
|
|
569
618
|
ndc: lib,
|
|
@@ -612,7 +661,8 @@ function describePair(pair) {
|
|
|
612
661
|
var refuseAnswers = Number(process.env.GHOSTLY_CALL_REFUSE_ANSWERS) || 0;
|
|
613
662
|
/** How long a description waits for its candidates: a server reflexive one and a little after it, or this long. */
|
|
614
663
|
var GATHER_MS = 5e3;
|
|
615
|
-
|
|
664
|
+
/** The signal keeps the first server reflexive candidate only: the apps' calls and the chats' data link settle as long. */
|
|
665
|
+
var SETTLE_MS = 150;
|
|
616
666
|
/**
|
|
617
667
|
* With a candidate in (a host one comes in milliseconds), the server reflexive one is waited for this long at most,
|
|
618
668
|
* not the whole of `GATHER_MS`: a STUN server that answers does in tens of milliseconds, and one that never does (UDP
|
|
@@ -629,8 +679,9 @@ var HOST_GATHER_MS = 2e3;
|
|
|
629
679
|
async function openConnection(stack, options, offer, held = false) {
|
|
630
680
|
const { ndc } = stack;
|
|
631
681
|
const bind = process.env.GHOSTLY_CALL_BIND;
|
|
682
|
+
const iceServers = callIceServers(options.iceServers);
|
|
632
683
|
const pc = new ndc.PeerConnection("ghostly-call", {
|
|
633
|
-
iceServers
|
|
684
|
+
iceServers,
|
|
634
685
|
...bind ? { bindAddress: bind } : {}
|
|
635
686
|
});
|
|
636
687
|
const born = Date.now();
|
|
@@ -674,7 +725,7 @@ async function openConnection(stack, options, offer, held = false) {
|
|
|
674
725
|
videoTrack = pc.addTrack(video);
|
|
675
726
|
videoTrack.onMessage(() => {});
|
|
676
727
|
}
|
|
677
|
-
const gathered = gathering(pc);
|
|
728
|
+
const gathered = gathering(pc, { turn: callUsesTurn(iceServers) });
|
|
678
729
|
if (offer) pc.setRemoteDescription(buildSdpFromSignal(held ? {
|
|
679
730
|
...offer,
|
|
680
731
|
c: []
|
|
@@ -864,8 +915,18 @@ function mdnsNote(signal, log) {
|
|
|
864
915
|
lookup(host, { all: true }).then((found) => log(`${host} resolves here to ${found.map((a) => a.address).join(", ")}`), () => log(`${host} does not resolve here: the call connects only if the contact's checks reach this side`));
|
|
865
916
|
}
|
|
866
917
|
}
|
|
867
|
-
/**
|
|
868
|
-
function
|
|
918
|
+
/** Whether a call's ICE servers (`callIceServers`) have a TURN server: its relay candidate is then the one waited for. */
|
|
919
|
+
function callUsesTurn(servers) {
|
|
920
|
+
return servers.some((server) => typeof server !== "string");
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Resolves once the local description has its candidates (see GATHER_MS and HOST_GATHER_MS). `turn`: the connection
|
|
924
|
+
* has a TURN server, and its relay candidate is waited for rather than the reflexive one, as the apps' calls do
|
|
925
|
+
* (`waitForIceGathering`): an allocation takes two round trips, and a TURN server 100 ms away gave its relay after the
|
|
926
|
+
* description had gone without it, on the networks where only the relay connects.
|
|
927
|
+
*/
|
|
928
|
+
function gathering(pc, { turn = false } = {}) {
|
|
929
|
+
const wanted = turn ? / typ relay/ : / typ srflx/;
|
|
869
930
|
return new Promise((resolve) => {
|
|
870
931
|
let settle = null;
|
|
871
932
|
let any = false;
|
|
@@ -884,7 +945,7 @@ function gathering(pc) {
|
|
|
884
945
|
});
|
|
885
946
|
pc.onLocalCandidate((candidate) => {
|
|
886
947
|
any = true;
|
|
887
|
-
if (!settle &&
|
|
948
|
+
if (!settle && wanted.test(candidate)) settle = setTimeout(done, SETTLE_MS);
|
|
888
949
|
});
|
|
889
950
|
});
|
|
890
951
|
}
|
|
@@ -1413,7 +1474,11 @@ var CallManager = class {
|
|
|
1413
1474
|
this.calls.delete(call.id);
|
|
1414
1475
|
if (call.direction === "out" && call.connectedAt === null && call.state === "ringing" && (reason === "hangup" || reason === "unanswered")) this.unansweredAt.set(call.chat, this.now);
|
|
1415
1476
|
if (report) process.stderr.write(`ghostly: call ${call.id}: ended (${reason})${call.media ? `, ice ${call.media.ice.state}` : ""}\n`);
|
|
1416
|
-
|
|
1477
|
+
try {
|
|
1478
|
+
call.media?.close();
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
process.stderr.write(`ghostly: call ${call.id}: closing its media failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1481
|
+
}
|
|
1417
1482
|
this.stopEarly(call);
|
|
1418
1483
|
call.early?.close();
|
|
1419
1484
|
call.early = null;
|
|
@@ -1828,6 +1893,34 @@ var EventHub = class {
|
|
|
1828
1893
|
this.journalLines = kept.length;
|
|
1829
1894
|
this.journalBytes = bytes;
|
|
1830
1895
|
}
|
|
1896
|
+
/**
|
|
1897
|
+
* What was removed here leaves the journal too: the journal is written again without the events that name it (a
|
|
1898
|
+
* line that names it and cannot be read goes as well), and a journal set aside as unreadable, which cannot be sorted,
|
|
1899
|
+
* is deleted. `ids`: what was removed (a message's id, a chat's or a group's); `gone`: whether an event is of it.
|
|
1900
|
+
*/
|
|
1901
|
+
forget(ids, gone) {
|
|
1902
|
+
if (!ids.length) return;
|
|
1903
|
+
try {
|
|
1904
|
+
rmSync(this.journalPath + ".unreadable", { force: true });
|
|
1905
|
+
if (!existsSync(this.journalPath)) return;
|
|
1906
|
+
const lines = journalTail(this.journalPath).lines;
|
|
1907
|
+
const kept = lines.filter((line) => {
|
|
1908
|
+
if (!ids.some((id) => line.includes(JSON.stringify(id)))) return true;
|
|
1909
|
+
try {
|
|
1910
|
+
return !gone(JSON.parse(line));
|
|
1911
|
+
} catch {
|
|
1912
|
+
return false;
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
if (kept.length === lines.length) return;
|
|
1916
|
+
writeFileSync(this.journalPath + ".tmp", kept.length ? kept.join("\n") + "\n" : "", { mode: 384 });
|
|
1917
|
+
renameSync(this.journalPath + ".tmp", this.journalPath);
|
|
1918
|
+
this.journalLines = kept.length;
|
|
1919
|
+
this.journalBytes = kept.reduce((bytes, line) => bytes + Buffer.byteLength(line) + 1, 0);
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
process.stderr.write(`ghostly: event journal error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1831
1924
|
handle(message) {
|
|
1832
1925
|
switch (message.kind) {
|
|
1833
1926
|
case "state":
|
|
@@ -1903,11 +1996,12 @@ var EventHub = class {
|
|
|
1903
1996
|
...typingFields(shape)
|
|
1904
1997
|
});
|
|
1905
1998
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1999
|
+
const removedChats = [...this.chats.keys()].filter((id) => !links.has(id));
|
|
2000
|
+
for (const id of removedChats) {
|
|
1908
2001
|
this.chats.delete(id);
|
|
1909
2002
|
if (!quiet) this.emit("chat.removed", `chat.removed:${id}`, { chat: id });
|
|
1910
2003
|
}
|
|
2004
|
+
this.forget(removedChats, (event) => removedChats.includes(event.chat) && event.type !== "chat.removed");
|
|
1911
2005
|
const groups = new Map(state.groups.map((group) => [group.id, group]));
|
|
1912
2006
|
for (const [id, group] of groups) {
|
|
1913
2007
|
const shape = groupShape(group), before = this.groups.get(id);
|
|
@@ -1946,11 +2040,12 @@ var EventHub = class {
|
|
|
1946
2040
|
member
|
|
1947
2041
|
});
|
|
1948
2042
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
2043
|
+
const removedGroups = [...this.groups.keys()].filter((id) => !groups.has(id));
|
|
2044
|
+
for (const id of removedGroups) {
|
|
1951
2045
|
this.groups.delete(id);
|
|
1952
2046
|
if (!quiet) this.emit("group.removed", `group.removed:${id}`, { group: id });
|
|
1953
2047
|
}
|
|
2048
|
+
this.forget(removedGroups, (event) => removedGroups.includes(event.group) && event.type !== "group.removed");
|
|
1954
2049
|
for (const [id, payment] of Object.entries(state.payments ?? {})) {
|
|
1955
2050
|
const shape = `${payment.state}|${payment.error ?? ""}|${payment.closed ? 1 : 0}`, before = this.payments.get(id);
|
|
1956
2051
|
this.payments.set(id, shape);
|
|
@@ -2205,10 +2300,12 @@ var EventHub = class {
|
|
|
2205
2300
|
});
|
|
2206
2301
|
}
|
|
2207
2302
|
}
|
|
2303
|
+
const gone = [];
|
|
2208
2304
|
for (const id of deleted ?? [...known.keys()]) {
|
|
2209
2305
|
if (present.has(id) || !known.has(id)) continue;
|
|
2210
2306
|
known.delete(id);
|
|
2211
2307
|
changes.push([id, null]);
|
|
2308
|
+
gone.push(id);
|
|
2212
2309
|
if (!quiet) this.emit(group ? "group.deleted" : "message.deleted", `${group ? "group" : "message"}.deleted:${group ?? chat}:${id}`, group ? {
|
|
2213
2310
|
group,
|
|
2214
2311
|
messageId: id
|
|
@@ -2218,6 +2315,8 @@ var EventHub = class {
|
|
|
2218
2315
|
});
|
|
2219
2316
|
}
|
|
2220
2317
|
if (changes.length) this.persist(chat, changes);
|
|
2318
|
+
const whole = !!group && !deleted && messages.length === 0 && gone.length > 0;
|
|
2319
|
+
this.forget(whole ? [group] : gone, (event) => (group ? event.group === group : event.chat === chat) && !event.type.endsWith(".deleted") && (whole ? "message" in event || "messageId" in event || "replyId" in event : gone.includes(event.messageId) || gone.includes(event.message?.id) || gone.includes(event.replyId)));
|
|
2221
2320
|
for (const [fileId, messageId] of files) this.flushFile(fileId, messageId);
|
|
2222
2321
|
}
|
|
2223
2322
|
/**
|
|
@@ -2411,6 +2510,45 @@ function journalTail(path) {
|
|
|
2411
2510
|
}
|
|
2412
2511
|
}
|
|
2413
2512
|
//#endregion
|
|
2513
|
+
//#region src/joinLog.ts
|
|
2514
|
+
/**
|
|
2515
|
+
* A join through a group's link, as a daemon says it in its log (stderr): the steps of the knock and of the entry
|
|
2516
|
+
* session, one short line each, from the engine's join trace (packages/browser/src/engine/joinTrace.ts). Without them a
|
|
2517
|
+
* join that stalled left nothing in the admin's log to tell a knock never seen from a session that never opened
|
|
2518
|
+
* (2026-10-16). The reads of the knocks, every few seconds, and an edge's own steps are not said.
|
|
2519
|
+
*/
|
|
2520
|
+
var SAID = {
|
|
2521
|
+
"join.start": "joining through its link",
|
|
2522
|
+
"knock.published": "knock left",
|
|
2523
|
+
"knock.seen": "a knock seen: opening an entry session",
|
|
2524
|
+
"link.start": "entry session starting",
|
|
2525
|
+
"link.presence": "entry session: the other side's packet seen",
|
|
2526
|
+
"link.offering": "entry session: offering a connection",
|
|
2527
|
+
"link.answering": "entry session: answering a connection",
|
|
2528
|
+
"link.connecting": "entry session: connecting",
|
|
2529
|
+
"link.open": "entry session open",
|
|
2530
|
+
"link.idle": "entry session not open",
|
|
2531
|
+
"link.native": "entry session: WebRTC connected nothing, going on over a native transport",
|
|
2532
|
+
"invite.sent": "invitation sent on the entry session",
|
|
2533
|
+
"invite.received": "invitation received on the entry session: accepting",
|
|
2534
|
+
"welcome.sent": "welcome sent",
|
|
2535
|
+
"welcome.received": "welcome received: in the group"
|
|
2536
|
+
};
|
|
2537
|
+
/** The log line for one join-trace line, or null when it is not said. */
|
|
2538
|
+
function joinLogLine(traced) {
|
|
2539
|
+
let step;
|
|
2540
|
+
try {
|
|
2541
|
+
step = JSON.parse(traced);
|
|
2542
|
+
} catch {
|
|
2543
|
+
return null;
|
|
2544
|
+
}
|
|
2545
|
+
if (typeof step.step !== "string" || typeof step.g !== "string" || !Object.hasOwn(SAID, step.step)) return null;
|
|
2546
|
+
const role = step.role === "host" || step.role === "guest" ? step.role : void 0;
|
|
2547
|
+
if (step.step.startsWith("link.") && !role) return null;
|
|
2548
|
+
const at = typeof step.t === "number" ? ` at ${new Date(step.t).toISOString()}` : "";
|
|
2549
|
+
return `ghostly: group ${step.g}: ${SAID[step.step]}${role ? ` (${role === "host" ? "letting in" : "joining"})` : ""}${at}`;
|
|
2550
|
+
}
|
|
2551
|
+
//#endregion
|
|
2414
2552
|
//#region src/runtime/fedimint.ts
|
|
2415
2553
|
/**
|
|
2416
2554
|
* The Fedimint client on Node (WISP 1100 § Wallet SDKs on Node): the app's own SDK and WebAssembly, each database in
|
|
@@ -4412,10 +4550,12 @@ async function startRuntime(paths, options = {}) {
|
|
|
4412
4550
|
const pinned = pkarrRelays();
|
|
4413
4551
|
const pinnedIroh = irohRelays();
|
|
4414
4552
|
const trace = process.env.GHOSTLY_LINK_TRACE;
|
|
4415
|
-
if (trace) {
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4553
|
+
if (trace) setLinkTraceSink((line) => appendFileSync(trace, line + "\n", { mode: 384 }));
|
|
4554
|
+
if (trace || options.logJoins) (await import("./joinTrace-D-fnt2p3.js")).setJoinTraceSink((line) => {
|
|
4555
|
+
if (trace) appendFileSync(trace, line + "\n", { mode: 384 });
|
|
4556
|
+
const said = options.logJoins ? joinLogLine(line) : null;
|
|
4557
|
+
if (said) process.stderr.write(said + "\n");
|
|
4558
|
+
});
|
|
4419
4559
|
const store = await openPersistentIndexedDb(paths.db);
|
|
4420
4560
|
const restored = join(paths.dir, RESTORED_MARK);
|
|
4421
4561
|
if (existsSync(restored)) {
|
|
@@ -4438,17 +4578,17 @@ async function startRuntime(paths, options = {}) {
|
|
|
4438
4578
|
if (testMints) (await import("./mints-CqTdA4Gk.js")).declareTestMints(testMints.split(","));
|
|
4439
4579
|
await installEventSource();
|
|
4440
4580
|
await preloadWalletModules();
|
|
4441
|
-
(await import("./ghostly_iroh_web-
|
|
4581
|
+
(await import("./ghostly_iroh_web-BsAEFIjl.js")).initSync({ module: irohWasmBytes() });
|
|
4442
4582
|
const [{ registerFileBytes }, { NodeFileBytes }] = await Promise.all([import("./fileBytes-CgbibpbV.js"), import("./fileBytes-CfSXjTFa.js")]);
|
|
4443
4583
|
registerFileBytes("native", async () => new NodeFileBytes(paths.files), true);
|
|
4444
|
-
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./server-
|
|
4584
|
+
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./server-Bx3RGkBC.js"), import("./endpoint-BOTFMpfS.js")]);
|
|
4445
4585
|
const network = hyperdhtNetwork();
|
|
4446
4586
|
const dht = mainlineNetwork();
|
|
4447
|
-
const mainline = dht.off ? null : new (await (import("./mainline-
|
|
4587
|
+
const mainline = dht.off ? null : new (await (import("./mainline-CLTo0nGa.js"))).Mainline({
|
|
4448
4588
|
bootstrap: dht.bootstrap,
|
|
4449
4589
|
host: dht.host
|
|
4450
4590
|
});
|
|
4451
|
-
const transport = mainline || pinned ? new (await (import("./mainline-
|
|
4591
|
+
const transport = mainline || pinned ? new (await (import("./mainline-CLTo0nGa.js"))).RelaysAndDht(mainline, pinned ? { relays: pinned } : {}, !!pinned) : null;
|
|
4452
4592
|
const apps = servedApps(paths.dir).length > 0;
|
|
4453
4593
|
const server = new EngineServer({
|
|
4454
4594
|
...transport ? { transport } : {},
|
|
@@ -4496,6 +4636,13 @@ async function startRuntime(paths, options = {}) {
|
|
|
4496
4636
|
/** The longest request line the daemon reads (WISP 1100 § Framing). */
|
|
4497
4637
|
var MAX_LINE = 16777216;
|
|
4498
4638
|
/**
|
|
4639
|
+
* The most a connection may leave unread (answers and events the daemon wrote that its client has not taken): past
|
|
4640
|
+
* it, the connection ends. A `listen` stopped with Ctrl+Z, or a client that stops reading, kept every later event in
|
|
4641
|
+
* the daemon's memory until it read again (155 MB after 6,000 long texts); `listen` reconnects after the last seq it
|
|
4642
|
+
* took, and the journal still has the rest. A replay is the journal's size at most and does not count against it.
|
|
4643
|
+
*/
|
|
4644
|
+
var MAX_UNREAD = 8388608;
|
|
4645
|
+
/**
|
|
4499
4646
|
* How many of a chat's newest messages the start reads to see whether the events are in step with its history: the
|
|
4500
4647
|
* whole history of every chat and group, read at every start, took a daemon's start and its memory with every message
|
|
4501
4648
|
* the profile ever stored (1 s and 70 MB at 100,000).
|
|
@@ -4510,7 +4657,10 @@ async function openHost(paths, mode, version, options = {}) {
|
|
|
4510
4657
|
const release = acquireLock(paths);
|
|
4511
4658
|
let runtime;
|
|
4512
4659
|
try {
|
|
4513
|
-
runtime = await startRuntime(paths,
|
|
4660
|
+
runtime = await startRuntime(paths, mode === "daemon" ? {
|
|
4661
|
+
...options,
|
|
4662
|
+
logJoins: true
|
|
4663
|
+
} : options);
|
|
4514
4664
|
} catch (error) {
|
|
4515
4665
|
release();
|
|
4516
4666
|
throw error;
|
|
@@ -4654,12 +4804,23 @@ function ownerOnlyUmask() {
|
|
|
4654
4804
|
}
|
|
4655
4805
|
function connection(ctx, socket) {
|
|
4656
4806
|
const unsubscribes = [];
|
|
4657
|
-
|
|
4658
|
-
|
|
4807
|
+
let limit = MAX_UNREAD;
|
|
4808
|
+
const unsubscribe = () => {
|
|
4809
|
+
for (const off of unsubscribes.splice(0)) off();
|
|
4810
|
+
};
|
|
4811
|
+
const write = (value, replayed = false) => {
|
|
4812
|
+
if (socket.destroyed || socket.writableEnded) return;
|
|
4813
|
+
if (!replayed && socket.writableLength > limit) {
|
|
4814
|
+
unsubscribe();
|
|
4815
|
+
socket.end();
|
|
4816
|
+
return;
|
|
4817
|
+
}
|
|
4818
|
+
socket.write(JSON.stringify(value) + "\n");
|
|
4659
4819
|
};
|
|
4660
|
-
socket.on("
|
|
4661
|
-
|
|
4820
|
+
socket.on("drain", () => {
|
|
4821
|
+
limit = MAX_UNREAD;
|
|
4662
4822
|
});
|
|
4823
|
+
socket.on("close", unsubscribe);
|
|
4663
4824
|
socket.on("error", () => {});
|
|
4664
4825
|
const lines = createInterface({
|
|
4665
4826
|
input: socket,
|
|
@@ -4692,6 +4853,13 @@ function connection(ctx, socket) {
|
|
|
4692
4853
|
return;
|
|
4693
4854
|
}
|
|
4694
4855
|
if (request.method === "events.subscribe") {
|
|
4856
|
+
if (unsubscribes.length) {
|
|
4857
|
+
write({
|
|
4858
|
+
id,
|
|
4859
|
+
error: new CliError("bad_request", "This connection is subscribed already: open another one for a new stream").toJSON()
|
|
4860
|
+
});
|
|
4861
|
+
return;
|
|
4862
|
+
}
|
|
4695
4863
|
const params = request.params ?? {};
|
|
4696
4864
|
const since = typeof params.since === "number" ? params.since : ctx.hub.lastSeq;
|
|
4697
4865
|
let replay;
|
|
@@ -4719,9 +4887,10 @@ function connection(ctx, socket) {
|
|
|
4719
4887
|
});
|
|
4720
4888
|
let last = since;
|
|
4721
4889
|
for (const event of replay) {
|
|
4722
|
-
write({ event });
|
|
4890
|
+
write({ event }, true);
|
|
4723
4891
|
last = event.seq;
|
|
4724
4892
|
}
|
|
4893
|
+
limit = Math.max(limit, socket.writableLength + MAX_UNREAD);
|
|
4725
4894
|
replaying = false;
|
|
4726
4895
|
for (const event of queued) if (event.seq > last) write({ event });
|
|
4727
4896
|
return;
|
|
@@ -4736,6 +4905,6 @@ function connection(ctx, socket) {
|
|
|
4736
4905
|
});
|
|
4737
4906
|
}
|
|
4738
4907
|
//#endregion
|
|
4739
|
-
export { BASELINE_NEWEST, MAX_LINE, keepServing, openHost, serve };
|
|
4908
|
+
export { BASELINE_NEWEST, MAX_LINE, MAX_UNREAD, keepServing, openHost, serve };
|
|
4740
4909
|
|
|
4741
|
-
//# sourceMappingURL=host-
|
|
4910
|
+
//# sourceMappingURL=host-Dj3C1fn3.js.map
|