@ghostlytools/cli 1.0.1 → 1.0.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.
- package/README.md +8 -5
- package/dist/assets/{hyperdhtRelay-_rJsllcP.js → hyperdhtRelay-cMlRWrmD.js} +3 -3
- package/dist/assets/{hyperdhtRelay-_rJsllcP.js.map → hyperdhtRelay-cMlRWrmD.js.map} +1 -1
- package/dist/assets/{mainline-B0mUxYPH.js → mainline-_roUnpDA.js} +2 -2
- package/dist/assets/{mainline-B0mUxYPH.js.map → mainline-_roUnpDA.js.map} +1 -1
- package/dist/assets/{server-C-8wbsAX.js → server-Cajp8VLY.js} +626 -167
- package/dist/assets/server-Cajp8VLY.js.map +1 -0
- package/dist/ghostly.mjs +436 -45
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/assets/server-C-8wbsAX.js.map +0 -1
package/dist/ghostly.mjs
CHANGED
|
@@ -50,7 +50,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
50
50
|
};
|
|
51
51
|
var package_default = {
|
|
52
52
|
name: "@ghostlytools/cli",
|
|
53
|
-
version: "1.0.
|
|
53
|
+
version: "1.0.2",
|
|
54
54
|
type: "module",
|
|
55
55
|
description: "Ghostly for bots and terminals: the app's own engine, headless on Node, behind a daemon and a `ghostly` command with JSON output.",
|
|
56
56
|
license: "MIT",
|
|
@@ -94,7 +94,7 @@ var package_default = {
|
|
|
94
94
|
"@scure/bip39": "2.4.0",
|
|
95
95
|
"@scure/btc-signer": "2.4.1",
|
|
96
96
|
"@secondts/bark": "0.25.0",
|
|
97
|
-
"@synonymdev/pubky": "0.
|
|
97
|
+
"@synonymdev/pubky": "0.14.0",
|
|
98
98
|
"@tetherto/wdk-wallet-evm": "1.0.0-beta.19",
|
|
99
99
|
"b4a": "1.9.0",
|
|
100
100
|
"bittorrent-dht": "11.0.12",
|
|
@@ -4032,7 +4032,87 @@ var LinkSession = class {
|
|
|
4032
4032
|
}
|
|
4033
4033
|
};
|
|
4034
4034
|
//#endregion
|
|
4035
|
-
//#region ../core/src/
|
|
4035
|
+
//#region ../core/src/directPath.ts
|
|
4036
|
+
/** Evidence older than this says nothing about the network now. */
|
|
4037
|
+
var DIRECT_EVIDENCE_MS = 18e5;
|
|
4038
|
+
/** Server reflexive candidates on one address with this many different ports: a mapping per destination. */
|
|
4039
|
+
var SYMMETRIC_PORTS = 3;
|
|
4040
|
+
/**
|
|
4041
|
+
* Whether a local description shows a NAT that maps each destination apart: the app asks several STUN servers from one
|
|
4042
|
+
* socket, and a NAT that keeps one mapping answers with one address and port (the browser lists it once). Three ports
|
|
4043
|
+
* on one address, not two: a device on two interfaces behind one router (Wi-Fi and a cable) has two.
|
|
4044
|
+
*/
|
|
4045
|
+
function symmetricNat(sdp) {
|
|
4046
|
+
const ports = /* @__PURE__ */ new Map();
|
|
4047
|
+
for (const [, address, port] of (sdp ?? "").matchAll(/^a=candidate:\S+ \d+ udp \d+ (\S+) (\d+) typ srflx\b/gim)) {
|
|
4048
|
+
const seen = ports.get(address) ?? /* @__PURE__ */ new Set();
|
|
4049
|
+
ports.set(address, seen.add(port));
|
|
4050
|
+
}
|
|
4051
|
+
return [...ports.values()].some((seen) => seen.size >= SYMMETRIC_PORTS);
|
|
4052
|
+
}
|
|
4053
|
+
/** The rule alone: whether `failures` (every one after the last `open`) say direct connections are blocked at `now`. */
|
|
4054
|
+
function directBlocked(failures, now) {
|
|
4055
|
+
const fresh = failures.filter((f) => now - f.at < DIRECT_EVIDENCE_MS);
|
|
4056
|
+
if (fresh.filter((f) => f.kind !== "no-path").length >= 2) return true;
|
|
4057
|
+
return new Set(fresh.map((f) => f.peer)).size >= 3;
|
|
4058
|
+
}
|
|
4059
|
+
var DirectPathWatch = class {
|
|
4060
|
+
onChange;
|
|
4061
|
+
now;
|
|
4062
|
+
failures = [];
|
|
4063
|
+
/** Contacts a WebRTC connection is open with now. */
|
|
4064
|
+
open = /* @__PURE__ */ new Set();
|
|
4065
|
+
was = false;
|
|
4066
|
+
timer = null;
|
|
4067
|
+
/** `onChange`: `blocked` changed (evidence came, a connection opened, the network changed, the evidence aged out). */
|
|
4068
|
+
constructor(onChange = () => {}, now = Date.now) {
|
|
4069
|
+
this.onChange = onChange;
|
|
4070
|
+
this.now = now;
|
|
4071
|
+
}
|
|
4072
|
+
/** One WebRTC attempt with `peer` ended this way (or its open connection closed). */
|
|
4073
|
+
note(peer, evidence) {
|
|
4074
|
+
if (evidence === "open") {
|
|
4075
|
+
this.open.add(peer);
|
|
4076
|
+
this.failures = [];
|
|
4077
|
+
} else if (evidence === "closed") this.open.delete(peer);
|
|
4078
|
+
else this.failures = [...this.failures, {
|
|
4079
|
+
peer,
|
|
4080
|
+
kind: evidence,
|
|
4081
|
+
at: this.now()
|
|
4082
|
+
}].slice(-64);
|
|
4083
|
+
this.settle();
|
|
4084
|
+
}
|
|
4085
|
+
/** The network changed: what failed on the old one says nothing about this one. */
|
|
4086
|
+
reset() {
|
|
4087
|
+
this.failures = [];
|
|
4088
|
+
this.settle();
|
|
4089
|
+
}
|
|
4090
|
+
get blocked() {
|
|
4091
|
+
return !this.open.size && directBlocked(this.failures, this.now());
|
|
4092
|
+
}
|
|
4093
|
+
/** Stops the timer that lets the evidence age out. */
|
|
4094
|
+
close() {
|
|
4095
|
+
if (this.timer) clearTimeout(this.timer);
|
|
4096
|
+
this.timer = null;
|
|
4097
|
+
}
|
|
4098
|
+
settle() {
|
|
4099
|
+
this.close();
|
|
4100
|
+
const blocked = this.blocked;
|
|
4101
|
+
if (blocked) {
|
|
4102
|
+
const oldest = Math.min(...this.failures.map((f) => f.at));
|
|
4103
|
+
this.timer = setTimeout(() => {
|
|
4104
|
+
this.failures = this.failures.filter((f) => this.now() - f.at < DIRECT_EVIDENCE_MS);
|
|
4105
|
+
this.settle();
|
|
4106
|
+
}, Math.max(1e3, oldest + DIRECT_EVIDENCE_MS - this.now()));
|
|
4107
|
+
this.timer.unref?.();
|
|
4108
|
+
}
|
|
4109
|
+
if (blocked === this.was) return;
|
|
4110
|
+
this.was = blocked;
|
|
4111
|
+
this.onChange(blocked);
|
|
4112
|
+
}
|
|
4113
|
+
};
|
|
4114
|
+
/** A candidate another network can reach: server reflexive (STUN answered), relayed (TURN), or learnt from the peer. */
|
|
4115
|
+
var PUBLIC_CANDIDATE = /^a=candidate:.* typ (srflx|relay|prflx)\b/m;
|
|
4036
4116
|
var CONNECT_TIMEOUT_MS = 9e4;
|
|
4037
4117
|
var ICE_GATHERING_TIMEOUT_MS = 5e3;
|
|
4038
4118
|
/**
|
|
@@ -4052,6 +4132,8 @@ var DataLink = class {
|
|
|
4052
4132
|
lastSignalTs = 0;
|
|
4053
4133
|
/** The peer's description as this connection was given it: Chrome shows it as `remoteDescription` only once applied. */
|
|
4054
4134
|
remoteSdp = null;
|
|
4135
|
+
/** When the connection had both descriptions (`connecting`). */
|
|
4136
|
+
connectingSince = 0;
|
|
4055
4137
|
connectTimer = null;
|
|
4056
4138
|
disconnectTimer = null;
|
|
4057
4139
|
/** The offer this side answered in the current attempt, and how many times it answered it again. */
|
|
@@ -4093,7 +4175,10 @@ var DataLink = class {
|
|
|
4093
4175
|
this.lastSignalTs = signal.ts;
|
|
4094
4176
|
const wasOpen = this.state === "open";
|
|
4095
4177
|
this.teardown();
|
|
4096
|
-
if (wasOpen)
|
|
4178
|
+
if (wasOpen) {
|
|
4179
|
+
this.options.onDirect?.("closed");
|
|
4180
|
+
this.options.onClose();
|
|
4181
|
+
}
|
|
4097
4182
|
this.answered = {
|
|
4098
4183
|
offer: signal,
|
|
4099
4184
|
again: 0
|
|
@@ -4134,8 +4219,20 @@ var DataLink = class {
|
|
|
4134
4219
|
}
|
|
4135
4220
|
}
|
|
4136
4221
|
close() {
|
|
4222
|
+
const evidence = Date.now() - this.connectingSince >= 5e3 ? this.directEvidence() : null;
|
|
4137
4223
|
this.answered = null;
|
|
4138
4224
|
this.reset();
|
|
4225
|
+
if (evidence) this.options.onDirect?.(evidence);
|
|
4226
|
+
}
|
|
4227
|
+
/**
|
|
4228
|
+
* What this attempt says about direct connections if it ends now without opening: nothing unless both descriptions
|
|
4229
|
+
* were exchanged (this side's offer answered, or its answer out). Then, what this device gathered: no public
|
|
4230
|
+
* candidate, a public port per STUN server, or (for the side that offered) candidates that read as reachable.
|
|
4231
|
+
*/
|
|
4232
|
+
directEvidence() {
|
|
4233
|
+
if (this.state !== "connecting" || !this.pc) return null;
|
|
4234
|
+
const local = this.pc.localDescription?.sdp ?? "";
|
|
4235
|
+
return !PUBLIC_CANDIDATE.test(local) ? "no-public" : symmetricNat(local) ? "symmetric" : this.myOfferTs ? "no-path" : null;
|
|
4139
4236
|
}
|
|
4140
4237
|
/**
|
|
4141
4238
|
* The attempt on this connection failed (it timed out, ICE failed or closed, its channel closed). An answer the
|
|
@@ -4145,6 +4242,7 @@ var DataLink = class {
|
|
|
4145
4242
|
failed() {
|
|
4146
4243
|
const answered = this.state === "connecting" ? this.answered : null;
|
|
4147
4244
|
const left = answered ? CONNECT_TIMEOUT_MS - (Date.now() - answered.offer.ts) : 0;
|
|
4245
|
+
const evidence = this.directEvidence();
|
|
4148
4246
|
if (answered && answered.again < 2 && left > REANSWER_MARGIN_MS && this.options.offerStanding?.(answered.offer.ts)) {
|
|
4149
4247
|
answered.again++;
|
|
4150
4248
|
traceLink(this.options.myPubKeyZ32, "reanswer", {
|
|
@@ -4156,6 +4254,7 @@ var DataLink = class {
|
|
|
4156
4254
|
return;
|
|
4157
4255
|
}
|
|
4158
4256
|
this.reset();
|
|
4257
|
+
if (evidence) this.options.onDirect?.(evidence);
|
|
4159
4258
|
}
|
|
4160
4259
|
async answer(offer) {
|
|
4161
4260
|
this.setState("answering");
|
|
@@ -4213,6 +4312,7 @@ var DataLink = class {
|
|
|
4213
4312
|
});
|
|
4214
4313
|
if (attempt >= 3) {
|
|
4215
4314
|
this.reset();
|
|
4315
|
+
this.options.onDirect?.("no-public");
|
|
4216
4316
|
return null;
|
|
4217
4317
|
}
|
|
4218
4318
|
stalled = pc;
|
|
@@ -4233,6 +4333,7 @@ var DataLink = class {
|
|
|
4233
4333
|
this.setState("open");
|
|
4234
4334
|
this.options.setFastPoll(false);
|
|
4235
4335
|
this.options.publishSignal(null);
|
|
4336
|
+
this.options.onDirect?.("open");
|
|
4236
4337
|
this.options.onOpen(wrapDataChannel(dc));
|
|
4237
4338
|
});
|
|
4238
4339
|
dc.addEventListener("close", () => {
|
|
@@ -4295,11 +4396,15 @@ var DataLink = class {
|
|
|
4295
4396
|
this.setState("idle");
|
|
4296
4397
|
this.options.setFastPoll(false);
|
|
4297
4398
|
this.options.publishSignal(null);
|
|
4298
|
-
if (wasOpen)
|
|
4399
|
+
if (wasOpen) {
|
|
4400
|
+
this.options.onDirect?.("closed");
|
|
4401
|
+
this.options.onClose();
|
|
4402
|
+
}
|
|
4299
4403
|
}
|
|
4300
4404
|
setState(state) {
|
|
4301
4405
|
if (this.state === state) return;
|
|
4302
4406
|
this.state = state;
|
|
4407
|
+
if (state === "connecting") this.connectingSince = Date.now();
|
|
4303
4408
|
this.options.onState?.(state);
|
|
4304
4409
|
}
|
|
4305
4410
|
};
|
|
@@ -4575,19 +4680,43 @@ function verifyChain(raw, groupId) {
|
|
|
4575
4680
|
//#endregion
|
|
4576
4681
|
//#region ../core/src/messageTime.ts
|
|
4577
4682
|
/**
|
|
4578
|
-
* How far ahead of this device's clock a
|
|
4579
|
-
* is the peer's choice, not its clock.
|
|
4683
|
+
* How far ahead of this device's clock a number a peer made from its clock may be (a pin's number, a status card's
|
|
4684
|
+
* times): clocks drift, but a time past this is the peer's choice, not its clock.
|
|
4580
4685
|
*/
|
|
4581
4686
|
var MESSAGE_CLOCK_SKEW_MS = 3e5;
|
|
4582
4687
|
/**
|
|
4583
|
-
* The time a
|
|
4584
|
-
*
|
|
4585
|
-
*
|
|
4688
|
+
* The time a line of a group's history is kept under (a membership change, a rename): what its commit says, but at
|
|
4689
|
+
* most `MESSAGE_CLOCK_SKEW_MS` past this clock, and now when it is not a positive number. Messages do not use it:
|
|
4690
|
+
* they are placed where they arrive (below).
|
|
4586
4691
|
*/
|
|
4587
4692
|
function receivedTimestamp(ts, now = Date.now()) {
|
|
4588
4693
|
if (!Number.isFinite(ts) || ts <= 0) return now;
|
|
4589
4694
|
return Math.min(ts, now + MESSAGE_CLOCK_SKEW_MS);
|
|
4590
4695
|
}
|
|
4696
|
+
/** The latest moment a date can hold. */
|
|
4697
|
+
var MAX_DATE_MS = 864e13;
|
|
4698
|
+
/** The time a sender says, as it is kept: a positive time a date can hold, or nothing. */
|
|
4699
|
+
function claimedTime(ts) {
|
|
4700
|
+
return typeof ts === "number" && Number.isFinite(ts) && ts > 0 && ts <= MAX_DATE_MS ? ts : void 0;
|
|
4701
|
+
}
|
|
4702
|
+
/**
|
|
4703
|
+
* The place of a row stored now: this clock, and past the last place given in its chat, so rows that come in the
|
|
4704
|
+
* same millisecond (a hold pickup, a group's catch-up) keep the order they came in.
|
|
4705
|
+
*/
|
|
4706
|
+
function arrivalKey(now, last = 0) {
|
|
4707
|
+
return Math.max(now, last + 1);
|
|
4708
|
+
}
|
|
4709
|
+
/** The time a message shows: what its sender said, never later than when it came. A row of mine shows its own time. */
|
|
4710
|
+
function shownTime(message) {
|
|
4711
|
+
return message.sentAt === void 0 ? message.timestamp : Math.min(message.sentAt, message.timestamp);
|
|
4712
|
+
}
|
|
4713
|
+
/**
|
|
4714
|
+
* When something a peer dated happened (an edit), for this device: what the peer says, now at the latest, and now
|
|
4715
|
+
* when it says nothing a date can hold.
|
|
4716
|
+
*/
|
|
4717
|
+
function heardTime(ts, now = Date.now()) {
|
|
4718
|
+
return Math.min(claimedTime(ts) ?? now, now);
|
|
4719
|
+
}
|
|
4591
4720
|
//#endregion
|
|
4592
4721
|
//#region ../core/src/statusCards.ts
|
|
4593
4722
|
var STATUS_CARD_LIMITS = {
|
|
@@ -6945,6 +7074,10 @@ var PairedFiles = class {
|
|
|
6945
7074
|
this.channel = channel;
|
|
6946
7075
|
this.events = events;
|
|
6947
7076
|
}
|
|
7077
|
+
/** A file going either way on this session right now. */
|
|
7078
|
+
get busy() {
|
|
7079
|
+
return this.incoming.size > 0 || this.outgoing.size > 0;
|
|
7080
|
+
}
|
|
6948
7081
|
sendFrame(frame) {
|
|
6949
7082
|
if (this.closed) throw new Error("Connection lost");
|
|
6950
7083
|
this.channel.send(JSON.stringify(frame));
|
|
@@ -7196,6 +7329,31 @@ var TRANSPORTS = [
|
|
|
7196
7329
|
"hyperdht/1",
|
|
7197
7330
|
"webrtc/1"
|
|
7198
7331
|
];
|
|
7332
|
+
function irohRelayUrl(value, spelling = "dotted") {
|
|
7333
|
+
let url;
|
|
7334
|
+
try {
|
|
7335
|
+
url = new URL(value);
|
|
7336
|
+
} catch {
|
|
7337
|
+
return value;
|
|
7338
|
+
}
|
|
7339
|
+
const host = url.hostname, name = host.replace(/\.$/, "");
|
|
7340
|
+
if (!name.includes(".") && name !== "localhost") return value;
|
|
7341
|
+
if (name.startsWith("[") || /^\d+(\.\d+){3}$/.test(name)) return value;
|
|
7342
|
+
const wanted = spelling === "plain" || name === "localhost" ? name : `${name}.`;
|
|
7343
|
+
if (wanted === host) return value;
|
|
7344
|
+
url.hostname = wanted;
|
|
7345
|
+
return url.href;
|
|
7346
|
+
}
|
|
7347
|
+
/** An Iroh descriptor with its relay in one spelling (`irohRelayUrl`); anything else as it is. */
|
|
7348
|
+
function irohDescriptor(descriptor, spelling = "dotted") {
|
|
7349
|
+
const relay = descriptor?.relay;
|
|
7350
|
+
if (typeof relay !== "string") return descriptor;
|
|
7351
|
+
const canonical = irohRelayUrl(relay, spelling);
|
|
7352
|
+
return canonical === relay ? descriptor : {
|
|
7353
|
+
...descriptor,
|
|
7354
|
+
relay: canonical
|
|
7355
|
+
};
|
|
7356
|
+
}
|
|
7199
7357
|
/**
|
|
7200
7358
|
* Relay only (WISP 100, "Relayed"): the transport reaches this contact only through a relay server, never
|
|
7201
7359
|
* directly. A descriptor says so with `relayed: true`, and an Iroh descriptor with no direct address is relayed
|
|
@@ -10044,7 +10202,7 @@ function vapidKeysMatch(keys) {
|
|
|
10044
10202
|
}
|
|
10045
10203
|
/**
|
|
10046
10204
|
* The push services a browser subscribes with: Google (Chrome, Edge on Android, Brave, Opera), Apple (Safari), Mozilla
|
|
10047
|
-
* (Firefox), Microsoft (Edge on Windows). The same list as the push relay's (services/push-relay/relay.mjs).
|
|
10205
|
+
* (Firefox), Microsoft (Edge on Windows). The same list as the push relay's (infra/services/push-relay/relay.mjs).
|
|
10048
10206
|
*/
|
|
10049
10207
|
var PUSH_SERVICE_HOSTS = [
|
|
10050
10208
|
/^fcm\.googleapis\.com$/,
|
|
@@ -11633,6 +11791,13 @@ function parsePairedCallFrame(frame, now = Date.now()) {
|
|
|
11633
11791
|
if (typeof signal !== "string" || signal.length > 8192) return null;
|
|
11634
11792
|
return parseCallSignal(signal, now) ? signal : null;
|
|
11635
11793
|
}
|
|
11794
|
+
function signalKind(signal) {
|
|
11795
|
+
try {
|
|
11796
|
+
return JSON.parse(signal).t;
|
|
11797
|
+
} catch {
|
|
11798
|
+
return;
|
|
11799
|
+
}
|
|
11800
|
+
}
|
|
11636
11801
|
/**
|
|
11637
11802
|
* This side's latest call signal. What could not go (the session dropped between two frames) goes on the
|
|
11638
11803
|
* next session while it is still fresh, as the `_call` record of a compatibility chat is read on the next
|
|
@@ -11641,11 +11806,16 @@ function parsePairedCallFrame(frame, now = Date.now()) {
|
|
|
11641
11806
|
var PairedCalls = class {
|
|
11642
11807
|
now;
|
|
11643
11808
|
latest = null;
|
|
11809
|
+
/** When this side last said anything about a call, a clear included. */
|
|
11810
|
+
saidAt = 0;
|
|
11811
|
+
/** The contact's latest signal on a session: its kind, and when it came. */
|
|
11812
|
+
peer = null;
|
|
11644
11813
|
constructor(now = Date.now) {
|
|
11645
11814
|
this.now = now;
|
|
11646
11815
|
}
|
|
11647
11816
|
/** A new signal to send (null clears it). Returns the frame to send now, if any. */
|
|
11648
11817
|
set(signal) {
|
|
11818
|
+
this.saidAt = this.now();
|
|
11649
11819
|
if (signal === null) {
|
|
11650
11820
|
this.latest = null;
|
|
11651
11821
|
return null;
|
|
@@ -11653,13 +11823,34 @@ var PairedCalls = class {
|
|
|
11653
11823
|
if (signal.length > 8192) throw new Error("Call signal too large");
|
|
11654
11824
|
this.latest = {
|
|
11655
11825
|
signal,
|
|
11656
|
-
at: this.
|
|
11826
|
+
at: this.saidAt
|
|
11657
11827
|
};
|
|
11658
11828
|
return {
|
|
11659
11829
|
t: PAIRED_CALL_FRAME,
|
|
11660
11830
|
s: signal
|
|
11661
11831
|
};
|
|
11662
11832
|
}
|
|
11833
|
+
/** A signal the contact sent (one `parsePairedCallFrame` took). */
|
|
11834
|
+
heard(signal) {
|
|
11835
|
+
this.peer = {
|
|
11836
|
+
kind: signalKind(signal),
|
|
11837
|
+
at: this.now()
|
|
11838
|
+
};
|
|
11839
|
+
}
|
|
11840
|
+
/**
|
|
11841
|
+
* Whether a call is on, as the latest signal of either side says: an answer or a picture change, or an offer still
|
|
11842
|
+
* fresh enough to ring. A hang-up, a clear, or an offer nobody answered in time ends it. Read by kind, as `hangUp` is:
|
|
11843
|
+
* the media runs on a connection of its own, so an hour-long call shows nothing else on the session.
|
|
11844
|
+
*/
|
|
11845
|
+
get on() {
|
|
11846
|
+
const own = {
|
|
11847
|
+
kind: this.latest ? signalKind(this.latest.signal) : "h",
|
|
11848
|
+
at: this.saidAt
|
|
11849
|
+
};
|
|
11850
|
+
const last = this.peer && this.peer.at > own.at ? this.peer : own;
|
|
11851
|
+
if (last.kind === "a" || last.kind === "v") return true;
|
|
11852
|
+
return last.kind === "o" && this.now() - last.at <= 12e4;
|
|
11853
|
+
}
|
|
11663
11854
|
/**
|
|
11664
11855
|
* This app is going away: a call it placed or answered is hung up, as its person would (its latest signal an
|
|
11665
11856
|
* offer, an answer, or a picture change, which only a call on sends). Returns the hang-up to send before the
|
|
@@ -11799,14 +11990,16 @@ function unpack(bytes, withLoad, max) {
|
|
|
11799
11990
|
/**
|
|
11800
11991
|
* The hubs that take turns at the door (WISP 9xx § Admission): those that said so lately and have
|
|
11801
11992
|
* been hubs for a minute, so that every hub, having read the beacon at least once since, agrees on
|
|
11802
|
-
* the same set. Only when there is none,
|
|
11993
|
+
* the same set. Only when there is none (a group's first minute), the one that has been a hub longest. And when none said so lately, a hub asking (`me`)
|
|
11803
11994
|
* that sees no other fresh hub in the beacon is the door alone, listed or not: its own entry may not have reached the
|
|
11804
11995
|
* relays yet (a new group's first hub, whose write the relays' budget holds back), and there is nobody to agree with.
|
|
11805
11996
|
*/
|
|
11806
11997
|
function doorHubs(hubs, now = Date.now(), me) {
|
|
11807
11998
|
const lately = hubs.filter((h) => now - h.ts < COMMUNITY_TOPOLOGY.beaconEveryMs * 1.5);
|
|
11808
11999
|
const settled = lately.filter((h) => now - (h.since ?? h.ts) >= 6e4);
|
|
11809
|
-
const
|
|
12000
|
+
const since = (h) => Math.floor((h.since ?? h.ts) / 1e3);
|
|
12001
|
+
const first = [...lately].sort((a, b) => since(a) - since(b) || (a.key < b.key ? -1 : 1)).slice(0, 1);
|
|
12002
|
+
const door = (settled.length ? settled : first).map((h) => h.key);
|
|
11810
12003
|
if (door.length || me === void 0 || freshHubs(hubs, now).some((h) => h.key !== me)) return door;
|
|
11811
12004
|
return [me];
|
|
11812
12005
|
}
|
|
@@ -12222,6 +12415,20 @@ var RACE_DIRECT_MS = 8e3;
|
|
|
12222
12415
|
* over a relay stays there (no probing while live, WISP 100), where the answer would have made it direct.
|
|
12223
12416
|
*/
|
|
12224
12417
|
var RACE_RELAYED_MS = 4e4;
|
|
12418
|
+
/**
|
|
12419
|
+
* A WebRTC offer this side made that was answered, and has not connected this long after the answer was applied: where
|
|
12420
|
+
* a path exists ICE finds it in a second or two, so there is none (a VPN's NAT, a firewall that drops UDP, and no TURN
|
|
12421
|
+
* server), and every transport ranked after WebRTC is dialled now, the relayed ones too, the attempt still standing.
|
|
12422
|
+
* Without it the chat waited for ICE to give up (about 15 s in Chromium, the attempt's 90 s where the connection never
|
|
12423
|
+
* says `failed`), then for the next attempt.
|
|
12424
|
+
*/
|
|
12425
|
+
var RACE_ANSWERED_MS = 6e3;
|
|
12426
|
+
/**
|
|
12427
|
+
* WebRTC that could not start an attempt at all (no candidate gathered: UDP blocked, a VPN's browser extension; or no
|
|
12428
|
+
* connection could be made) is tried last for this long: each try costs its stall time (`GATHER_STALL_MS` three times
|
|
12429
|
+
* over) before the transport that works is dialled.
|
|
12430
|
+
*/
|
|
12431
|
+
var RTC_UNSTARTED_MS = 6e5;
|
|
12225
12432
|
/** A transport due in the race that cannot be dialled yet (its endpoint still starting, no address of the contact's) is looked at again this often. */
|
|
12226
12433
|
var RACE_RETRY_MS = 2e3;
|
|
12227
12434
|
var DEMOTE_MS = 36e5;
|
|
@@ -12249,6 +12456,35 @@ var LIVENESS_PING_MS = 15e3;
|
|
|
12249
12456
|
*/
|
|
12250
12457
|
var SWITCH_RETIRE_MS = 3e3;
|
|
12251
12458
|
/**
|
|
12459
|
+
* Frames that keep a session going rather than say something of the two people's: what the contact sent last outside
|
|
12460
|
+
* these is when the chat was last in use (`canYieldEndpoint`). Every other frame (a text, a receipt, typing, a
|
|
12461
|
+
* reaction, an edit, a file, a call, a payment) is use; what this side sends is answered with one of them.
|
|
12462
|
+
*/
|
|
12463
|
+
/** How long a session giving its listener up waits after its goodbye before it closes, so the goodbye gets there. */
|
|
12464
|
+
var YIELD_BYE_MS = 300;
|
|
12465
|
+
var QUIET_FRAMES = /* @__PURE__ */ new Set([
|
|
12466
|
+
"paired-ping",
|
|
12467
|
+
"paired-pong",
|
|
12468
|
+
"paired-bye",
|
|
12469
|
+
"paired-policy",
|
|
12470
|
+
"paired-switch-plan",
|
|
12471
|
+
"paired-switch-ready",
|
|
12472
|
+
"paired-switch-failed",
|
|
12473
|
+
"paired-switch-keep",
|
|
12474
|
+
"paired-rtc",
|
|
12475
|
+
"paired-adapters",
|
|
12476
|
+
"paired-nick",
|
|
12477
|
+
"paired-avatar",
|
|
12478
|
+
"paired-capabilities",
|
|
12479
|
+
"paired-wake",
|
|
12480
|
+
"paired-hold",
|
|
12481
|
+
"paired-payments",
|
|
12482
|
+
"paired-groups",
|
|
12483
|
+
"paired-services",
|
|
12484
|
+
"paired-reconnect",
|
|
12485
|
+
"paired-reconnect-ack"
|
|
12486
|
+
]);
|
|
12487
|
+
/**
|
|
12252
12488
|
* A ping with nothing at all back this long after it went: the contact may have crashed and started again, with its new
|
|
12253
12489
|
* offer waiting on the relays, while this side still holds the old session and reads them at a live chat's pace (30 s).
|
|
12254
12490
|
* They are read now. Nothing more: the session is let go only by `LIVENESS_MISSED_PINGS`, or by the contact's offer
|
|
@@ -12300,7 +12536,8 @@ function recordDescriptor(transport, known, record, newer, listed) {
|
|
|
12300
12536
|
if (!known) return record;
|
|
12301
12537
|
const k = known, r = record;
|
|
12302
12538
|
if (transport === "iroh/1" ? k.id !== r.id : k.publicKey !== r.publicKey) return newer ? record : known;
|
|
12303
|
-
|
|
12539
|
+
const same = r.relay === k.relay || transport === "iroh/1" && typeof r.relay === "string" && typeof k.relay === "string" && irohRelayUrl(r.relay) === irohRelayUrl(k.relay);
|
|
12540
|
+
if (!r.relay || same || k.relay && !newer) return known;
|
|
12304
12541
|
return transport === "iroh/1" ? {
|
|
12305
12542
|
...k,
|
|
12306
12543
|
relay: r.relay
|
|
@@ -12440,6 +12677,14 @@ var GhostLink = class {
|
|
|
12440
12677
|
racing = false;
|
|
12441
12678
|
/** The WebRTC offer this side's dial made last, with fallback allowed: what a transport that comes up meanwhile joins. */
|
|
12442
12679
|
offered;
|
|
12680
|
+
/** When that offer's answer was applied (`RACE_ANSWERED_MS`); 0 while the data link is not connecting on it. */
|
|
12681
|
+
answeredAt = 0;
|
|
12682
|
+
/** This side's offer is out unanswered, or answered and not connected yet: what is ranked after WebRTC may race it. */
|
|
12683
|
+
get offerOut() {
|
|
12684
|
+
return this.dataLink.state === "offering" || this.dataLink.state === "connecting" && this.answeredAt > 0;
|
|
12685
|
+
}
|
|
12686
|
+
/** The attempt (`connectionEpoch`) in which the data link began answering the contact's offer. */
|
|
12687
|
+
answeringEpoch = -1;
|
|
12443
12688
|
lastAutoConnectAt = 0;
|
|
12444
12689
|
autoConnectFailures = 0;
|
|
12445
12690
|
/** Native transports a record just read listed with no way to dial them (`record-undescribed`), until one describes them again. */
|
|
@@ -12459,6 +12704,10 @@ var GhostLink = class {
|
|
|
12459
12704
|
peerSeenAt = 0;
|
|
12460
12705
|
/** When the channel carrying the chat now was attached. */
|
|
12461
12706
|
channelSince = 0;
|
|
12707
|
+
/** When the contact last sent something other than the session's upkeep (`QUIET_FRAMES`) on a session. */
|
|
12708
|
+
activityAt = 0;
|
|
12709
|
+
/** Set while `yieldEndpoint` ends the session to let its listener go: nothing dials meanwhile. */
|
|
12710
|
+
yielding = false;
|
|
12462
12711
|
/**
|
|
12463
12712
|
* The contact said goodbye on the live session (`paired-bye`): its app is going away, likely to restart. Its packet
|
|
12464
12713
|
* then was `packet`; it is dialled again once a newer one shows it back, or after `until`.
|
|
@@ -12660,6 +12909,7 @@ var GhostLink = class {
|
|
|
12660
12909
|
setFastPoll: (fast, offer) => this.session.setFastPoll(fast, !!offer && !!options.pairing?.credentials.peerKey),
|
|
12661
12910
|
offerStanding: (ts) => !this.leaving && !this.stopped && !this.streamBlocked && this.peerOfferTs() === ts,
|
|
12662
12911
|
onAnswerReplaced: () => this.redial(),
|
|
12912
|
+
onDirect: (evidence) => events.onDirectEvidence?.(evidence),
|
|
12663
12913
|
onOpen: (channel) => {
|
|
12664
12914
|
if (this.streamBlocked || this.keyStopped) {
|
|
12665
12915
|
channel.close();
|
|
@@ -12677,12 +12927,21 @@ var GhostLink = class {
|
|
|
12677
12927
|
if (wasLive) this.peerLost("closed");
|
|
12678
12928
|
},
|
|
12679
12929
|
onState: (state) => {
|
|
12930
|
+
const was = this.lastDataLinkState;
|
|
12680
12931
|
traceLink(this.myPubKeyZ32, "datalink", { state });
|
|
12681
12932
|
this.trackDataLink(state);
|
|
12682
12933
|
if (this.activeBinding) return;
|
|
12683
12934
|
events.onDataLinkState?.(state);
|
|
12684
12935
|
if (state === "open") this.afterRtc = void 0;
|
|
12936
|
+
if (state === "answering") this.answeringEpoch = this.connectionEpoch;
|
|
12937
|
+
this.answeredAt = 0;
|
|
12938
|
+
if (state === "connecting" && was === "offering" && this.offered?.epoch === this.connectionEpoch) {
|
|
12939
|
+
this.answeredAt = Date.now();
|
|
12940
|
+
this.scheduleRace(this.offered.epoch, this.offered.at);
|
|
12941
|
+
}
|
|
12685
12942
|
if (state !== "idle") return;
|
|
12943
|
+
const unanswerable = was === "answering" && this.answeringEpoch === this.connectionEpoch && !this.channel && !this.stopped && !this.leaving;
|
|
12944
|
+
if (unanswerable) this.rtcUnstarted();
|
|
12686
12945
|
this.clearRace();
|
|
12687
12946
|
if (this.racing) return;
|
|
12688
12947
|
const next = this.afterRtc;
|
|
@@ -12695,6 +12954,7 @@ var GhostLink = class {
|
|
|
12695
12954
|
this.afterRtc = void 0;
|
|
12696
12955
|
if (!this.dialing) this.attemptEnded();
|
|
12697
12956
|
this.rejectWaiters(new GhostlyHttpError("unreachable", "Could not connect to the peer"));
|
|
12957
|
+
if (unanswerable && this.dialsPastRtc()) this.redial();
|
|
12698
12958
|
},
|
|
12699
12959
|
attemptTimeoutMs: () => this.tracker && !this.tracker.done ? PAIRING_ATTEMPT_MS : void 0,
|
|
12700
12960
|
onDisconnected: (disconnected) => {
|
|
@@ -13542,6 +13802,46 @@ var GhostLink = class {
|
|
|
13542
13802
|
this.advertiseTransports();
|
|
13543
13803
|
this.options.events?.onTransportsChanged?.();
|
|
13544
13804
|
}
|
|
13805
|
+
/** When the chat was last in use on its session: the contact's last frame of use, or the session's start. */
|
|
13806
|
+
get lastActivityAt() {
|
|
13807
|
+
return Math.max(this.activityAt, this.channelSince);
|
|
13808
|
+
}
|
|
13809
|
+
/**
|
|
13810
|
+
* Whether the live session on this transport may end so another chat takes its listener (`yieldEndpoint`): open, and
|
|
13811
|
+
* unused, for `idleMs` at least (a session that just started has been in use for none of it); no call on, no file
|
|
13812
|
+
* moving, nothing being dialled or switched. `canReleaseEndpoint` never gives up a live session; this does, once idle.
|
|
13813
|
+
*/
|
|
13814
|
+
canYieldEndpoint(transport, idleMs) {
|
|
13815
|
+
if (this.activeBinding?.transport !== transport || !this.isDataLinkOpen || this.paired?.state.status !== "ready") return false;
|
|
13816
|
+
if (this.candidate || this.dialing || this.switcher.pending || this.transitionTarget) return false;
|
|
13817
|
+
if (this.pairedCalls.on || this.pairedFiles?.busy) return false;
|
|
13818
|
+
return Date.now() - this.lastActivityAt >= idleMs;
|
|
13819
|
+
}
|
|
13820
|
+
/**
|
|
13821
|
+
* Ends the idle live session on this transport and lets its listener go, for a chat in use that found every one
|
|
13822
|
+
* taken. The contact hears a goodbye, as when an app quits, rather than finding out when its pings go unanswered; the
|
|
13823
|
+
* record then lists the transport undescribed, so it stops dialling here. The chat goes on over the DHT (or WebRTC),
|
|
13824
|
+
* and takes a listener again the same way once it is in use. False when the session may not end (see above).
|
|
13825
|
+
*/
|
|
13826
|
+
async yieldEndpoint(transport, idleMs) {
|
|
13827
|
+
if (!this.canYieldEndpoint(transport, idleMs)) return false;
|
|
13828
|
+
traceLink(this.myPubKeyZ32, "native-yield", {
|
|
13829
|
+
transport,
|
|
13830
|
+
idle: Date.now() - this.lastActivityAt
|
|
13831
|
+
});
|
|
13832
|
+
this.yielding = true;
|
|
13833
|
+
try {
|
|
13834
|
+
try {
|
|
13835
|
+
this.channel?.send(JSON.stringify({ t: "paired-bye" }));
|
|
13836
|
+
} catch {}
|
|
13837
|
+
await new Promise((resolve) => setTimeout(resolve, YIELD_BYE_MS));
|
|
13838
|
+
this.disconnect();
|
|
13839
|
+
await this.releaseEndpoint(transport);
|
|
13840
|
+
} finally {
|
|
13841
|
+
this.yielding = false;
|
|
13842
|
+
}
|
|
13843
|
+
return !this.endpoints.has(transport);
|
|
13844
|
+
}
|
|
13545
13845
|
advertiseTransports() {
|
|
13546
13846
|
this.publishPacketTransports();
|
|
13547
13847
|
if (this.paired?.state.status !== "ready" || !this.options.params.profile) return;
|
|
@@ -13641,11 +13941,15 @@ var GhostLink = class {
|
|
|
13641
13941
|
at: offeredAt
|
|
13642
13942
|
} : void 0;
|
|
13643
13943
|
await this.dataLink.connect();
|
|
13644
|
-
if (
|
|
13944
|
+
if (this.dataLink.state !== "idle" || epoch !== this.connectionEpoch) {
|
|
13645
13945
|
this.scheduleRace(epoch, offeredAt);
|
|
13646
13946
|
return;
|
|
13647
13947
|
}
|
|
13948
|
+
this.rtcUnstarted();
|
|
13949
|
+
const next = this.afterRtc;
|
|
13950
|
+
if (!next) return;
|
|
13648
13951
|
this.afterRtc = void 0;
|
|
13952
|
+
for (const joined of next.rest) if (!ordered.includes(joined)) ordered.push(joined);
|
|
13649
13953
|
continue;
|
|
13650
13954
|
}
|
|
13651
13955
|
const result = await this.dialNative(transport, epoch);
|
|
@@ -13730,9 +14034,10 @@ var GhostLink = class {
|
|
|
13730
14034
|
scheduleRace(epoch, offeredAt) {
|
|
13731
14035
|
this.clearRace();
|
|
13732
14036
|
const next = this.afterRtc;
|
|
13733
|
-
if (!next || next.epoch !== epoch || epoch !== this.connectionEpoch || this.
|
|
14037
|
+
if (!next || next.epoch !== epoch || epoch !== this.connectionEpoch || !this.offerOut) return;
|
|
13734
14038
|
const relayed = this.relayedTransports, now = Date.now();
|
|
13735
|
-
const
|
|
14039
|
+
const answered = this.answeredAt ? this.answeredAt + RACE_ANSWERED_MS : Infinity;
|
|
14040
|
+
const at = next.rest.filter((t) => t !== "webrtc/1").map((t) => Math.max(Math.min(offeredAt + (relayed.includes(t) ? RACE_RELAYED_MS : RACE_DIRECT_MS), answered), this.canDial(t) ? 0 : now + RACE_RETRY_MS));
|
|
13736
14041
|
if (!at.length) return;
|
|
13737
14042
|
this.raceTimer = setTimeout(() => {
|
|
13738
14043
|
this.raceTimer = null;
|
|
@@ -13745,7 +14050,7 @@ var GhostLink = class {
|
|
|
13745
14050
|
*/
|
|
13746
14051
|
joinRace(transport) {
|
|
13747
14052
|
const offered = this.offered;
|
|
13748
|
-
if (!offered || offered.epoch !== this.connectionEpoch || this.
|
|
14053
|
+
if (!offered || offered.epoch !== this.connectionEpoch || !this.offerOut || this.channel) return;
|
|
13749
14054
|
if (!this.peerFallback || !this.peerTransports?.includes(transport) || !this.transportOffer().includes(transport)) return;
|
|
13750
14055
|
const rest = this.afterRtc?.epoch === offered.epoch ? this.afterRtc.rest : [];
|
|
13751
14056
|
if (rest.includes(transport)) return;
|
|
@@ -13756,6 +14061,19 @@ var GhostLink = class {
|
|
|
13756
14061
|
traceLink(this.myPubKeyZ32, "race-join", { transport });
|
|
13757
14062
|
if (!this.racing) this.scheduleRace(offered.epoch, offered.at);
|
|
13758
14063
|
}
|
|
14064
|
+
/** WebRTC could not start an attempt here: it goes last in the dial order for `RTC_UNSTARTED_MS`, while another transport remains. */
|
|
14065
|
+
rtcUnstarted() {
|
|
14066
|
+
traceLink(this.myPubKeyZ32, "rtc-unstarted", {});
|
|
14067
|
+
this.demotedUntil.set("webrtc/1", Date.now() + RTC_UNSTARTED_MS);
|
|
14068
|
+
}
|
|
14069
|
+
/**
|
|
14070
|
+
* Whether this side dials although its key says it waits for the contact's offer: WebRTC cannot start here for now
|
|
14071
|
+
* (`rtcUnstarted`), so an offer of the contact's finds no answer, and this side has a native transport to dial, with
|
|
14072
|
+
* fallback allowed on both sides.
|
|
14073
|
+
*/
|
|
14074
|
+
dialsPastRtc() {
|
|
14075
|
+
return (this.demotedUntil.get("webrtc/1") ?? 0) > Date.now() && this.fallback && this.peerFallback && TRANSPORTS.some((t) => this.canDial(t) && !!this.peerTransports?.includes(t) && this.transportOffer().includes(t));
|
|
14076
|
+
}
|
|
13759
14077
|
/** Whether a native dial of `transport` can go now: this side's endpoint is up and the contact's descriptor known. */
|
|
13760
14078
|
canDial(transport) {
|
|
13761
14079
|
return transport !== "webrtc/1" && this.endpoints.has(transport) && !!this.peerDescriptors[transport];
|
|
@@ -13771,8 +14089,9 @@ var GhostLink = class {
|
|
|
13771
14089
|
*/
|
|
13772
14090
|
async race(epoch, offeredAt) {
|
|
13773
14091
|
const next = this.afterRtc;
|
|
13774
|
-
if (!next || next.epoch !== epoch || epoch !== this.connectionEpoch || this.channel || this.dialing || this.stopped || this.leaving || this.streamBlocked || this.keyStopped || this.
|
|
13775
|
-
const relayed = this.relayedTransports
|
|
14092
|
+
if (!next || next.epoch !== epoch || epoch !== this.connectionEpoch || this.channel || this.dialing || this.stopped || this.leaving || this.streamBlocked || this.keyStopped || !this.offerOut) return;
|
|
14093
|
+
const relayed = this.relayedTransports;
|
|
14094
|
+
const late = Date.now() - offeredAt >= 4e4 || this.answeredAt > 0 && Date.now() - this.answeredAt >= 6e3;
|
|
13776
14095
|
const due = next.rest.filter((t) => this.canDial(t) && (late || !relayed.includes(t)));
|
|
13777
14096
|
const left = next.rest.filter((t) => t !== "webrtc/1" && !due.includes(t));
|
|
13778
14097
|
if (!due.length) {
|
|
@@ -14014,7 +14333,7 @@ var GhostLink = class {
|
|
|
14014
14333
|
* the lower-key rule settles any collision, on both sides.
|
|
14015
14334
|
*/
|
|
14016
14335
|
maybeAutoConnect(presence) {
|
|
14017
|
-
if (this.stopped || this.leaving || this.streamBlocked || this.keyStopped || !this.options.autoConnect || !presence.online || this.channel || this.dialing || this.dataLink.state !== "idle") return;
|
|
14336
|
+
if (this.stopped || this.leaving || this.yielding || this.streamBlocked || this.keyStopped || !this.options.autoConnect || !presence.online || this.channel || this.dialing || this.dataLink.state !== "idle") return;
|
|
14018
14337
|
if (this.resuming && this.resuming !== "webrtc/1" && !this.endpoints.has(this.resuming) && Date.now() < this.resumeWaitUntil) return;
|
|
14019
14338
|
if (this.departed) {
|
|
14020
14339
|
if (presence.lastPacketAt === this.departed.packet && Date.now() < this.departed.until) return;
|
|
@@ -14027,7 +14346,7 @@ var GhostLink = class {
|
|
|
14027
14346
|
this.session.expectPeer();
|
|
14028
14347
|
return;
|
|
14029
14348
|
}
|
|
14030
|
-
} else if (role !== "joiner" && this.myPubKeyZ32 > this.options.params.peerPubKeyZ32 && !this.resuming) {
|
|
14349
|
+
} else if (role !== "joiner" && this.myPubKeyZ32 > this.options.params.peerPubKeyZ32 && !this.resuming && !this.dialsPastRtc()) {
|
|
14031
14350
|
if (Date.now() - presence.lastPacketAt < 3e4 && presence.lastPacketAt !== this.offerAwaitedFor) {
|
|
14032
14351
|
this.offerAwaitedFor = presence.lastPacketAt;
|
|
14033
14352
|
this.session.expectPeer();
|
|
@@ -14954,6 +15273,7 @@ var GhostLink = class {
|
|
|
14954
15273
|
} catch {
|
|
14955
15274
|
return;
|
|
14956
15275
|
}
|
|
15276
|
+
if (frame && typeof frame === "object" && !QUIET_FRAMES.has(String(frame.t))) this.activityAt = Date.now();
|
|
14957
15277
|
if (frame?.t === "paired-ping") {
|
|
14958
15278
|
try {
|
|
14959
15279
|
channel.send(JSON.stringify({ t: "paired-pong" }));
|
|
@@ -15125,6 +15445,7 @@ var GhostLink = class {
|
|
|
15125
15445
|
}
|
|
15126
15446
|
if (frame?.t === "paired-call") {
|
|
15127
15447
|
const signal = this.supportsCalls ? parsePairedCallFrame(frame) : null;
|
|
15448
|
+
if (signal) this.pairedCalls.heard(signal);
|
|
15128
15449
|
if (signal) this.options.events?.onCallSignal?.(signal);
|
|
15129
15450
|
return;
|
|
15130
15451
|
}
|
|
@@ -15326,7 +15647,7 @@ var GhostLink = class {
|
|
|
15326
15647
|
* once, and the contact is watched closely for a while, since an app that went away is usually back in seconds.
|
|
15327
15648
|
*/
|
|
15328
15649
|
peerLost(why) {
|
|
15329
|
-
if (this.stopped || this.leaving) return;
|
|
15650
|
+
if (this.stopped || this.leaving || this.yielding) return;
|
|
15330
15651
|
traceLink(this.myPubKeyZ32, "peer-lost", { why });
|
|
15331
15652
|
this.lostUntil = Date.now() + WATCH_PEER_MS;
|
|
15332
15653
|
this.autoConnectFailures = 0;
|
|
@@ -20842,6 +21163,7 @@ var GroupSession = class {
|
|
|
20842
21163
|
if (frame.e > this.epoch) {
|
|
20843
21164
|
this.ask(from);
|
|
20844
21165
|
this.offerMeta(from, frame.mt);
|
|
21166
|
+
this.handOwn(from, frame);
|
|
20845
21167
|
return;
|
|
20846
21168
|
}
|
|
20847
21169
|
if (frame.h !== commitHash(this.state.chain[frame.e])) {
|
|
@@ -20872,7 +21194,7 @@ var GroupSession = class {
|
|
|
20872
21194
|
secrets: secrets.slice(i, i + GROUP_LIMITS.secretsPerFrame)
|
|
20873
21195
|
});
|
|
20874
21196
|
const lacks = missingIn(frame, from);
|
|
20875
|
-
|
|
21197
|
+
this.handOwn(from, frame);
|
|
20876
21198
|
const asked = new Set(Array.isArray(frame.ask) ? frame.ask.filter((k) => typeof k === "string" && k !== from && k !== this.myKey && rosterHas(this.roster, k)) : []);
|
|
20877
21199
|
if (asked.size) {
|
|
20878
21200
|
for (const kept of this.state.relay ?? []) if (asked.has(kept.s) && this.state.chain[kept.e] && rosterHas(this.state.chain[kept.e].m, from) && lacks(kept)) this.hooks.send(from, kept);
|
|
@@ -20883,6 +21205,11 @@ var GroupSession = class {
|
|
|
20883
21205
|
}
|
|
20884
21206
|
this.offerMeta(from, frame.mt);
|
|
20885
21207
|
}
|
|
21208
|
+
/** My own messages a member's sync says it lacks, from my bounded log, for the epochs it was a member of. */
|
|
21209
|
+
handOwn(to, frame) {
|
|
21210
|
+
const lacks = missingIn(frame, to);
|
|
21211
|
+
for (const sent of this.state.sent) if (this.state.chain[sent.e] && rosterHas(this.state.chain[sent.e].m, to) && lacks(sent)) this.hooks.send(to, sent);
|
|
21212
|
+
}
|
|
20886
21213
|
/** Sets (or, with null, removes) the group's picture: only the admin, signed under the current commit. */
|
|
20887
21214
|
setPicture(picture, now = Date.now()) {
|
|
20888
21215
|
return this.serialize(async () => {
|
|
@@ -20947,7 +21274,7 @@ var GroupSession = class {
|
|
|
20947
21274
|
for (const key of this.others) this.hooks.send(key, frame);
|
|
20948
21275
|
}
|
|
20949
21276
|
const change = groupMetaChange(before, meta, this.state.name);
|
|
20950
|
-
if (change) this.hooks.metaChanged?.(this.myKey, change);
|
|
21277
|
+
if (change) this.hooks.metaChanged?.(this.myKey, change, meta.ts);
|
|
20951
21278
|
this.hooks.changed();
|
|
20952
21279
|
}
|
|
20953
21280
|
/** My statement, to a member whose sync says it holds an older one (or none); nothing to an app that says nothing. */
|
|
@@ -20992,7 +21319,7 @@ var GroupSession = class {
|
|
|
20992
21319
|
this.state.meta = opened.meta;
|
|
20993
21320
|
await this.persist();
|
|
20994
21321
|
const change = !before && !rosterHas(commit.m, this.myKey) ? null : groupMetaChange(before, opened.meta, this.state.name);
|
|
20995
|
-
if (change) this.hooks.metaChanged?.(s.by, change);
|
|
21322
|
+
if (change) this.hooks.metaChanged?.(s.by, change, opened.meta.ts);
|
|
20996
21323
|
this.hooks.changed();
|
|
20997
21324
|
this.took({
|
|
20998
21325
|
t: "group-meta",
|
|
@@ -21715,12 +22042,44 @@ var CommunitySession = class {
|
|
|
21715
22042
|
}
|
|
21716
22043
|
/** Someone the chain took out (removed or left) and who is not back in. */
|
|
21717
22044
|
wasRemoved(key) {
|
|
21718
|
-
|
|
22045
|
+
return this.outIndex(key) >= 0;
|
|
22046
|
+
}
|
|
22047
|
+
/** When the chain took them out (the commit's time, by its signer's clock), if it did and they are not back in. */
|
|
22048
|
+
outAt(key) {
|
|
22049
|
+
const i = this.outIndex(key);
|
|
22050
|
+
return i < 0 ? void 0 : this.state.chain[i].ts;
|
|
22051
|
+
}
|
|
22052
|
+
outIndex(key) {
|
|
22053
|
+
if (rosterHas(this.roster, key)) return -1;
|
|
21719
22054
|
for (let i = this.state.chain.length - 1; i >= 0; i--) {
|
|
21720
22055
|
const c = this.state.chain[i];
|
|
21721
|
-
if ((c.k === "remove" || c.k === "leave") && c.s === key) return
|
|
22056
|
+
if ((c.k === "remove" || c.k === "leave") && c.s === key) return i;
|
|
21722
22057
|
}
|
|
21723
|
-
return
|
|
22058
|
+
return -1;
|
|
22059
|
+
}
|
|
22060
|
+
/**
|
|
22061
|
+
* What someone the chain took out is told when it comes back (WISP 9xx § Leaving and removal): the commits from where
|
|
22062
|
+
* it says it is (`sync`: its tip, or its locator) up to the one that took it out, and nothing after. Later commits
|
|
22063
|
+
* are not its business (a `link` commit names the new entry key); no secret, no stored frame, no seed goes with them.
|
|
22064
|
+
* Empty when it is a member, was never one, or already holds that commit.
|
|
22065
|
+
*/
|
|
22066
|
+
farewell(key, sync) {
|
|
22067
|
+
if (!this.isMember) return [];
|
|
22068
|
+
const chain = this.state.chain, out = this.outIndex(key);
|
|
22069
|
+
if (out < 0) return [];
|
|
22070
|
+
let start;
|
|
22071
|
+
if (typeof sync.h === "string" && this.mainIndex.has(sync.h)) start = this.mainIndex.get(sync.h) + 1;
|
|
22072
|
+
else {
|
|
22073
|
+
const path = typeof sync.h === "string" && this.known.has(sync.h) ? this.pathToMain(sync.h) : null;
|
|
22074
|
+
const common = (Array.isArray(sync.loc) ? sync.loc : []).find((x) => typeof x === "string" && this.mainIndex.has(x));
|
|
22075
|
+
start = path ? this.mainIndex.get(this.known.get(path[0]).p) + 1 : common !== void 0 ? this.mainIndex.get(common) + 1 : Math.max(0, out + 1 - COMMUNITY_LIMITS.side);
|
|
22076
|
+
}
|
|
22077
|
+
return chain.slice(start, out + 1).map((commit) => ({
|
|
22078
|
+
t: "group-commit",
|
|
22079
|
+
v: 2,
|
|
22080
|
+
g: this.id,
|
|
22081
|
+
commit
|
|
22082
|
+
}));
|
|
21724
22083
|
}
|
|
21725
22084
|
missing(sender) {
|
|
21726
22085
|
const entry = this.state.seen[sender]?.[seenKey$1(this.epoch, shortHash(this.topHash))];
|
|
@@ -22812,7 +23171,7 @@ var CommunitySession = class {
|
|
|
22812
23171
|
const frame = this.metaFrame();
|
|
22813
23172
|
if (frame) this.hooks.broadcast(frame);
|
|
22814
23173
|
const change = groupMetaChange(before, meta, this.state.name);
|
|
22815
|
-
if (change) this.hooks.metaChanged?.(this.myKey, change);
|
|
23174
|
+
if (change) this.hooks.metaChanged?.(this.myKey, change, meta.ts);
|
|
22816
23175
|
this.hooks.changed();
|
|
22817
23176
|
}
|
|
22818
23177
|
/** My statement sealed under my current epoch, when I hold its secret. */
|
|
@@ -22865,7 +23224,7 @@ var CommunitySession = class {
|
|
|
22865
23224
|
this.state.meta = opened.meta;
|
|
22866
23225
|
await this.persist();
|
|
22867
23226
|
const change = !before && !rosterHas(this.rosterAt(s.h) ?? [], this.myKey) ? null : groupMetaChange(before, opened.meta, this.state.name);
|
|
22868
|
-
if (change) this.hooks.metaChanged?.(s.by, change);
|
|
23227
|
+
if (change) this.hooks.metaChanged?.(s.by, change, opened.meta.ts);
|
|
22869
23228
|
this.hooks.changed();
|
|
22870
23229
|
return true;
|
|
22871
23230
|
}
|
|
@@ -24906,7 +25265,7 @@ async function sendCard(ctx, params, kind) {
|
|
|
24906
25265
|
text,
|
|
24907
25266
|
card
|
|
24908
25267
|
});
|
|
24909
|
-
if (result.error || !result.messageId) throw new CliError("unavailable", result.error ?? "Nothing to send");
|
|
25268
|
+
if (result.error || !result.messageId) throw new CliError(result.refused ? "refused" : "unavailable", result.error ?? "Nothing to send");
|
|
24910
25269
|
const edges = wait === "sent" ? await waitForGroupFrame(ctx, target.id, result.messageId, void 0, ms) : void 0;
|
|
24911
25270
|
return answer(target, kind, id, result.messageId, {
|
|
24912
25271
|
card,
|
|
@@ -25306,10 +25665,20 @@ function resumeHolds(ctx) {
|
|
|
25306
25665
|
}
|
|
25307
25666
|
//#endregion
|
|
25308
25667
|
//#region src/views.ts
|
|
25668
|
+
/**
|
|
25669
|
+
* A live session carries the chat now, in this run: its link is open and, on a paired chat, the session authenticated
|
|
25670
|
+
* on a transport. The engine's own rule for a chat's connection line (`observeTransport`). `textDelivery` alone says
|
|
25671
|
+
* how a text would go, and nothing the engine remembers from an earlier run (a chat that was live when the app last
|
|
25672
|
+
* ran, WISP 100) counts: until the contact answers again the link is not open and the pairing is `connecting`.
|
|
25673
|
+
*/
|
|
25674
|
+
function isLive(link) {
|
|
25675
|
+
if (link.textDelivery !== "stream" || link.dataLink !== "open") return false;
|
|
25676
|
+
return !link.profile || link.pairing?.status === "ready" && !!link.pairing.transport;
|
|
25677
|
+
}
|
|
25309
25678
|
function chatJson(link) {
|
|
25310
25679
|
const label = link.label?.trim() || null;
|
|
25311
25680
|
const peerName = link.peerNick?.trim() || null;
|
|
25312
|
-
const live = link
|
|
25681
|
+
const live = isLive(link);
|
|
25313
25682
|
return {
|
|
25314
25683
|
id: link.id,
|
|
25315
25684
|
name: label ?? peerName,
|
|
@@ -25364,6 +25733,7 @@ function messageJson(message) {
|
|
|
25364
25733
|
from: message.sender,
|
|
25365
25734
|
text: message.text,
|
|
25366
25735
|
timestamp: message.timestamp,
|
|
25736
|
+
...message.sentAt !== void 0 ? { sentAt: message.sentAt } : {},
|
|
25367
25737
|
delivery: message.delivery ?? (message.sender === "peer" ? null : "sent"),
|
|
25368
25738
|
deliveryError: message.deliveryError ?? null,
|
|
25369
25739
|
via: message.via,
|
|
@@ -30599,7 +30969,7 @@ var SERVICE_METHODS = {
|
|
|
30599
30969
|
const link = chatOf(ctx, params);
|
|
30600
30970
|
return {
|
|
30601
30971
|
chat: link.id,
|
|
30602
|
-
live: link
|
|
30972
|
+
live: isLive(link),
|
|
30603
30973
|
services: link.peerServices ?? []
|
|
30604
30974
|
};
|
|
30605
30975
|
},
|
|
@@ -33433,7 +33803,7 @@ var METHODS = {
|
|
|
33433
33803
|
webrtc: ctx.runtime.webrtc,
|
|
33434
33804
|
calls: ctx.runtime.callsUnavailable === null,
|
|
33435
33805
|
chats: s.links.length,
|
|
33436
|
-
live: s.links.filter(
|
|
33806
|
+
live: s.links.filter(isLive).length,
|
|
33437
33807
|
groups: s.groups.length,
|
|
33438
33808
|
discovery: {
|
|
33439
33809
|
protocol: s.transport.protocol,
|
|
@@ -33844,14 +34214,15 @@ var METHODS = {
|
|
|
33844
34214
|
return chatJson(await waitForState(ctx, (s) => {
|
|
33845
34215
|
const now = s.links.find((l) => l.id === link.id);
|
|
33846
34216
|
if (!now) throw new CliError("not_found", `Chat ${link.id} was removed`);
|
|
33847
|
-
return (until === "live" ? now
|
|
34217
|
+
return (until === "live" ? isLive(now) : until === "text" ? now.textDelivery === "stream" || now.textDelivery === "dht" || now.textDelivery === "hold" : !!now.pairingProgress?.peerSeen || now.pairing?.status === "ready" || isLive(now)) ? now : void 0;
|
|
33848
34218
|
}, num(params, "timeout", 60, {
|
|
33849
34219
|
min: 1,
|
|
33850
34220
|
max: 86400
|
|
33851
34221
|
}) * 1e3, `chat ${link.id} to be ${until}`));
|
|
33852
34222
|
},
|
|
33853
34223
|
async "group.create"(ctx, params) {
|
|
33854
|
-
const name = str(params, "name", true);
|
|
34224
|
+
const name = groupName(str(params, "name", true));
|
|
34225
|
+
if (!name) throw new CliError("bad_request", `A group's name is 1 to 64 characters on one line`);
|
|
33855
34226
|
const profile = oneOf(params, "profile", ["community", "mesh"], "community");
|
|
33856
34227
|
const { groupId } = await node(ctx).createGroup({
|
|
33857
34228
|
name,
|
|
@@ -33899,7 +34270,7 @@ var METHODS = {
|
|
|
33899
34270
|
...replyTo ? { replyTo } : {},
|
|
33900
34271
|
...card ? { card } : {}
|
|
33901
34272
|
});
|
|
33902
|
-
if (result.error) throw new CliError("
|
|
34273
|
+
if (result.error) throw result.refused ? new CliError("refused", result.error) : groupSendRefused(result.error);
|
|
33903
34274
|
const messageId = result.messageId ?? null;
|
|
33904
34275
|
const edges = messageId ? wait === "sent" ? await waitForGroupFrame(ctx, group.id, messageId, void 0, num(params, "timeout", 30, {
|
|
33905
34276
|
min: 1,
|
|
@@ -33911,7 +34282,7 @@ var METHODS = {
|
|
|
33911
34282
|
return {
|
|
33912
34283
|
group: group.id,
|
|
33913
34284
|
messageId,
|
|
33914
|
-
sent:
|
|
34285
|
+
sent: edges > 0,
|
|
33915
34286
|
edges,
|
|
33916
34287
|
...card ? {
|
|
33917
34288
|
buttons: card.id,
|
|
@@ -33934,6 +34305,7 @@ var METHODS = {
|
|
|
33934
34305
|
}
|
|
33935
34306
|
const mentions = mentionsFor(text, list(params, "mentions"), group);
|
|
33936
34307
|
const wait = oneOf(params, "wait", ["none", "sent"], "none");
|
|
34308
|
+
const before = (await node(ctx).groupMessages({ groupId: group.id })).find((m) => m.id === messageId)?.edit?.seq ?? 0;
|
|
33937
34309
|
const result = await node(ctx).editMessage({
|
|
33938
34310
|
linkId: `group:${group.id}`,
|
|
33939
34311
|
messageId,
|
|
@@ -33943,6 +34315,14 @@ var METHODS = {
|
|
|
33943
34315
|
if (result.error) throw editRefused(await node(ctx).groupMessages({ groupId: group.id }), messageId, "group", result.error, result.refused);
|
|
33944
34316
|
const message = (await node(ctx).groupMessages({ groupId: group.id })).find((m) => m.id === messageId);
|
|
33945
34317
|
const edits = message?.edit?.seq ?? 0;
|
|
34318
|
+
if (edits === before) return {
|
|
34319
|
+
group: group.id,
|
|
34320
|
+
messageId,
|
|
34321
|
+
edits,
|
|
34322
|
+
sent: false,
|
|
34323
|
+
edges: 0,
|
|
34324
|
+
unchanged: true
|
|
34325
|
+
};
|
|
33946
34326
|
const edges = !edits ? 0 : wait === "sent" ? await waitForGroupFrame(ctx, group.id, messageId, edits, num(params, "timeout", 30, {
|
|
33947
34327
|
min: 1,
|
|
33948
34328
|
max: 3600
|
|
@@ -34062,7 +34442,7 @@ async function react(ctx, linkId, params) {
|
|
|
34062
34442
|
messageId,
|
|
34063
34443
|
emoji: emoji ?? ""
|
|
34064
34444
|
});
|
|
34065
|
-
if (result.error) throw new CliError(/not in this chat|No reaction of yours/.test(result.error) ? "not_found" : "bad_request", result.error);
|
|
34445
|
+
if (result.error) throw new CliError(result.refused ? "refused" : /not in this chat|No reaction of yours/.test(result.error) ? "not_found" : "bad_request", result.error);
|
|
34066
34446
|
return {
|
|
34067
34447
|
messageId,
|
|
34068
34448
|
emoji: emoji || null,
|
|
@@ -34088,6 +34468,17 @@ function typingWord(params, typing) {
|
|
|
34088
34468
|
};
|
|
34089
34469
|
}
|
|
34090
34470
|
/**
|
|
34471
|
+
* A group message the engine did not send, by what a bot can do about it: a `--reply` to a message this group does not
|
|
34472
|
+
* have is `not_found` (exit 3), as `group react`, `group edit` and `pin` answer for one; out of the group (removed,
|
|
34473
|
+
* left, a forked history) is `refused`: it will not go later either. Anything else may go on a later try
|
|
34474
|
+
* (`unavailable`): the epoch's key is still on its way. All three were `unavailable` (exit 1) before.
|
|
34475
|
+
*/
|
|
34476
|
+
function groupSendRefused(error) {
|
|
34477
|
+
if (/not in this chat|cannot be replied to/.test(error)) return new CliError("not_found", error);
|
|
34478
|
+
if (/^You (were removed from|left|are no longer in) this group$|Membership changes are halted/.test(error)) return new CliError("refused", error);
|
|
34479
|
+
return new CliError("unavailable", error);
|
|
34480
|
+
}
|
|
34481
|
+
/**
|
|
34091
34482
|
* An edit the engine refused. A message that is not in the chat or group at all is `not_found` (exit 3), naming the id
|
|
34092
34483
|
* to give, as `button update` and `task update` do; before, a mistyped or lost id read "Only your own text messages
|
|
34093
34484
|
* can be edited" (`refused`, exit 1), as if the message were someone else's.
|
|
@@ -39066,14 +39457,14 @@ async function startRuntime(paths, options = {}) {
|
|
|
39066
39457
|
(await import("./assets/ghostly_iroh_web-CPa_YEfb.js")).initSync({ module: irohWasmBytes() });
|
|
39067
39458
|
const [{ registerFileBytes }, { NodeFileBytes }] = await Promise.all([Promise.resolve().then(() => fileBytes_exports), import("./assets/fileBytes-CLv0gryb.js")]);
|
|
39068
39459
|
registerFileBytes("native", async () => new NodeFileBytes(paths.files), true);
|
|
39069
|
-
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./assets/server-
|
|
39460
|
+
const [{ EngineServer }, { createHyperEndpoint }] = await Promise.all([import("./assets/server-Cajp8VLY.js").then((n) => n.t), import("./assets/endpoint-CaSbSFDw.js")]);
|
|
39070
39461
|
const network = hyperdhtNetwork();
|
|
39071
39462
|
const dht = mainlineNetwork();
|
|
39072
|
-
const mainline = dht.off ? null : new (await (import("./assets/mainline-
|
|
39463
|
+
const mainline = dht.off ? null : new (await (import("./assets/mainline-_roUnpDA.js"))).Mainline({
|
|
39073
39464
|
bootstrap: dht.bootstrap,
|
|
39074
39465
|
host: dht.host
|
|
39075
39466
|
});
|
|
39076
|
-
const transport = mainline || pinned ? new (await (import("./assets/mainline-
|
|
39467
|
+
const transport = mainline || pinned ? new (await (import("./assets/mainline-_roUnpDA.js"))).RelaysAndDht(mainline, pinned ? { relays: pinned } : {}, !!pinned) : null;
|
|
39077
39468
|
const server = new EngineServer({
|
|
39078
39469
|
...transport ? { transport } : {},
|
|
39079
39470
|
irohWeb: true,
|
|
@@ -40606,6 +40997,6 @@ async function run(argv) {
|
|
|
40606
40997
|
//#region src/bin.ts
|
|
40607
40998
|
run(process.argv.slice(2));
|
|
40608
40999
|
//#endregion
|
|
40609
|
-
export { boundedIdentityFetch as $,
|
|
41000
|
+
export { boundedIdentityFetch as $, GROUP_MEMBER_CAP as $i, LIVENESS_PING_MS as $n, IDENTITY_REVOCATION_LABEL as $r, GroupWakeLimiter as $t, TEST_MINT as A, normalizeRelayUrl as Aa, GROUP_REACTION_FRAME as Ai, assertTokenUnits as An, nextPinNumber as Ar, transact as At, registeredLightningProviders as B, identityFromSeedB64 as Ba, pairedReplyAuthor as Bi, isPubkyKey as Bn, vapidKeysMatch as Br, communityHasFrame as Bt, networkLabel as C, createChatInvite as Ca, EDIT_RATE_WINDOW_MS as Ci, ONCHAIN_PROVIDER as Cn, transferEnded as Cr, STORES as Ct, WALLET_TYPES as D, DEFAULT_RELAYS as Da, forwardedAgain as Di, TEST_USDT_FAUCET_AMOUNT as Dn, GROUP_PIN_FRAME as Dr, fileStore as Dt, MESSAGE_DETAILS_MAX_BYTES as E, inviteOwnership as Ea, parseLinkPreview as Ei, SEPOLIA_TEST_USDT_FAUCET as En, typingActivity as Er, deleteDatabase as Et, paysItsOwnInvoices as F, createRelayPayload as Fa, reactionEmoji as Fi, SPARK_NETWORKS as Fn, WakeLimiter as Fr, openGroupTyping as Ft, SourceConfigError as G, randomBytes$1 as Ga, checkStatusCard as Gi, cashuRequestPayload as Gn, HoldKeys as Gr, decodeGroupEntryLink as Gt, reserveAdapterIds as H, sanitizeAvatar as Ha, replySnippet as Hi, engineText as Hn, EDIT_CAPABILITY as Hr, decodeCommunityLink as Ht, BACKUP_MEDIA_TYPE as I, newerPacket as Ia, reactionIsNewer as Ii, SPARK_PROVIDER as In, newWakeToken as Ir, sealGroupTyping as It, networkMode as J, utf8Decode as Ja, arrivalKey as Ji, isFederationId as Jn, readManifest as Jr, knockIdentity as Jt, SourceUnreachableError as K, toBase64Url$1 as Ka, sameButtonText as Ki, fedimintRequestPayload as Kn, HoldRefusedError as Kr, encodeGroupEntryLink as Kt, MAX_BACKUP_BYTES as L, parseRelayPayload as La, readReaction as Li, sparkAddressKind as Ln, relayRequest as Lr, COMMUNITY_LIMITS as Lt, isTestMint as M, isDiscoveryBudgetError as Ma, ReactionWindow as Mi, formatPaymentAmount as Mn, pinNumberHolds as Mr, GROUP_TYPING_FRAME as Mt, isWorthlessMint as N, withRequestOptions as Na, nextReactionNumber as Ni, validatePaymentTarget as Nn, readPin as Nr, GroupTypingBoard as Nt, BITCOIN_INVOICE_ON_TESTNET as O, RelayTransport as Oa, readForwarded as Oi, USDT_PUBLIC_RPC as On, PIN_LIMITS as Or, openDb as Ot, mintNetwork as P, PacketTooLargeError as Pa, queueReaction as Pi, walletNetworkOf as Pn, WAKE_CALL_INTERVAL_MS as Pr, groupTypingEpoch as Pt, setOwnDidSource as Q, shownTime as Qi, INVITE_TAKEN as Qn, dialDescriptors as Qr, GROUP_WAKE_FRAME as Qt, identityProviders as R, createIdentity as Ra, validReactionNumber as Ri, sparkInvoiceDetails as Rn, wakeRequest as Rr, CommunitySession as Rt, WrongNetworkError as S, OwnInviteError as Sa, EDIT_BUFFER_MS as Si, EVM_TEST_CHAINS as Sn, FILE_LIMITS as Sr, digestText as St, cashuMint as T, encodeInviteCode as Ta, dhtEditId as Ti, SEPOLIA_TEST_USDT as Tn, TypingSender as Tr, databaseName as Tt, NothingSpentError as U, bytesEqual as Ua, BUTTON_ID as Ui, decodeBolt11 as Un, HOLD_CAPABILITY as Ur, encodeCommunityLink as Ut, registeredOnchainProviders as V, publicKeyFromZ32 as Va, readReply as Vi, engineError as Vn, BUTTONS_CAPABILITY as Vr, communityMessageId as Vt, PROVIDER_PLATFORMS as W, fromBase64Url$1 as Wa, STATUS_CARD_LIMITS as Wi, ENDPOINT as Wn, HOLD_LIMITS as Wr, KNOCK_TTL_MS as Wt, assertPublicServer as X, __exportAll as Xa, heardTime as Xi, parseFedimintRequestPayload as Xn, DHT_TEXT_CAPABILITY as Xr, mergeKnocks as Xt, redact as Y, utf8Encode as Ya, claimedTime as Yi, parseCashuRequestPayload as Yn, CapsExchange as Yr, knockRecords as Yt, resolveAtprotoDid as Z, receivedTimestamp as Zi, GhostLink as Zn, capsDescriptors as Zr, readKnocks as Zt, newDeviceKey as _, parseCallSignal as _a, fileMessageText as _i, didDhtFromPublicKey as _n, readBeacon as _r, fileBytes as _t, createFailure as a, DirectPathWatch as aa, receivedIdentityStatus as ai, GroupSession as an, COMMUNITY_TOPOLOGY as ar, replyRef as at, ModeChanged as b, LEGACY_SERVICES as ba, DHT_MESSAGE_TTL as bi, signDidDhtPacket as bn, shouldBeHub as br, removeFileBytes as bt, paymentNetwork as c, traceLink as ca, PeerProofs as ci, GROUP_EDIT_FRAME as cn, doorHubs as cr, paymentWireId as ct, networkReason as d, parseLocalTarget as da, TRANSPORTS as di, parseCommunityEdit as dn, lobbyRecords as dr, removeStored as dt, MAX_GROUP_CHAIN as ea, IdentityExchange as ei, groupWakeFrame as en, pairedMessageFrame as er, verifyIdentity as et, rpcHost as f, webLocalFetch as fa, automaticTransport as fi, mentionsMember as fn, mergeBeacon as fr, storedSize as ft, intentRepository as g, RTC_CONFIG as ga, safeBlobType as gi, didDhtDocument as gn, rankHubs as gr, checkFileId as gt, ProviderSources as h, encodeControl as ha, parseImageMeta as hi, normalizeAtprotoHandle as hn, pickHubs as hr, blobDigest as ht, assertConfirmedReal as i, edgeParams as ia, newIdentityBinding as ii, GROUP_READ_NOTE as in, pickMeshHubs as ir, receivedPairedReply as it, fakesLightning as j, budgetRetryMs as ja, REACTION_LIMITS as ji, assertWholeSats as jn, pinIsNewer as jr, wrap as jt, DEFAULT_MINTS as k, currentRelays as ka, COMMUNITY_REACTION_FRAME as ki, WALLET_NETWORKS as kn, mayPin as kr, store$1 as kt, paymentNetworksOf as l, GhostlyHttpError as la, emptyProofLedger as li, GROUP_EDIT_TEXT_BYTES as ln, freshHubs as lr, FileAppender as lt, LightningCards as m, decodeControl as ma, irohRelayUrl as mi, isAtprotoDid as mn, newerHead as mr, SMALL_FILE_BYTES as mt, loadFedimintSdk as n, rosterHas as na, identityRevocationValue as ni, parseGroupWakeFrame as nn, mayBeHub as nr, groupReply as nt, createTiming as o, EXPECT_PEER_MS as oa, revokesIdentity as oi, groupMessageId as on, beaconKeys as or, replyTo as ot, CASHU_CARD as p, LIMITS as pa, irohDescriptor as pi, groupName as pn, mergeLobby as pr, streamStored as pt, isNothingSpentError as q, toZ32 as qa, statusCardText as qi, findEndpoint as qn, newMailbox as qr, entryParams as qt, WALLET_NAMES as r, verifyCommitSignature as ra, identityStatement as ri, GROUP_REACTED_FRAME as rn, meshHubs as rr, pairedWireReply as rt, crossNetwork as s, RELAY_POLL_INTERVALS as sa, lastSharedWithMe as si, COMMUNITY_EDIT_FRAME as sn, beaconRecords as sr, paymentAlias as st, fedimintDatabase as t, MEMBER_KEY as ta, emptyIdentityLedger as ti, groupWakes as tn, MESH_HUBS as tr, chosenResolver as tt, walletInstances as u, formatLocalTarget as ua, validExternalKey as ui, carryMentions as un, lobbyKeys as ur, readStored as ut, sealSeed as v, emptyLinkRecords as va, parseVoiceMeta as vi, encodeDidDhtDeactivated as vn, readBeaconHead as vr, fileBytesOf as vt, CASHU_MINT_SOURCE as w, decodeInviteCode as wa, RateWindow as wi, PaymentPreflightError as wn, transferStalled as wr, clearProfileStores as wt, ModeGate as x, serviceIdFromName as xa, peekDhtMailbox as xi, ETHEREUM_USDT as xn, ChatFiles as xr, FILE_BYTES_STEP as xt, unsealSeed as y, HTTP_SERVICE_PROTO as ya, parseVideoMeta as yi, encodeDidDhtPacket as yn, readLobby as yr, fileSpace as yt, onAdaptersChanged as z, identityFromSeed as za, wireReaction as zi, isBitcoinAddress as zn, checkPushEndpoint as zr, GROUP_READ_NOTE_COMMUNITY as zt };
|
|
40610
41001
|
|
|
40611
41002
|
//# sourceMappingURL=ghostly.mjs.map
|