@ghostlytools/cli 1.2.3 → 1.2.4
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 +2 -1
- package/dist/assets/{arkade-B-ALutmU.js → arkade-CdnKGDbg.js} +2 -2
- package/dist/assets/{arkade-B-ALutmU.js.map → arkade-CdnKGDbg.js.map} +1 -1
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
- package/dist/assets/{hyperdhtRelay-hKC9DpIH.js → hyperdhtRelay-C1p0nkle.js} +3 -3
- package/dist/assets/{hyperdhtRelay-hKC9DpIH.js.map → hyperdhtRelay-C1p0nkle.js.map} +1 -1
- package/dist/assets/{mainline-DriRu_hb.js → mainline-BH6s8HVi.js} +5 -2
- package/dist/assets/{mainline-DriRu_hb.js.map → mainline-BH6s8HVi.js.map} +1 -1
- package/dist/assets/{server-DHMXMwNn.js → server-0zfNpABQ.js} +196 -51
- package/dist/assets/server-0zfNpABQ.js.map +1 -0
- package/dist/assets/{src-_FzJkeAJ.js → src-CbLkHfz8.js} +268 -37
- package/dist/assets/src-CbLkHfz8.js.map +1 -0
- package/dist/assets/{usdt-D0lO2pRR.js → usdt-Qf4UuygS.js} +2 -2
- package/dist/assets/{usdt-D0lO2pRR.js.map → usdt-Qf4UuygS.js.map} +1 -1
- package/dist/ghostly.mjs +122 -37
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/assets/server-DHMXMwNn.js.map +0 -1
- package/dist/assets/src-_FzJkeAJ.js.map +0 -1
|
@@ -584,6 +584,7 @@ function withRequestOptions(transport, extra) {
|
|
|
584
584
|
if (transport.subscribe) wrapped.subscribe = (listener) => transport.subscribe(listener);
|
|
585
585
|
if (transport.networkChanged) wrapped.networkChanged = () => transport.networkChanged();
|
|
586
586
|
if (transport.readAnsweredAt) wrapped.readAnsweredAt = (pubKeyZ32) => transport.readAnsweredAt(pubKeyZ32);
|
|
587
|
+
if (transport.readHeldBackFor) wrapped.readHeldBackFor = (pubKeyZ32) => transport.readHeldBackFor(pubKeyZ32);
|
|
587
588
|
if (transport.onServerTime) wrapped.onServerTime = (listener) => transport.onServerTime(listener);
|
|
588
589
|
if (transport.configure) wrapped.configure = (options) => transport.configure(options);
|
|
589
590
|
if (transport.turnRead) wrapped.turnRead = (pubKeyZ32, options) => transport.turnRead(pubKeyZ32, options);
|
|
@@ -798,6 +799,11 @@ var isGroupSignal = (who) => who.group && !who.background && (who.signal || isWa
|
|
|
798
799
|
var watchLane = (who) => `${+who.urgent}${+who.background}`;
|
|
799
800
|
/** A group's read that is not a background one: an edge's, a private group's knock poll (`GROUP_RATION_WINDOW_MS`). */
|
|
800
801
|
var isGroupRead = (who) => who.group && !who.write && !who.background;
|
|
802
|
+
/**
|
|
803
|
+
* A read that takes its turn per key with the others (`READ_TURN_MS`): a background one, and a 1:1 chat's at any pace.
|
|
804
|
+
* Not a community door's bell.
|
|
805
|
+
*/
|
|
806
|
+
var isTurnRead = (who) => !who.write && !who.door && (who.background || !who.group);
|
|
801
807
|
/** A write that goes first once the budget frees a request (see `WRITE_FIRST_MS`): a chat's or a group's, never a background one. */
|
|
802
808
|
var firstWriter = (who) => !who.write || who.background ? null : who.group ? "group" : "chat";
|
|
803
809
|
/**
|
|
@@ -871,6 +877,29 @@ var WATCH_SHARE = 1 / 3;
|
|
|
871
877
|
* waits, the others wait too (`WATCH_SHARE`, `GROUP_RATION_WINDOW_MS`). One that stops asking loses it.
|
|
872
878
|
*/
|
|
873
879
|
var WATCH_TURN_MS = 5e3;
|
|
880
|
+
/**
|
|
881
|
+
* Background reads, and a 1:1 chat's at any pace, take turns per key the same way: while a key read longer ago asked
|
|
882
|
+
* within this long and was not read since, a read of another key waits (a chat's, at a pace that is not the background
|
|
883
|
+
* one, only for another chat's key). A saved chat that is not live reads its contact every 30 s in the
|
|
884
|
+
* background, and one the budget refused read again only at its next poll. A request frees 60 s after the one that took
|
|
885
|
+
* it, which is the poll after next of the chat that took it: the same chats took every request that freed. A daemon with
|
|
886
|
+
* 30 chats and 5 groups, back after a restart while its groups signaled, read some contacts once in 10 min, and those
|
|
887
|
+
* chats stayed down although both sides were online and offering (CLI bug hunt, 2026-10-12). A link's background read
|
|
888
|
+
* the budget refused reads again when it frees a request (`LinkSession`), so the key whose turn it is is there to take it.
|
|
889
|
+
* A chat not live after a restart looks fast while its offer is out, and those reads took no turn: the budget answered
|
|
890
|
+
* them from the packet read before, every 2 to 8 s for 10 minutes, while the chats that were live already, the groups'
|
|
891
|
+
* edges and the writes took every request that freed. A quarter of a daemon's 30 chats read their contact once in 10
|
|
892
|
+
* minutes, their contacts online and offering, and showed them offline at 6 minutes (CLI bug hunt, 2026-10-13).
|
|
893
|
+
*/
|
|
894
|
+
var READ_TURN_MS = 5e3;
|
|
895
|
+
/**
|
|
896
|
+
* A 1:1 chat's read the budget held back this long without a break is starved: it goes before the writes that wait
|
|
897
|
+
* (`WRITE_FIRST_MS`) and past the background share, within the minute, the key held back longest first. A daemon back
|
|
898
|
+
* after a restart spends its whole minute on writes (each chat's and edge's offer, its packets after the attempt), which
|
|
899
|
+
* go first, and requests free in bursts as the minute's first requests age out: every burst went to the writes that
|
|
900
|
+
* waited, and the reads of the chats that were not in an attempt never had their turn (CLI restart lab, 2026-10-13).
|
|
901
|
+
*/
|
|
902
|
+
var READ_STARVED_MS = 6e4;
|
|
874
903
|
/** How long a read that let an older one go first waits before it asks again. */
|
|
875
904
|
var TURN_WAIT_MS = 1e3;
|
|
876
905
|
/**
|
|
@@ -990,6 +1019,8 @@ var RelayTransport = class {
|
|
|
990
1019
|
* it is under the watch share (`WATCH_SHARE`) and while the groups' reads are rationed (`GROUP_RATION_WINDOW_MS`).
|
|
991
1020
|
*/
|
|
992
1021
|
readTurns = /* @__PURE__ */ new Map();
|
|
1022
|
+
/** Per key read in turn (`isTurnRead`), when a read of it last went out and when it last asked (`READ_TURN_MS`). */
|
|
1023
|
+
keyTurns = /* @__PURE__ */ new Map();
|
|
993
1024
|
/** Groups' reads (not background ones) on each relay in the last `GROUP_RATION_WINDOW_MS`. */
|
|
994
1025
|
spentGroupReads = /* @__PURE__ */ new Map();
|
|
995
1026
|
/** When a group's read last found each relay's minute full: its reads there are rationed for `GROUP_RATION_MS`. */
|
|
@@ -1009,6 +1040,11 @@ var RelayTransport = class {
|
|
|
1009
1040
|
/** Per relay and key, the newest packet that relay held back for its budget while another relay took it (`catchUpLater`). */
|
|
1010
1041
|
catchUp = /* @__PURE__ */ new Map();
|
|
1011
1042
|
catchUpTimer = null;
|
|
1043
|
+
/**
|
|
1044
|
+
* Per key whose reads the budget held back since its last read went out: when a request frees for it
|
|
1045
|
+
* (`readHeldBackFor`), how many times in a row, and since when (`READ_STARVED_MS`).
|
|
1046
|
+
*/
|
|
1047
|
+
heldBack = /* @__PURE__ */ new Map();
|
|
1012
1048
|
/** When the relays last answered a read of each key, for `FRESH_READ_MS`. */
|
|
1013
1049
|
readAt = /* @__PURE__ */ new Map();
|
|
1014
1050
|
freshReadMs;
|
|
@@ -1398,8 +1434,14 @@ var RelayTransport = class {
|
|
|
1398
1434
|
}
|
|
1399
1435
|
const resting = this.relays.every((r) => this.isCoolingDown(r, "GET") || this.breaker.blockedFor(r) > 0 || this.heldFor(r, who, Date.now(), pubKeyZ32) > 0);
|
|
1400
1436
|
const known = down ? this.newest.get(pubKeyZ32) : newerPacket(this.newest.get(pubKeyZ32), this.written.get(pubKeyZ32));
|
|
1401
|
-
if (resting && known)
|
|
1402
|
-
|
|
1437
|
+
if (resting && known) {
|
|
1438
|
+
if (!down && budgetWait < Infinity) this.heldBackUntil(pubKeyZ32, Date.now() + budgetWait);
|
|
1439
|
+
return known;
|
|
1440
|
+
}
|
|
1441
|
+
if (!down && budgetWait < Infinity) {
|
|
1442
|
+
this.heldBackUntil(pubKeyZ32, Date.now() + budgetWait);
|
|
1443
|
+
throw new DiscoveryBudgetError(budgetWait);
|
|
1444
|
+
}
|
|
1403
1445
|
throw new Error("No Pkarr relay reachable");
|
|
1404
1446
|
}
|
|
1405
1447
|
/** The order a key's read asks the relays in: in turn per key (see `resolve`), relays found slow lately last. */
|
|
@@ -1466,9 +1508,25 @@ var RelayTransport = class {
|
|
|
1466
1508
|
readAnsweredAt(pubKeyZ32) {
|
|
1467
1509
|
return this.readAt.get(pubKeyZ32);
|
|
1468
1510
|
}
|
|
1511
|
+
readHeldBackFor(pubKeyZ32) {
|
|
1512
|
+
const held = this.heldBack.get(pubKeyZ32);
|
|
1513
|
+
const left = held === void 0 ? 0 : held.until - Date.now();
|
|
1514
|
+
return left > 0 ? left : void 0;
|
|
1515
|
+
}
|
|
1516
|
+
heldBackUntil(pubKeyZ32, until) {
|
|
1517
|
+
const held = this.heldBack.get(pubKeyZ32), times = (held?.times ?? 0) + 1;
|
|
1518
|
+
this.heldBack.delete(pubKeyZ32);
|
|
1519
|
+
this.heldBack.set(pubKeyZ32, {
|
|
1520
|
+
until,
|
|
1521
|
+
times,
|
|
1522
|
+
since: held?.since ?? Date.now()
|
|
1523
|
+
});
|
|
1524
|
+
if (this.heldBack.size > READ_TURNS_KEPT) this.heldBack.delete(this.heldBack.keys().next().value);
|
|
1525
|
+
}
|
|
1469
1526
|
/** The relays answered a read of this key now; answers older than `FRESH_READ_MS` are dropped as the list grows. */
|
|
1470
1527
|
answeredRead(pubKeyZ32) {
|
|
1471
1528
|
const now = Date.now();
|
|
1529
|
+
this.heldBack.delete(pubKeyZ32);
|
|
1472
1530
|
this.readAt.delete(pubKeyZ32);
|
|
1473
1531
|
this.readAt.set(pubKeyZ32, now);
|
|
1474
1532
|
if (this.readAt.size > 64) for (const [key, at] of this.readAt) {
|
|
@@ -1584,6 +1642,7 @@ var RelayTransport = class {
|
|
|
1584
1642
|
if (isChat(who)) this.chatNeed(key).urgentAt = now;
|
|
1585
1643
|
}
|
|
1586
1644
|
if (isGroupRead(who)) this.readAsked(key, who, now);
|
|
1645
|
+
if (isTurnRead(who)) this.turnAsked(key, who, now);
|
|
1587
1646
|
if (this.heldFor(relay, who, now, key) > 0) {
|
|
1588
1647
|
if (isGroupRead(who) && (this.spent.get(relay)?.length ?? 0) >= this.limitOf(relay)) this.groupsFullAt.set(relay, now);
|
|
1589
1648
|
if (isChat(who)) this.chatNeed(key).refused.set(relay, now);
|
|
@@ -1607,6 +1666,12 @@ var RelayTransport = class {
|
|
|
1607
1666
|
lane: watchLane(who)
|
|
1608
1667
|
});
|
|
1609
1668
|
}
|
|
1669
|
+
if (isTurnRead(who)) this.keyTurns.set(key, {
|
|
1670
|
+
servedAt: now,
|
|
1671
|
+
askedAt: now,
|
|
1672
|
+
waiting: false,
|
|
1673
|
+
who
|
|
1674
|
+
});
|
|
1610
1675
|
return true;
|
|
1611
1676
|
}
|
|
1612
1677
|
/**
|
|
@@ -1618,7 +1683,7 @@ var RelayTransport = class {
|
|
|
1618
1683
|
* signaling goes over the limit by its allowance (`SIGNALING_ALLOWANCE_SHARE`), and so do a few of its reads of a
|
|
1619
1684
|
* contact that just went away (`DEPARTED_READS`).
|
|
1620
1685
|
*/
|
|
1621
|
-
heldFor(relay, who, now = Date.now(), key) {
|
|
1686
|
+
heldFor(relay, who, now = Date.now(), key, turns = true) {
|
|
1622
1687
|
const recent = (this.spent.get(relay) ?? []).filter((at) => now - at < 6e4);
|
|
1623
1688
|
const recentBackground = (this.spentBackground.get(relay) ?? []).filter((at) => now - at < 6e4);
|
|
1624
1689
|
this.spent.set(relay, recent);
|
|
@@ -1628,10 +1693,11 @@ var RelayTransport = class {
|
|
|
1628
1693
|
const departed = isDepartedRead(who) && key !== void 0 && recent.length >= limit && this.departedReads(key, now) < 6;
|
|
1629
1694
|
const share = isChatSignal(who) || departed ? SIGNALING_ALLOWANCE_SHARE : isGroupSignal(who) ? GROUP_SIGNALING_ALLOWANCE_SHARE : 0;
|
|
1630
1695
|
let wait = over(recent, limit + (share ? Math.max(0, Math.floor(limit * share) - (who.write ? 0 : 1)) : 0));
|
|
1696
|
+
const starved = key !== void 0 && this.starved(key, who, now);
|
|
1631
1697
|
const groupOver = (this.spentGroupOver.get(relay) ?? []).filter((at) => now - at < GROUP_OVER_PACE_MS);
|
|
1632
1698
|
this.spentGroupOver.set(relay, groupOver);
|
|
1633
1699
|
if (isGroupSignal(who) && !who.write && recent.length >= limit && groupOver.length > 0) wait = Math.max(wait, groupOver[groupOver.length - 1] + GROUP_OVER_PACE_MS - now);
|
|
1634
|
-
if (who.background) {
|
|
1700
|
+
if (who.background && !starved) {
|
|
1635
1701
|
wait = Math.max(wait, over(recentBackground, this.backgroundPerMinute));
|
|
1636
1702
|
const signaling = this.urgentAt + SIGNALING_WINDOW_MS - now;
|
|
1637
1703
|
if (signaling > 0 && !who.door) wait = Math.max(wait, Math.min(signaling, over(recentBackground, this.backgroundWhileSignaling)));
|
|
@@ -1666,8 +1732,9 @@ var RelayTransport = class {
|
|
|
1666
1732
|
const at = this.writeWaiting.get(`${lane} ${relay}`);
|
|
1667
1733
|
return at !== void 0 && now - at < 5e3 ? at + WRITE_FIRST_MS - now : 0;
|
|
1668
1734
|
};
|
|
1669
|
-
if (writer !== "chat" && !departed) wait = Math.max(wait, waiting("chat"));
|
|
1670
|
-
if (writer === null && who.group && !pastLimit || who.background) wait = Math.max(wait, waiting("group"));
|
|
1735
|
+
if (writer !== "chat" && !departed && !starved) wait = Math.max(wait, waiting("chat"));
|
|
1736
|
+
if ((writer === null && who.group && !pastLimit || who.background) && !starved) wait = Math.max(wait, waiting("group"));
|
|
1737
|
+
if (turns && wait <= 0 && isTurnRead(who) && this.turnAhead(relay, key, who, now)) wait = TURN_WAIT_MS;
|
|
1671
1738
|
return Math.max(wait, 0);
|
|
1672
1739
|
}
|
|
1673
1740
|
/** A group's read of `key` asks: it waits for its turn from now on, unless it is taken (`readsAhead`). */
|
|
@@ -1697,6 +1764,39 @@ var RelayTransport = class {
|
|
|
1697
1764
|
}
|
|
1698
1765
|
return ahead;
|
|
1699
1766
|
}
|
|
1767
|
+
/** A read of `key` that takes turns asks: it waits for its turn from now on, unless it is taken (`turnAhead`). */
|
|
1768
|
+
turnAsked(key, who, now) {
|
|
1769
|
+
for (const [other, turn] of this.keyTurns) if (now - Math.max(turn.askedAt, turn.servedAt) >= 6e4) this.keyTurns.delete(other);
|
|
1770
|
+
this.keyTurns.set(key, {
|
|
1771
|
+
servedAt: this.keyTurns.get(key)?.servedAt ?? -Infinity,
|
|
1772
|
+
askedAt: now,
|
|
1773
|
+
waiting: true,
|
|
1774
|
+
who
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Whether a key read in turn longer ago than `key`, still asking, would take this relay's next request: one read in the
|
|
1779
|
+
* background for a background read, and a chat's that is starved (`READ_STARVED_MS`) for any. One asks again after
|
|
1780
|
+
* the budget held it back (a link's poll does, a one-off look does not), and keeps asking within `READ_TURN_MS` of its
|
|
1781
|
+
* last ask, or of when it was told the budget frees a request for it (`readHeldBackFor`).
|
|
1782
|
+
* One the budget holds back on its own account (a group's read, for the chats' reserve) keeps no turn: a group key
|
|
1783
|
+
* waiting for the chats held every chat's read back (CLI lab, 2026-10-13).
|
|
1784
|
+
*/
|
|
1785
|
+
turnAhead(relay, key, who, now) {
|
|
1786
|
+
const mine = key === void 0 ? -Infinity : this.keyTurns.get(key)?.servedAt ?? -Infinity;
|
|
1787
|
+
for (const [other, turn] of this.keyTurns) {
|
|
1788
|
+
const held = this.heldBack.get(other);
|
|
1789
|
+
if (other === key || !turn.waiting || turn.servedAt >= mine || !held || held.times < 2) continue;
|
|
1790
|
+
if (!(who.background && turn.who.background) && !this.starved(other, turn.who, now)) continue;
|
|
1791
|
+
if (now - Math.max(turn.askedAt, held.until) < READ_TURN_MS && this.heldFor(relay, turn.who, now, other, false) === 0) return true;
|
|
1792
|
+
}
|
|
1793
|
+
return false;
|
|
1794
|
+
}
|
|
1795
|
+
/** A 1:1 chat's read of `key` the budget has held back for `READ_STARVED_MS` without a break. */
|
|
1796
|
+
starved(key, who, now) {
|
|
1797
|
+
const since = this.heldBack.get(key)?.since;
|
|
1798
|
+
return !who.group && !who.write && !who.door && since !== void 0 && now - since >= READ_STARVED_MS;
|
|
1799
|
+
}
|
|
1700
1800
|
/** This contact's departed reads past the limit in the last minute (`DEPARTED_READS`). */
|
|
1701
1801
|
departedReads(key, now) {
|
|
1702
1802
|
const reads = (this.departedOver.get(key) ?? []).filter((at) => now - at < 6e4);
|
|
@@ -6096,13 +6196,15 @@ var LinkSession = class {
|
|
|
6096
6196
|
nextInMs: 0
|
|
6097
6197
|
});
|
|
6098
6198
|
const started = Date.now();
|
|
6199
|
+
let heldBackMs;
|
|
6099
6200
|
try {
|
|
6100
6201
|
const pace = this.pace();
|
|
6101
6202
|
const signal = pace === "fast" && this.fastStepsAfter > 0 && this.rtcSignalOut !== null;
|
|
6102
6203
|
const watch = !signal && this.watching();
|
|
6103
6204
|
const departed = !signal && Date.now() < this.departedUntil;
|
|
6205
|
+
const background = pace === "background" || pace === "connected";
|
|
6104
6206
|
const packet = await this.transport.resolve(this.peerPubKeyZ32, {
|
|
6105
|
-
background
|
|
6207
|
+
background,
|
|
6106
6208
|
urgent: pace === "fast",
|
|
6107
6209
|
...signal && { signal },
|
|
6108
6210
|
...watch && { watch },
|
|
@@ -6117,6 +6219,7 @@ var LinkSession = class {
|
|
|
6117
6219
|
const read = answered !== void 0 && answered >= started;
|
|
6118
6220
|
if (read) this.lastReadAt = started;
|
|
6119
6221
|
this.readMissed = !!this.transport.readAnsweredAt && !read;
|
|
6222
|
+
if (this.readMissed) heldBackMs = this.transport.readHeldBackFor?.(this.peerPubKeyZ32);
|
|
6120
6223
|
let receivedNew = false;
|
|
6121
6224
|
const batch = packet ? parseLinkRecords(packet, this.encKey) : null;
|
|
6122
6225
|
if (batch && isEmptyLinkPacket(batch)) {
|
|
@@ -6205,7 +6308,8 @@ var LinkSession = class {
|
|
|
6205
6308
|
ms: Date.now() - started,
|
|
6206
6309
|
error: String(error)
|
|
6207
6310
|
});
|
|
6208
|
-
if (
|
|
6311
|
+
if (isDiscoveryBudgetError(error)) heldBackMs = error.retryInMs;
|
|
6312
|
+
else {
|
|
6209
6313
|
this.discoveryResult("read", error);
|
|
6210
6314
|
this.events.onStatus?.("error");
|
|
6211
6315
|
}
|
|
@@ -6223,7 +6327,7 @@ var LinkSession = class {
|
|
|
6223
6327
|
this.poll();
|
|
6224
6328
|
} else if (this.running && !this.pollTimer) {
|
|
6225
6329
|
const interval = this.nextInterval();
|
|
6226
|
-
const wait = Math.max(MIN_POLL_GAP_MS, interval - (Date.now() - started));
|
|
6330
|
+
const wait = heldBackMs !== void 0 ? Math.min(interval, Math.max(BUDGET_RETRY_MIN_MS, heldBackMs)) : Math.max(MIN_POLL_GAP_MS, interval - (Date.now() - started));
|
|
6227
6331
|
this.events.onPoll?.({
|
|
6228
6332
|
polling: false,
|
|
6229
6333
|
nextInMs: wait
|
|
@@ -9177,6 +9281,13 @@ var DHT_EXPIRY_SKEW_MS = PKARR_FUTURE_SKEW_MS;
|
|
|
9177
9281
|
*/
|
|
9178
9282
|
var DHT_ISSUED_BACK_MS = 12e4;
|
|
9179
9283
|
var MAX_ATTEMPTS = 8;
|
|
9284
|
+
/**
|
|
9285
|
+
* Envelope sequences are set aside this many at a time (`DhtDeliveryState.sequence` is the highest set aside): a
|
|
9286
|
+
* keep-alive, every four minutes per chat, and one the relays' budget turns away, every 30 s on a profile idling at
|
|
9287
|
+
* it, then cost no disk write of their own. A reader only needs the sequence to grow, so those a restart skips cost
|
|
9288
|
+
* nothing; one the budget turned away is not used again, as it may have gone out on the DHT (the CLI puts there too).
|
|
9289
|
+
*/
|
|
9290
|
+
var SEQUENCE_BLOCK = 64;
|
|
9180
9291
|
/** The wait before publication attempt `attempts + 1` of a text, or of a receipt the contact still asks for. */
|
|
9181
9292
|
var backoff = (attempts) => Math.min(6e4, 4e3 * 2 ** attempts);
|
|
9182
9293
|
/** The contact's mailbox is read this often while either side is DHT-only, and otherwise… */
|
|
@@ -9230,6 +9341,10 @@ var emptyDhtDeliveryState = () => ({
|
|
|
9230
9341
|
var DhtDelivery = class DhtDelivery {
|
|
9231
9342
|
options;
|
|
9232
9343
|
state;
|
|
9344
|
+
/** The state as last saved: a save that would write the same row again is skipped. */
|
|
9345
|
+
saved;
|
|
9346
|
+
/** The sequence of the last envelope signed here; the state's is the highest set aside (`SEQUENCE_BLOCK`). */
|
|
9347
|
+
sequence;
|
|
9233
9348
|
mode;
|
|
9234
9349
|
running = false;
|
|
9235
9350
|
ticking = false;
|
|
@@ -9284,6 +9399,8 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9284
9399
|
const { peerRejected: _peerRejected, ...state } = structuredClone(options.state ?? emptyDhtDeliveryState());
|
|
9285
9400
|
this.state = state;
|
|
9286
9401
|
this.mode = options.mode;
|
|
9402
|
+
this.sequence = state.sequence;
|
|
9403
|
+
if (options.state) this.saved = JSON.stringify(state);
|
|
9287
9404
|
this.unsealedOut = state.sequence > 0 && !options.credentials.peerKey;
|
|
9288
9405
|
this.from = identityFromSeedB64(options.params.seedB64).pubKeyZ32;
|
|
9289
9406
|
this.to = options.params.peerPubKeyZ32;
|
|
@@ -9372,7 +9489,11 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9372
9489
|
return operation;
|
|
9373
9490
|
}
|
|
9374
9491
|
async persist(next) {
|
|
9375
|
-
|
|
9492
|
+
const json = JSON.stringify(next);
|
|
9493
|
+
if (json !== this.saved) {
|
|
9494
|
+
await this.options.save(structuredClone(next));
|
|
9495
|
+
this.saved = json;
|
|
9496
|
+
}
|
|
9376
9497
|
this.state = next;
|
|
9377
9498
|
}
|
|
9378
9499
|
sealedKey(peer) {
|
|
@@ -9547,7 +9668,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9547
9668
|
}
|
|
9548
9669
|
packetError(message, expires, reply, edit) {
|
|
9549
9670
|
try {
|
|
9550
|
-
this.records(this.body(this.
|
|
9671
|
+
this.records(this.body(this.sequence + 1, Date.now(), expires, message, this.state.receipt?.id ?? "abcdefghijklmnopqrstuv", reply, [], edit), this.options.credentials.peerKey ?? this.participation.pubKeyZ32);
|
|
9551
9672
|
return null;
|
|
9552
9673
|
} catch (error) {
|
|
9553
9674
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -9555,7 +9676,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9555
9676
|
}
|
|
9556
9677
|
/** Whether a text with its hop count fits the packet and what a reader reads (`MAX_ENVELOPE_PLAINTEXT`). */
|
|
9557
9678
|
roomForHops(message, expires, reply, forwarded) {
|
|
9558
|
-
const body = this.body(this.
|
|
9679
|
+
const body = this.body(this.sequence + 1, Date.now(), expires, message, this.state.receipt?.id ?? "abcdefghijklmnopqrstuv", reply, [], void 0, forwarded);
|
|
9559
9680
|
try {
|
|
9560
9681
|
this.records(body, this.options.credentials.peerKey ?? this.participation.pubKeyZ32);
|
|
9561
9682
|
} catch {
|
|
@@ -9634,7 +9755,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9634
9755
|
], expires = now + DHT_MESSAGE_TTL;
|
|
9635
9756
|
const hops = readForwarded(forwarded);
|
|
9636
9757
|
const fits = (element, withReply, withHops) => {
|
|
9637
|
-
const body = this.body(this.
|
|
9758
|
+
const body = this.body(this.sequence + 1, now, expires, message, this.state.receipt?.id ?? "abcdefghijklmnopqrstuv", withReply ? reply : void 0, [], void 0, withHops ? hops : void 0, element);
|
|
9638
9759
|
try {
|
|
9639
9760
|
this.records(body, this.options.credentials.peerKey ?? this.participation.pubKeyZ32);
|
|
9640
9761
|
} catch {
|
|
@@ -9823,13 +9944,14 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9823
9944
|
if (keepAlive && this.controlDue > now) return;
|
|
9824
9945
|
const expires = pending?.expires ?? now - 12e4 + CONTROL_TTL;
|
|
9825
9946
|
const issued = Math.min(now, Math.max(now - DHT_ISSUED_BACK_MS, expires - (pending ? DHT_MESSAGE_TTL : CONTROL_TTL), (pending?.message[1] ?? 0) - 3e4));
|
|
9826
|
-
const { body, records, reactions } = this.fitted(this.
|
|
9947
|
+
const { body, records, reactions } = this.fitted(this.sequence + 1, issued, expires, pending?.message ?? null, receipt?.id ?? null, pending?.reply, pending?.edit, pending?.forwarded, pending?.file);
|
|
9827
9948
|
const reactionsWereDue = this.reactionsDue;
|
|
9828
9949
|
this.reactionsDue = !reactions && !!this.options.reactions?.().length;
|
|
9829
9950
|
const identity = !reseal && this.state.peerPinned && this.state.peerPinned !== "can" && this.pinned()?.identity || this.identity;
|
|
9951
|
+
const sequence = body[1];
|
|
9830
9952
|
await this.persist({
|
|
9831
9953
|
...this.state,
|
|
9832
|
-
sequence:
|
|
9954
|
+
sequence: sequence > this.state.sequence ? sequence + SEQUENCE_BLOCK - 1 : this.state.sequence,
|
|
9833
9955
|
pending: pending ? {
|
|
9834
9956
|
...pending,
|
|
9835
9957
|
attempts: pending.attempts + 1,
|
|
@@ -9841,6 +9963,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9841
9963
|
next: now + backoff(receipt.attempts)
|
|
9842
9964
|
} : this.state.receipt
|
|
9843
9965
|
});
|
|
9966
|
+
this.sequence = sequence;
|
|
9844
9967
|
const before = {
|
|
9845
9968
|
lastPublish: this.lastPublish,
|
|
9846
9969
|
controlDue: this.controlDue
|
|
@@ -15619,7 +15742,7 @@ var ChatFiles = class {
|
|
|
15619
15742
|
}
|
|
15620
15743
|
return;
|
|
15621
15744
|
case "pf-done":
|
|
15622
|
-
if (record.state !== "verifying") return;
|
|
15745
|
+
if (record.state !== "verifying" && record.confirmed !== record.file.size) return;
|
|
15623
15746
|
record.confirmed = record.file.size;
|
|
15624
15747
|
this.end(outgoing, "done");
|
|
15625
15748
|
return;
|
|
@@ -16846,6 +16969,7 @@ var ENGINE_ERRORS = {
|
|
|
16846
16969
|
notEnoughSats: "Not enough sats in your wallet",
|
|
16847
16970
|
noSharedMint: "You share no mint with this contact",
|
|
16848
16971
|
ecashAlreadySpent: "This ecash was already spent somewhere else",
|
|
16972
|
+
ecashBeingReceived: "This ecash is already being received",
|
|
16849
16973
|
ecashOtherUnit: "This ecash is in {unit}: only sat ecash can be redeemed here",
|
|
16850
16974
|
reviewedEcashSpent: "The ecash for this payment was already spent somewhere else. Nothing was sent, and your balance now shows what the mint still holds.",
|
|
16851
16975
|
reviewedSatsGone: "The sats this payment was reviewed with went to another payment. Nothing was sent: review it again.",
|
|
@@ -16954,8 +17078,6 @@ var PACKET_TRANSPORTS_FAST_ATTEMPTS = 3;
|
|
|
16954
17078
|
var DHT_PIN_GRACE_MS = 1e4;
|
|
16955
17079
|
/** Back after a restart (`resume`), dials wait this long at most for the native endpoint that carried the chat to start. */
|
|
16956
17080
|
var RESUME_WAIT_MS = 1e4;
|
|
16957
|
-
/** Two dials crossing: how long the lower key's side waits for its own to connect before it takes the contact's. */
|
|
16958
|
-
var CROSSED_WAIT_MS = 3e3;
|
|
16959
17081
|
/** A WebRTC attempt under way, not yet open: what a member's packet saying it has no WebRTC ends (`peerPacketTransports`). */
|
|
16960
17082
|
var WEBRTC_ATTEMPT = /* @__PURE__ */ new Set([
|
|
16961
17083
|
"offering",
|
|
@@ -17296,10 +17418,15 @@ var GhostLink = class {
|
|
|
17296
17418
|
* was the app's in the background, not a restart (`resumeAfterAbsence`).
|
|
17297
17419
|
*/
|
|
17298
17420
|
offered;
|
|
17421
|
+
/**
|
|
17422
|
+
* The WebRTC offer this side's dial made last, fallback allowed or not: one made before the contact's record was read
|
|
17423
|
+
* races once it is (`raceStandingOffer`).
|
|
17424
|
+
*/
|
|
17425
|
+
offerMade;
|
|
17299
17426
|
/** The attempt (its epoch) whose direct transports were knocked on as its offer went out (`knockEarly`), and whether that is under way. */
|
|
17300
17427
|
knockedEarly = -1;
|
|
17301
17428
|
knockingEarly = false;
|
|
17302
|
-
/** When
|
|
17429
|
+
/** When this side's offer's answer was applied (`RACE_ANSWERED_MS`); 0 while the data link is not connecting on it. */
|
|
17303
17430
|
answeredAt = 0;
|
|
17304
17431
|
/** This side's offer is out unanswered, or answered and not connected yet: what is ranked after WebRTC may race it. */
|
|
17305
17432
|
get offerOut() {
|
|
@@ -17632,6 +17759,7 @@ var GhostLink = class {
|
|
|
17632
17759
|
channel.close();
|
|
17633
17760
|
return;
|
|
17634
17761
|
}
|
|
17762
|
+
if (this.rtcStartedUnpinned) this.openedUnpinned.add(channel);
|
|
17635
17763
|
const plan = this.switcher.pending;
|
|
17636
17764
|
if (plan?.choices.includes("webrtc/1") && this.paired?.state.status === "ready") this.attachCandidate(channel, void 0, plan).catch(() => {});
|
|
17637
17765
|
else if (this.activeBinding) channel.close();
|
|
@@ -17647,6 +17775,7 @@ var GhostLink = class {
|
|
|
17647
17775
|
const was = this.lastDataLinkState;
|
|
17648
17776
|
traceLink(this.myPubKeyZ32, "datalink", { state });
|
|
17649
17777
|
this.trackDataLink(state);
|
|
17778
|
+
if (state === "offering" || state === "answering") this.rtcStartedUnpinned = !options.pairing?.credentials.peerKey && !options.pairing?.credentials.expectedPeerKey;
|
|
17650
17779
|
if (this.activeBinding) return;
|
|
17651
17780
|
events.onDataLinkState?.(state);
|
|
17652
17781
|
if (state === "open") {
|
|
@@ -17666,9 +17795,9 @@ var GhostLink = class {
|
|
|
17666
17795
|
}
|
|
17667
17796
|
}
|
|
17668
17797
|
this.answeredAt = 0;
|
|
17669
|
-
if (state === "connecting" && was === "offering" && this.
|
|
17798
|
+
if (state === "connecting" && was === "offering" && this.offerMade?.epoch === this.connectionEpoch) {
|
|
17670
17799
|
this.answeredAt = Date.now();
|
|
17671
|
-
this.scheduleRace(this.offered.epoch, this.offered.at);
|
|
17800
|
+
if (this.offered?.epoch === this.connectionEpoch) this.scheduleRace(this.offered.epoch, this.offered.at);
|
|
17672
17801
|
}
|
|
17673
17802
|
if (state !== "idle") return;
|
|
17674
17803
|
this.dialUnseen = was === "offering" && this.offerUnsent;
|
|
@@ -18259,6 +18388,7 @@ var GhostLink = class {
|
|
|
18259
18388
|
}
|
|
18260
18389
|
if (held && heldOn && reidentified.has(heldOn) && this.restartedRedial(held, heldOn)) return;
|
|
18261
18390
|
if (this.redescribedDial()) return;
|
|
18391
|
+
if (!this.dialing) this.raceStandingOffer();
|
|
18262
18392
|
if (this.channel || this.dialing) return;
|
|
18263
18393
|
if (news || !again) {
|
|
18264
18394
|
this.autoConnectFailures = 0;
|
|
@@ -18836,6 +18966,10 @@ var GhostLink = class {
|
|
|
18836
18966
|
...resume && { resume },
|
|
18837
18967
|
...woke && { woke }
|
|
18838
18968
|
} : void 0;
|
|
18969
|
+
this.offerMade = {
|
|
18970
|
+
epoch,
|
|
18971
|
+
at: offeredAt
|
|
18972
|
+
};
|
|
18839
18973
|
const gate = this.dialGate;
|
|
18840
18974
|
this.dialGate = void 0;
|
|
18841
18975
|
const replaced = new Promise((resolve) => {
|
|
@@ -18847,10 +18981,12 @@ var GhostLink = class {
|
|
|
18847
18981
|
traceLink(this.myPubKeyZ32, "dial-held", {});
|
|
18848
18982
|
this.afterRtc = void 0;
|
|
18849
18983
|
this.offered = void 0;
|
|
18984
|
+
this.offerMade = void 0;
|
|
18850
18985
|
return;
|
|
18851
18986
|
}
|
|
18852
18987
|
if (this.dataLink.state !== "idle" || epoch !== this.connectionEpoch) {
|
|
18853
18988
|
this.scheduleRace(epoch, offeredAt);
|
|
18989
|
+
this.raceStandingOffer();
|
|
18854
18990
|
return;
|
|
18855
18991
|
}
|
|
18856
18992
|
this.rtcUnstarted();
|
|
@@ -19016,6 +19152,31 @@ var GhostLink = class {
|
|
|
19016
19152
|
}, Math.max(0, Math.min(...at) - now));
|
|
19017
19153
|
}
|
|
19018
19154
|
/**
|
|
19155
|
+
* This side's offer went out before the contact's record was read (a joiner's early one, `dialEarly`): nothing was
|
|
19156
|
+
* known of what the contact runs, so it was made with no fallback and nothing raced it. Once the record says, and both
|
|
19157
|
+
* sides allow it, the offer races what both run as one made then would: answered and not connected `RACE_ANSWERED_MS`
|
|
19158
|
+
* later, the relayed ones too. Behind a VPN that drops UDP, a first pairing went live over the relayed Iroh only once
|
|
19159
|
+
* the attempt timed out and the next one raced, 32 to 66 s in (zero's VPN lab, 2026-10-13).
|
|
19160
|
+
*/
|
|
19161
|
+
raceStandingOffer() {
|
|
19162
|
+
const made = this.offerMade, epoch = this.connectionEpoch;
|
|
19163
|
+
if (!made || made.epoch !== epoch || this.offered?.epoch === epoch || !this.offerOut || this.channel || this.stopped) return;
|
|
19164
|
+
if (!this.fallback || !this.peerFallback || !this.peerTransports) return;
|
|
19165
|
+
const relayed = relayedTransports(this.localDescriptors(), this.peerDescriptors);
|
|
19166
|
+
const rest = rankTransports(this.transportOffer(), this.peerTransports, relayed).filter((t) => t !== "webrtc/1");
|
|
19167
|
+
if (!rest.length) return;
|
|
19168
|
+
this.offered = {
|
|
19169
|
+
epoch,
|
|
19170
|
+
at: made.at
|
|
19171
|
+
};
|
|
19172
|
+
this.afterRtc = {
|
|
19173
|
+
epoch,
|
|
19174
|
+
rest
|
|
19175
|
+
};
|
|
19176
|
+
traceLink(this.myPubKeyZ32, "race-standing", { transports: rest });
|
|
19177
|
+
this.scheduleRace(epoch, made.at);
|
|
19178
|
+
}
|
|
19179
|
+
/**
|
|
19019
19180
|
* Back after an absence, this side answers an offer the contact made while it was away (it dials whatever its key):
|
|
19020
19181
|
* the contact looks for that answer at the pace of an offer out a while (every 8 s on the relays), and its attempt may
|
|
19021
19182
|
* end before it does. The direct transports both run are knocked on meanwhile, and the relayed ones where both apps
|
|
@@ -19148,6 +19309,7 @@ var GhostLink = class {
|
|
|
19148
19309
|
const left = next.rest.filter((t) => t !== "webrtc/1" && !due.includes(t));
|
|
19149
19310
|
if (!due.length) {
|
|
19150
19311
|
this.scheduleRace(epoch, offeredAt);
|
|
19312
|
+
this.raceStandingOffer();
|
|
19151
19313
|
return;
|
|
19152
19314
|
}
|
|
19153
19315
|
this.afterRtc = left.length ? {
|
|
@@ -19172,6 +19334,7 @@ var GhostLink = class {
|
|
|
19172
19334
|
if (live || epoch !== this.connectionEpoch || this.channel || this.stopped) return;
|
|
19173
19335
|
if (this.dataLink.state !== "idle") {
|
|
19174
19336
|
this.scheduleRace(epoch, offeredAt);
|
|
19337
|
+
this.raceStandingOffer();
|
|
19175
19338
|
return;
|
|
19176
19339
|
}
|
|
19177
19340
|
const rest = this.afterRtc;
|
|
@@ -20247,8 +20410,8 @@ var GhostLink = class {
|
|
|
20247
20410
|
* Both sides dialled at once (two apps back after a restart, each knocking): the lower key's dial carries the chat, on
|
|
20248
20411
|
* both sides; the higher key's side takes it over as a replacement (`attachReplacement`). Here, on the lower key's
|
|
20249
20412
|
* side, the contact's connection waits: dropped once this side's own dial connects, taken if that dial fails, or has
|
|
20250
|
-
* not connected within `CROSSED_WAIT_MS` (
|
|
20251
|
-
*
|
|
20413
|
+
* not connected within `CROSSED_WAIT_MS` (it stalled, or goes to an endpoint the contact no longer runs, for as long
|
|
20414
|
+
* as it takes to give up).
|
|
20252
20415
|
*/
|
|
20253
20416
|
crossed = null;
|
|
20254
20417
|
parkCrossed(channel, binding) {
|
|
@@ -20265,7 +20428,7 @@ var GhostLink = class {
|
|
|
20265
20428
|
this.dialing = false;
|
|
20266
20429
|
this.attempt = null;
|
|
20267
20430
|
this.attach(channel, binding);
|
|
20268
|
-
},
|
|
20431
|
+
}, 500);
|
|
20269
20432
|
channel.onClose = () => {
|
|
20270
20433
|
if (this.crossed?.channel === channel) {
|
|
20271
20434
|
clearTimeout(timer);
|
|
@@ -20347,6 +20510,8 @@ var GhostLink = class {
|
|
|
20347
20510
|
dialedIn = /* @__PURE__ */ new WeakSet();
|
|
20348
20511
|
/** Connections whose session started with no key pinned nor named by the invite. */
|
|
20349
20512
|
openedUnpinned = /* @__PURE__ */ new WeakSet();
|
|
20513
|
+
/** The WebRTC offer under way was made or answered with no key pinned nor named by the invite (its channel opens later). */
|
|
20514
|
+
rtcStartedUnpinned = false;
|
|
20350
20515
|
/**
|
|
20351
20516
|
* `migration`: the channel is a candidate that takes over from the session open now once it authenticated. With a
|
|
20352
20517
|
* `plan`, a live switch both sides agreed; without one, a replacement (`attachReplacement`): the contact dialled
|
|
@@ -25251,6 +25416,12 @@ var byeSigned = (f) => utf8Encode(JSON.stringify([
|
|
|
25251
25416
|
f.e,
|
|
25252
25417
|
f.ts
|
|
25253
25418
|
]));
|
|
25419
|
+
var leftSigned = (commit) => utf8Encode(JSON.stringify([
|
|
25420
|
+
"ghostly-group/1 left",
|
|
25421
|
+
commit.g,
|
|
25422
|
+
commit.e,
|
|
25423
|
+
commitHash(commit)
|
|
25424
|
+
]));
|
|
25254
25425
|
var editSigned = (f) => utf8Encode(JSON.stringify([
|
|
25255
25426
|
"ghostly-group/1 edit",
|
|
25256
25427
|
f.g,
|
|
@@ -25731,11 +25902,43 @@ var GroupSession = class {
|
|
|
25731
25902
|
}];
|
|
25732
25903
|
});
|
|
25733
25904
|
}
|
|
25734
|
-
|
|
25905
|
+
/**
|
|
25906
|
+
* A welcome for a member already in the roster that never got the first one (its chat closed while it was let in):
|
|
25907
|
+
* nothing is committed. It carries the secrets of the epochs it was in, from its admission on, sealed to its key.
|
|
25908
|
+
*/
|
|
25909
|
+
rewelcome(memberKey) {
|
|
25735
25910
|
return this.serialize(async () => {
|
|
25736
25911
|
this.requireAdmin();
|
|
25737
25912
|
if (!rosterHas(this.roster, memberKey) || memberKey === this.myKey) throw new Error("Not a member");
|
|
25738
|
-
|
|
25913
|
+
const chain = this.state.chain;
|
|
25914
|
+
let admitted = chain.length - 1;
|
|
25915
|
+
while (admitted >= 0 && !(chain[admitted].k === "add" && chain[admitted].s === memberKey)) admitted--;
|
|
25916
|
+
if (admitted < 0) throw new Error("Not a member");
|
|
25917
|
+
const secrets = chain.slice(admitted).filter((c) => this.state.secrets[c.e] && rosterHas(c.m, memberKey)).map((c) => ({
|
|
25918
|
+
e: c.e,
|
|
25919
|
+
s: sealSecret(memberKey, this.secret(c.e), secretAad$1(this.id, c.e, memberKey))
|
|
25920
|
+
}));
|
|
25921
|
+
const pieces = chainPieces(chain);
|
|
25922
|
+
const tail = pieces.pop();
|
|
25923
|
+
return [...pieces.map((commits) => ({
|
|
25924
|
+
t: "group-chain",
|
|
25925
|
+
g: this.id,
|
|
25926
|
+
commits
|
|
25927
|
+
})), {
|
|
25928
|
+
t: "group-welcome",
|
|
25929
|
+
g: this.id,
|
|
25930
|
+
name: this.name,
|
|
25931
|
+
commits: tail,
|
|
25932
|
+
secrets
|
|
25933
|
+
}];
|
|
25934
|
+
});
|
|
25935
|
+
}
|
|
25936
|
+
/** `left`: the member asked to leave (on the admin's chat), and the commit says so (`GroupCommitFrame.left`). */
|
|
25937
|
+
remove(memberKey, now = Date.now(), { left = false } = {}) {
|
|
25938
|
+
return this.serialize(async () => {
|
|
25939
|
+
this.requireAdmin();
|
|
25940
|
+
if (!rosterHas(this.roster, memberKey) || memberKey === this.myKey) throw new Error("Not a member");
|
|
25941
|
+
await this.commit("remove", memberKey, now, left);
|
|
25739
25942
|
});
|
|
25740
25943
|
}
|
|
25741
25944
|
transferAdmin(memberKey, now = Date.now()) {
|
|
@@ -25752,7 +25955,7 @@ var GroupSession = class {
|
|
|
25752
25955
|
await this.commit("rotate", void 0, now);
|
|
25753
25956
|
});
|
|
25754
25957
|
}
|
|
25755
|
-
async commit(kind, subject, now) {
|
|
25958
|
+
async commit(kind, subject, now, left = false) {
|
|
25756
25959
|
if (this.hooks.adminWork?.() === false) throw new Error(GROUP_ADMIN_OFF_ERROR);
|
|
25757
25960
|
if (this.hooks.adminTurn && !await this.hooks.adminTurn()) throw engineError("groupTurnUnconfirmed");
|
|
25758
25961
|
if (this.state.chain.length >= 1024) throw new Error("This group has reached its membership history limit. Create a new group.");
|
|
@@ -25776,6 +25979,8 @@ var GroupSession = class {
|
|
|
25776
25979
|
...draft,
|
|
25777
25980
|
c: confirmationTag(epochKeys(secret, this.id, epoch).confirm, commitUntaggedHash(draft))
|
|
25778
25981
|
}, this.identity.seed);
|
|
25982
|
+
const mark = left && kind === "remove" ? toBase64Url(sign(leftSigned(commit), this.identity.seed)) : void 0;
|
|
25983
|
+
if (mark) (this.state.leftMarks ??= {})[epoch] = mark;
|
|
25779
25984
|
this.state.chain.push(commit);
|
|
25780
25985
|
this.chainMovedAt = this.clock();
|
|
25781
25986
|
this.state.secrets[epoch] = toBase64Url(secret);
|
|
@@ -25791,7 +25996,8 @@ var GroupSession = class {
|
|
|
25791
25996
|
t: "group-commit",
|
|
25792
25997
|
g: this.id,
|
|
25793
25998
|
commit,
|
|
25794
|
-
...member ? { secret: sealSecret(key, secret, secretAad$1(this.id, epoch, key)) } : {}
|
|
25999
|
+
...member ? { secret: sealSecret(key, secret, secretAad$1(this.id, epoch, key)) } : {},
|
|
26000
|
+
...mark ? { left: mark } : {}
|
|
25795
26001
|
});
|
|
25796
26002
|
}
|
|
25797
26003
|
this.hooks.changed();
|
|
@@ -26191,7 +26397,7 @@ var GroupSession = class {
|
|
|
26191
26397
|
case "group-bye":
|
|
26192
26398
|
await this.receiveBye(from, raw);
|
|
26193
26399
|
break;
|
|
26194
|
-
case "group-leave": if (this.isAdmin && this.hooks.adminWork?.() !== false && rosterHas(this.roster, from) && from !== this.myKey) await this.commitUnlessTurnUnconfirmed("remove", from);
|
|
26400
|
+
case "group-leave": if (this.isAdmin && this.hooks.adminWork?.() !== false && rosterHas(this.roster, from) && from !== this.myKey) await this.commitUnlessTurnUnconfirmed("remove", from, true);
|
|
26195
26401
|
}
|
|
26196
26402
|
return this.state.status === "active" ? this.passOn : this.state.status === "removed" ? this.passOn.filter((f) => f.t === "group-commit") : [];
|
|
26197
26403
|
} finally {
|
|
@@ -26306,7 +26512,7 @@ var GroupSession = class {
|
|
|
26306
26512
|
return;
|
|
26307
26513
|
}
|
|
26308
26514
|
if (bye.e < this.admittedAt(bye.k)) return;
|
|
26309
|
-
if (this.isAdmin && this.hooks.adminWork?.() !== false && await this.commitUnlessTurnUnconfirmed("remove", bye.k)) return;
|
|
26515
|
+
if (this.isAdmin && this.hooks.adminWork?.() !== false && await this.commitUnlessTurnUnconfirmed("remove", bye.k, true)) return;
|
|
26310
26516
|
if (from !== bye.k && this.byesPassed.get(bye.k)?.sig === bye.sig) return;
|
|
26311
26517
|
this.byesPassed.set(bye.k, bye);
|
|
26312
26518
|
this.took(bye);
|
|
@@ -26331,9 +26537,9 @@ var GroupSession = class {
|
|
|
26331
26537
|
* An automatic commit (a member's leave): made, or not made when no fresh turn read says this device is the active
|
|
26332
26538
|
* one (`adminTurn`). Then false, and the caller goes on as a member would. Any other refusal is thrown as before.
|
|
26333
26539
|
*/
|
|
26334
|
-
async commitUnlessTurnUnconfirmed(kind, subject) {
|
|
26540
|
+
async commitUnlessTurnUnconfirmed(kind, subject, left = false) {
|
|
26335
26541
|
try {
|
|
26336
|
-
await this.commit(kind, subject, Date.now());
|
|
26542
|
+
await this.commit(kind, subject, Date.now(), left);
|
|
26337
26543
|
return true;
|
|
26338
26544
|
} catch (error) {
|
|
26339
26545
|
if (error instanceof EngineError && error.engineCode === "groupTurnUnconfirmed") return false;
|
|
@@ -26595,6 +26801,7 @@ var GroupSession = class {
|
|
|
26595
26801
|
const result = verifyCommit(raw, this.state.chain[e - 1] ?? null, this.id);
|
|
26596
26802
|
if ("error" in result || result.commit.by !== known.by) return;
|
|
26597
26803
|
if (commitHash(result.commit) !== commitHash(known)) await this.fork(`Member ${from.slice(0, 8)} holds a different membership history for epoch ${e}`);
|
|
26804
|
+
else if (this.takeLeftMark(known, frame.left)) await this.persist();
|
|
26598
26805
|
return;
|
|
26599
26806
|
}
|
|
26600
26807
|
if (e > this.epoch + 1) {
|
|
@@ -26608,11 +26815,13 @@ var GroupSession = class {
|
|
|
26608
26815
|
if (result.error.startsWith("Commit does not follow") && verifyCommitSignature(raw)?.by === this.admin) await this.fork(`Member ${from.slice(0, 8)} holds a different membership history for epoch ${e}`);
|
|
26609
26816
|
return;
|
|
26610
26817
|
}
|
|
26818
|
+
const mark = this.takeLeftMark(result.commit, frame.left) ? frame.left : void 0;
|
|
26611
26819
|
await this.apply(result.commit, frame.secret);
|
|
26612
26820
|
this.took({
|
|
26613
26821
|
t: "group-commit",
|
|
26614
26822
|
g: this.id,
|
|
26615
|
-
commit: result.commit
|
|
26823
|
+
commit: result.commit,
|
|
26824
|
+
...mark ? { left: mark } : {}
|
|
26616
26825
|
});
|
|
26617
26826
|
if (this.state.status === "active" && !this.state.secrets[this.epoch]) this.ask(from);
|
|
26618
26827
|
const next = this.pendingCommits.get(this.epoch + 1);
|
|
@@ -26621,6 +26830,17 @@ var GroupSession = class {
|
|
|
26621
26830
|
await this.receiveCommit(from, next);
|
|
26622
26831
|
}
|
|
26623
26832
|
}
|
|
26833
|
+
/** Keeps the admin's mark on a commit that took out a member who left (`GroupCommitFrame.left`) when it holds; true when new. */
|
|
26834
|
+
takeLeftMark(commit, mark) {
|
|
26835
|
+
if (commit.k !== "remove" || typeof mark !== "string" || mark.length !== 86 || !B64$1.test(mark) || this.state.leftMarks?.[commit.e]) return false;
|
|
26836
|
+
try {
|
|
26837
|
+
if (!verify(fromBase64Url(mark), leftSigned(commit), publicKeyFromZ32(commit.by))) return false;
|
|
26838
|
+
} catch {
|
|
26839
|
+
return false;
|
|
26840
|
+
}
|
|
26841
|
+
(this.state.leftMarks ??= {})[commit.e] = mark;
|
|
26842
|
+
return true;
|
|
26843
|
+
}
|
|
26624
26844
|
async apply(commit, sealed) {
|
|
26625
26845
|
this.state.chain.push(commit);
|
|
26626
26846
|
this.chainMovedAt = this.clock();
|
|
@@ -26683,11 +26903,13 @@ var GroupSession = class {
|
|
|
26683
26903
|
const answer = [];
|
|
26684
26904
|
for (let e = frame.e + 1; e <= this.epoch; e++) {
|
|
26685
26905
|
const commit = this.state.chain[e], secret = this.secret(e);
|
|
26906
|
+
const left = this.state.leftMarks?.[e];
|
|
26686
26907
|
answer.push({
|
|
26687
26908
|
t: "group-commit",
|
|
26688
26909
|
g: this.id,
|
|
26689
26910
|
commit,
|
|
26690
|
-
...secret && rosterHas(commit.m, from) ? { secret: sealSecret(from, secret, secretAad$1(this.id, e, from)) } : {}
|
|
26911
|
+
...secret && rosterHas(commit.m, from) ? { secret: sealSecret(from, secret, secretAad$1(this.id, e, from)) } : {},
|
|
26912
|
+
...left ? { left } : {}
|
|
26691
26913
|
});
|
|
26692
26914
|
}
|
|
26693
26915
|
const theirs = new Set(Array.isArray(frame.secrets) ? frame.secrets.filter((n) => Number.isSafeInteger(n)) : []);
|
|
@@ -27677,6 +27899,15 @@ var CommunitySession = class {
|
|
|
27677
27899
|
return -1;
|
|
27678
27900
|
}
|
|
27679
27901
|
/**
|
|
27902
|
+
* Someone the chain removed (not one who left) and who is not back in: nothing it signs is heard any more, even for
|
|
27903
|
+
* an epoch it was in. What one who left wrote while in the group is still taken and handed on, for the epochs it was
|
|
27904
|
+
* in (WISP 903 § Leaving and removal): its goodbye reaches the members who were away.
|
|
27905
|
+
*/
|
|
27906
|
+
wasExpelled(key) {
|
|
27907
|
+
const i = this.outIndex(key);
|
|
27908
|
+
return i >= 0 && this.state.chain[i].k === "remove";
|
|
27909
|
+
}
|
|
27910
|
+
/**
|
|
27680
27911
|
* What someone the chain took out is told when it comes back (WISP 903 § Leaving and removal): the commits from where
|
|
27681
27912
|
* it says it is (`sync`: its tip, or its locator) up to the one that took it out, and nothing after. Later commits
|
|
27682
27913
|
* are not its business (a `link` commit names the new entry key); no secret, no stored frame, no seed goes with them.
|
|
@@ -28639,7 +28870,7 @@ var CommunitySession = class {
|
|
|
28639
28870
|
return false;
|
|
28640
28871
|
}
|
|
28641
28872
|
if (!verify(fromBase64Url(raw.sig), messageSigned(raw), publicKeyFromZ32(raw.s))) return false;
|
|
28642
|
-
if (this.
|
|
28873
|
+
if (this.wasExpelled(raw.s)) return false;
|
|
28643
28874
|
const found = this.commitByShort(raw.e, raw.h);
|
|
28644
28875
|
if (!found) {
|
|
28645
28876
|
this.park(from, raw);
|
|
@@ -28883,7 +29114,7 @@ var CommunitySession = class {
|
|
|
28883
29114
|
if (stored.s === from) continue;
|
|
28884
29115
|
const high = have[stored.s]?.[seenKey(stored.e, stored.h)];
|
|
28885
29116
|
if (Number.isSafeInteger(high) && high >= stored.n) continue;
|
|
28886
|
-
if (this.
|
|
29117
|
+
if (this.wasExpelled(stored.s)) continue;
|
|
28887
29118
|
const found = this.commitByShort(stored.e, stored.h);
|
|
28888
29119
|
if (!found) continue;
|
|
28889
29120
|
const reads = rosterHas(this.rosterAt(found.hash) ?? [], from), mine = stored.s === this.myKey && unheard.has(frameKey(stored));
|
|
@@ -30009,4 +30240,4 @@ function appStoreDecision(held, read) {
|
|
|
30009
30240
|
//#endregion
|
|
30010
30241
|
export { parseCommunityEdit as $, receivedTimestamp as $a, identityFromSeed as $c, DHT_FILE_TOO_LARGE as $i, readGroupHave as $n, HANDOFF_PAKE as $o, vapidKeysMatch as $r, verifyEnrollProof as $s, signDidDhtPacket as $t, encodeCommunityLink as A, reactionEmoji as Aa, signTurnPacket as Ac, DHT_TEXT_CAPABILITY as Ai, resolveLightningDestination as An, webLocalFetch as Ao, FILE_LIMITS as Ar, readHandoffTurnFrame as As, DidError as At, GroupWakeLimiter as B, PR_CHECKS as Ba, normalizeRelayUrl as Bc, identityRevocationValue as Bi, INVITE_TAKEN as Bn, extractParamsFromSdp as Bo, mayPin as Br, ENROLL_DONE as Bs, didWebHost as Bt, openGroupTyping as C, readForwarded as Ca, classifyTurnRead as Cc, normalizeDomain as Ci, normalizePubkyKey as Cn, DHT_POLL_INTERVALS as Co, pickHubs as Cr, newHandoffId as Cs, parseAtprotoDidDocument as Ct, GROUP_READ_NOTE_COMMUNITY as D, ReactionWindow as Da, firstDeviceSetSecret as Dc, txtValue as Di, homeserverWebEndpoint as Dn, GhostlyHttpError as Do, readLobby as Dr, readHandoffHave as Ds, DID_DOCUMENT_MAX_BYTES as Dt, CommunitySession as E, REACTION_LIMITS as Ea, TURN_MAX as Ec, recordsName as Ei, pubkyProofPath as En, presenceSeenAt as Eo, readBeaconHead as Er, readHandoffDone as Es, verifyAtprotoRecordProof as Et, knockIdentity as F, pairedReplyAuthor as Fa, turnStatedVerifier as Fc, IDENTITY_REVOCATION_LABEL as Fi, findEndpoint as Fn, RTC_CONFIG as Fo, TypingSender as Fr, readSetUpdate as Fs, didSignedMessages as Ft, GROUP_REACTED_FRAME as G, cardLine as Ga, withRequestOptions as Gc, lastSharedWithMe as Gi, GROUP_FILE_LIMITS as Gn, LEGACY_SERVICES as Go, WAKE_CALL_INTERVAL_MS as Gr, enrollCancelReason as Gs, signingKeys as Gt, groupWakes as H, RUN_RESULTS as Ha, traceLink as Hc, newIdentityBinding as Hi, pairedMessageFrame as Hn, signalHasVideo as Ho, pinIsNewer as Hr, ENROLL_HELLO as Hs, parseDid as Ht, knockRecords as I, readReply as Ia, turnVerifierMark as Ic, IdentityCheckUnavailable as Ii, isFederationId as In, answersOffer as Io, sanitizeTypingStatus as Ir, setAckFrame as Is, didWebDocumentUrl as It, frameBytes as J, sameButtonText as Ja, createRelayPayload as Jc, validExternalKey as Ji, GROUP_WANT_NO_FRAME as Jn, HANDOFF_CANCEL as Jo, newWakeToken as Jr, enrollGrantFrame as Js, didDhtDocument as Jt, GROUP_READ_NOTE as K, cardUrl as Ka, PacketTooLargeError as Kc, PeerProofs as Ki, GROUP_HAVE_FRAME as Kn, serviceIdFromName as Ko, WakeLimiter as Kr, enrollDigits as Ks, staticDidDocument as Kt, mergeKnocks as L, replySnippet as La, DEFAULT_RELAYS as Lc, IdentityExchange as Li, parseCashuRequestPayload as Ln, buildSdpFromSignal as Lo, typingActivity as Lr, setUpdateFrame as Ls, didWebFile as Lt, decodeGroupEntryLink as M, readReaction as Ma, turnInstance as Mc, dialDescriptors as Mi, ENDPOINT as Mn, decodeControl as Mo, transferStalled as Mr, sealHandoffPiece as Ms, checkDidSignature as Mt, encodeGroupEntryLink as N, validReactionNumber as Na, turnKeys as Nc, IDENTITY_MAX_EVIDENCE as Ni, cashuRequestPayload as Nn, encodeControl as No, TYPING_KINDS as Nr, SET_ACK as Ns, didGhostlyServiceEntry as Nt, communityHasFrame as O, nextReactionNumber as Oa, nextTurnPosition as Oc, wellKnownUrl as Oi, pubkyHomeserverOf as On, formatLocalTarget as Oo, shouldBeHub as Or, readHandoffHello as Os, DID_FILE_MAX_BYTES as Ot, entryParams as P, wireReaction as Pa, turnName as Pc, IDENTITY_MAX_VALIDITY as Pi, fedimintRequestPayload as Pn, CALL_SIGNAL_MAX_AGE_MS as Po, TYPING_REFRESH_MS as Pr, SET_UPDATE as Ps, didJwsAlg as Pt, carryMentions as Q, heardTime as Qa, createIdentity as Qc, irohRelayUrl as Qi, readGroupFileMeta as Qn, HANDOFF_OFFER as Qo, checkPushEndpoint as Qr, readEnrollHello as Qs, resolveDidDht as Qt, readKnocks as R, BUTTON_ID as Ra, RelayTransport as Rc, boundVerifiedIdentity as Ri, parseFedimintRequestPayload as Rn, callRtcConfig as Ro, GROUP_PIN_FRAME as Rr, signTombstone as Rs, didWebFileNames as Rt, groupTypingEpoch as S, forwardedAgain as Sa, TURN_SETTLE_MS as Sc, nip05RootKey as Si, newPubkyProofFolder as Sn, DirectPathWatch as So, newerHead as Sr, handoffVersions as Ss, normalizeAtprotoHandle as St, COMMUNITY_LIMITS as T, GROUP_REACTION_FRAME as Ta, TURN_LAST_SEQUENCE as Tc, recordsFromWellKnown as Ti, pubkyProofFile as Tn, RELAY_POLL_INTERVALS as To, readBeacon as Tr, readHandoffCancel as Ts, AtprotoProofError as Tt, parseGroupWakeFrame as U, STATUS_CARD_LIMITS as Ua, budgetRetryMs as Uc, receivedIdentityStatus as Ui, engineError as Un, emptyLinkRecords as Uo, pinNumberHolds as Ur, ENROLL_PROOF as Us, parseDidDocument as Ut, groupWakeFrame as V, PR_STATES as Va, setLinkTraceSink as Vc, identityStatement as Vi, LIVENESS_PING_MS as Vn, parseCallSignal as Vo, nextPinNumber as Vr, ENROLL_GRANT as Vs, normalizeDid as Vt, GROUP_LIMITS as W, buttonLabelClash as Wa, isDiscoveryBudgetError as Wc, revokesIdentity as Wi, engineText as Wn, HTTP_SERVICE_PROTO as Wo, readPin as Wr, enrollCancelFrame as Ws, parseDidSignaturePaste as Wt, GROUP_EDIT_FRAME as X, arrivalKey as Xa, openRelayPayload as Xc, automaticTransport as Xi, groupFileFallback as Xn, HANDOFF_HAVE as Xo, relayRequest as Xr, enrollProofFrame as Xs, encodeDidDhtDeactivated as Xt, COMMUNITY_EDIT_FRAME as Y, statusCardText as Ya, newerPacket as Yc, TRANSPORTS as Yi, fetchesByItself as Yn, HANDOFF_DONE as Yo, parseWakeFrame as Yr, enrollHelloFrame as Ys, didDhtFromPublicKey as Yt, GROUP_EDIT_TEXT_BYTES as Z, claimedTime as Za, parseRelayPayload as Zc, irohDescriptor as Zi, isGroupMessageId as Zn, HANDOFF_HELLO as Zo, wakeRequest as Zr, readEnrollGrant as Zs, encodeDidDhtPacket as Zt, checkAppManifest as _, EDIT_BUFFER_MS as _a, newDeviceSetSecret as _c, dnsProofName as _i, sparkInvoiceDetails as _n, MEMBER_KEY as _o, freshHubs as _r, handoffOfferFrame as _s, atprotoDidMethod as _t, planAppUpdate as a, readImageMeta as aa, decodeInviteCode as ac, STATUS_CARD_CAPABILITY as ai, bytesEqual as al, SEPOLIA_TEST_USDT_FAUCET as an, checkAppRevokeStatement as ao, SIGNAL_HOLD_MS as ar, handoffAttemptAllowed as as, parseSshPublicKey as at, GROUP_TYPING_FRAME as b, dhtEditId as ba, TURN_PUT_WINDOW_MS as bc, encodeDnsQuery as bi, isPubkyKey as bn, verifyCommitSignature as bo, mergeBeacon as br, handoffRetryAfter as bs, checkAtprotoProofRecord as bt, readAppStore as c, sanitizeFileName as ca, inviteOwnership as cc, HOLD_DATED_BACK_MS as ci, toBase64Url as cl, WALLET_NETWORKS as cn, readAppRevocation as co, readGroupSignal as cr, handoffBusyFrame as cs, sshSignCommand as ct, APP_BUNDLE_LIMITS as d, VOICE_LIMITS as da, HANDOFF_CAPABILITY as dc, HoldRefusedError as di, utf8Encode as dl, formatPaymentAmount as dn, signAppRevocation as do, meshHubs as dr, handoffContextProof as ds, BitcoinAddressError as dt, DHT_MESSAGE_TTL as ea, createDeviceInvite as ec, chatAppId as ei, identityFromSeedB64 as el, ETHEREUM_USDT as en, shownTime as eo, readGroupWant as er, HANDOFF_PIECE_BYTES as es, mentionsMember as et, APP_BUNDLE_MAGIC as f, baseMime as fa, deviceEchoFrame as fc, isHoldUrl as fi, validatePaymentTarget as fn, canonicalJson as fo, pickMeshHubs as fr, handoffDataFrame as fs, decodeAnyBitcoinAddress as ft, assembleAppBundle as g, parseVideoMeta as ga, devicePingFrame as gc, decodeDnsResponse as gi, sparkAddressKind as gn, MAX_GROUP_CHAIN as go, doorHubs as gr, handoffManifestFrames as gs, ATPROTO_PROOF_SCOPE as gt, appViewOf as h, voicePeaksOf as ha, deviceLinkPairing as hc, DOMAIN_FILE_MAX_BYTES as hi, SPARK_PROVIDER as hn, GROUP_MEMBER_CAP as ho, beaconRecords as hr, handoffHelloFrame as hs, ATPROTO_PROOF_COLLECTION as ht, checkAppBeforeRun as i, parseImageMeta as ia, createChatInvite as ic, EDIT_CAPABILITY as ii, sanitizeAvatar as il, SEPOLIA_TEST_USDT as in, appRef as io, SIGNAL_FRESH_MS as ir, filePartName as is, SshSigError as it, KNOCK_TTL_MS as j, reactionIsNewer as ja, signTurnRelease as jc, capsDescriptors as ji, decodeBolt11 as jn, LIMITS as jo, transferEnded as jr, readHandoffVerified as js, checkDidJws as jt, decodeCommunityLink as k, queueReaction as ka, readTurnPacket as kc, CapsExchange as ki, requestLnurlInvoice as kn, parseLocalTarget as ko, ChatFiles as kr, readHandoffPake as ks, DID_JWS_MAX_LENGTH as kt, readAppStoreSteps as l, sanitizeMime as la, DEVICES_CAPABILITY as lc, HOLD_LIMITS as li, toZ32 as ll, assertTokenUnits as ln, readAppRevocations as lo, MESH_HUBS as lr, handoffCancelFrame as ls, verifySshSignature as lt, appDigest as m, parseVoiceMeta as ma, deviceKeyZ32 as mc, readManifest as mi, SPARK_NETWORKS as mn, readCanonicalJson as mo, beaconKeys as mr, handoffEphemeral as ms, ATPROTO_FULL_SCOPE as mt, appStoreDecision as n, DHT_FILE_CAPABILITY as na, readDeviceInvite as nc, APPS_CAPABILITY as ni, verify as nl, ONCHAIN_PROVIDER as nn, APP_STATEMENT_LIMITS as no, CarriedTransport as nr, HANDOFF_TIMINGS as ns, SSHSIG_MAX_ARMOR as nt, APP_STORE_LIMITS as o, formatFileSize as oa, encodeInviteCode as oc, UPGRADE_CAPABILITY as oi, fromBase64Url as ol, TEST_USDT_FAUCET_AMOUNT as on, isAppKey as oo, SIGNAL_REPLY_MS as or, handoffAttemptFailed as os, parseSshSignature as ot, APP_ICON_PATH as p, fileMessageText as pa, deviceEchoNonce as pc, newMailbox as pi, walletNetworkOf as pn, canonicalJsonBytes as po, COMMUNITY_TOPOLOGY as pr, handoffDigest as ps, ATPROTO_DECLARED_SCOPE as pt, GroupSession as q, checkStatusCard as qa, RELAY_PAYLOAD_MAX_BYTES as qc, emptyProofLedger as qi, GROUP_WANT_FRAME as qn, HANDOFF_ACK as qo, deviceWakeRequest as qr, enrollDoneFrame as qs, strictBase64Url as qt, appUpdateDecision as r, dhtFileId as ra, OwnInviteError as rc, BUTTONS_CAPABILITY as ri, MAX_AVATAR_LENGTH as rl, PaymentPreflightError as rn, appFingerprint as ro, GROUP_SIGNAL_FRAME as rr, HANDOFF_VERIFIED as rs, SSHSIG_NAMESPACE as rt, checkAppStoreIndex as s, safeBlobType as sa, inviteLink as sc, HOLD_CAPABILITY as si, randomBytes as sl, USDT_PUBLIC_RPC as sn, isAppRef as so, groupSignalFrame as sr, handoffAttemptSucceeded as ss, sameSshKey as st, appRevocationFor as t, peekDhtMailbox as ta, deviceInviteJoinerParams as tc, isAppVersion as ti, publicKeyFromZ32 as tl, EVM_TEST_CHAINS as tn, APP_PREFIXES as to, readGroupWantNo as tr, HANDOFF_REQUEST as ts, groupName as tt, signAppStore as u, PLAYABLE_AUDIO as ua, ENROLL_CAPABILITY as uc, HoldKeys as ui, utf8Decode as ul, assertWholeSats as un, signAppObject as uo, mayBeHub as ur, handoffContext as us, verifyBitcoinMessage as ut, isAppUrl as v, EDIT_RATE_WINDOW_MS as va, seedSigner as vc, domainTxtRecord as vi, isBitcoinAddress as vn, commitHash as vo, lobbyKeys as vr, handoffPakeFrame as vs, atprotoProofRecord as vt, sealGroupTyping as w, COMMUNITY_REACTION_FRAME as wa, turnPutSummary as wc, recordsFromTxt as wi, pubkyProofCapability as wn, EXPECT_PEER_MS as wo, rankHubs as wr, readHandoffAck as ws, shortAtprotoDid as wt, GroupTypingBoard as x, parseLinkPreview as xa, TURN_RAISE_READ_TIMEOUT_MS as xc, isPublicAddress as xi, isPubkyProofFolder as xn, edgeParams as xo, mergeLobby as xr, handoffStreamKey as xs, isAtprotoDid as xt, readAppBundle as y, RateWindow as ya, webCryptoSigner as yc, domainWellKnownFile as yi, PUBKY_PROOF_MAX_BYTES as yn, rosterHas as yo, lobbyRecords as yr, handoffReleaseFrame as ys, atprotoProofRkey as yt, GROUP_WAKE_FRAME as z, ITEM_STATES as za, currentRelays as zc, emptyIdentityLedger as zi, GhostLink as zn, callSignalHeardAt as zo, PIN_LIMITS as zr, verifySetUpdate as zs, didWebFileUrl as zt };
|
|
30011
30242
|
|
|
30012
|
-
//# sourceMappingURL=src-
|
|
30243
|
+
//# sourceMappingURL=src-CbLkHfz8.js.map
|