@ghostlytools/cli 1.1.6 → 1.2.0
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 +16 -11
- package/SKILL.md +2 -2
- package/dist/assets/arkade-3szZ1IpP.js +362 -0
- package/dist/assets/arkade-3szZ1IpP.js.map +1 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/{endpoint-D_3bYafG.js → endpoint-pgFkR2AS.js} +47 -10
- package/dist/assets/endpoint-pgFkR2AS.js.map +1 -0
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
- package/dist/assets/{fileBytes-3AQ2tJzW.js → fileBytes-BcYZhNgF.js} +3 -3
- package/dist/assets/{fileBytes-3AQ2tJzW.js.map → fileBytes-BcYZhNgF.js.map} +1 -1
- package/dist/assets/{fileBytes-CAZDydIe.js → fileBytes-Dbifu5I5.js} +2 -2
- package/dist/assets/{fileBytes-CAZDydIe.js.map → fileBytes-Dbifu5I5.js.map} +1 -1
- package/dist/assets/{fileBytesIdb-CmG00qE-.js → fileBytesIdb-DzSaqI0A.js} +3 -3
- package/dist/assets/{fileBytesIdb-CmG00qE-.js.map → fileBytesIdb-DzSaqI0A.js.map} +1 -1
- package/dist/assets/{fileBytesOpfs-CTgqt-p3.js → fileBytesOpfs-Dx8RZiV5.js} +3 -3
- package/dist/assets/{fileBytesOpfs-CTgqt-p3.js.map → fileBytesOpfs-Dx8RZiV5.js.map} +1 -1
- package/dist/assets/{hyperdhtRelay-CnEkFl8R.js → hyperdhtRelay-DmTrpmba.js} +3 -3
- package/dist/assets/{hyperdhtRelay-CnEkFl8R.js.map → hyperdhtRelay-DmTrpmba.js.map} +1 -1
- package/dist/assets/{idb-BIdus0WX.js → idb-CTF6LF5B.js} +62 -6
- package/dist/assets/idb-CTF6LF5B.js.map +1 -0
- package/dist/assets/{mainline-DQxDSWXm.js → mainline-BueFJx3f.js} +2 -2
- package/dist/assets/{mainline-DQxDSWXm.js.map → mainline-BueFJx3f.js.map} +1 -1
- package/dist/assets/{restoredRows-lhTKGmCO.js → restoredRows-C_84q0Pd.js} +2 -2
- package/dist/assets/{restoredRows-lhTKGmCO.js.map → restoredRows-C_84q0Pd.js.map} +1 -1
- package/dist/assets/{server-HMzdsyn_.js → server-BGXmSYx_.js} +1170 -1049
- package/dist/assets/server-BGXmSYx_.js.map +1 -0
- package/dist/assets/{src-DtxVo2Rm.js → src-BMUcYQ7b.js} +770 -244
- package/dist/assets/src-BMUcYQ7b.js.map +1 -0
- package/dist/assets/usdt-DklGBSeq.js +410 -0
- package/dist/assets/usdt-DklGBSeq.js.map +1 -0
- package/dist/ghostly.mjs +858 -137
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/assets/endpoint-D_3bYafG.js.map +0 -1
- package/dist/assets/idb-BIdus0WX.js.map +0 -1
- package/dist/assets/server-HMzdsyn_.js.map +0 -1
- package/dist/assets/src-DtxVo2Rm.js.map +0 -1
|
@@ -38,11 +38,38 @@ function bytesEqual(a, b) {
|
|
|
38
38
|
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
39
39
|
return diff === 0;
|
|
40
40
|
}
|
|
41
|
+
/** `Uint8Array.prototype.toBase64` (Node 25+, Chrome 140+, Firefox 133+, Safari 18.2+): ~200x the JS below. */
|
|
42
|
+
var nativeToBase64 = Uint8Array.prototype.toBase64;
|
|
43
|
+
var tableOf = (chars) => Uint8Array.from(chars, (c) => c.charCodeAt(0));
|
|
44
|
+
var BASE64_TABLE = tableOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
|
|
45
|
+
var BASE64URL_TABLE = tableOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
|
|
46
|
+
/**
|
|
47
|
+
* Base64 where the runtime has no native encoder (Node 22 and 24, older WebViews): the characters go into bytes and
|
|
48
|
+
* become one string at the end. Every file chunk is encoded (files/3 `pf-data`), so this is on a transfer's path.
|
|
49
|
+
*/
|
|
50
|
+
function encodeBase64(bytes, table, pad) {
|
|
51
|
+
const out = new Uint8Array(Math.ceil(bytes.length / 3) * 4);
|
|
52
|
+
let o = 0, i = 0;
|
|
53
|
+
for (; i + 2 < bytes.length; i += 3) {
|
|
54
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
55
|
+
out[o++] = table[n >>> 18];
|
|
56
|
+
out[o++] = table[n >>> 12 & 63];
|
|
57
|
+
out[o++] = table[n >>> 6 & 63];
|
|
58
|
+
out[o++] = table[n & 63];
|
|
59
|
+
}
|
|
60
|
+
const rest = bytes.length - i;
|
|
61
|
+
if (rest) {
|
|
62
|
+
const n = bytes[i] << 16 | (rest === 2 ? bytes[i + 1] << 8 : 0);
|
|
63
|
+
out[o++] = table[n >>> 18];
|
|
64
|
+
out[o++] = table[n >>> 12 & 63];
|
|
65
|
+
if (rest === 2) out[o++] = table[n >>> 6 & 63];
|
|
66
|
+
else if (pad) out[o++] = 61;
|
|
67
|
+
if (pad) out[o++] = 61;
|
|
68
|
+
}
|
|
69
|
+
return textDecoder$1.decode(out.subarray(0, o));
|
|
70
|
+
}
|
|
41
71
|
function toBase64(bytes) {
|
|
42
|
-
|
|
43
|
-
const CHUNK = 32768;
|
|
44
|
-
for (let i = 0; i < bytes.length; i += CHUNK) binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
45
|
-
return btoa(binary);
|
|
72
|
+
return nativeToBase64 ? nativeToBase64.call(bytes) : encodeBase64(bytes, BASE64_TABLE, true);
|
|
46
73
|
}
|
|
47
74
|
function fromBase64(str) {
|
|
48
75
|
const binary = atob(str);
|
|
@@ -51,7 +78,10 @@ function fromBase64(str) {
|
|
|
51
78
|
return bytes;
|
|
52
79
|
}
|
|
53
80
|
function toBase64Url(bytes) {
|
|
54
|
-
return
|
|
81
|
+
return nativeToBase64 ? nativeToBase64.call(bytes, {
|
|
82
|
+
alphabet: "base64url",
|
|
83
|
+
omitPadding: true
|
|
84
|
+
}) : encodeBase64(bytes, BASE64URL_TABLE, false);
|
|
55
85
|
}
|
|
56
86
|
function fromBase64Url(str) {
|
|
57
87
|
let b = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -108,9 +138,13 @@ function randomBytes(length) {
|
|
|
108
138
|
var HIDDEN = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
|
|
109
139
|
/** Zero-width non-joiner and zero-width joiner. */
|
|
110
140
|
var KEEP = /[\u200c\u200d]/;
|
|
141
|
+
/** `text` without its invisible and direction-changing characters, at any length: for display text with a cut of its own. */
|
|
142
|
+
function stripHidden(text) {
|
|
143
|
+
return text.replace(HIDDEN, (char) => KEEP.test(char) ? char : "");
|
|
144
|
+
}
|
|
111
145
|
/** Undefined when nothing visible is left, so callers can fall back. */
|
|
112
146
|
function sanitizeDisplayText(text, max) {
|
|
113
|
-
return [...text
|
|
147
|
+
return [...stripHidden(text)].slice(0, max).join("").trim() || void 0;
|
|
114
148
|
}
|
|
115
149
|
/** A nickname as it arrived from a peer, ready to show. */
|
|
116
150
|
function sanitizeNick(nick) {
|
|
@@ -1952,9 +1986,11 @@ var isZero = (bytes) => bytes.every((b) => b === 0);
|
|
|
1952
1986
|
/**
|
|
1953
1987
|
* A device's name as its slot holds it: UTF-8, cut at a character boundary to 16 bytes, and with no NUL (the padding).
|
|
1954
1988
|
* A character is what a person sees as one (a grapheme), so a cut never leaves half an emoji or a lone joiner.
|
|
1989
|
+
* A name is display text a device chooses for itself and every device of the set shows: it is cleaned as a nickname is
|
|
1990
|
+
* (`sanitizeDisplayText`: no control, invisible or direction-changing character) and trimmed, made and read alike.
|
|
1955
1991
|
*/
|
|
1956
1992
|
function turnName(name) {
|
|
1957
|
-
const clean = name.
|
|
1993
|
+
const clean = stripHidden(name).trim();
|
|
1958
1994
|
const Segmenter = typeof Intl === "undefined" ? void 0 : Intl.Segmenter;
|
|
1959
1995
|
const characters = Segmenter ? Array.from(new Segmenter(void 0, { granularity: "grapheme" }).segment(clean), (s) => s.segment) : Array.from(clean);
|
|
1960
1996
|
let out = "", length = 0;
|
|
@@ -1964,7 +2000,7 @@ function turnName(name) {
|
|
|
1964
2000
|
out += character;
|
|
1965
2001
|
length += size;
|
|
1966
2002
|
}
|
|
1967
|
-
return out;
|
|
2003
|
+
return out.trimEnd();
|
|
1968
2004
|
}
|
|
1969
2005
|
function u32$3(value) {
|
|
1970
2006
|
return Uint8Array.of(value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255);
|
|
@@ -2069,7 +2105,7 @@ function readTurnBody(body, address) {
|
|
|
2069
2105
|
if (slots.some((other) => other && bytesEqual(other.key, key))) refuse("slots", "one key in two slots");
|
|
2070
2106
|
slots.push({
|
|
2071
2107
|
key,
|
|
2072
|
-
name: text
|
|
2108
|
+
name: turnName(text)
|
|
2073
2109
|
});
|
|
2074
2110
|
}
|
|
2075
2111
|
if (slots.filter(Boolean).length !== count) refuse("count");
|
|
@@ -3212,13 +3248,17 @@ function encodeSetSlots(set) {
|
|
|
3212
3248
|
}
|
|
3213
3249
|
/** The bytes the remover signs: the old turn address, then the frame's fields, the tombstone by its digest. */
|
|
3214
3250
|
function setUpdateMessage(oldAddress, update) {
|
|
3251
|
+
return signedBytes(oldAddress, update, encodeSetSlots(update.set));
|
|
3252
|
+
}
|
|
3253
|
+
/** `slots`: the set as the frame carries it. A reader hands over the frame's own, a name cleaned for display is not what was signed. */
|
|
3254
|
+
function signedBytes(oldAddress, update, slots) {
|
|
3215
3255
|
if (oldAddress.length !== 32) throw new Error("A turn address is 32 bytes");
|
|
3216
3256
|
if (update.d.length !== 32) throw new Error("The device-set secret is 32 bytes");
|
|
3217
3257
|
return utf8Encode(JSON.stringify([
|
|
3218
3258
|
"ghostly-set-update",
|
|
3219
3259
|
toBase64Url(oldAddress),
|
|
3220
3260
|
toBase64Url(update.d),
|
|
3221
|
-
|
|
3261
|
+
slots,
|
|
3222
3262
|
update.turn,
|
|
3223
3263
|
update.rev,
|
|
3224
3264
|
toBase64Url(sha256(update.tomb))
|
|
@@ -3254,16 +3294,19 @@ function readSetUpdate(frame) {
|
|
|
3254
3294
|
if (!Number.isInteger(frame.turn) || frame.turn < 0 || frame.turn > TURN_MAX) return null;
|
|
3255
3295
|
if (!Number.isInteger(frame.rev) || frame.rev < 0 || frame.rev >= TURN_REV_LIMIT) return null;
|
|
3256
3296
|
const set = [];
|
|
3297
|
+
const signed = [];
|
|
3257
3298
|
for (const slot of frame.set) {
|
|
3258
3299
|
if (slot === null) {
|
|
3259
3300
|
set.push(null);
|
|
3301
|
+
signed.push(null);
|
|
3260
3302
|
continue;
|
|
3261
3303
|
}
|
|
3262
3304
|
if (!Array.isArray(slot) || slot.length !== 2 || typeof slot[0] !== "string" || !KEY$4.test(slot[0]) || typeof slot[1] !== "string" || slot[1].length > 64) return null;
|
|
3263
|
-
if (
|
|
3305
|
+
if (utf8Encode(slot[1]).length > 16 || slot[1].includes("\0")) return null;
|
|
3306
|
+
signed.push([slot[0], slot[1]]);
|
|
3264
3307
|
set.push({
|
|
3265
3308
|
key: fromBase64Url(slot[0]),
|
|
3266
|
-
name: slot[1]
|
|
3309
|
+
name: turnName(slot[1])
|
|
3267
3310
|
});
|
|
3268
3311
|
}
|
|
3269
3312
|
const keys = set.flatMap((slot) => slot ? [toBase64Url(slot.key)] : []);
|
|
@@ -3271,6 +3314,7 @@ function readSetUpdate(frame) {
|
|
|
3271
3314
|
return {
|
|
3272
3315
|
d: fromBase64Url(frame.d),
|
|
3273
3316
|
set,
|
|
3317
|
+
signed,
|
|
3274
3318
|
turn: frame.turn,
|
|
3275
3319
|
rev: frame.rev,
|
|
3276
3320
|
tomb: fromBase64Url(frame.tomb),
|
|
@@ -3282,7 +3326,7 @@ function readSetUpdate(frame) {
|
|
|
3282
3326
|
/** Whether `by` signed this update for the turn address `oldAddress`. */
|
|
3283
3327
|
function verifySetUpdate(update, oldAddress) {
|
|
3284
3328
|
try {
|
|
3285
|
-
return verify(update.signature,
|
|
3329
|
+
return verify(update.signature, signedBytes(oldAddress, update, update.signed ?? encodeSetSlots(update.set)), update.by);
|
|
3286
3330
|
} catch {
|
|
3287
3331
|
return false;
|
|
3288
3332
|
}
|
|
@@ -4438,6 +4482,13 @@ function waitForIceGathering(pc, timeoutMs = 1e4, { stallMs, fresh = false, sett
|
|
|
4438
4482
|
}
|
|
4439
4483
|
//#endregion
|
|
4440
4484
|
//#region ../core/src/signal.ts
|
|
4485
|
+
/**
|
|
4486
|
+
* Signaling for the WebRTC data link, carried in the `_rtc` record. It follows
|
|
4487
|
+
* the convention `_call` established: only what is unique to the session
|
|
4488
|
+
* travels through Pkarr and the SDP is rebuilt locally. The DTLS fingerprint
|
|
4489
|
+
* arrives inside a packet that is signed by the peer's key and encrypted with
|
|
4490
|
+
* the link key, which is what binds the WebRTC connection to the peer identity.
|
|
4491
|
+
*/
|
|
4441
4492
|
var DATA_CHANNEL_LABEL = "ghostly/1";
|
|
4442
4493
|
/** An offer older than this is ignored. */
|
|
4443
4494
|
var RTC_SIGNAL_MAX_AGE_MS = 12e4;
|
|
@@ -4484,17 +4535,34 @@ var CANDIDATE_TYPE_NAME = {
|
|
|
4484
4535
|
s: "srflx",
|
|
4485
4536
|
r: "relay"
|
|
4486
4537
|
};
|
|
4538
|
+
/**
|
|
4539
|
+
* The signal carries two host candidates, and a computer often has more interfaces than that: Wi-Fi, Ethernet, VM
|
|
4540
|
+
* bridges, Tailscale, a VPN. The browser lists them in its own order, and the first two are not always ones the
|
|
4541
|
+
* contact can reach: on a Mac with two VM bridges they are the bridges (addresses that exist on that Mac only), and
|
|
4542
|
+
* the Wi-Fi and Ethernet addresses never left it, so a contact on the same network was reached only if the router
|
|
4543
|
+
* sends a packet for its own public address back in (2026-10-02). So host candidates go in this order of preference:
|
|
4544
|
+
* those a server reflexive candidate was gathered from (`raddr`: an interface with a way out, the network a contact
|
|
4545
|
+
* nearby shares), then the others, and in each case those the browser marks as costly last (`network-cost` 50 or
|
|
4546
|
+
* more: a VPN, or an interface it does not know), as a call's candidates do (`pickCallCandidates`). A host on a
|
|
4547
|
+
* global IPv4 address has a way out too: it gets no reflexive candidate (Chromium drops one equal to a host), and the
|
|
4548
|
+
* gathering wait ends on it (`isGlobalIpv4Host`). That wait ends 400 ms after the first reflexive candidate, so only
|
|
4549
|
+
* the interfaces whose STUN answer came by then are known to have a way out. With no reflexive candidate, or none
|
|
4550
|
+
* that says where it came from, the order is the browser's, as before.
|
|
4551
|
+
*/
|
|
4552
|
+
function hostRank(parts, reflexiveBases) {
|
|
4553
|
+
const cost = Number(parts[parts.indexOf("network-cost") + 1]);
|
|
4554
|
+
const costly = parts.includes("network-cost") && cost >= 50;
|
|
4555
|
+
const wayOut = reflexiveBases.has(parts[4]) || isGlobalIpv4Host(parts.join(" "));
|
|
4556
|
+
return (costly ? 2 : 0) + (wayOut ? 0 : 1);
|
|
4557
|
+
}
|
|
4487
4558
|
function extractRtcParams(sdp) {
|
|
4488
4559
|
let u = "";
|
|
4489
4560
|
let p = "";
|
|
4490
4561
|
let f = "";
|
|
4491
4562
|
let s = "";
|
|
4492
|
-
|
|
4493
|
-
const
|
|
4494
|
-
|
|
4495
|
-
s: 0,
|
|
4496
|
-
r: 0
|
|
4497
|
-
};
|
|
4563
|
+
/** UDP component-1 candidates of a type the signal carries, in the SDP's order. */
|
|
4564
|
+
const found = [];
|
|
4565
|
+
const reflexiveBases = /* @__PURE__ */ new Set();
|
|
4498
4566
|
for (const line of sdp.split(/\r?\n/)) if (line.startsWith("a=ice-ufrag:") && !u) u = line.slice(12);
|
|
4499
4567
|
else if (line.startsWith("a=ice-pwd:") && !p) p = line.slice(10);
|
|
4500
4568
|
else if (line.toLowerCase().startsWith("a=fingerprint:sha-256 ") && !f) f = line.slice(22).replace(/:/g, "").toLowerCase();
|
|
@@ -4503,11 +4571,33 @@ function extractRtcParams(sdp) {
|
|
|
4503
4571
|
const parts = line.slice(12).split(" ");
|
|
4504
4572
|
if (parts.length < 8 || parts[1] !== "1" || parts[2].toLowerCase() !== "udp") continue;
|
|
4505
4573
|
const type = CANDIDATE_TYPES[parts[7]];
|
|
4506
|
-
if (!type
|
|
4574
|
+
if (!type) continue;
|
|
4507
4575
|
const compact = `${type},${parts[4]},${parts[5]}`;
|
|
4508
|
-
if (
|
|
4509
|
-
|
|
4510
|
-
|
|
4576
|
+
if (found.some((c) => c.compact === compact)) continue;
|
|
4577
|
+
found.push({
|
|
4578
|
+
type,
|
|
4579
|
+
parts,
|
|
4580
|
+
compact
|
|
4581
|
+
});
|
|
4582
|
+
const base = type === "s" && parts.includes("raddr") ? parts[parts.indexOf("raddr") + 1] : void 0;
|
|
4583
|
+
if (base && base !== "0.0.0.0" && base !== "::") reflexiveBases.add(base);
|
|
4584
|
+
}
|
|
4585
|
+
const hosts = found.filter((c) => c.type === "h");
|
|
4586
|
+
const preferred = new Set(hosts.map((c, at) => ({
|
|
4587
|
+
c,
|
|
4588
|
+
at,
|
|
4589
|
+
rank: hostRank(c.parts, reflexiveBases)
|
|
4590
|
+
})).sort((a, b) => a.rank - b.rank || a.at - b.at).slice(0, CANDIDATE_LIMITS.h).map(({ c }) => c));
|
|
4591
|
+
const candidates = [];
|
|
4592
|
+
const counts = {
|
|
4593
|
+
h: 0,
|
|
4594
|
+
s: 0,
|
|
4595
|
+
r: 0
|
|
4596
|
+
};
|
|
4597
|
+
for (const c of found) {
|
|
4598
|
+
if (c.type === "h" ? !preferred.has(c) : counts[c.type] >= CANDIDATE_LIMITS[c.type]) continue;
|
|
4599
|
+
counts[c.type]++;
|
|
4600
|
+
candidates.push(c.compact);
|
|
4511
4601
|
}
|
|
4512
4602
|
return {
|
|
4513
4603
|
u,
|
|
@@ -4589,6 +4679,8 @@ function buildDataSdp(signal) {
|
|
|
4589
4679
|
var LIMITS = {
|
|
4590
4680
|
/** Largest control frame accepted, headers included. */
|
|
4591
4681
|
maxControlFrameBytes: 61440,
|
|
4682
|
+
/** Longest payment endpoint value (a Cashu token, an invoice) a payment frame may carry; a longer one is dropped. */
|
|
4683
|
+
maxPaymentEndpointChars: 32768,
|
|
4592
4684
|
/** Largest DataChannel message sent. 16 KiB is safe across WebRTC stacks. */
|
|
4593
4685
|
maxChunkMessageBytes: 16384,
|
|
4594
4686
|
maxRequestBodyBytes: 8388608,
|
|
@@ -4642,7 +4734,7 @@ function isUnit(value) {
|
|
|
4642
4734
|
return typeof value === "string" && /^[a-z0-9]{1,12}$/.test(value);
|
|
4643
4735
|
}
|
|
4644
4736
|
function isEndpoint(value) {
|
|
4645
|
-
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && value[0].length <= 64 && /^[a-z0-9][a-z0-9-]*(?:\/[1-9][0-9]*)?$/.test(value[0]) && typeof value[1] === "string" && value[1].length <=
|
|
4737
|
+
return Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && value[0].length <= 64 && /^[a-z0-9][a-z0-9-]*(?:\/[1-9][0-9]*)?$/.test(value[0]) && typeof value[1] === "string" && value[1].length <= LIMITS.maxPaymentEndpointChars;
|
|
4646
4738
|
}
|
|
4647
4739
|
/** Returns null for anything malformed; callers drop the frame. */
|
|
4648
4740
|
function decodeControl(text) {
|
|
@@ -5435,6 +5527,11 @@ var EXPECT_PEER_MS = 3e4;
|
|
|
5435
5527
|
* or the session dropped): an app that restarts is back within this, and its dial or offer is seen in seconds.
|
|
5436
5528
|
*/
|
|
5437
5529
|
var WATCH_PEER_MS = 12e4;
|
|
5530
|
+
/**
|
|
5531
|
+
* The longest a link reads fast for a data link whose connection went `disconnected` (`setDataLinkStalled`): a little
|
|
5532
|
+
* past the 12 s such a connection is given (`DISCONNECT_GRACE_MS`), by when it is back or the session is gone.
|
|
5533
|
+
*/
|
|
5534
|
+
var STALLED_LOOK_MS = 15e3;
|
|
5438
5535
|
var PUBLISH_RETRY_MS = 4e3;
|
|
5439
5536
|
/**
|
|
5440
5537
|
* A packet the relays' request budget held back goes again when the budget frees a request, and at least this often
|
|
@@ -5471,6 +5568,9 @@ var LinkSession = class {
|
|
|
5471
5568
|
/** A group link's `_tr` value this side publishes (`setTransports`), and the last the peer's packet carried. */
|
|
5472
5569
|
transports = null;
|
|
5473
5570
|
lastTransportsIn = null;
|
|
5571
|
+
/** When a read first found the peer's packet advertising with no `_tr` (0: the last packet that said carried one). */
|
|
5572
|
+
transportsAbsentSince = 0;
|
|
5573
|
+
transportsGone = false;
|
|
5474
5574
|
running = false;
|
|
5475
5575
|
/** Stopped without a last packet (`stop(false)`): nothing more goes out. */
|
|
5476
5576
|
silent = false;
|
|
@@ -5495,6 +5595,8 @@ var LinkSession = class {
|
|
|
5495
5595
|
publishRetryTimer = null;
|
|
5496
5596
|
active = false;
|
|
5497
5597
|
connected = false;
|
|
5598
|
+
/** The open data link's connection is `disconnected`, and read fast until then (`setDataLinkStalled`). */
|
|
5599
|
+
stalledUntil = 0;
|
|
5498
5600
|
firstPublish;
|
|
5499
5601
|
firstPublishTimer = null;
|
|
5500
5602
|
firstPollDone = false;
|
|
@@ -5584,6 +5686,10 @@ var LinkSession = class {
|
|
|
5584
5686
|
if (!this.running || this.lastPublishedAt > 0 || this.publishing) return;
|
|
5585
5687
|
this.publish().catch(() => {});
|
|
5586
5688
|
}
|
|
5689
|
+
/** This side's packet goes out again as it is: another writer of its key (a second device on a device code) may have replaced it. */
|
|
5690
|
+
republish() {
|
|
5691
|
+
if (this.running) this.publish().catch(() => {});
|
|
5692
|
+
}
|
|
5587
5693
|
/** Stops the loops. With `announce`, first tells the peer we are gone. */
|
|
5588
5694
|
async stop(announce = true) {
|
|
5589
5695
|
if (!this.running) return;
|
|
@@ -5648,8 +5754,22 @@ var LinkSession = class {
|
|
|
5648
5754
|
/** The data link carries chat and services while it is up. */
|
|
5649
5755
|
setDataLinkOpen(open) {
|
|
5650
5756
|
this.connected = open;
|
|
5757
|
+
this.stalledUntil = 0;
|
|
5651
5758
|
if (!open) this.pollNow();
|
|
5652
5759
|
}
|
|
5760
|
+
/**
|
|
5761
|
+
* The open data link's connection went `disconnected` (true), or came back: the contact's app may have crashed, and
|
|
5762
|
+
* once it starts again its offer is on Pkarr, which nothing on a dead data link says. Read fast for as long as it
|
|
5763
|
+
* lasts (the connection is given up or back within seconds), not at the connected pace. One read as it went
|
|
5764
|
+
* `disconnected` found only an offer already out: an app back a moment later (a tab opened again by hand) waited for
|
|
5765
|
+
* the connection's grace to run out (web/restart-relink.spec.ts, 20 s on a busy machine). Never longer than
|
|
5766
|
+
* `STALLED_LOOK_MS`, whatever becomes of the connection.
|
|
5767
|
+
*/
|
|
5768
|
+
setDataLinkStalled(stalled) {
|
|
5769
|
+
if (stalled === Date.now() < this.stalledUntil) return;
|
|
5770
|
+
this.stalledUntil = stalled ? Date.now() + STALLED_LOOK_MS : 0;
|
|
5771
|
+
if (stalled) this.pollNow();
|
|
5772
|
+
}
|
|
5653
5773
|
async setCallSignal(signal) {
|
|
5654
5774
|
this.callSignal = signal;
|
|
5655
5775
|
this.lastActivity = Date.now();
|
|
@@ -5773,6 +5893,7 @@ var LinkSession = class {
|
|
|
5773
5893
|
}
|
|
5774
5894
|
/** How urgently this link looks right now. */
|
|
5775
5895
|
pace() {
|
|
5896
|
+
if (Date.now() < this.stalledUntil) return "fast";
|
|
5776
5897
|
if (this.connected) return "connected";
|
|
5777
5898
|
if (Date.now() < this.fastPollUntil) return "fast";
|
|
5778
5899
|
if (Date.now() < this.watchUntil) return "active";
|
|
@@ -5845,7 +5966,10 @@ var LinkSession = class {
|
|
|
5845
5966
|
ms,
|
|
5846
5967
|
rtc: !!rtcSignal,
|
|
5847
5968
|
error: error instanceof Error ? error.message : String(error),
|
|
5848
|
-
...waiting && {
|
|
5969
|
+
...waiting && {
|
|
5970
|
+
waiting,
|
|
5971
|
+
retryInMs: error.retryInMs
|
|
5972
|
+
}
|
|
5849
5973
|
});
|
|
5850
5974
|
throw error;
|
|
5851
5975
|
}
|
|
@@ -5939,9 +6063,20 @@ var LinkSession = class {
|
|
|
5939
6063
|
nick: batch.nick,
|
|
5940
6064
|
services: online ? batch.services : null
|
|
5941
6065
|
};
|
|
5942
|
-
if (batch.transports !== null
|
|
5943
|
-
this.
|
|
5944
|
-
this.
|
|
6066
|
+
if (batch.transports !== null) {
|
|
6067
|
+
this.transportsAbsentSince = 0;
|
|
6068
|
+
this.transportsGone = false;
|
|
6069
|
+
if (batch.transports !== this.lastTransportsIn) {
|
|
6070
|
+
this.lastTransportsIn = batch.transports;
|
|
6071
|
+
this.events.onPeerTransports?.(batch.transports);
|
|
6072
|
+
}
|
|
6073
|
+
} else if (batch.services !== null && !this.transportsGone) {
|
|
6074
|
+
this.transportsAbsentSince ||= Date.now();
|
|
6075
|
+
if (Date.now() - this.transportsAbsentSince >= 12e4) {
|
|
6076
|
+
this.transportsGone = true;
|
|
6077
|
+
this.lastTransportsIn = null;
|
|
6078
|
+
this.events.onPeerTransportsGone?.();
|
|
6079
|
+
}
|
|
5945
6080
|
}
|
|
5946
6081
|
this.events.onPresence?.(this.presence);
|
|
5947
6082
|
if (read && readBefore && batch.packetTimestamp !== wasSeen) this.events.onPeerClock?.(batch.packetTimestamp, readBefore, Date.now());
|
|
@@ -6125,6 +6260,29 @@ var GATHER_STALL_MS = 3e3;
|
|
|
6125
6260
|
var DISCONNECT_GRACE_MS = 12e3;
|
|
6126
6261
|
/** An offer this close to the end of the offerer's attempt is not answered again: the new answer would come too late. */
|
|
6127
6262
|
var REANSWER_MARGIN_MS = 15e3;
|
|
6263
|
+
/**
|
|
6264
|
+
* Traces the candidate pair an opened data link uses, by type only (host, srflx, prflx, relay; never an address): two
|
|
6265
|
+
* devices on one network should meet host to host. A connection without stats (a test's fake) traces nothing.
|
|
6266
|
+
*/
|
|
6267
|
+
async function tracePair(me, pc) {
|
|
6268
|
+
try {
|
|
6269
|
+
if (typeof pc.getStats !== "function") return;
|
|
6270
|
+
const stats = await pc.getStats();
|
|
6271
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6272
|
+
stats.forEach((s) => byId.set(s.id, s));
|
|
6273
|
+
let pairId;
|
|
6274
|
+
for (const s of byId.values()) if (s.type === "transport" && s.selectedCandidatePairId) pairId = s.selectedCandidatePairId;
|
|
6275
|
+
const pair = pairId !== void 0 ? byId.get(pairId) : [...byId.values()].find((s) => s.type === "candidate-pair" && s.nominated && s.state === "succeeded");
|
|
6276
|
+
if (!pair) return;
|
|
6277
|
+
const kind = (id) => byId.get(id)?.candidateType ?? "?";
|
|
6278
|
+
const rtt = typeof pair.currentRoundTripTime === "number" ? Math.round(pair.currentRoundTripTime * 1e3) : void 0;
|
|
6279
|
+
traceLink(me, "rtc-pair", {
|
|
6280
|
+
local: kind(pair.localCandidateId),
|
|
6281
|
+
remote: kind(pair.remoteCandidateId),
|
|
6282
|
+
...rtt !== void 0 && { rttMs: rtt }
|
|
6283
|
+
});
|
|
6284
|
+
} catch {}
|
|
6285
|
+
}
|
|
6128
6286
|
var DataLink = class {
|
|
6129
6287
|
options;
|
|
6130
6288
|
state = "idle";
|
|
@@ -6224,7 +6382,7 @@ var DataLink = class {
|
|
|
6224
6382
|
if (signal.t === "o") {
|
|
6225
6383
|
if (this.state === "offering") {
|
|
6226
6384
|
if (this.options.myPubKeyZ32 < this.options.peerPubKeyZ32) return;
|
|
6227
|
-
}
|
|
6385
|
+
}
|
|
6228
6386
|
this.lastSignalTs = signal.ts;
|
|
6229
6387
|
const wasOpen = this.state === "open";
|
|
6230
6388
|
this.teardown();
|
|
@@ -6354,7 +6512,7 @@ var DataLink = class {
|
|
|
6354
6512
|
this.options.publishSignal(JSON.stringify(signal));
|
|
6355
6513
|
this.setState("connecting");
|
|
6356
6514
|
} catch {
|
|
6357
|
-
this.reset();
|
|
6515
|
+
if (this.answered?.offer === offer) this.reset();
|
|
6358
6516
|
}
|
|
6359
6517
|
}
|
|
6360
6518
|
/**
|
|
@@ -6418,6 +6576,7 @@ var DataLink = class {
|
|
|
6418
6576
|
this.options.publishSignal(null);
|
|
6419
6577
|
this.options.onDirect?.("open");
|
|
6420
6578
|
this.options.onOpen(wrapDataChannel(dc));
|
|
6579
|
+
tracePair(this.options.myPubKeyZ32, pc);
|
|
6421
6580
|
});
|
|
6422
6581
|
dc.addEventListener("close", () => {
|
|
6423
6582
|
if (this.pc === pc) this.failed();
|
|
@@ -7844,8 +8003,12 @@ var REACTION_LIMITS = {
|
|
|
7844
8003
|
/** Reactions a sender says per window on one chat; the newest of each message waits for room. */
|
|
7845
8004
|
send: 20,
|
|
7846
8005
|
windowMs: 1e4,
|
|
7847
|
-
/**
|
|
7848
|
-
|
|
8006
|
+
/**
|
|
8007
|
+
* A reaction to a message not here yet waits this long for it, at most `buffer` of them per sender in each chat: a
|
|
8008
|
+
* member back in a busy group hears every member's latest reactions as their edges open, before the catch-up (over a
|
|
8009
|
+
* minute for a full log) brings what they name.
|
|
8010
|
+
*/
|
|
8011
|
+
bufferMs: 3e5,
|
|
7849
8012
|
buffer: 64,
|
|
7850
8013
|
/** Reactions of this side not confirmed yet, per chat: the oldest is dropped past it. */
|
|
7851
8014
|
pending: 32,
|
|
@@ -8966,11 +9129,19 @@ var DhtDelivery = class DhtDelivery {
|
|
|
8966
9129
|
timer = null;
|
|
8967
9130
|
/** The first control envelope of a start that waits (`firstControlAfterMs`). */
|
|
8968
9131
|
controlTimer = null;
|
|
9132
|
+
/** A new capability revision to name once the publication spacing allows (`announce`). */
|
|
9133
|
+
announceTimer = null;
|
|
8969
9134
|
chain = Promise.resolve();
|
|
8970
9135
|
lastPublish = 0;
|
|
8971
9136
|
/** The sequence of the last envelope of the contact that was past its expiry (traced once). */
|
|
8972
9137
|
expiredSeen = 0;
|
|
8973
9138
|
controlDue = 0;
|
|
9139
|
+
/**
|
|
9140
|
+
* This side's envelope in the invite's mailbox is sealed with the invite key alone (published before the pin). Once the
|
|
9141
|
+
* contact is pinned, one sealed to it replaces it as soon as the publication spacing allows (`resealDue`): another
|
|
9142
|
+
* holder of the invite reads there that it was taken (`sealedToAnother`), instead of pinning this side on it.
|
|
9143
|
+
*/
|
|
9144
|
+
unsealedOut;
|
|
8974
9145
|
/** Until when the relays' request budget holds publications back: what is due then goes at once. */
|
|
8975
9146
|
budgetUntil = 0;
|
|
8976
9147
|
errors = {};
|
|
@@ -8995,6 +9166,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
8995
9166
|
const { peerRejected: _peerRejected, ...state } = structuredClone(options.state ?? emptyDhtDeliveryState());
|
|
8996
9167
|
this.state = state;
|
|
8997
9168
|
this.mode = options.mode;
|
|
9169
|
+
this.unsealedOut = state.sequence > 0 && !options.credentials.peerKey;
|
|
8998
9170
|
this.from = identityFromSeedB64(options.params.seedB64).pubKeyZ32;
|
|
8999
9171
|
this.to = options.params.peerPubKeyZ32;
|
|
9000
9172
|
this.participation = identityFromSeedB64(options.credentials.seedB64);
|
|
@@ -9010,7 +9182,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9010
9182
|
mode: this.mode,
|
|
9011
9183
|
peerMode: this.state.peerMode,
|
|
9012
9184
|
authenticated: !!this.options.credentials.peerKey,
|
|
9013
|
-
error:
|
|
9185
|
+
error: [this.errors.read, this.errors.publish].filter(Boolean).join(". ") || void 0,
|
|
9014
9186
|
pendingUntil: this.state.pending?.expires,
|
|
9015
9187
|
maxTextBytes: 256,
|
|
9016
9188
|
...this.lastPublished && { lastPublished: this.lastPublished },
|
|
@@ -9144,7 +9316,8 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9144
9316
|
this.running = false;
|
|
9145
9317
|
if (this.timer) clearTimeout(this.timer);
|
|
9146
9318
|
if (this.controlTimer) clearTimeout(this.controlTimer);
|
|
9147
|
-
this.
|
|
9319
|
+
if (this.announceTimer) clearTimeout(this.announceTimer);
|
|
9320
|
+
this.timer = this.controlTimer = this.announceTimer = null;
|
|
9148
9321
|
await this.chain;
|
|
9149
9322
|
}
|
|
9150
9323
|
async setMode(mode) {
|
|
@@ -9417,6 +9590,14 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9417
9590
|
await this.serialize(async () => {
|
|
9418
9591
|
const rev = this.options.capsRev?.();
|
|
9419
9592
|
if (rev === void 0 || rev === this.namedRev) return;
|
|
9593
|
+
const wait = this.lastPublish + 4e3 - Date.now();
|
|
9594
|
+
if (wait > 0) {
|
|
9595
|
+
this.announceTimer ??= setTimeout(() => {
|
|
9596
|
+
this.announceTimer = null;
|
|
9597
|
+
if (!this.live) this.announce();
|
|
9598
|
+
}, wait);
|
|
9599
|
+
return;
|
|
9600
|
+
}
|
|
9420
9601
|
try {
|
|
9421
9602
|
await this.publish(true);
|
|
9422
9603
|
} catch {}
|
|
@@ -9509,13 +9690,14 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9509
9690
|
const pending = this.state.pending && this.state.pending.expires > now && this.state.pending.attempts < MAX_ATTEMPTS ? this.state.pending : void 0;
|
|
9510
9691
|
const receipt = this.state.receipt && this.state.receipt.expires > now && this.state.receipt.attempts < MAX_ATTEMPTS ? this.state.receipt : void 0;
|
|
9511
9692
|
const receiptDue = !!receipt && !receipt.settled && (receipt.next ?? 0) <= now;
|
|
9512
|
-
|
|
9693
|
+
const reseal = this.resealDue;
|
|
9694
|
+
if (!force && (!pending || pending.next > now) && !receiptDue && !this.reactionsDue && !reseal && this.controlDue > now) return;
|
|
9513
9695
|
const expires = pending?.expires ?? now - 12e4 + CONTROL_TTL;
|
|
9514
9696
|
const issued = Math.min(now, Math.max(now - DHT_ISSUED_BACK_MS, expires - (pending ? DHT_MESSAGE_TTL : CONTROL_TTL), (pending?.message[1] ?? 0) - 3e4));
|
|
9515
9697
|
const { body, records, reactions } = this.fitted(this.state.sequence + 1, issued, expires, pending?.message ?? null, receipt?.id ?? null, pending?.reply, pending?.edit, pending?.forwarded, pending?.file);
|
|
9516
9698
|
const reactionsWereDue = this.reactionsDue;
|
|
9517
9699
|
this.reactionsDue = !reactions && !!this.options.reactions?.().length;
|
|
9518
|
-
const identity = this.state.peerPinned && this.state.peerPinned !== "can" && this.pinned()?.identity || this.identity;
|
|
9700
|
+
const identity = !reseal && this.state.peerPinned && this.state.peerPinned !== "can" && this.pinned()?.identity || this.identity;
|
|
9519
9701
|
await this.persist({
|
|
9520
9702
|
...this.state,
|
|
9521
9703
|
sequence: body[1],
|
|
@@ -9550,9 +9732,14 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9550
9732
|
throw error;
|
|
9551
9733
|
}
|
|
9552
9734
|
this.namedRev = body[8] ?? void 0;
|
|
9735
|
+
if (identity === this.identity) this.unsealedOut = !this.options.credentials.peerKey;
|
|
9553
9736
|
delete this.errors.publish;
|
|
9554
9737
|
this.changed();
|
|
9555
9738
|
}
|
|
9739
|
+
/** The contact is pinned and this side's envelope in the invite's mailbox is still unsealed (`unsealedOut`). */
|
|
9740
|
+
get resealDue() {
|
|
9741
|
+
return this.unsealedOut && !!this.options.credentials.peerKey;
|
|
9742
|
+
}
|
|
9556
9743
|
/**
|
|
9557
9744
|
* The relays' request budget held the envelope back: nothing went out, so it was no attempt (a text keeps its eight),
|
|
9558
9745
|
* and what it carried (a text, a receipt, a new mode) goes the moment the budget frees a request: the text and the
|
|
@@ -9823,6 +10010,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9823
10010
|
const signal = this.signalReads > 0;
|
|
9824
10011
|
this.urgent = false;
|
|
9825
10012
|
if (signal) this.signalReads--;
|
|
10013
|
+
if (!this.options.credentials.peerKey && !this.options.credentials.expectedPeerKey && !this.state.sequence) await this.publishOnTick();
|
|
9826
10014
|
const readAt = Date.now();
|
|
9827
10015
|
try {
|
|
9828
10016
|
await this.read(background, signal);
|
|
@@ -9832,12 +10020,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9832
10020
|
if (!isDiscoveryBudgetError(error) && readAt >= this.networkBackAt) this.errors.read = `Could not read DHT delivery: ${error instanceof Error ? error.message : String(error)}`;
|
|
9833
10021
|
}
|
|
9834
10022
|
if (this.state.peerMode !== "dht") this.signalReads = 0;
|
|
9835
|
-
|
|
9836
|
-
try {
|
|
9837
|
-
await this.publish();
|
|
9838
|
-
} catch (error) {
|
|
9839
|
-
if (!isDiscoveryBudgetError(error) && publishAt >= this.networkBackAt) this.errors.publish = `Could not publish DHT delivery: ${error instanceof Error ? error.message : String(error)}`;
|
|
9840
|
-
}
|
|
10023
|
+
await this.publishOnTick();
|
|
9841
10024
|
this.changed();
|
|
9842
10025
|
});
|
|
9843
10026
|
} finally {
|
|
@@ -9850,6 +10033,14 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9850
10033
|
}
|
|
9851
10034
|
this.schedule();
|
|
9852
10035
|
}
|
|
10036
|
+
async publishOnTick() {
|
|
10037
|
+
const publishAt = Date.now();
|
|
10038
|
+
try {
|
|
10039
|
+
await this.publish();
|
|
10040
|
+
} catch (error) {
|
|
10041
|
+
if (!isDiscoveryBudgetError(error) && publishAt >= this.networkBackAt) this.errors.publish = `Could not publish DHT delivery: ${error instanceof Error ? error.message : String(error)}`;
|
|
10042
|
+
}
|
|
10043
|
+
}
|
|
9853
10044
|
/** How long until the next read of the contact's mailbox (WISP 403, poll pace). */
|
|
9854
10045
|
get pollMs() {
|
|
9855
10046
|
if (this.options.pollMs) return this.options.pollMs;
|
|
@@ -9863,7 +10054,7 @@ var DhtDelivery = class DhtDelivery {
|
|
|
9863
10054
|
if (!this.running || this.ticking) return;
|
|
9864
10055
|
if (this.timer) clearTimeout(this.timer);
|
|
9865
10056
|
const held = this.budgetUntil - Date.now();
|
|
9866
|
-
const due = this.reactionsDue ? Math.max(0, this.lastPublish + 4e3 - Date.now()) : Infinity;
|
|
10057
|
+
const due = this.reactionsDue || this.resealDue && !this.errors.publish ? Math.max(0, this.lastPublish + 4e3 - Date.now(), held) : Infinity;
|
|
9867
10058
|
this.timer = setTimeout(() => void this.tick(), Math.min(held > 0 ? Math.min(this.pollMs, held) : this.pollMs, due));
|
|
9868
10059
|
}
|
|
9869
10060
|
};
|
|
@@ -12044,7 +12235,7 @@ var CapsKeys = class {
|
|
|
12044
12235
|
capabilities,
|
|
12045
12236
|
extensions,
|
|
12046
12237
|
descriptors,
|
|
12047
|
-
name,
|
|
12238
|
+
name: sanitizeNick(name) ?? "",
|
|
12048
12239
|
...choice !== void 0 ? { choice } : {}
|
|
12049
12240
|
};
|
|
12050
12241
|
}
|
|
@@ -12053,14 +12244,17 @@ var emptyCapsState = () => ({ rev: 0 });
|
|
|
12053
12244
|
var digestOf = (content) => toBase64Url(sha256(utf8Encode(JSON.stringify(content)))).slice(0, 22);
|
|
12054
12245
|
/**
|
|
12055
12246
|
* Publishes this side's record (at first start, at every change, hourly) and reads the contact's (at
|
|
12056
|
-
* pairing, when an envelope names a newer revision, when the chat drops to the DHT).
|
|
12057
|
-
*
|
|
12247
|
+
* pairing, when an envelope names a newer revision, when the chat drops to the DHT). Publications go one
|
|
12248
|
+
* at a time, and reads one at a time beside them (a read never waits for a put); reads close together are merged.
|
|
12058
12249
|
*/
|
|
12059
12250
|
var CapsExchange = class {
|
|
12060
12251
|
options;
|
|
12061
12252
|
state;
|
|
12062
12253
|
keys;
|
|
12254
|
+
/** Publications, one at a time; reads of the contact's record (another key) have their own, so neither waits for the other. */
|
|
12063
12255
|
chain = Promise.resolve();
|
|
12256
|
+
readChain = Promise.resolve();
|
|
12257
|
+
saveChain = Promise.resolve();
|
|
12064
12258
|
timer = null;
|
|
12065
12259
|
running = false;
|
|
12066
12260
|
lastRead = 0;
|
|
@@ -12090,14 +12284,21 @@ var CapsExchange = class {
|
|
|
12090
12284
|
get address() {
|
|
12091
12285
|
return this.keys.identity.pubKeyZ32;
|
|
12092
12286
|
}
|
|
12093
|
-
serialize(run) {
|
|
12094
|
-
const next = this
|
|
12095
|
-
this
|
|
12287
|
+
serialize(run, chain = "chain") {
|
|
12288
|
+
const next = this[chain].then(run);
|
|
12289
|
+
this[chain] = next.catch(() => {});
|
|
12096
12290
|
return next;
|
|
12097
12291
|
}
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
this.
|
|
12292
|
+
/** Saves are one at a time, each over the state as it is then: a read and a publication change different fields. */
|
|
12293
|
+
persist(patch) {
|
|
12294
|
+
return this.serialize(async () => {
|
|
12295
|
+
const next = {
|
|
12296
|
+
...this.state,
|
|
12297
|
+
...patch
|
|
12298
|
+
};
|
|
12299
|
+
await this.options.save(structuredClone(next));
|
|
12300
|
+
this.state = next;
|
|
12301
|
+
}, "saveChain");
|
|
12101
12302
|
}
|
|
12102
12303
|
start() {
|
|
12103
12304
|
if (this.running || this.ended) return;
|
|
@@ -12114,7 +12315,8 @@ var CapsExchange = class {
|
|
|
12114
12315
|
if (this.readTimer) clearTimeout(this.readTimer);
|
|
12115
12316
|
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
12116
12317
|
this.timer = this.readTimer = this.retryTimer = null;
|
|
12117
|
-
await this.chain.
|
|
12318
|
+
await Promise.all([this.chain, this.readChain]);
|
|
12319
|
+
await this.saveChain;
|
|
12118
12320
|
}
|
|
12119
12321
|
/**
|
|
12120
12322
|
* Publishes when the content changed, when the contact was pinned since the last publication (the
|
|
@@ -12139,7 +12341,6 @@ var CapsExchange = class {
|
|
|
12139
12341
|
const { records, dropped } = this.keys.seal(content, rev, peerKey, now);
|
|
12140
12342
|
this.dropped = dropped;
|
|
12141
12343
|
await this.persist({
|
|
12142
|
-
...this.state,
|
|
12143
12344
|
rev,
|
|
12144
12345
|
digest,
|
|
12145
12346
|
sealedFor: peerKey,
|
|
@@ -12151,10 +12352,7 @@ var CapsExchange = class {
|
|
|
12151
12352
|
this.schedule(isDiscoveryBudgetError(error) ? budgetRetryMs(error, 1e3, 6e4) : 6e4);
|
|
12152
12353
|
throw error;
|
|
12153
12354
|
}
|
|
12154
|
-
await this.persist({
|
|
12155
|
-
...this.state,
|
|
12156
|
-
publishedAt: now
|
|
12157
|
-
});
|
|
12355
|
+
await this.persist({ publishedAt: now });
|
|
12158
12356
|
this.lastOut = content;
|
|
12159
12357
|
if (changed || unpublished) this.options.published?.(rev);
|
|
12160
12358
|
if (dialable) {
|
|
@@ -12212,14 +12410,11 @@ var CapsExchange = class {
|
|
|
12212
12410
|
return this.state.peer;
|
|
12213
12411
|
}
|
|
12214
12412
|
if (this.state.peer && record.rev === this.state.peer.rev && record.author === this.state.peer.author) return this.state.peer;
|
|
12215
|
-
await this.persist({
|
|
12216
|
-
...this.state,
|
|
12217
|
-
peer: record
|
|
12218
|
-
});
|
|
12413
|
+
await this.persist({ peer: record });
|
|
12219
12414
|
this.retries = 0;
|
|
12220
12415
|
this.options.changed?.(record);
|
|
12221
12416
|
return record;
|
|
12222
|
-
}).finally(() => this.readAgainIfBehind());
|
|
12417
|
+
}, "readChain").finally(() => this.readAgainIfBehind());
|
|
12223
12418
|
}
|
|
12224
12419
|
/**
|
|
12225
12420
|
* Read again in a moment, a few times at most, while the record known is short of what the contact has: of the
|
|
@@ -14420,9 +14615,14 @@ var ChatFiles = class {
|
|
|
14420
14615
|
/**
|
|
14421
14616
|
* Removes what this side stored of an incoming file that ends here. Through its target, opened now if need be: after
|
|
14422
14617
|
* a restart a transfer has none until bytes arrive again, and one cancelled before that kept its part on the disk.
|
|
14618
|
+
* After the write under way, and with the ones queued behind it stopped: a write landing after the removal would
|
|
14619
|
+
* store a part of the file again.
|
|
14423
14620
|
*/
|
|
14424
14621
|
discardStored(entry) {
|
|
14425
|
-
|
|
14622
|
+
const incoming = entry.in;
|
|
14623
|
+
if (!incoming) return Promise.resolve();
|
|
14624
|
+
incoming.dropped = true;
|
|
14625
|
+
return incoming.writing.then(() => this.target(entry)).then((t) => t.discard()).catch(() => {});
|
|
14426
14626
|
}
|
|
14427
14627
|
target(entry) {
|
|
14428
14628
|
const incoming = entry.in;
|
|
@@ -14736,7 +14936,7 @@ var ChatFiles = class {
|
|
|
14736
14936
|
incoming.written += bytes.length;
|
|
14737
14937
|
const target = incoming.target;
|
|
14738
14938
|
incoming.writing = incoming.writing.then(async () => {
|
|
14739
|
-
if (transferEnded(record)) return;
|
|
14939
|
+
if (transferEnded(record) || incoming.dropped) return;
|
|
14740
14940
|
await (await target).append(bytes);
|
|
14741
14941
|
incoming.stored = offset + bytes.length;
|
|
14742
14942
|
if (incoming.stored === incoming.written || incoming.stored - incoming.acked >= FILE_LIMITS.ackEveryBytes) {
|
|
@@ -14906,7 +15106,10 @@ var ChatFiles = class {
|
|
|
14906
15106
|
switch (frame.t) {
|
|
14907
15107
|
case "pf-accept":
|
|
14908
15108
|
if (!isInt$1(frame.offset, record.file.size)) return;
|
|
14909
|
-
if (record.state === "paused" && record.pausedBy === "me")
|
|
15109
|
+
if (record.state === "paused" && record.pausedBy === "me") {
|
|
15110
|
+
this.send(this.offerFrame(record, true));
|
|
15111
|
+
return;
|
|
15112
|
+
}
|
|
14910
15113
|
out.generation++;
|
|
14911
15114
|
record.confirmed = frame.offset;
|
|
14912
15115
|
out.next = frame.offset;
|
|
@@ -14920,6 +15123,7 @@ var ChatFiles = class {
|
|
|
14920
15123
|
}
|
|
14921
15124
|
return;
|
|
14922
15125
|
case "pf-wait":
|
|
15126
|
+
if (record.state === "paused" && record.pausedBy === "me") return;
|
|
14923
15127
|
out.generation++;
|
|
14924
15128
|
out.next = record.confirmed;
|
|
14925
15129
|
clearTimeout(out.timer);
|
|
@@ -14966,7 +15170,13 @@ var ChatFiles = class {
|
|
|
14966
15170
|
entry.out.block = void 0;
|
|
14967
15171
|
entry.out.source = void 0;
|
|
14968
15172
|
}
|
|
14969
|
-
if (entry.in)
|
|
15173
|
+
if (entry.in) {
|
|
15174
|
+
const incoming = entry.in;
|
|
15175
|
+
clearTimeout(incoming.timer);
|
|
15176
|
+
incoming.writing.finally(() => {
|
|
15177
|
+
if (transferEnded(record)) incoming.target = void 0;
|
|
15178
|
+
});
|
|
15179
|
+
}
|
|
14970
15180
|
this.changed(entry);
|
|
14971
15181
|
}
|
|
14972
15182
|
entry(direction, id) {
|
|
@@ -16014,6 +16224,15 @@ var PairingTracker = class {
|
|
|
16014
16224
|
const { stage, reason } = this.progress;
|
|
16015
16225
|
if (stage === "publishing" || stage === "failed" && reason === "publish") this.set(this.idle());
|
|
16016
16226
|
}
|
|
16227
|
+
/** This side's first packet waits for the relays' request budget, which frees one at `until`: said, still `publishing`. */
|
|
16228
|
+
heldBack(until) {
|
|
16229
|
+
if (this.progress.stage !== "publishing") return;
|
|
16230
|
+
this.progress = {
|
|
16231
|
+
...this.progress,
|
|
16232
|
+
relayWaitUntil: until
|
|
16233
|
+
};
|
|
16234
|
+
this.changed(this.progress);
|
|
16235
|
+
}
|
|
16017
16236
|
/** The contact's packet was seen: they are on the network. */
|
|
16018
16237
|
sawPeer() {
|
|
16019
16238
|
if (this.peerSeen) return;
|
|
@@ -16045,7 +16264,7 @@ var PairingTracker = class {
|
|
|
16045
16264
|
retryable: true,
|
|
16046
16265
|
...detail ? { detail } : {}
|
|
16047
16266
|
});
|
|
16048
|
-
else this.set(this.idle(), { detail: detail ??
|
|
16267
|
+
else this.set(this.idle(), { detail: detail ?? reason });
|
|
16049
16268
|
return;
|
|
16050
16269
|
}
|
|
16051
16270
|
this.set("failed", {
|
|
@@ -16127,6 +16346,124 @@ var PairingTracker = class {
|
|
|
16127
16346
|
}
|
|
16128
16347
|
};
|
|
16129
16348
|
//#endregion
|
|
16349
|
+
//#region ../core/src/engineErrors.ts
|
|
16350
|
+
/**
|
|
16351
|
+
* The engine's known errors: a stable code each, and its English text with the values in it. The engine throws
|
|
16352
|
+
* `engineError(code, values)`; the text is the one it always wrote, so the CLI and every log keep printing the same.
|
|
16353
|
+
* The app's pages get only the text (it crosses the engine's RPC and is kept on payments and operations as a string),
|
|
16354
|
+
* and `parseEngineError` finds the code and the values back in it, to say it in the app's language. A text that is
|
|
16355
|
+
* none of these (a mint's own words, a provider's) stays as it is.
|
|
16356
|
+
*
|
|
16357
|
+
* `{name}` in a template is a value: a host or domain (no spaces), a network ("Mainnet" or "Testnet"), a chain's name,
|
|
16358
|
+
* an ecash unit ("usd"), or an amount (digits, with the separators of whatever locale wrote it).
|
|
16359
|
+
*/
|
|
16360
|
+
var ENGINE_ERRORS = {
|
|
16361
|
+
mintUnreachable: "Could not reach {host}. Check the address: it should be a Cashu mint.",
|
|
16362
|
+
lnurlUnreachable: "Could not reach {host}. It may be down, or not allow web apps to read it (CORS).",
|
|
16363
|
+
hostUnreachable: "Could not reach {host}",
|
|
16364
|
+
networkUnreachable: "Could not reach the network",
|
|
16365
|
+
hostTimedOut: "{host} did not answer in time",
|
|
16366
|
+
networkTimedOut: "The network did not answer in time",
|
|
16367
|
+
testMintOnMainnet: "This is a test mint, and its sats are worth nothing: add it to a Testnet Cashu wallet",
|
|
16368
|
+
realMintOnTestnet: "This mint holds real sats: add it to a Mainnet Cashu wallet",
|
|
16369
|
+
mintHoldsSats: "Move your sats out of this mint before removing it",
|
|
16370
|
+
lastMint: "This is the last mint of your {network} Cashu wallet: remove the wallet to remove it",
|
|
16371
|
+
invalidMintUrl: "That is not a valid mint URL",
|
|
16372
|
+
mintNotHttps: "Mints must use https",
|
|
16373
|
+
notEnoughSats: "Not enough sats in your wallet",
|
|
16374
|
+
noSharedMint: "You share no mint with this contact",
|
|
16375
|
+
ecashAlreadySpent: "This ecash was already spent somewhere else",
|
|
16376
|
+
ecashOtherUnit: "This ecash is in {unit}: only sat ecash can be redeemed here",
|
|
16377
|
+
reviewedEcashSpent: "The ecash for this payment was already spent somewhere else. Nothing was sent, and your balance now shows what the mint still holds.",
|
|
16378
|
+
reviewedSatsGone: "The sats this payment was reviewed with went to another payment. Nothing was sent: review it again.",
|
|
16379
|
+
lightningFeeTooHigh: "The Lightning fee ({fee} sats) is too high",
|
|
16380
|
+
invoiceExpired: "That invoice has expired",
|
|
16381
|
+
lnurlExactly: "{domain} asks for exactly {amount} sats",
|
|
16382
|
+
lnurlRange: "{domain} takes between {min} and {max} sats",
|
|
16383
|
+
feeAboveLimit: "The fee exceeds your limit",
|
|
16384
|
+
cashuFeeAboveLimit: "Cashu fee exceeds your limit",
|
|
16385
|
+
invalidMaxFee: "Invalid maximum fee",
|
|
16386
|
+
methodUnavailable: "This payment method is unavailable",
|
|
16387
|
+
unknownPaymentIntent: "Unknown payment intent",
|
|
16388
|
+
paymentExpired: "Payment request expired or has an invalid expiry",
|
|
16389
|
+
requestHasPayment: "This request already has a payment. Reconcile it before trying again.",
|
|
16390
|
+
requestPaymentInFlight: "This request already has a payment; reconcile it instead",
|
|
16391
|
+
requestNotAwaiting: "This request is no longer awaiting payment. Check its status before spending.",
|
|
16392
|
+
reconnectBeforeApprove: "Reconnect the data link before approving. Your review was saved.",
|
|
16393
|
+
cannotSubmitAgain: "This payment cannot be submitted again. Reconcile its existing transaction.",
|
|
16394
|
+
alreadySubmitted: "This payment was already submitted or could not be saved",
|
|
16395
|
+
sentAfterReview: "A payment from this wallet was sent after this review was made. Create a new review",
|
|
16396
|
+
cannotCancelSubmitted: "A submitted payment cannot be cancelled; reconcile it instead",
|
|
16397
|
+
outcomeUnknown: "Outcome unknown. Check the existing payment; do not send another.",
|
|
16398
|
+
notYetConfirmed: "Not yet confirmed. No second payment was sent.",
|
|
16399
|
+
couldNotVerify: "Could not verify this payment yet. No second payment was sent.",
|
|
16400
|
+
paymentTakenBack: "This payment was taken back. The sats are in your wallet.",
|
|
16401
|
+
parkedSigned: "Needs your decision: this payment was signed before this copy of your profile took over. It is never sent again from here. Check it before you pay again.",
|
|
16402
|
+
contactAway: "Your contact is offline. Nothing was sent: pay once the chat is live.",
|
|
16403
|
+
realMoneyUnconfirmed: "This pays with real money: confirm it with Send real money first. Nothing was sent.",
|
|
16404
|
+
mainnetPaymentOnTestnet: "This is a Mainnet payment (real money): a Testnet wallet never pays it. Use a Mainnet wallet.",
|
|
16405
|
+
testnetPaymentOnMainnet: "This is a Testnet payment (test coins): a Mainnet wallet never pays it. Use a Testnet wallet.",
|
|
16406
|
+
usdtRpcUnavailable: "USDT RPC unavailable",
|
|
16407
|
+
usdtNotEnoughTokens: "Insufficient token balance",
|
|
16408
|
+
usdtNotEnoughGas: "Insufficient ETH for gas",
|
|
16409
|
+
usdtGasAboveLimit: "Estimated maximum gas exceeds your limit",
|
|
16410
|
+
usdtNeedsSepoliaGas: "This needs a little Sepolia ETH for gas first",
|
|
16411
|
+
fedimintNotInvite: "That is not a Fedimint invite code: it starts with fed1",
|
|
16412
|
+
fedimintAlreadyJoined: "You already joined this federation",
|
|
16413
|
+
fedimintConnecting: "Wait for the federation to connect",
|
|
16414
|
+
fedimintNotEnough: "Not enough in this federation",
|
|
16415
|
+
fedimintRealOnTestnet: "This federation holds real bitcoin: it belongs in a Mainnet Fedimint wallet",
|
|
16416
|
+
fedimintTestOnMainnet: "This federation is on {chain}, a test network: it belongs in a Testnet Fedimint wallet",
|
|
16417
|
+
walletAway: "{wallet} can't be used here. Use it on {device}.",
|
|
16418
|
+
walletAwayUnnamed: "{wallet} can't be used here: its home is another device.",
|
|
16419
|
+
groupTurnUnconfirmed: "Can't check which device is active, so the group was not changed. Try again in a moment."
|
|
16420
|
+
};
|
|
16421
|
+
/**
|
|
16422
|
+
* An error the engine knows: `engineCode` travels with it inside the engine, the text everywhere. Not `code`: the
|
|
16423
|
+
* CLI reads a `code` as its own exit code, and Node's errors carry one of theirs.
|
|
16424
|
+
*/
|
|
16425
|
+
var EngineError = class extends Error {
|
|
16426
|
+
engineCode;
|
|
16427
|
+
values;
|
|
16428
|
+
constructor(engineCode, values = {}) {
|
|
16429
|
+
super(engineText(engineCode, values));
|
|
16430
|
+
this.engineCode = engineCode;
|
|
16431
|
+
this.values = values;
|
|
16432
|
+
}
|
|
16433
|
+
};
|
|
16434
|
+
/** The English text of `code` with its values in it, exactly as the engine writes it. */
|
|
16435
|
+
function engineText(code, values = {}) {
|
|
16436
|
+
return ENGINE_ERRORS[code].replace(/\{(\w+)\}/g, (_, name) => name in values ? String(values[name]) : `{${name}}`);
|
|
16437
|
+
}
|
|
16438
|
+
var engineError = (code, values) => new EngineError(code, values);
|
|
16439
|
+
/** What each kind of value looks like in a text: never a space in a host, a network by its name, an amount as digits. */
|
|
16440
|
+
var VALUE = {
|
|
16441
|
+
host: "(\\S+)",
|
|
16442
|
+
domain: "(\\S+)",
|
|
16443
|
+
chain: "(\\S+)",
|
|
16444
|
+
unit: "(\\S+)",
|
|
16445
|
+
network: "(Mainnet|Testnet)",
|
|
16446
|
+
amount: "(\\d[\\d.,\\u00a0\\u202f' ]*?)",
|
|
16447
|
+
fee: "(\\d[\\d.,\\u00a0\\u202f' ]*?)",
|
|
16448
|
+
min: "(\\d[\\d.,\\u00a0\\u202f' ]*?)",
|
|
16449
|
+
max: "(\\d[\\d.,\\u00a0\\u202f' ]*?)"
|
|
16450
|
+
};
|
|
16451
|
+
var escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16452
|
+
Object.keys(ENGINE_ERRORS).map((code) => {
|
|
16453
|
+
const names = [];
|
|
16454
|
+
const source = ENGINE_ERRORS[code].split(/(\{\w+\})/).map((part) => {
|
|
16455
|
+
const value = /^\{(\w+)\}$/.exec(part);
|
|
16456
|
+
if (!value) return escape(part);
|
|
16457
|
+
names.push(value[1]);
|
|
16458
|
+
return VALUE[value[1]] ?? "(.+?)";
|
|
16459
|
+
}).join("");
|
|
16460
|
+
return {
|
|
16461
|
+
code,
|
|
16462
|
+
names,
|
|
16463
|
+
pattern: new RegExp(`^${source}$`)
|
|
16464
|
+
};
|
|
16465
|
+
});
|
|
16466
|
+
//#endregion
|
|
16130
16467
|
//#region ../core/src/ghostlink.ts
|
|
16131
16468
|
/**
|
|
16132
16469
|
* One link to one peer, complete: Pkarr presence and signaling, the WebRTC
|
|
@@ -16258,7 +16595,7 @@ var QUIET_FRAMES = /* @__PURE__ */ new Set([
|
|
|
16258
16595
|
* `disconnected`) otherwise went on until its consent checks gave up, 27 s after the crash (CI run 36741701666).
|
|
16259
16596
|
*/
|
|
16260
16597
|
var PONG_WAIT_MS = 4e3;
|
|
16261
|
-
/** How long `handled` waits for the contact to answer its ping before it says it
|
|
16598
|
+
/** How long `handled` waits for the contact to answer its ping before it says it is late. */
|
|
16262
16599
|
var HANDLED_WAIT_MS = 1e4;
|
|
16263
16600
|
/**
|
|
16264
16601
|
* Back in front after an absence (`wake`), a live session that ends this soon after it was lost while the app was
|
|
@@ -16321,6 +16658,8 @@ function recordDescriptor(transport, known, record, newer, listed) {
|
|
|
16321
16658
|
relay: r.relay
|
|
16322
16659
|
} : record;
|
|
16323
16660
|
}
|
|
16661
|
+
/** How many keys' signals a one-time enrollment link remembers: past these, further keys are not counted. */
|
|
16662
|
+
var SIGNAL_SIGNERS_MAX = 16;
|
|
16324
16663
|
var PAYMENT_METHODS = [
|
|
16325
16664
|
"cashu",
|
|
16326
16665
|
"lightning",
|
|
@@ -16388,6 +16727,10 @@ var GhostLink = class {
|
|
|
16388
16727
|
signalPublishFailed = false;
|
|
16389
16728
|
peerDescriptors;
|
|
16390
16729
|
peerTransports;
|
|
16730
|
+
/** A group link: this side published a `_tr` on it, in this run or an earlier one (`packetTransportsSaid`). */
|
|
16731
|
+
saidTransports;
|
|
16732
|
+
/** A group link: what is known of the member's transports came from its packet (`_tr`), read now or kept from before. */
|
|
16733
|
+
peerPacketSaid;
|
|
16391
16734
|
peerFallback;
|
|
16392
16735
|
preferred;
|
|
16393
16736
|
fallback;
|
|
@@ -16534,6 +16877,30 @@ var GhostLink = class {
|
|
|
16534
16877
|
lostUntil = 0;
|
|
16535
16878
|
/** Counts the signals a signer was asked to sign: only the latest one goes out. */
|
|
16536
16879
|
signalTurn = 0;
|
|
16880
|
+
/** Puts this side's packet out again as it is: another writer of its key may have replaced it. */
|
|
16881
|
+
republish() {
|
|
16882
|
+
if (!this.stopped) this.session.republish();
|
|
16883
|
+
}
|
|
16884
|
+
/** The keys that signed a signal in the other end's packet, on a link that tells of refused keys (`onPeerKeyRefused`). */
|
|
16885
|
+
signalSigners = /* @__PURE__ */ new Set();
|
|
16886
|
+
/**
|
|
16887
|
+
* A one-time enrollment link (WISP 06 § Adding a device: one session per invite): every device that reads the code
|
|
16888
|
+
* writes the same packet, each signing its signals with its own device signing key, and the later one replaces the
|
|
16889
|
+
* earlier. A validly signed signal by a key other than the one pinned, or than the first one seen there, is a second
|
|
16890
|
+
* device on the code: said once per key, before a pin as after it.
|
|
16891
|
+
*/
|
|
16892
|
+
noteSignalSigner(signal) {
|
|
16893
|
+
const events = this.options.events, credentials = this.options.pairing?.credentials;
|
|
16894
|
+
if (!events?.onPeerKeyRefused || !credentials?.signer || !this.options.params.profile || this.signalSigners.size >= SIGNAL_SIGNERS_MAX) return;
|
|
16895
|
+
if (!verifyPairedSignal(signal, this.options.params.peerPubKeyZ32, this.myPubKeyZ32, void 0, true)) return;
|
|
16896
|
+
const key = JSON.parse(signal).auth.key;
|
|
16897
|
+
if (this.signalSigners.has(key)) return;
|
|
16898
|
+
this.signalSigners.add(key);
|
|
16899
|
+
if (this.signalSigners.size > 1 || credentials.peerKey && key !== credentials.peerKey) {
|
|
16900
|
+
traceLink(this.myPubKeyZ32, "signal-second-key", { keys: this.signalSigners.size });
|
|
16901
|
+
events.onPeerKeyRefused();
|
|
16902
|
+
}
|
|
16903
|
+
}
|
|
16537
16904
|
/** A signal ready to go: on the live session during a switch to WebRTC, in this side's packet otherwise. */
|
|
16538
16905
|
signalOut(signed, report) {
|
|
16539
16906
|
if (this.switcher?.pending?.choices.includes("webrtc/1") && this.channel && this.paired?.state.status === "ready") {
|
|
@@ -16652,6 +17019,8 @@ var GhostLink = class {
|
|
|
16652
17019
|
this.streamWasBlocked = this.streamBlocked;
|
|
16653
17020
|
this.peerDescriptors = options.native?.peerDescriptors ?? {};
|
|
16654
17021
|
this.peerTransports = options.native?.peerTransports;
|
|
17022
|
+
this.saidTransports = !!options.packetTransports && !!options.packetTransportsSaid;
|
|
17023
|
+
this.peerPacketSaid = !!options.packetTransports && !!options.native?.peerTransports;
|
|
16655
17024
|
this.peerFallback = options.native?.peerFallback ?? false;
|
|
16656
17025
|
this.preferred = options.native?.preferred ?? "webrtc/1";
|
|
16657
17026
|
this.fallback = options.native?.fallback ?? true;
|
|
@@ -16665,7 +17034,7 @@ var GhostLink = class {
|
|
|
16665
17034
|
lastSeenTimestamp: options.lastSeenTimestamp,
|
|
16666
17035
|
pollIntervals: options.pollIntervals,
|
|
16667
17036
|
getServices: options.getServices,
|
|
16668
|
-
firstPublish: options.firstPublish ?? (this.tracker && options.pairingProgress?.role === "joiner" || this.resuming ? "after-first-poll" : "at-start"),
|
|
17037
|
+
firstPublish: options.firstPublish ?? (this.tracker && options.pairingProgress?.role === "joiner" && options.rtcAvailable !== false || this.resuming ? "after-first-poll" : "at-start"),
|
|
16669
17038
|
events: {
|
|
16670
17039
|
onFirstPoll: () => {
|
|
16671
17040
|
const online = this.session.peerPresence.online, early = this.firstLookDone;
|
|
@@ -16690,7 +17059,10 @@ var GhostLink = class {
|
|
|
16690
17059
|
this.maybeAutoConnect(presence);
|
|
16691
17060
|
},
|
|
16692
17061
|
onPublish: (result) => {
|
|
16693
|
-
if (result.waiting)
|
|
17062
|
+
if (result.waiting) {
|
|
17063
|
+
if (result.retryInMs !== void 0) this.tracker?.heldBack(Date.now() + result.retryInMs);
|
|
17064
|
+
return;
|
|
17065
|
+
}
|
|
16694
17066
|
if (result.error) {
|
|
16695
17067
|
this.tracker?.failed("publish", true, result.error);
|
|
16696
17068
|
return;
|
|
@@ -16722,6 +17094,7 @@ var GhostLink = class {
|
|
|
16722
17094
|
},
|
|
16723
17095
|
onPeerClock: (packetAt, readBefore, readAt) => events.onPeerClock?.(packetAt, readBefore, readAt),
|
|
16724
17096
|
onPeerTransports: (value) => this.peerPacketTransports(value),
|
|
17097
|
+
onPeerTransportsGone: () => this.peerPacketTransportsGone(),
|
|
16725
17098
|
onDiscoveryError: (error) => events.onDiscoveryError?.(error),
|
|
16726
17099
|
onStatus: (status) => events.onStatus?.(status),
|
|
16727
17100
|
onPoll: (poll) => events.onPoll?.(poll)
|
|
@@ -16836,9 +17209,13 @@ var GhostLink = class {
|
|
|
16836
17209
|
},
|
|
16837
17210
|
attemptTimeoutMs: () => this.tracker && !this.tracker.done ? PAIRING_ATTEMPT_MS : void 0,
|
|
16838
17211
|
onDisconnected: (disconnected) => {
|
|
16839
|
-
if (!disconnected
|
|
17212
|
+
if (!disconnected) {
|
|
17213
|
+
this.session.setDataLinkStalled(false);
|
|
17214
|
+
return;
|
|
17215
|
+
}
|
|
17216
|
+
if (this.activeBinding || !this.channel) return;
|
|
16840
17217
|
traceLink(this.myPubKeyZ32, "rtc-disconnected", {});
|
|
16841
|
-
this.session.
|
|
17218
|
+
this.session.setDataLinkStalled(true);
|
|
16842
17219
|
}
|
|
16843
17220
|
});
|
|
16844
17221
|
this.switcher = new TransportSwitch({
|
|
@@ -16955,6 +17332,7 @@ var GhostLink = class {
|
|
|
16955
17332
|
const firstContact = !!this.options.ownRecords || !!this.options.pairing && !this.options.pairing.credentials.peerKey;
|
|
16956
17333
|
const sight = seen && seen.after === null && !firstContact ? void 0 : seen;
|
|
16957
17334
|
const options = this.options, credentials = options.pairing?.credentials;
|
|
17335
|
+
this.noteSignalSigner(signal);
|
|
16958
17336
|
const verified = options.params.profile ? verifyPairedSignal(signal, options.params.peerPubKeyZ32, this.myPubKeyZ32, credentials?.peerKey, credentials?.requireSignedSignals) : signal;
|
|
16959
17337
|
if (verified && this.predatesLastSession(verified, sight)) return;
|
|
16960
17338
|
if (verified) this.dataLink.handleSignal(verified, sight);
|
|
@@ -17091,6 +17469,13 @@ var GhostLink = class {
|
|
|
17091
17469
|
unproven(channel) {
|
|
17092
17470
|
return this.dialedIn.has(channel) && !!this.options.pairing?.credentials.peerKey;
|
|
17093
17471
|
}
|
|
17472
|
+
/**
|
|
17473
|
+
* A session that started before the pin and was refused for another key: the pin came by another path meanwhile (the
|
|
17474
|
+
* DHT's first contact), and whoever this session reached held only a copy of the invite. It proves nothing either.
|
|
17475
|
+
*/
|
|
17476
|
+
pinnedSince(channel, paired) {
|
|
17477
|
+
return this.openedUnpinned.has(channel) && !!paired.state.keyMismatch;
|
|
17478
|
+
}
|
|
17094
17479
|
get textDelivery() {
|
|
17095
17480
|
if (this.isDataLinkOpen) return "stream";
|
|
17096
17481
|
if (!this.dht || this.securityRejected || this.keyStopped) return "unavailable";
|
|
@@ -17152,6 +17537,7 @@ var GhostLink = class {
|
|
|
17152
17537
|
this.emitDeliveryState();
|
|
17153
17538
|
}
|
|
17154
17539
|
start() {
|
|
17540
|
+
if (this.saidTransports) this.publishPacketTransports();
|
|
17155
17541
|
if (this.deliveryMode !== "dht") this.session.start();
|
|
17156
17542
|
if (this.resuming && this.deliveryMode !== "dht") {
|
|
17157
17543
|
this.session.expectPeer();
|
|
@@ -17170,6 +17556,7 @@ var GhostLink = class {
|
|
|
17170
17556
|
*/
|
|
17171
17557
|
dialEarly() {
|
|
17172
17558
|
if (!this.options.params.profile || !this.options.autoConnect || this.deliveryMode === "dht" || this.streamBlocked || this.dialing || this.channel) return;
|
|
17559
|
+
if (this.options.rtcAvailable === false) return;
|
|
17173
17560
|
const startedAt = this.options.pairingProgress?.startedAt ?? 0;
|
|
17174
17561
|
if (Date.now() - startedAt > 6e4) return;
|
|
17175
17562
|
this.dialGate = new Promise((resolve) => {
|
|
@@ -17619,11 +18006,20 @@ var GhostLink = class {
|
|
|
17619
18006
|
this.maybeAutoConnect(this.presence);
|
|
17620
18007
|
}
|
|
17621
18008
|
}
|
|
17622
|
-
/**
|
|
18009
|
+
/**
|
|
18010
|
+
* A group link's `_tr` in its packet: what this side runs and how to dial it, once it runs a native endpoint there.
|
|
18011
|
+
* Having said one, it goes on saying what it runs with no endpoint up too (`["webrtc/1"]`): the member's app keeps the
|
|
18012
|
+
* last value it read and acts only on a packet that carries one, so a `_tr` that just stopped left it dialling a
|
|
18013
|
+
* native transport nobody listens on, across restarts of both apps (an edge whose WebRTC failed once, node.ts
|
|
18014
|
+
* `edgeWithoutRtc`, in the next run of the app). Never an empty list: an app with no WebRTC whose endpoint is not up
|
|
18015
|
+
* yet says nothing, as before, and what it said in the run before holds.
|
|
18016
|
+
*/
|
|
17623
18017
|
publishPacketTransports() {
|
|
17624
18018
|
if (!this.options.packetTransports) return;
|
|
17625
|
-
const value = this.endpoints.size ? encodePacketTransports(this.availableTransports, this.localDescriptors()) : null;
|
|
17626
18019
|
const signer = this.options.pairing?.credentials.signer;
|
|
18020
|
+
if (this.endpoints.size && !signer) this.saidTransports = true;
|
|
18021
|
+
const available = this.availableTransports;
|
|
18022
|
+
const value = this.endpoints.size || this.saidTransports && available.length ? encodePacketTransports(available, this.localDescriptors()) : null;
|
|
17627
18023
|
if (!signer) {
|
|
17628
18024
|
this.session.setTransports(value);
|
|
17629
18025
|
return;
|
|
@@ -17666,10 +18062,42 @@ var GhostLink = class {
|
|
|
17666
18062
|
if (!said) return;
|
|
17667
18063
|
const transports = said.transports, descriptors = dialDescriptors(said.descriptors);
|
|
17668
18064
|
traceLink(this.myPubKeyZ32, "packet-transports", { transports });
|
|
18065
|
+
this.peerPacketSaid = true;
|
|
17669
18066
|
if (!transports.includes("webrtc/1") && !this.channel && WEBRTC_ATTEMPT.has(this.dataLink.state)) this.disconnect();
|
|
17670
18067
|
this.options.events?.onPacketTransports?.(transports, descriptors);
|
|
17671
18068
|
this.learnPeerTransports(transports, descriptors, true);
|
|
17672
18069
|
}
|
|
18070
|
+
/**
|
|
18071
|
+
* The member's packet advertises and has carried no `_tr` for `TRANSPORTS_GONE_MS`: its app publishes none, which is
|
|
18072
|
+
* what an app with WebRTC that runs no native endpoint on this link does. What an earlier packet said (kept by the
|
|
18073
|
+
* owner across restarts) no longer holds: an app that said `["iroh/1"]` while its WebRTC failed and came back with
|
|
18074
|
+
* WebRTC, from before it said so itself (`packetTransportsSaid`). Left as it was, this side ranked its transports
|
|
18075
|
+
* against that list, found a native one nobody listens on or none at all, and never offered WebRTC again.
|
|
18076
|
+
* Not on a device link: there only a signed value counts, and a missing one is signed by nobody.
|
|
18077
|
+
*/
|
|
18078
|
+
peerPacketTransportsGone() {
|
|
18079
|
+
if (!this.options.packetTransports || this.stopped || !this.peerPacketSaid || this.options.pairing?.credentials.signer) return;
|
|
18080
|
+
this.peerPacketSaid = false;
|
|
18081
|
+
traceLink(this.myPubKeyZ32, "packet-transports-gone", {});
|
|
18082
|
+
this.options.events?.onPacketTransportsGone?.();
|
|
18083
|
+
if (this.channel) return;
|
|
18084
|
+
this.peerTransports = void 0;
|
|
18085
|
+
this.peerRecordTransports = void 0;
|
|
18086
|
+
this.peerFallback = false;
|
|
18087
|
+
this.peerDescriptors = {};
|
|
18088
|
+
this.undescribed.clear();
|
|
18089
|
+
for (const t of TRANSPORTS) if (t !== "webrtc/1") {
|
|
18090
|
+
this.nativeFailures.delete(t);
|
|
18091
|
+
this.demotedUntil.delete(t);
|
|
18092
|
+
}
|
|
18093
|
+
if (this.resuming && this.resuming !== "webrtc/1") {
|
|
18094
|
+
this.resuming = void 0;
|
|
18095
|
+
this.resumingWoke = false;
|
|
18096
|
+
}
|
|
18097
|
+
this.autoConnectFailures = 0;
|
|
18098
|
+
this.lastAutoConnectAt = 0;
|
|
18099
|
+
this.notifyWait();
|
|
18100
|
+
}
|
|
17673
18101
|
/** A connection the contact dialled on one of this side's native endpoints. */
|
|
17674
18102
|
takeDialIn(channel, binding) {
|
|
17675
18103
|
if (this.streamBlocked) {
|
|
@@ -18195,6 +18623,15 @@ var GhostLink = class {
|
|
|
18195
18623
|
canDial(transport) {
|
|
18196
18624
|
return transport !== "webrtc/1" && this.endpoints.has(transport) && !!this.peerDescriptors[transport];
|
|
18197
18625
|
}
|
|
18626
|
+
/**
|
|
18627
|
+
* Whether a dial has anything to try: WebRTC here (the contact's record, once read, listing it), or a native transport
|
|
18628
|
+
* both apps run with this side's endpoint up and the contact's descriptor read. A group link counts every dial: its
|
|
18629
|
+
* fast looks for the member's `_tr` end after a few (`PACKET_TRANSPORTS_FAST_ATTEMPTS`).
|
|
18630
|
+
*/
|
|
18631
|
+
anythingToDial() {
|
|
18632
|
+
if (!this.options.params.profile || this.options.packetTransports) return true;
|
|
18633
|
+
return this.transportOffer().some((t) => t === "webrtc/1" ? !this.peerTransports || this.peerTransports.includes(t) : this.canDial(t) && !!this.peerTransports?.includes(t));
|
|
18634
|
+
}
|
|
18198
18635
|
clearRace() {
|
|
18199
18636
|
if (this.raceTimer) clearTimeout(this.raceTimer);
|
|
18200
18637
|
this.raceTimer = null;
|
|
@@ -18378,7 +18815,7 @@ var GhostLink = class {
|
|
|
18378
18815
|
async request(serviceId, request) {
|
|
18379
18816
|
if (this.options.params.profile) {
|
|
18380
18817
|
await this.connect();
|
|
18381
|
-
if (!this.supportsServices) throw new GhostlyHttpError("unavailable", this.isDataLinkOpen ? "Your contact's app cannot share
|
|
18818
|
+
if (!this.supportsServices) throw new GhostlyHttpError("unavailable", this.isDataLinkOpen ? "Your contact's app cannot share services in this chat, or needs an updated Ghostly" : "Shared services need a live connection");
|
|
18382
18819
|
if (!this.pairedHttp) throw new GhostlyHttpError("closed", "Data link is closed");
|
|
18383
18820
|
return this.pairedHttp.client.request(serviceId, request);
|
|
18384
18821
|
}
|
|
@@ -18508,10 +18945,20 @@ var GhostLink = class {
|
|
|
18508
18945
|
return;
|
|
18509
18946
|
}
|
|
18510
18947
|
traceLink(this.myPubKeyZ32, "dial", { failures: this.autoConnectFailures });
|
|
18948
|
+
this.autoDial();
|
|
18949
|
+
}
|
|
18950
|
+
/**
|
|
18951
|
+
* An automatic dial. One with nothing to try (`anythingToDial`) still says why (`liveAttempt`) and keeps the pace, but
|
|
18952
|
+
* is no failed attempt: the wait between attempts does not grow, and a first pairing does not turn `on-dht` for it.
|
|
18953
|
+
* Two Linux Desktops pairing over a slow DHT, neither having read the other's record yet, counted three such dials in
|
|
18954
|
+
* 12 s: the pairing turned `on-dht` and the next dial waited 160 s, though nothing had been tried.
|
|
18955
|
+
*/
|
|
18956
|
+
autoDial() {
|
|
18957
|
+
const tries = this.anythingToDial();
|
|
18511
18958
|
this.lastAutoConnectAt = Date.now();
|
|
18512
|
-
this.autoConnectFailures++;
|
|
18959
|
+
if (tries) this.autoConnectFailures++;
|
|
18513
18960
|
this.dial().catch((error) => {
|
|
18514
|
-
this.tracker?.failed("transport", true);
|
|
18961
|
+
if (tries) this.tracker?.failed("transport", true);
|
|
18515
18962
|
this.dialFailed(error instanceof Error ? error.message : String(error));
|
|
18516
18963
|
this.lookAtRetry();
|
|
18517
18964
|
});
|
|
@@ -18528,13 +18975,7 @@ var GhostLink = class {
|
|
|
18528
18975
|
failures: this.autoConnectFailures,
|
|
18529
18976
|
again: true
|
|
18530
18977
|
});
|
|
18531
|
-
this.
|
|
18532
|
-
this.autoConnectFailures++;
|
|
18533
|
-
this.dial().catch((error) => {
|
|
18534
|
-
this.tracker?.failed("transport", true);
|
|
18535
|
-
this.dialFailed(error instanceof Error ? error.message : String(error));
|
|
18536
|
-
this.lookAtRetry();
|
|
18537
|
-
});
|
|
18978
|
+
this.autoDial();
|
|
18538
18979
|
}
|
|
18539
18980
|
/**
|
|
18540
18981
|
* A failed dial waits `dialWait` before the next, and only a look at the contact's record dials: the next one came at
|
|
@@ -19183,8 +19624,17 @@ var GhostLink = class {
|
|
|
19183
19624
|
}));
|
|
19184
19625
|
} catch {}
|
|
19185
19626
|
}
|
|
19627
|
+
/**
|
|
19628
|
+
* The contact said goodbye on the last live session (its app quit or stopped) and has published nothing since but its
|
|
19629
|
+
* leave packet: its app is not running, so a dial now waits for nobody until it gives up.
|
|
19630
|
+
*/
|
|
19631
|
+
get contactAway() {
|
|
19632
|
+
const presence = this.session.peerPresence;
|
|
19633
|
+
return !this.isDataLinkOpen && !!this.departed && (presence.lastPacketAt === this.departed.packet || !presence.online);
|
|
19634
|
+
}
|
|
19186
19635
|
async requirePaymentSupport() {
|
|
19187
19636
|
if (!PAYMENT_METHODS.some((m) => this.paymentEnabled(m))) throw new Error("Payments are turned off in this chat.");
|
|
19637
|
+
if (this.contactAway) throw engineError("contactAway");
|
|
19188
19638
|
await this.connect();
|
|
19189
19639
|
if (!this.supportsPayments) throw new Error("This contact does not support payments. Both peers need an updated Ghostly.");
|
|
19190
19640
|
}
|
|
@@ -19386,6 +19836,8 @@ var GhostLink = class {
|
|
|
19386
19836
|
* no more than on the DHT, so it closes that connection and nothing else.
|
|
19387
19837
|
*/
|
|
19388
19838
|
dialedIn = /* @__PURE__ */ new WeakSet();
|
|
19839
|
+
/** Connections whose session started with no key pinned nor named by the invite. */
|
|
19840
|
+
openedUnpinned = /* @__PURE__ */ new WeakSet();
|
|
19389
19841
|
/**
|
|
19390
19842
|
* `migration`: the channel is a candidate that takes over from the session open now once it authenticated. With a
|
|
19391
19843
|
* `plan`, a live switch both sides agreed; without one, a replacement (`attachReplacement`): the contact dialled
|
|
@@ -19414,6 +19866,7 @@ var GhostLink = class {
|
|
|
19414
19866
|
}
|
|
19415
19867
|
};
|
|
19416
19868
|
const unproven = this.unproven(channel);
|
|
19869
|
+
if (!this.options.pairing.credentials.peerKey && !this.options.pairing.credentials.expectedPeerKey) this.openedUnpinned.add(channel);
|
|
19417
19870
|
if (!migration) {
|
|
19418
19871
|
this.activeBinding = binding;
|
|
19419
19872
|
this.channel = channel;
|
|
@@ -19441,13 +19894,14 @@ var GhostLink = class {
|
|
|
19441
19894
|
lightningPaymentsSupport: this.paymentEnabled("lightning"),
|
|
19442
19895
|
paymentsSupport: PAYMENT_METHODS.some((m) => this.paymentEnabled(m)) && !!this.options.events?.onPayment && !!this.options.events?.onPaymentRequest && !!this.options.events?.onPaymentResult,
|
|
19443
19896
|
onState: () => {
|
|
19444
|
-
if (this.channel === channel && (!this.unproven(channel) || paired.state.status === "ready")) this.emitPairingState();
|
|
19897
|
+
if (this.channel === channel && (!this.unproven(channel) && !this.pinnedSince(channel, paired) || paired.state.status === "ready")) this.emitPairingState();
|
|
19445
19898
|
},
|
|
19446
19899
|
onFailure: () => {
|
|
19447
19900
|
if (paired.state.keyMismatch) this.options.events?.onPeerKeyRefused?.();
|
|
19448
|
-
if (this.unproven(channel) || paired.authTimedOut || paired.overloaded) {
|
|
19901
|
+
if (this.unproven(channel) || this.pinnedSince(channel, paired) || paired.authTimedOut || paired.overloaded) {
|
|
19449
19902
|
if (paired.state.keyMismatch) this.dht?.foreignKeySeen("stream");
|
|
19450
19903
|
if (this.unproven(channel)) traceLink(this.myPubKeyZ32, "dialed-in-refused", { keyMismatch: !!paired.state.keyMismatch });
|
|
19904
|
+
else if (this.pinnedSince(channel, paired)) traceLink(this.myPubKeyZ32, "pinned-since-refused", { transport: binding?.transport ?? "webrtc/1" });
|
|
19451
19905
|
else if (paired.overloaded) traceLink(this.myPubKeyZ32, "receive-overload", { transport: binding?.transport ?? "webrtc/1" });
|
|
19452
19906
|
else traceLink(this.myPubKeyZ32, "auth-timeout", { transport: binding?.transport ?? "webrtc/1" });
|
|
19453
19907
|
migration?.reject(new Error(paired.state.error ?? "Candidate authentication failed"));
|
|
@@ -19966,7 +20420,8 @@ var GhostLink = class {
|
|
|
19966
20420
|
* Resolves once the contact's app handled every frame sent on this session so far: it answers a ping in the order
|
|
19967
20421
|
* frames come (every app since 1.0), so the pong to one sent now says so, however slowly it handles them. A group
|
|
19968
20422
|
* member's catch-up answer goes a slice at a time on it (`GroupSessionHooks.handled`). `false` when it cannot tell:
|
|
19969
|
-
* no session, a contact that answers no ping
|
|
20423
|
+
* no session, or a contact that answers no ping. `"late"` with no answer within `HANDLED_WAIT_MS`: a contact that
|
|
20424
|
+
* answers pings is still handling what came before (a phone with a long history), not one that cannot say.
|
|
19970
20425
|
*/
|
|
19971
20426
|
handled() {
|
|
19972
20427
|
const channel = this.channel;
|
|
@@ -19986,7 +20441,7 @@ var GhostLink = class {
|
|
|
19986
20441
|
resolve(exact);
|
|
19987
20442
|
}
|
|
19988
20443
|
};
|
|
19989
|
-
const timer = setTimeout(() => waiter.done(
|
|
20444
|
+
const timer = setTimeout(() => waiter.done("late"), HANDLED_WAIT_MS);
|
|
19990
20445
|
this.handledWaiters.push(waiter);
|
|
19991
20446
|
});
|
|
19992
20447
|
}
|
|
@@ -20480,121 +20935,6 @@ function decodeBolt11(text) {
|
|
|
20480
20935
|
};
|
|
20481
20936
|
}
|
|
20482
20937
|
//#endregion
|
|
20483
|
-
//#region ../core/src/engineErrors.ts
|
|
20484
|
-
/**
|
|
20485
|
-
* The engine's known errors: a stable code each, and its English text with the values in it. The engine throws
|
|
20486
|
-
* `engineError(code, values)`; the text is the one it always wrote, so the CLI and every log keep printing the same.
|
|
20487
|
-
* The app's pages get only the text (it crosses the engine's RPC and is kept on payments and operations as a string),
|
|
20488
|
-
* and `parseEngineError` finds the code and the values back in it, to say it in the app's language. A text that is
|
|
20489
|
-
* none of these (a mint's own words, a provider's) stays as it is.
|
|
20490
|
-
*
|
|
20491
|
-
* `{name}` in a template is a value: a host or domain (no spaces), a network ("Mainnet" or "Testnet"), a chain's name,
|
|
20492
|
-
* or an amount (digits, with the separators of whatever locale wrote it).
|
|
20493
|
-
*/
|
|
20494
|
-
var ENGINE_ERRORS = {
|
|
20495
|
-
mintUnreachable: "Could not reach {host}. Check the address: it should be a Cashu mint.",
|
|
20496
|
-
lnurlUnreachable: "Could not reach {host}. It may be down, or not allow web apps to read it (CORS).",
|
|
20497
|
-
hostUnreachable: "Could not reach {host}",
|
|
20498
|
-
networkUnreachable: "Could not reach the network",
|
|
20499
|
-
hostTimedOut: "{host} did not answer in time",
|
|
20500
|
-
networkTimedOut: "The network did not answer in time",
|
|
20501
|
-
testMintOnMainnet: "This is a test mint, and its sats are worth nothing: add it to a Testnet Cashu wallet",
|
|
20502
|
-
realMintOnTestnet: "This mint holds real sats: add it to a Mainnet Cashu wallet",
|
|
20503
|
-
mintHoldsSats: "Move your sats out of this mint before removing it",
|
|
20504
|
-
lastMint: "This is the last mint of your {network} Cashu wallet: remove the wallet to remove it",
|
|
20505
|
-
invalidMintUrl: "That is not a valid mint URL",
|
|
20506
|
-
mintNotHttps: "Mints must use https",
|
|
20507
|
-
notEnoughSats: "Not enough sats in your wallet",
|
|
20508
|
-
noSharedMint: "You share no mint with this contact",
|
|
20509
|
-
ecashAlreadySpent: "This ecash was already spent somewhere else",
|
|
20510
|
-
reviewedEcashSpent: "The ecash for this payment was already spent somewhere else. Nothing was sent, and your balance now shows what the mint still holds.",
|
|
20511
|
-
reviewedSatsGone: "The sats this payment was reviewed with went to another payment. Nothing was sent: review it again.",
|
|
20512
|
-
lightningFeeTooHigh: "The Lightning fee ({fee} sats) is too high",
|
|
20513
|
-
invoiceExpired: "That invoice has expired",
|
|
20514
|
-
lnurlExactly: "{domain} asks for exactly {amount} sats",
|
|
20515
|
-
lnurlRange: "{domain} takes between {min} and {max} sats",
|
|
20516
|
-
feeAboveLimit: "The fee exceeds your limit",
|
|
20517
|
-
cashuFeeAboveLimit: "Cashu fee exceeds your limit",
|
|
20518
|
-
invalidMaxFee: "Invalid maximum fee",
|
|
20519
|
-
methodUnavailable: "This payment method is unavailable",
|
|
20520
|
-
unknownPaymentIntent: "Unknown payment intent",
|
|
20521
|
-
paymentExpired: "Payment request expired or has an invalid expiry",
|
|
20522
|
-
requestHasPayment: "This request already has a payment. Reconcile it before trying again.",
|
|
20523
|
-
requestPaymentInFlight: "This request already has a payment; reconcile it instead",
|
|
20524
|
-
requestNotAwaiting: "This request is no longer awaiting payment. Check its status before spending.",
|
|
20525
|
-
reconnectBeforeApprove: "Reconnect the data link before approving. Your review was saved.",
|
|
20526
|
-
cannotSubmitAgain: "This payment cannot be submitted again. Reconcile its existing transaction.",
|
|
20527
|
-
alreadySubmitted: "This payment was already submitted or could not be saved",
|
|
20528
|
-
sentAfterReview: "A payment from this wallet was sent after this review was made. Create a new review",
|
|
20529
|
-
cannotCancelSubmitted: "A submitted payment cannot be cancelled; reconcile it instead",
|
|
20530
|
-
outcomeUnknown: "Outcome unknown. Check the existing payment; do not send another.",
|
|
20531
|
-
notYetConfirmed: "Not yet confirmed. No second payment was sent.",
|
|
20532
|
-
couldNotVerify: "Could not verify this payment yet. No second payment was sent.",
|
|
20533
|
-
paymentTakenBack: "This payment was taken back. The sats are in your wallet.",
|
|
20534
|
-
parkedSigned: "Needs your decision: this payment was signed before this copy of your profile took over. It is never sent again from here. Check it before you pay again.",
|
|
20535
|
-
realMoneyUnconfirmed: "This pays with real money: confirm it with Send real money first. Nothing was sent.",
|
|
20536
|
-
mainnetPaymentOnTestnet: "This is a Mainnet payment (real money): a Testnet wallet never pays it. Use a Mainnet wallet.",
|
|
20537
|
-
testnetPaymentOnMainnet: "This is a Testnet payment (test coins): a Mainnet wallet never pays it. Use a Testnet wallet.",
|
|
20538
|
-
usdtRpcUnavailable: "USDT RPC unavailable",
|
|
20539
|
-
usdtNotEnoughTokens: "Insufficient token balance",
|
|
20540
|
-
usdtNotEnoughGas: "Insufficient ETH for gas",
|
|
20541
|
-
usdtGasAboveLimit: "Estimated maximum gas exceeds your limit",
|
|
20542
|
-
usdtNeedsSepoliaGas: "This needs a little Sepolia ETH for gas first",
|
|
20543
|
-
fedimintNotInvite: "That is not a Fedimint invite code: it starts with fed1",
|
|
20544
|
-
fedimintAlreadyJoined: "You already joined this federation",
|
|
20545
|
-
fedimintConnecting: "Wait for the federation to connect",
|
|
20546
|
-
fedimintNotEnough: "Not enough in this federation",
|
|
20547
|
-
fedimintRealOnTestnet: "This federation holds real bitcoin: it belongs in a Mainnet Fedimint wallet",
|
|
20548
|
-
fedimintTestOnMainnet: "This federation is on {chain}, a test network: it belongs in a Testnet Fedimint wallet",
|
|
20549
|
-
walletAway: "{wallet} can't be used here. Use it on {device}.",
|
|
20550
|
-
walletAwayUnnamed: "{wallet} can't be used here: its home is another device.",
|
|
20551
|
-
groupTurnUnconfirmed: "Can't check which device is active, so the group was not changed. Try again in a moment."
|
|
20552
|
-
};
|
|
20553
|
-
/**
|
|
20554
|
-
* An error the engine knows: `engineCode` travels with it inside the engine, the text everywhere. Not `code`: the
|
|
20555
|
-
* CLI reads a `code` as its own exit code, and Node's errors carry one of theirs.
|
|
20556
|
-
*/
|
|
20557
|
-
var EngineError = class extends Error {
|
|
20558
|
-
engineCode;
|
|
20559
|
-
values;
|
|
20560
|
-
constructor(engineCode, values = {}) {
|
|
20561
|
-
super(engineText(engineCode, values));
|
|
20562
|
-
this.engineCode = engineCode;
|
|
20563
|
-
this.values = values;
|
|
20564
|
-
}
|
|
20565
|
-
};
|
|
20566
|
-
/** The English text of `code` with its values in it, exactly as the engine writes it. */
|
|
20567
|
-
function engineText(code, values = {}) {
|
|
20568
|
-
return ENGINE_ERRORS[code].replace(/\{(\w+)\}/g, (_, name) => name in values ? String(values[name]) : `{${name}}`);
|
|
20569
|
-
}
|
|
20570
|
-
var engineError = (code, values) => new EngineError(code, values);
|
|
20571
|
-
/** What each kind of value looks like in a text: never a space in a host, a network by its name, an amount as digits. */
|
|
20572
|
-
var VALUE = {
|
|
20573
|
-
host: "(\\S+)",
|
|
20574
|
-
domain: "(\\S+)",
|
|
20575
|
-
chain: "(\\S+)",
|
|
20576
|
-
network: "(Mainnet|Testnet)",
|
|
20577
|
-
amount: "(\\d[\\d.,\\u00a0\\u202f' ]*?)",
|
|
20578
|
-
fee: "(\\d[\\d.,\\u00a0\\u202f' ]*?)",
|
|
20579
|
-
min: "(\\d[\\d.,\\u00a0\\u202f' ]*?)",
|
|
20580
|
-
max: "(\\d[\\d.,\\u00a0\\u202f' ]*?)"
|
|
20581
|
-
};
|
|
20582
|
-
var escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20583
|
-
Object.keys(ENGINE_ERRORS).map((code) => {
|
|
20584
|
-
const names = [];
|
|
20585
|
-
const source = ENGINE_ERRORS[code].split(/(\{\w+\})/).map((part) => {
|
|
20586
|
-
const value = /^\{(\w+)\}$/.exec(part);
|
|
20587
|
-
if (!value) return escape(part);
|
|
20588
|
-
names.push(value[1]);
|
|
20589
|
-
return VALUE[value[1]] ?? "(.+?)";
|
|
20590
|
-
}).join("");
|
|
20591
|
-
return {
|
|
20592
|
-
code,
|
|
20593
|
-
names,
|
|
20594
|
-
pattern: new RegExp(`^${source}$`)
|
|
20595
|
-
};
|
|
20596
|
-
});
|
|
20597
|
-
//#endregion
|
|
20598
20938
|
//#region ../core/src/lnurl.ts
|
|
20599
20939
|
var LNURL_LIMITS = {
|
|
20600
20940
|
timeoutMs: 15e3,
|
|
@@ -24393,9 +24733,10 @@ var CatchUpAnswers = class {
|
|
|
24393
24733
|
return;
|
|
24394
24734
|
}
|
|
24395
24735
|
const rest = [...frames], told = [];
|
|
24736
|
+
const ask = () => handled(to).catch(() => false);
|
|
24396
24737
|
const next = () => {
|
|
24397
24738
|
if (!this.sendSlice(to, rest)) return false;
|
|
24398
|
-
if (rest.length) told.push(
|
|
24739
|
+
if (rest.length) told.push(ask());
|
|
24399
24740
|
return true;
|
|
24400
24741
|
};
|
|
24401
24742
|
if (!next() || !next() || !rest.length) return;
|
|
@@ -24403,7 +24744,9 @@ var CatchUpAnswers = class {
|
|
|
24403
24744
|
(async () => {
|
|
24404
24745
|
try {
|
|
24405
24746
|
while (rest.length) {
|
|
24406
|
-
|
|
24747
|
+
let answer = await told.shift();
|
|
24748
|
+
while (answer === "late" && this.hooks.active()) answer = await ask();
|
|
24749
|
+
if (!answer) await new Promise((resolve) => setTimeout(resolve, 500));
|
|
24407
24750
|
if (!this.hooks.active() || !next()) break;
|
|
24408
24751
|
}
|
|
24409
24752
|
} finally {
|
|
@@ -24772,6 +25115,8 @@ var GroupSession = class {
|
|
|
24772
25115
|
pendingCommits = /* @__PURE__ */ new Map();
|
|
24773
25116
|
/** When each member was last asked to catch me up, so a stream of unreadable frames is one question, not a loop. */
|
|
24774
25117
|
asked = /* @__PURE__ */ new Map();
|
|
25118
|
+
/** The signed leave of each member I last passed on, kept for an admin that was away (`byesHeld`). In memory only. */
|
|
25119
|
+
byesPassed = /* @__PURE__ */ new Map();
|
|
24775
25120
|
queue = Promise.resolve();
|
|
24776
25121
|
/** One metadata frame that names a commit or an epoch I do not have yet: tried again when the chain moves. */
|
|
24777
25122
|
pendingMeta;
|
|
@@ -24987,6 +25332,38 @@ var GroupSession = class {
|
|
|
24987
25332
|
const lowest = Math.max(0, entry.high - GROUP_LIMITS.window + 1);
|
|
24988
25333
|
return Math.max(0, entry.high - lowest + 1 - (1 + entry.window.filter((n) => n >= lowest).length));
|
|
24989
25334
|
}
|
|
25335
|
+
/**
|
|
25336
|
+
* The other members some of whose messages below the highest seen never arrived (what a sync names in `miss`), each
|
|
25337
|
+
* with a mark that changes when what is missing does: an author answers only from its own last `outlog`, so the rest
|
|
25338
|
+
* is for another member to hand on.
|
|
25339
|
+
*/
|
|
25340
|
+
lacking() {
|
|
25341
|
+
const out = /* @__PURE__ */ new Map();
|
|
25342
|
+
for (const [sender, epochs] of Object.entries(this.state.seen)) {
|
|
25343
|
+
if (sender === this.myKey || !rosterHas(this.roster, sender)) continue;
|
|
25344
|
+
const marks = Object.entries(epochs).filter(([, entry]) => this.gaps(entry).length).map(([e, entry]) => `${e}:${entry.high}:${entry.window.length}`);
|
|
25345
|
+
if (marks.length) out.set(sender, marks.join(","));
|
|
25346
|
+
}
|
|
25347
|
+
return out;
|
|
25348
|
+
}
|
|
25349
|
+
/** What a sync names in `miss` for one sender now: per epoch, the newest `GROUP_LIMITS.miss` numbers that never arrived. */
|
|
25350
|
+
missOf(sender) {
|
|
25351
|
+
const out = {};
|
|
25352
|
+
for (const [e, entry] of Object.entries(this.state.seen[sender] ?? {})) {
|
|
25353
|
+
const gaps = this.gaps(entry);
|
|
25354
|
+
if (gaps.length) out[e] = gaps;
|
|
25355
|
+
}
|
|
25356
|
+
return out;
|
|
25357
|
+
}
|
|
25358
|
+
/** Whether any of `asked` (per epoch, as `missOf` gave them) arrived since: the member asked holds what is missing. */
|
|
25359
|
+
filledSince(sender, asked) {
|
|
25360
|
+
return Object.entries(asked).some(([e, numbers]) => {
|
|
25361
|
+
const entry = this.state.seen[sender]?.[e];
|
|
25362
|
+
if (!entry) return false;
|
|
25363
|
+
const got = new Set(entry.window);
|
|
25364
|
+
return numbers.some((n) => got.has(n));
|
|
25365
|
+
});
|
|
25366
|
+
}
|
|
24990
25367
|
serialize(run) {
|
|
24991
25368
|
const operation = this.queue.then(run);
|
|
24992
25369
|
this.queue = operation.then(() => {}, () => {});
|
|
@@ -25619,10 +25996,29 @@ var GroupSession = class {
|
|
|
25619
25996
|
} catch {
|
|
25620
25997
|
return;
|
|
25621
25998
|
}
|
|
25999
|
+
if (bye.e < this.admittedAt(bye.k)) return;
|
|
25622
26000
|
if (this.isAdmin && this.hooks.adminWork?.() !== false && await this.commitUnlessTurnUnconfirmed("remove", bye.k)) return;
|
|
26001
|
+
if (from !== bye.k && this.byesPassed.get(bye.k)?.sig === bye.sig) return;
|
|
26002
|
+
this.byesPassed.set(bye.k, bye);
|
|
25623
26003
|
this.took(bye);
|
|
25624
26004
|
}
|
|
25625
26005
|
/**
|
|
26006
|
+
* The signed leaves I passed on whose members are still in the roster: the admin was away when they went round, so
|
|
26007
|
+
* they are handed to it again when its edge opens. One goes with the commit that removes its member.
|
|
26008
|
+
*/
|
|
26009
|
+
byesHeld() {
|
|
26010
|
+
const held = [];
|
|
26011
|
+
for (const [key, bye] of this.byesPassed) if (rosterHas(this.roster, key) && bye.e >= this.admittedAt(key)) held.push(bye);
|
|
26012
|
+
else this.byesPassed.delete(key);
|
|
26013
|
+
return held;
|
|
26014
|
+
}
|
|
26015
|
+
/** The epoch of the commit that last admitted `key` (0 for the creator, whom no commit admits). */
|
|
26016
|
+
admittedAt(key) {
|
|
26017
|
+
const chain = this.state.chain;
|
|
26018
|
+
for (let e = chain.length - 1; e > 0; e--) if (chain[e].k === "add" && chain[e].s === key) return e;
|
|
26019
|
+
return 0;
|
|
26020
|
+
}
|
|
26021
|
+
/**
|
|
25626
26022
|
* An automatic commit (a member's leave): made, or not made when no fresh turn read says this device is the active
|
|
25627
26023
|
* one (`adminTurn`). Then false, and the caller goes on as a member would. Any other refusal is thrown as before.
|
|
25628
26024
|
*/
|
|
@@ -26137,7 +26533,7 @@ var GroupSession = class {
|
|
|
26137
26533
|
this.state.meta = opened.meta;
|
|
26138
26534
|
await this.persist();
|
|
26139
26535
|
const change = !before && !rosterHas(commit.m, this.myKey) ? null : groupMetaChange(before, opened.meta, this.state.name);
|
|
26140
|
-
if (change) this.hooks.metaChanged?.(
|
|
26536
|
+
if (change) this.hooks.metaChanged?.(this.metaAuthor(before, s), change, opened.meta.ts);
|
|
26141
26537
|
this.hooks.changed();
|
|
26142
26538
|
this.took({
|
|
26143
26539
|
t: "group-meta",
|
|
@@ -26147,6 +26543,16 @@ var GroupSession = class {
|
|
|
26147
26543
|
c: frame.c
|
|
26148
26544
|
});
|
|
26149
26545
|
}
|
|
26546
|
+
/**
|
|
26547
|
+
* Who made the change a statement brings me: its signer, when they were the admin all along since the statement I
|
|
26548
|
+
* held (or, with none, since I got in). A new admin signs again what the last one set (`metaFollowsChain`): to a
|
|
26549
|
+
* member that missed that, it is no change of theirs it can tell apart, so nobody is named.
|
|
26550
|
+
*/
|
|
26551
|
+
metaAuthor(before, s) {
|
|
26552
|
+
const chain = this.state.chain, from = before ? before.e : chain.findIndex((c) => rosterHas(c.m, this.myKey));
|
|
26553
|
+
for (let e = Math.max(0, from); e <= s.e; e++) if (rosterAdmin(chain[e].m) !== s.by) return void 0;
|
|
26554
|
+
return s.by;
|
|
26555
|
+
}
|
|
26150
26556
|
/** After the chain or my secrets moved: the waiting statement, and, if I became the admin, the name and picture signed again as mine. */
|
|
26151
26557
|
async metaFollowsChain() {
|
|
26152
26558
|
const pending = this.pendingMeta;
|
|
@@ -26510,8 +26916,11 @@ function parseCommit(raw) {
|
|
|
26510
26916
|
if (c.ls !== void 0) out.ls = c.ls;
|
|
26511
26917
|
return out;
|
|
26512
26918
|
}
|
|
26513
|
-
/**
|
|
26514
|
-
|
|
26919
|
+
/**
|
|
26920
|
+
* The roster a commit of this kind makes of the previous one; null when it is not allowed. `trusted`: the commit was
|
|
26921
|
+
* verified when it was accepted (a replay of the stored main branch), so a leave's statement is not checked again.
|
|
26922
|
+
*/
|
|
26923
|
+
function communityRoster(previous, c, trusted = false) {
|
|
26515
26924
|
if (c.k === "create") return previous === null && !c.s && !!c.x && !!c.n ? [[c.by, "admin"]] : null;
|
|
26516
26925
|
if (!previous || !rosterHas(previous, c.by)) return null;
|
|
26517
26926
|
const admin = rosterAdmin(previous);
|
|
@@ -26521,7 +26930,7 @@ function communityRoster(previous, c) {
|
|
|
26521
26930
|
return sortRoster([...previous, [c.s, "member"]]);
|
|
26522
26931
|
case "leave":
|
|
26523
26932
|
if (!c.s || c.s === c.by || c.s === admin || !rosterHas(previous, c.s) || !c.ls) return null;
|
|
26524
|
-
if (!verify(fromBase64Url(c.ls), leaveStatement(c.g, c.s), publicKeyFromZ32(c.s))) return null;
|
|
26933
|
+
if (!trusted && !verify(fromBase64Url(c.ls), leaveStatement(c.g, c.s), publicKeyFromZ32(c.s))) return null;
|
|
26525
26934
|
return previous.filter(([k]) => k !== c.s);
|
|
26526
26935
|
case "remove":
|
|
26527
26936
|
if (c.by !== admin || !c.s || c.s === c.by || !rosterHas(previous, c.s)) return null;
|
|
@@ -26694,6 +27103,8 @@ var CommunitySession = class {
|
|
|
26694
27103
|
known = /* @__PURE__ */ new Map();
|
|
26695
27104
|
/** Commit hash → roster after it, for the window of the main branch and the side commits. */
|
|
26696
27105
|
rosters = /* @__PURE__ */ new Map();
|
|
27106
|
+
/** Commit hash → roster after it, for a few main-branch commits older than the window that `couldRead` was asked about. */
|
|
27107
|
+
pastRosters = /* @__PURE__ */ new Map();
|
|
26697
27108
|
mainIndex = /* @__PURE__ */ new Map();
|
|
26698
27109
|
pendingCommits = /* @__PURE__ */ new Map();
|
|
26699
27110
|
pendingSecrets = /* @__PURE__ */ new Map();
|
|
@@ -26702,6 +27113,8 @@ var CommunitySession = class {
|
|
|
26702
27113
|
asked = /* @__PURE__ */ new Map();
|
|
26703
27114
|
/** Syncs answered per member, a few a minute (`COMMUNITY_LIMITS.syncAnswers`). In memory only. */
|
|
26704
27115
|
syncsAnswered = /* @__PURE__ */ new Map();
|
|
27116
|
+
/** The entry seed sealed to each member, for the seed it seals (`entryFrame`): every sync answer carries it. In memory only. */
|
|
27117
|
+
sealedEntries = /* @__PURE__ */ new Map();
|
|
26705
27118
|
/** Catch-up answers still going out, a slice at a time (`receiveSync`). */
|
|
26706
27119
|
answers;
|
|
26707
27120
|
queue = Promise.resolve();
|
|
@@ -26900,8 +27313,26 @@ var CommunitySession = class {
|
|
|
26900
27313
|
*/
|
|
26901
27314
|
couldRead(key, messageId) {
|
|
26902
27315
|
const [, e, h] = messageId.split(":");
|
|
26903
|
-
|
|
26904
|
-
|
|
27316
|
+
if (h === void 0 || !rosterHas(this.roster, key)) return false;
|
|
27317
|
+
const found = this.commitByShort(Number(e), h);
|
|
27318
|
+
if (found) return rosterHas(this.rosterAt(found.hash) ?? [], key);
|
|
27319
|
+
const i = this.state.chain.length - 1 - (this.epoch - Number(e));
|
|
27320
|
+
const commit = Number.isSafeInteger(i) && i >= 0 ? this.state.chain[i] : void 0;
|
|
27321
|
+
const hash = commit && communityCommitHash(commit);
|
|
27322
|
+
return !!hash && hash.startsWith(h) && rosterHas(this.pastRoster(i, hash), key);
|
|
27323
|
+
}
|
|
27324
|
+
/** The roster after an old commit of the main branch, replayed and kept apart from the window's (a few, the latest asked). */
|
|
27325
|
+
pastRoster(i, hash) {
|
|
27326
|
+
const known = this.pastRosters.get(hash);
|
|
27327
|
+
if (known) return known;
|
|
27328
|
+
let roster = null;
|
|
27329
|
+
for (let j = 0; j <= i && (j === 0 || roster); j++) roster = communityRoster(roster, this.state.chain[j]);
|
|
27330
|
+
this.pastRosters.set(hash, roster ?? []);
|
|
27331
|
+
for (const old of this.pastRosters.keys()) {
|
|
27332
|
+
if (this.pastRosters.size <= 8) break;
|
|
27333
|
+
this.pastRosters.delete(old);
|
|
27334
|
+
}
|
|
27335
|
+
return roster ?? [];
|
|
26905
27336
|
}
|
|
26906
27337
|
/** Someone the chain took out (removed or left) and who is not back in. */
|
|
26907
27338
|
wasRemoved(key) {
|
|
@@ -26950,7 +27381,10 @@ var CommunitySession = class {
|
|
|
26950
27381
|
const lowest = Math.max(0, entry.high - COMMUNITY_LIMITS.replay + 1);
|
|
26951
27382
|
return Math.max(0, entry.high - lowest + 1 - (1 + entry.window.filter((n) => n >= lowest).length));
|
|
26952
27383
|
}
|
|
26953
|
-
/**
|
|
27384
|
+
/**
|
|
27385
|
+
* Replays the main branch (rosters kept for its window) and places the side commits. The main branch was verified
|
|
27386
|
+
* commit by commit when it was accepted: its signatures are not checked again.
|
|
27387
|
+
*/
|
|
26954
27388
|
rebuild() {
|
|
26955
27389
|
this.known.clear();
|
|
26956
27390
|
this.rosters.clear();
|
|
@@ -26958,7 +27392,7 @@ var CommunitySession = class {
|
|
|
26958
27392
|
let roster = null;
|
|
26959
27393
|
const keepFrom = Math.max(0, this.state.chain.length - 1 - COMMUNITY_LIMITS.window);
|
|
26960
27394
|
this.state.chain.forEach((commit, i) => {
|
|
26961
|
-
roster = communityRoster(roster, commit);
|
|
27395
|
+
roster = communityRoster(roster, commit, true);
|
|
26962
27396
|
if (!roster) throw new Error("Stored membership chain does not replay");
|
|
26963
27397
|
const h = communityCommitHash(commit);
|
|
26964
27398
|
this.known.set(h, commit);
|
|
@@ -26987,7 +27421,7 @@ var CommunitySession = class {
|
|
|
26987
27421
|
const i = this.mainIndex.get(h);
|
|
26988
27422
|
if (i === void 0) return void 0;
|
|
26989
27423
|
let roster = null;
|
|
26990
|
-
for (let j = 0; j <= i; j++) roster = communityRoster(roster, this.state.chain[j]);
|
|
27424
|
+
for (let j = 0; j <= i; j++) roster = communityRoster(roster, this.state.chain[j], true);
|
|
26991
27425
|
if (roster) this.rosters.set(h, roster);
|
|
26992
27426
|
return roster ?? void 0;
|
|
26993
27427
|
}
|
|
@@ -28091,13 +28525,14 @@ var CommunitySession = class {
|
|
|
28091
28525
|
const handed = /* @__PURE__ */ new Set();
|
|
28092
28526
|
const unheard = new Set(this.state.unheard ?? []);
|
|
28093
28527
|
for (const stored of this.state.store) {
|
|
28094
|
-
if (stored.s === from
|
|
28528
|
+
if (stored.s === from) continue;
|
|
28529
|
+
const high = have[stored.s]?.[seenKey(stored.e, stored.h)];
|
|
28530
|
+
if (Number.isSafeInteger(high) && high >= stored.n) continue;
|
|
28531
|
+
if (this.wasRemoved(stored.s)) continue;
|
|
28095
28532
|
const found = this.commitByShort(stored.e, stored.h);
|
|
28096
28533
|
if (!found) continue;
|
|
28097
28534
|
const reads = rosterHas(this.rosterAt(found.hash) ?? [], from), mine = stored.s === this.myKey && unheard.has(frameKey(stored));
|
|
28098
28535
|
if (!reads && !(mine && rosterHas(this.roster, from))) continue;
|
|
28099
|
-
const high = have[stored.s]?.[seenKey(stored.e, stored.h)];
|
|
28100
|
-
if (Number.isSafeInteger(high) && high >= stored.n) continue;
|
|
28101
28536
|
if (!this.mainIndex.has(found.hash)) this.handSide(from, found.hash, handed, answer);
|
|
28102
28537
|
answer.push(stored);
|
|
28103
28538
|
if (mine && reads) unheard.delete(frameKey(stored));
|
|
@@ -28143,11 +28578,13 @@ var CommunitySession = class {
|
|
|
28143
28578
|
});
|
|
28144
28579
|
}
|
|
28145
28580
|
}
|
|
28146
|
-
/** The current entry seed sealed to a member, when I hold it. */
|
|
28581
|
+
/** The current entry seed sealed to a member, when I hold it: sealed once per seed, not on every sync answer. */
|
|
28147
28582
|
entryFrame(to) {
|
|
28148
28583
|
const { key, seedB64 } = this.state.entry;
|
|
28149
28584
|
if (!key || !seedB64) return null;
|
|
28150
|
-
|
|
28585
|
+
const sealed = this.sealedEntries.get(to);
|
|
28586
|
+
if (sealed?.seedB64 === seedB64 && sealed.frame.x === key) return { ...sealed.frame };
|
|
28587
|
+
const frame = {
|
|
28151
28588
|
t: "group-entry",
|
|
28152
28589
|
v: 2,
|
|
28153
28590
|
g: this.id,
|
|
@@ -28155,6 +28592,11 @@ var CommunitySession = class {
|
|
|
28155
28592
|
x: key,
|
|
28156
28593
|
s: sealSecret(to, fromBase64Url(seedB64), entryAad(this.id, to))
|
|
28157
28594
|
};
|
|
28595
|
+
this.sealedEntries.set(to, {
|
|
28596
|
+
seedB64,
|
|
28597
|
+
frame
|
|
28598
|
+
});
|
|
28599
|
+
return { ...frame };
|
|
28158
28600
|
}
|
|
28159
28601
|
/** A newer entry seed, from a member who holds it (see `replaceLink`): only the one the chain names. */
|
|
28160
28602
|
takeEntrySeed(seedB64) {
|
|
@@ -28276,10 +28718,26 @@ var CommunitySession = class {
|
|
|
28276
28718
|
this.state.meta = opened.meta;
|
|
28277
28719
|
await this.persist();
|
|
28278
28720
|
const change = !before && !rosterHas(this.rosterAt(s.h) ?? [], this.myKey) ? null : groupMetaChange(before, opened.meta, this.state.name);
|
|
28279
|
-
if (change) this.hooks.metaChanged?.(
|
|
28721
|
+
if (change) this.hooks.metaChanged?.(this.metaAuthor(before, s, at), change, opened.meta.ts);
|
|
28280
28722
|
this.hooks.changed();
|
|
28281
28723
|
return true;
|
|
28282
28724
|
}
|
|
28725
|
+
/**
|
|
28726
|
+
* Who made the change a statement brings me (`at`: its commit's place on my main branch): its signer, when they were
|
|
28727
|
+
* the admin all along since the statement I held (or, with none, since I got in). A new admin signs again what the
|
|
28728
|
+
* last one set (`metaFollowsChain`): to a member that missed that, it is no change of theirs it can tell apart.
|
|
28729
|
+
*/
|
|
28730
|
+
metaAuthor(before, s, at) {
|
|
28731
|
+
const from = before ? this.mainIndex.get(before.h) : void 0;
|
|
28732
|
+
let roster = before && from !== void 0 ? this.rosterAt(before.h) ?? null : null, counted = from !== void 0;
|
|
28733
|
+
for (let i = from ?? 0; i <= at; i++) {
|
|
28734
|
+
if (i !== from) roster = communityRoster(roster, this.state.chain[i]);
|
|
28735
|
+
if (!roster) return void 0;
|
|
28736
|
+
counted ||= rosterHas(roster, this.myKey);
|
|
28737
|
+
if (counted && rosterAdmin(roster) !== s.by) return void 0;
|
|
28738
|
+
}
|
|
28739
|
+
return s.by;
|
|
28740
|
+
}
|
|
28283
28741
|
/** After the chain or my secrets moved: the waiting statement, and, if I became the admin, the name and picture signed again as mine. */
|
|
28284
28742
|
async metaFollowsChain() {
|
|
28285
28743
|
const pending = this.pendingMeta;
|
|
@@ -28442,11 +28900,18 @@ var APP_PERMISSIONS = [
|
|
|
28442
28900
|
"internet",
|
|
28443
28901
|
"name"
|
|
28444
28902
|
];
|
|
28903
|
+
/**
|
|
28904
|
+
* The clients a writer may name in `runtime.clients` (WISP 1200 · Manifest). A reader also accepts a client name it does
|
|
28905
|
+
* not know (`APP_CLIENT_NAME`), since `runtime` is advisory and a later client (Android, say) must not make every older
|
|
28906
|
+
* reader refuse the bundle.
|
|
28907
|
+
*/
|
|
28445
28908
|
var APP_CLIENTS = [
|
|
28446
28909
|
"web",
|
|
28447
28910
|
"desktop",
|
|
28448
28911
|
"extension"
|
|
28449
28912
|
];
|
|
28913
|
+
/** A client name a reader accepts in `runtime.clients`, a later client's included. */
|
|
28914
|
+
var APP_CLIENT_NAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
28450
28915
|
/**
|
|
28451
28916
|
* Where an app shows (WISP 1200 · Manifest, `view`). `chat`, the default when the manifest has none: inside a 1:1 chat
|
|
28452
28917
|
* only, beside it on a wide screen. `full`: full screen, alone or from a chat, never beside one.
|
|
@@ -28478,8 +28943,39 @@ var OPTIONAL = [
|
|
|
28478
28943
|
"support",
|
|
28479
28944
|
"releaseNotes"
|
|
28480
28945
|
];
|
|
28481
|
-
/**
|
|
28946
|
+
/**
|
|
28947
|
+
* Reserved for phase 2 and refused in phase 1, by a reader too: an app that names a price or a recovery key asks for
|
|
28948
|
+
* something this client cannot honour, so it is not one to ignore.
|
|
28949
|
+
*/
|
|
28482
28950
|
var RESERVED = ["price", "recovery"];
|
|
28951
|
+
/**
|
|
28952
|
+
* Keys refused in every mode and at any level, wherever a reader ignores unknown keys: kept and passed on, they would
|
|
28953
|
+
* name an object's prototype the moment anything spreads or assigns a manifest or a listing.
|
|
28954
|
+
*/
|
|
28955
|
+
var APP_FORBIDDEN_KEYS = [
|
|
28956
|
+
"__proto__",
|
|
28957
|
+
"constructor",
|
|
28958
|
+
"prototype"
|
|
28959
|
+
];
|
|
28960
|
+
/**
|
|
28961
|
+
* The path of an `APP_FORBIDDEN_KEYS` key anywhere in a parsed JSON value (`later.0.__proto__`), the values of ignored
|
|
28962
|
+
* keys included, or undefined: WISP 1200 refuses them at any level. `JSON.parse` makes `__proto__` an own key, so
|
|
28963
|
+
* `Object.keys` sees it. A stack, not recursion: a `listing.json` is read with `JSON.parse` alone, at any depth.
|
|
28964
|
+
*/
|
|
28965
|
+
function findAppForbiddenKey(value) {
|
|
28966
|
+
const stack = [[value, ""]];
|
|
28967
|
+
for (let next = stack.pop(); next; next = stack.pop()) {
|
|
28968
|
+
const [v, path] = next;
|
|
28969
|
+
if (typeof v !== "object" || v === null) continue;
|
|
28970
|
+
for (const key of Object.keys(v)) {
|
|
28971
|
+
const at = path ? `${path}.${key}` : key;
|
|
28972
|
+
if (APP_FORBIDDEN_KEYS.includes(key)) return at;
|
|
28973
|
+
stack.push([v[key], at]);
|
|
28974
|
+
}
|
|
28975
|
+
}
|
|
28976
|
+
}
|
|
28977
|
+
/** At most this many clients in `runtime.clients`. */
|
|
28978
|
+
var MAX_CLIENTS = 16;
|
|
28483
28979
|
var SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
28484
28980
|
var HOST = /^>=(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?$/;
|
|
28485
28981
|
var SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
@@ -28563,21 +29059,30 @@ var bad = (field, detail = `${field} is not valid`) => ({
|
|
|
28563
29059
|
reason: "bad-field",
|
|
28564
29060
|
detail
|
|
28565
29061
|
});
|
|
28566
|
-
/**
|
|
28567
|
-
|
|
29062
|
+
/**
|
|
29063
|
+
* Checks a parsed manifest against every rule and bound of WISP 1200 that the manifest alone decides. An unknown key is
|
|
29064
|
+
* ignored unless `strict` (`AppManifestOptions`).
|
|
29065
|
+
*/
|
|
29066
|
+
function checkAppManifest(value, options = {}) {
|
|
28568
29067
|
if (!isObject$1(value)) return bad("manifest", "The manifest is not an object");
|
|
28569
29068
|
if (value.ghostlyApp !== void 0 && value.ghostlyApp !== 1) return {
|
|
28570
29069
|
ok: false,
|
|
28571
29070
|
reason: "unsupported-format",
|
|
28572
29071
|
detail: "ghostlyApp is not 1"
|
|
28573
29072
|
};
|
|
29073
|
+
const forbidden = findAppForbiddenKey(value);
|
|
29074
|
+
if (forbidden !== void 0) return {
|
|
29075
|
+
ok: false,
|
|
29076
|
+
reason: "unknown-key",
|
|
29077
|
+
detail: forbidden
|
|
29078
|
+
};
|
|
28574
29079
|
for (const key of Object.keys(value)) {
|
|
28575
29080
|
if (RESERVED.includes(key)) return {
|
|
28576
29081
|
ok: false,
|
|
28577
29082
|
reason: "reserved-key",
|
|
28578
29083
|
detail: key
|
|
28579
29084
|
};
|
|
28580
|
-
if (!REQUIRED.includes(key) && !OPTIONAL.includes(key)) return {
|
|
29085
|
+
if (options.strict && !REQUIRED.includes(key) && !OPTIONAL.includes(key)) return {
|
|
28581
29086
|
ok: false,
|
|
28582
29087
|
reason: "unknown-key",
|
|
28583
29088
|
detail: key
|
|
@@ -28602,9 +29107,18 @@ function checkAppManifest(value) {
|
|
|
28602
29107
|
if (!Array.isArray(permissions) || new Set(permissions).size !== permissions.length || !permissions.every((p) => APP_PERMISSIONS.includes(p))) return bad("permissions");
|
|
28603
29108
|
if (m.view !== void 0 && !APP_VIEWS.includes(m.view)) return bad("view", `view is "chat" or "full"`);
|
|
28604
29109
|
const runtime = m.runtime;
|
|
28605
|
-
if (!isObject$1(runtime)
|
|
29110
|
+
if (!isObject$1(runtime)) return bad("runtime");
|
|
29111
|
+
for (const key of Object.keys(runtime)) {
|
|
29112
|
+
if (key === "host" || key === "clients") continue;
|
|
29113
|
+
if (options.strict) return {
|
|
29114
|
+
ok: false,
|
|
29115
|
+
reason: "unknown-key",
|
|
29116
|
+
detail: `runtime.${key}`
|
|
29117
|
+
};
|
|
29118
|
+
}
|
|
29119
|
+
if (typeof runtime.host !== "string" || !HOST.test(runtime.host)) return bad("runtime");
|
|
28606
29120
|
const clients = runtime.clients;
|
|
28607
|
-
if (!Array.isArray(clients) || clients.length < 1 || new Set(clients).size !== clients.length || !clients.every((c) => APP_CLIENTS.includes(c))) return bad("runtime");
|
|
29121
|
+
if (!Array.isArray(clients) || clients.length < 1 || clients.length > MAX_CLIENTS || new Set(clients).size !== clients.length || !clients.every((c) => options.strict ? APP_CLIENTS.includes(c) : typeof c === "string" && APP_CLIENT_NAME.test(c))) return bad("runtime");
|
|
28608
29122
|
if (!isAppLicense(m.license)) return bad("license");
|
|
28609
29123
|
if (m.sources !== void 0 && (!Array.isArray(m.sources) || m.sources.length > APP_BUNDLE_LIMITS.sources || !m.sources.every(isAppUrl))) return bad("sources");
|
|
28610
29124
|
if (m.homepage !== void 0 && !isAppUrl(m.homepage)) return bad("homepage");
|
|
@@ -28817,8 +29331,14 @@ var isLine = (v, max, min = 0) => typeof v === "string" && [...v].length >= min
|
|
|
28817
29331
|
var isText = (v, max) => typeof v === "string" && [...v].length <= max && !/[^\P{Cc}\n]/u.test(v);
|
|
28818
29332
|
var isCount = (v, min) => typeof v === "number" && Number.isSafeInteger(v) && v >= min;
|
|
28819
29333
|
var fine = { ok: true };
|
|
28820
|
-
function keysOf(value, required, optional) {
|
|
28821
|
-
|
|
29334
|
+
function keysOf(value, required, optional, options = {}) {
|
|
29335
|
+
const forbidden = findAppForbiddenKey(value);
|
|
29336
|
+
if (forbidden !== void 0) return {
|
|
29337
|
+
ok: false,
|
|
29338
|
+
reason: "unknown-key",
|
|
29339
|
+
detail: forbidden
|
|
29340
|
+
};
|
|
29341
|
+
for (const k of Object.keys(value)) if (options.strict && !required.includes(k) && !optional.includes(k)) return {
|
|
28822
29342
|
ok: false,
|
|
28823
29343
|
reason: "unknown-key",
|
|
28824
29344
|
detail: k
|
|
@@ -28845,14 +29365,14 @@ var LISTING_OPTIONAL = [
|
|
|
28845
29365
|
"repo",
|
|
28846
29366
|
"support"
|
|
28847
29367
|
];
|
|
28848
|
-
/** Checks one entry of `apps`, which is also the whole of a `listing.json`. */
|
|
28849
|
-
function checkAppListing(value) {
|
|
29368
|
+
/** Checks one entry of `apps`, which is also the whole of a `listing.json`. An unknown key is ignored unless `strict`. */
|
|
29369
|
+
function checkAppListing(value, options = {}) {
|
|
28850
29370
|
if (!isObject(value)) return {
|
|
28851
29371
|
ok: false,
|
|
28852
29372
|
reason: "bad-field",
|
|
28853
29373
|
detail: "A listing is not an object"
|
|
28854
29374
|
};
|
|
28855
|
-
const keys = keysOf(value, LISTING_REQUIRED, LISTING_OPTIONAL);
|
|
29375
|
+
const keys = keysOf(value, LISTING_REQUIRED, LISTING_OPTIONAL, options);
|
|
28856
29376
|
if (!keys.ok) return keys;
|
|
28857
29377
|
const bad = (field) => ({
|
|
28858
29378
|
ok: false,
|
|
@@ -28874,7 +29394,7 @@ function checkAppListing(value) {
|
|
|
28874
29394
|
for (const k of ["repo", "support"]) if (value[k] !== void 0 && !isAppUrl(value[k])) return bad(k);
|
|
28875
29395
|
return fine;
|
|
28876
29396
|
}
|
|
28877
|
-
function checkRemoval(value) {
|
|
29397
|
+
function checkRemoval(value, options) {
|
|
28878
29398
|
if (!isObject(value)) return {
|
|
28879
29399
|
ok: false,
|
|
28880
29400
|
reason: "bad-field",
|
|
@@ -28885,7 +29405,7 @@ function checkRemoval(value) {
|
|
|
28885
29405
|
"digest",
|
|
28886
29406
|
"reason",
|
|
28887
29407
|
"at"
|
|
28888
|
-
], []);
|
|
29408
|
+
], [], options);
|
|
28889
29409
|
if (!keys.ok) return keys;
|
|
28890
29410
|
if (!isAppRef(value.ref) || !isAppHash(value.digest) || !isLine(value.reason, APP_STORE_LIMITS.reason, 1) || !isCount(value.at, 0)) return {
|
|
28891
29411
|
ok: false,
|
|
@@ -28907,9 +29427,11 @@ var STORE_REQUIRED = [
|
|
|
28907
29427
|
];
|
|
28908
29428
|
/**
|
|
28909
29429
|
* Checks an index's fields and bounds, and every revocation it carries (one that does not verify refuses the index:
|
|
28910
|
-
* the store copied it, so a broken one is the store's fault). The signature and `expires` are `readAppStore`'s.
|
|
29430
|
+
* the store copied it, so a broken one is the store's fault). The signature and `expires` are `readAppStore`'s. An
|
|
29431
|
+
* unknown key of the index, a listing or a removal is ignored unless `strict`; a revocation is its publisher's signed
|
|
29432
|
+
* statement and keeps its exact keys.
|
|
28911
29433
|
*/
|
|
28912
|
-
function checkAppStoreIndex(value) {
|
|
29434
|
+
function checkAppStoreIndex(value, options = {}) {
|
|
28913
29435
|
if (!isObject(value)) return {
|
|
28914
29436
|
ok: false,
|
|
28915
29437
|
reason: "bad-field",
|
|
@@ -28920,7 +29442,7 @@ function checkAppStoreIndex(value) {
|
|
|
28920
29442
|
reason: "unsupported-format",
|
|
28921
29443
|
detail: "ghostlyStore is not 1"
|
|
28922
29444
|
};
|
|
28923
|
-
const keys = keysOf(value, STORE_REQUIRED, ["description"]);
|
|
29445
|
+
const keys = keysOf(value, STORE_REQUIRED, ["description"], options);
|
|
28924
29446
|
if (!keys.ok) return keys;
|
|
28925
29447
|
const bad = (field) => ({
|
|
28926
29448
|
ok: false,
|
|
@@ -28937,7 +29459,7 @@ function checkAppStoreIndex(value) {
|
|
|
28937
29459
|
if (!Array.isArray(apps) || apps.length > APP_STORE_LIMITS.apps) return bad("apps");
|
|
28938
29460
|
const refs = /* @__PURE__ */ new Set();
|
|
28939
29461
|
for (const listing of apps) {
|
|
28940
|
-
const checked = checkAppListing(listing);
|
|
29462
|
+
const checked = checkAppListing(listing, options);
|
|
28941
29463
|
if (!checked.ok) return checked;
|
|
28942
29464
|
const ref = listing.ref;
|
|
28943
29465
|
if (refs.has(ref)) return {
|
|
@@ -28949,7 +29471,7 @@ function checkAppStoreIndex(value) {
|
|
|
28949
29471
|
}
|
|
28950
29472
|
if (!Array.isArray(removed) || removed.length > APP_STORE_LIMITS.removed) return bad("removed");
|
|
28951
29473
|
for (const entry of removed) {
|
|
28952
|
-
const checked = checkRemoval(entry);
|
|
29474
|
+
const checked = checkRemoval(entry, options);
|
|
28953
29475
|
if (!checked.ok) return checked;
|
|
28954
29476
|
}
|
|
28955
29477
|
if (!Array.isArray(revoked) || revoked.length > APP_STORE_LIMITS.revoked) return bad("revoked");
|
|
@@ -28966,7 +29488,8 @@ function checkAppStoreIndex(value) {
|
|
|
28966
29488
|
/**
|
|
28967
29489
|
* Reads `ghostly-store.json` with its `ghostly-store.sig`, in this order: size, JSON, canonical bytes, fields and
|
|
28968
29490
|
* revocations, the signature statement, the key the reader holds for this store (when it holds one), the signature,
|
|
28969
|
-
* then `expires` against `now` (seconds): more than 90 days ahead is refused, past is `expired`.
|
|
29491
|
+
* then `expires` against `now` (seconds): more than 90 days ahead is refused, past is `expired`. A key it does not know
|
|
29492
|
+
* is ignored (a later store's optional field) and stays in the bytes the signature covers.
|
|
28970
29493
|
*/
|
|
28971
29494
|
function readAppStore(indexBytes, sigBytes, now, heldKey) {
|
|
28972
29495
|
if (indexBytes.length > APP_STORE_LIMITS.indexBytes) return {
|
|
@@ -29000,10 +29523,13 @@ function readAppStore(indexBytes, sigBytes, now, heldKey) {
|
|
|
29000
29523
|
}
|
|
29001
29524
|
};
|
|
29002
29525
|
}
|
|
29003
|
-
/**
|
|
29526
|
+
/**
|
|
29527
|
+
* Signs an index with the store key: its canonical bytes and the `ghostly-store.sig` bytes. Throws on a bad index, and
|
|
29528
|
+
* on a key this format does not define: a writer is strict, so a misspelt key is never signed.
|
|
29529
|
+
*/
|
|
29004
29530
|
async function signAppStore(index, signer) {
|
|
29005
29531
|
if (index.key !== toZ32(signer.publicKey)) throw new Error("A store index is signed by its own key");
|
|
29006
|
-
const checked = checkAppStoreIndex(JSON.parse(JSON.stringify(index)));
|
|
29532
|
+
const checked = checkAppStoreIndex(JSON.parse(JSON.stringify(index)), { strict: true });
|
|
29007
29533
|
if (!checked.ok) throw new Error(`Not a valid store index (${checked.reason}${checked.detail ? `: ${checked.detail}` : ""})`);
|
|
29008
29534
|
const { bytes, signature } = await signAppObject(APP_PREFIXES.store, index, signer);
|
|
29009
29535
|
return {
|
|
@@ -29074,6 +29600,6 @@ function appStoreDecision(held, read) {
|
|
|
29074
29600
|
return read.digest === held.digest ? "same" : "equivocation";
|
|
29075
29601
|
}
|
|
29076
29602
|
//#endregion
|
|
29077
|
-
export { parseSshPublicKey as $, signAppRevocation as $a, formatFileSize as $i, GROUP_FILE_LIMITS as $n, handoffEphemeral as $o, WAKE_CALL_INTERVAL_MS as $r, deviceKeyZ32 as $s, SEPOLIA_TEST_USDT_FAUCET as $t, entryParams as A, replySnippet as Aa, createRelayPayload as Ac, IdentityExchange as Ai, nip05RootKey as An, LEGACY_SERVICES as Ao, newerHead as Ar, enrollCancelReason as As, didWebDocumentUrl as At, GROUP_REACTED_FRAME as B, claimedTime as Ba, sanitizeAvatar as Bc, emptyProofLedger as Bi, engineText as Bn, HANDOFF_REQUEST as Bo, transferStalled as Br, deviceInviteJoinerParams as Bs, staticDidDocument as Bt, GROUP_READ_NOTE_COMMUNITY as C, reactionEmoji as Ca, setLinkTraceSink as Cc, DHT_TEXT_CAPABILITY as Ci, DOMAIN_FILE_MAX_BYTES as Cn, callRtcConfig as Co, beaconRecords as Cr, signTombstone as Cs, DID_JWS_MAX_LENGTH as Ct, KNOCK_TTL_MS as D, wireReaction as Da, withRequestOptions as Dc, IDENTITY_MAX_VALIDITY as Di, domainWellKnownFile as Dn, signalHasVideo as Do, lobbyRecords as Dr, ENROLL_HELLO as Ds, didGhostlyServiceEntry as Dt, encodeCommunityLink as E, validReactionNumber as Ea, isDiscoveryBudgetError as Ec, IDENTITY_MAX_EVIDENCE as Ei, domainTxtRecord as En, parseCallSignal as Eo, lobbyKeys as Er, ENROLL_GRANT as Es, checkDidSignature as Et, GROUP_WAKE_FRAME as F, buttonLabelClash as Fa, identityFromSeed as Fc, newIdentityBinding as Fi, txtValue as Fn, HANDOFF_HAVE as Fo, readLobby as Fr, enrollProofFrame as Fs, normalizeDid as Ft, GROUP_EDIT_TEXT_BYTES as G, APP_STATEMENT_LIMITS as Ga, toZ32 as Gc, irohRelayUrl as Gi, findEndpoint as Gn, handoffAttemptFailed as Go, typingActivity as Gr, encodeInviteCode as Gs, encodeDidDhtPacket as Gt, GroupSession as H, receivedTimestamp as Ha, fromBase64Url as Hc, TRANSPORTS as Hi, ENDPOINT as Hn, HANDOFF_VERIFIED as Ho, TYPING_REFRESH_MS as Hr, OwnInviteError as Hs, didDhtDocument as Ht, GroupWakeLimiter as I, checkStatusCard as Ia, identityFromSeedB64 as Ic, receivedIdentityStatus as Ii, wellKnownUrl as In, HANDOFF_HELLO as Io, shouldBeHub as Ir, readEnrollGrant as Is, parseDid as It, mentionsMember as J, checkAppRevokeStatement as Ja, peekDhtMailbox as Ji, parseFedimintRequestPayload as Jn, handoffCancelFrame as Jo, mayPin as Jr, DEVICES_CAPABILITY as Js, ETHEREUM_USDT as Jt, carryMentions as K, appFingerprint as Ka, utf8Decode as Kc, DHT_FILE_TOO_LARGE as Ki, isFederationId as Kn, handoffAttemptSucceeded as Ko, GROUP_PIN_FRAME as Kr, inviteLink as Ks, resolveDidDht as Kt, groupWakeFrame as L, sameButtonText as La, publicKeyFromZ32 as Lc, revokesIdentity as Li, requestLnurlInvoice as Ln, HANDOFF_OFFER as Lo, ChatFiles as Lr, readEnrollHello as Ls, parseDidDocument as Lt, knockRecords as M, ITEM_STATES as Ma, openRelayPayload as Mc, emptyIdentityLedger as Mi, recordsFromTxt as Mn, HANDOFF_ACK as Mo, rankHubs as Mr, enrollDoneFrame as Ms, didWebFileNames as Mt, mergeKnocks as N, RUN_RESULTS as Na, parseRelayPayload as Nc, identityRevocationValue as Ni, recordsFromWellKnown as Nn, HANDOFF_CANCEL as No, readBeacon as Nr, enrollGrantFrame as Ns, didWebFileUrl as Nt, decodeGroupEntryLink as O, pairedReplyAuthor as Oa, PacketTooLargeError as Oc, IDENTITY_REVOCATION_LABEL as Oi, encodeDnsQuery as On, emptyLinkRecords as Oo, mergeBeacon as Or, ENROLL_PROOF as Os, didJwsAlg as Ot, readKnocks as P, STATUS_CARD_LIMITS as Pa, createIdentity as Pc, identityStatement as Pi, recordsName as Pn, HANDOFF_DONE as Po, readBeaconHead as Pr, enrollHelloFrame as Ps, didWebHost as Pt, SshSigError as Q, signAppObject as Qa, readImageMeta as Qi, pairedMessageFrame as Qn, handoffDigest as Qo, readPin as Qr, deviceEchoNonce as Qs, SEPOLIA_TEST_USDT as Qt, groupWakes as R, statusCardText as Ra, verify as Rc, lastSharedWithMe as Ri, resolveLightningDestination as Rn, HANDOFF_PAKE as Ro, FILE_LIMITS as Rr, verifyEnrollProof as Rs, parseDidSignaturePaste as Rt, CommunitySession as S, queueReaction as Sa, normalizeRelayUrl as Sc, CapsExchange as Si, pubkyHomeserverOf as Sn, buildSdpFromSignal as So, beaconKeys as Sr, setUpdateFrame as Ss, DID_FILE_MAX_BYTES as St, decodeCommunityLink as T, readReaction as Ta, budgetRetryMs as Tc, dialDescriptors as Ti, dnsProofName as Tn, extractParamsFromSdp as To, freshHubs as Tr, ENROLL_DONE as Ts, checkDidJws as Tt, COMMUNITY_EDIT_FRAME as U, shownTime as Ua, randomBytes as Uc, automaticTransport as Ui, cashuRequestPayload as Un, filePartName as Uo, TypingSender as Ur, createChatInvite as Us, didDhtFromPublicKey as Ut, GROUP_READ_NOTE as V, heardTime as Va, bytesEqual as Vc, validExternalKey as Vi, decodeBolt11 as Vn, HANDOFF_TIMINGS as Vo, TYPING_KINDS as Vr, readDeviceInvite as Vs, strictBase64Url as Vt, GROUP_EDIT_FRAME as W, APP_PREFIXES as Wa, toBase64Url as Wc, irohDescriptor as Wi, fedimintRequestPayload as Wn, handoffAttemptAllowed as Wo, sanitizeTypingStatus as Wr, decodeInviteCode as Ws, encodeDidDhtDeactivated as Wt, SSHSIG_MAX_ARMOR as X, isAppRef as Xa, dhtFileId as Xi, INVITE_TAKEN as Xn, handoffContextProof as Xo, pinIsNewer as Xr, HANDOFF_CAPABILITY as Xs, ONCHAIN_PROVIDER as Xt, groupName as Y, isAppKey as Ya, DHT_FILE_CAPABILITY as Yi, GhostLink as Yn, handoffContext as Yo, nextPinNumber as Yr, ENROLL_CAPABILITY as Ys, EVM_TEST_CHAINS as Yt, SSHSIG_NAMESPACE as Z, readAppRevocations as Za, parseImageMeta as Zi, LIVENESS_PING_MS as Zn, handoffDataFrame as Zo, pinNumberHolds as Zr, deviceEchoFrame as Zs, PaymentPreflightError as Zt, GroupTypingBoard as _, COMMUNITY_REACTION_FRAME as _a, turnKeys as _c, HOLD_LIMITS as _i, normalizePubkyKey as _n, decodeControl as _o, MESH_HUBS as _r, sealHandoffPiece as _s, parseAtprotoDidDocument as _t, checkAppStoreIndex as a, baseMime as aa, TURN_PUT_WINDOW_MS as ac, wakeRequest as ai, formatPaymentAmount as an, rosterHas as ao, isGroupMessageId as ar, handoffRetryAfter as as, BitcoinAddressError as at, sealGroupTyping as b, ReactionWindow as ba, RelayTransport as bc, newMailbox as bi, pubkyProofPath as bn, RTC_CONFIG as bo, pickMeshHubs as br, readSetUpdate as bs, verifyAtprotoRecordProof as bt, APP_BUNDLE_LIMITS as c, voicePeaksOf as ca, classifyTurnRead as cc, chatAppId as ci, SPARK_NETWORKS as cn, DirectPathWatch as co, readGroupWant as cr, newHandoffId as cs, ATPROTO_FULL_SCOPE as ct, appViewOf as d, EDIT_RATE_WINDOW_MS as da, TURN_MAX as dc, BUTTONS_CAPABILITY as di, sparkInvoiceDetails as dn, presenceSeenAt as do, GROUP_SIGNAL_FRAME as dr, readHandoffDone as ds, atprotoDidMethod as dt, safeBlobType as ea, deviceLinkPairing as ec, WakeLimiter as ei, TEST_USDT_FAUCET_AMOUNT as en, canonicalJson as eo, GROUP_HAVE_FRAME as er, handoffHelloFrame as es, parseSshSignature as et, assembleAppBundle as f, RateWindow as fa, firstDeviceSetSecret as fc, EDIT_CAPABILITY as fi, isBitcoinAddress as fn, GhostlyHttpError as fo, SIGNAL_FRESH_MS as fr, readHandoffHave as fs, atprotoProofRecord as ft, GROUP_TYPING_FRAME as g, readForwarded as ga, signTurnRelease as gc, HOLD_DATED_BACK_MS as gi, newPubkyProofFolder as gn, LIMITS as go, readGroupSignal as gr, readHandoffVerified as gs, normalizeAtprotoHandle as gt, readAppBundle as h, forwardedAgain as ha, signTurnPacket as hc, HOLD_CAPABILITY as hi, isPubkyProofFolder as hn, webLocalFetch as ho, groupSignalFrame as hr, readHandoffTurnFrame as hs, isAtprotoDid as ht, planAppUpdate as i, VOICE_LIMITS as ia, webCryptoSigner as ic, relayRequest as ii, assertWholeSats as in, MEMBER_KEY as io, groupFileFallback as ir, handoffReleaseFrame as is, verifyBitcoinMessage as it, knockIdentity as j, BUTTON_ID as ja, newerPacket as jc, boundVerifiedIdentity as ji, normalizeDomain as jn, serviceIdFromName as jo, pickHubs as jr, enrollDigits as js, didWebFile as jt, encodeGroupEntryLink as k, readReply as ka, RELAY_PAYLOAD_MAX_BYTES as kc, IdentityCheckUnavailable as ki, isPublicAddress as kn, HTTP_SERVICE_PROTO as ko, mergeLobby as kr, enrollCancelFrame as ks, didSignedMessages as kt, APP_BUNDLE_MAGIC as l, parseVideoMeta as la, turnPutSummary as lc, isAppVersion as li, SPARK_PROVIDER as ln, EXPECT_PEER_MS as lo, readGroupWantNo as lr, readHandoffAck as ls, ATPROTO_PROOF_COLLECTION as lt, isAppUrl as m, parseLinkPreview as ma, readTurnPacket as mc, UPGRADE_CAPABILITY as mi, isPubkyKey as mn, parseLocalTarget as mo, SIGNAL_REPLY_MS as mr, readHandoffPake as ms, checkAtprotoProofRecord as mt, appUpdateDecision as n, sanitizeMime as na, newDeviceSetSecret as nc, newWakeToken as ni, WALLET_NETWORKS as nn, GROUP_MEMBER_CAP as no, GROUP_WANT_NO_FRAME as nr, handoffOfferFrame as ns, sshSignCommand as nt, readAppStore as o, fileMessageText as oa, TURN_RAISE_READ_TIMEOUT_MS as oc, checkPushEndpoint as oi, validatePaymentTarget as on, verifyCommitSignature as oo, readGroupFileMeta as or, handoffStreamKey as os, decodeAnyBitcoinAddress as ot, checkAppManifest as p, dhtEditId as pa, nextTurnPosition as pc, STATUS_CARD_CAPABILITY as pi, PUBKY_PROOF_MAX_BYTES as pn, formatLocalTarget as po, SIGNAL_HOLD_MS as pr, readHandoffHello as ps, atprotoProofRkey as pt, parseCommunityEdit as q, appRef as qa, utf8Encode as qc, DHT_MESSAGE_TTL as qi, parseCashuRequestPayload as qn, handoffBusyFrame as qo, PIN_LIMITS as qr, inviteOwnership as qs, signDidDhtPacket as qt, checkAppBeforeRun as r, PLAYABLE_AUDIO as ra, seedSigner as rc, parseWakeFrame as ri, assertTokenUnits as rn, MAX_GROUP_CHAIN as ro, fetchesByItself as rr, handoffPakeFrame as rs, verifySshSignature as rt, signAppStore as s, parseVoiceMeta as sa, TURN_SETTLE_MS as sc, vapidKeysMatch as si, walletNetworkOf as sn, edgeParams as so, readGroupHave as sr, handoffVersions as ss, ATPROTO_DECLARED_SCOPE as st, appStoreDecision as t, sanitizeFileName as ta, devicePingFrame as tc, deviceWakeRequest as ti, USDT_PUBLIC_RPC as tn, canonicalJsonBytes as to, GROUP_WANT_FRAME as tr, handoffManifestFrames as ts, sameSshKey as tt, appDigest as u, EDIT_BUFFER_MS as ua, TURN_LAST_SEQUENCE as uc, APPS_CAPABILITY as ui, sparkAddressKind as un, RELAY_POLL_INTERVALS as uo, CarriedTransport as ur, readHandoffCancel as us, ATPROTO_PROOF_SCOPE as ut, groupTypingEpoch as v, GROUP_REACTION_FRAME as va, turnName as vc, HoldKeys as vi, pubkyProofCapability as vn, encodeControl as vo, mayBeHub as vr, SET_ACK as vs, shortAtprotoDid as vt, communityHasFrame as w, reactionIsNewer as wa, traceLink as wc, capsDescriptors as wi, decodeDnsResponse as wn, callSignalHeardAt as wo, doorHubs as wr, verifySetUpdate as ws, DidError as wt, COMMUNITY_LIMITS as x, nextReactionNumber as xa, currentRelays as xc, readManifest as xi, homeserverWebEndpoint as xn, answersOffer as xo, COMMUNITY_TOPOLOGY as xr, setAckFrame as xs, DID_DOCUMENT_MAX_BYTES as xt, openGroupTyping as y, REACTION_LIMITS as ya, DEFAULT_RELAYS as yc, HoldRefusedError as yi, pubkyProofFile as yn, CALL_SIGNAL_MAX_AGE_MS as yo, meshHubs as yr, SET_UPDATE as ys, AtprotoProofError as yt, parseGroupWakeFrame as z, arrivalKey as za, MAX_AVATAR_LENGTH as zc, PeerProofs as zi, engineError as zn, HANDOFF_PIECE_BYTES as zo, transferEnded as zr, createDeviceInvite as zs, signingKeys as zt };
|
|
29603
|
+
export { SSHSIG_NAMESPACE as $, readAppRevocations as $a, parseImageMeta as $i, engineError as $n, handoffContextProof as $o, pinNumberHolds as $r, HANDOFF_CAPABILITY as $s, PaymentPreflightError as $t, decodeGroupEntryLink as A, pairedReplyAuthor as Aa, withRequestOptions as Ac, IDENTITY_REVOCATION_LABEL as Ai, encodeDnsQuery as An, signalHasVideo as Ao, mergeBeacon as Ar, ENROLL_HELLO as As, didJwsAlg as At, groupWakes as B, statusCardText as Ba, publicKeyFromZ32 as Bc, lastSharedWithMe as Bi, resolveLightningDestination as Bn, HANDOFF_OFFER as Bo, FILE_LIMITS as Br, readEnrollHello as Bs, parseDidSignaturePaste as Bt, COMMUNITY_LIMITS as C, nextReactionNumber as Ca, RelayTransport as Cc, readManifest as Ci, homeserverWebEndpoint as Cn, RTC_CONFIG as Co, COMMUNITY_TOPOLOGY as Cr, readSetUpdate as Cs, DID_DOCUMENT_MAX_BYTES as Ct, decodeCommunityLink as D, readReaction as Da, traceLink as Dc, dialDescriptors as Di, dnsProofName as Dn, callSignalHeardAt as Do, freshHubs as Dr, verifySetUpdate as Ds, checkDidJws as Dt, communityHasFrame as E, reactionIsNewer as Ea, setLinkTraceSink as Ec, capsDescriptors as Ei, decodeDnsResponse as En, callRtcConfig as Eo, doorHubs as Er, signTombstone as Es, DidError as Et, mergeKnocks as F, RUN_RESULTS as Fa, openRelayPayload as Fc, identityRevocationValue as Fi, recordsFromWellKnown as Fn, HANDOFF_ACK as Fo, readBeacon as Fr, enrollDoneFrame as Fs, didWebFileUrl as Ft, COMMUNITY_EDIT_FRAME as G, shownTime as Ga, fromBase64Url as Gc, automaticTransport as Gi, findEndpoint as Gn, HANDOFF_VERIFIED as Go, TypingSender as Gr, OwnInviteError as Gs, didDhtFromPublicKey as Gt, GROUP_REACTED_FRAME as H, claimedTime as Ha, MAX_AVATAR_LENGTH as Hc, emptyProofLedger as Hi, ENDPOINT as Hn, HANDOFF_PIECE_BYTES as Ho, transferStalled as Hr, createDeviceInvite as Hs, staticDidDocument as Ht, readKnocks as I, STATUS_CARD_LIMITS as Ia, parseRelayPayload as Ic, identityStatement as Ii, recordsName as In, HANDOFF_CANCEL as Io, readBeaconHead as Ir, enrollGrantFrame as Is, didWebHost as It, carryMentions as J, appFingerprint as Ja, toZ32 as Jc, DHT_FILE_TOO_LARGE as Ji, parseFedimintRequestPayload as Jn, handoffAttemptFailed as Jo, GROUP_PIN_FRAME as Jr, encodeInviteCode as Js, resolveDidDht as Jt, GROUP_EDIT_FRAME as K, APP_PREFIXES as Ka, randomBytes as Kc, irohDescriptor as Ki, isFederationId as Kn, filePartName as Ko, sanitizeTypingStatus as Kr, createChatInvite as Ks, encodeDidDhtDeactivated as Kt, GROUP_WAKE_FRAME as L, buttonLabelClash as La, createIdentity as Lc, newIdentityBinding as Li, txtValue as Ln, HANDOFF_DONE as Lo, readLobby as Lr, enrollHelloFrame as Ls, normalizeDid as Lt, entryParams as M, replySnippet as Ma, RELAY_PAYLOAD_MAX_BYTES as Mc, IdentityExchange as Mi, nip05RootKey as Mn, HTTP_SERVICE_PROTO as Mo, newerHead as Mr, enrollCancelFrame as Ms, didWebDocumentUrl as Mt, knockIdentity as N, BUTTON_ID as Na, createRelayPayload as Nc, boundVerifiedIdentity as Ni, normalizeDomain as Nn, LEGACY_SERVICES as No, pickHubs as Nr, enrollCancelReason as Ns, didWebFile as Nt, encodeCommunityLink as O, validReactionNumber as Oa, budgetRetryMs as Oc, IDENTITY_MAX_EVIDENCE as Oi, domainTxtRecord as On, extractParamsFromSdp as Oo, lobbyKeys as Or, ENROLL_DONE as Os, checkDidSignature as Ot, knockRecords as P, ITEM_STATES as Pa, newerPacket as Pc, emptyIdentityLedger as Pi, recordsFromTxt as Pn, serviceIdFromName as Po, rankHubs as Pr, enrollDigits as Ps, didWebFileNames as Pt, SSHSIG_MAX_ARMOR as Q, isAppRef as Qa, dhtFileId as Qi, pairedMessageFrame as Qn, handoffContext as Qo, pinIsNewer as Qr, ENROLL_CAPABILITY as Qs, ONCHAIN_PROVIDER as Qt, GroupWakeLimiter as R, checkStatusCard as Ra, identityFromSeed as Rc, receivedIdentityStatus as Ri, wellKnownUrl as Rn, HANDOFF_HAVE as Ro, shouldBeHub as Rr, enrollProofFrame as Rs, parseDid as Rt, sealGroupTyping as S, ReactionWindow as Sa, DEFAULT_RELAYS as Sc, newMailbox as Si, pubkyProofPath as Sn, CALL_SIGNAL_MAX_AGE_MS as So, pickMeshHubs as Sr, SET_UPDATE as Ss, verifyAtprotoRecordProof as St, GROUP_READ_NOTE_COMMUNITY as T, reactionEmoji as Ta, normalizeRelayUrl as Tc, DHT_TEXT_CAPABILITY as Ti, DOMAIN_FILE_MAX_BYTES as Tn, buildSdpFromSignal as To, beaconRecords as Tr, setUpdateFrame as Ts, DID_JWS_MAX_LENGTH as Tt, GROUP_READ_NOTE as U, heardTime as Ua, sanitizeAvatar as Uc, validExternalKey as Ui, cashuRequestPayload as Un, HANDOFF_REQUEST as Uo, TYPING_KINDS as Ur, deviceInviteJoinerParams as Us, strictBase64Url as Ut, parseGroupWakeFrame as V, arrivalKey as Va, verify as Vc, PeerProofs as Vi, decodeBolt11 as Vn, HANDOFF_PAKE as Vo, transferEnded as Vr, verifyEnrollProof as Vs, signingKeys as Vt, GroupSession as W, receivedTimestamp as Wa, bytesEqual as Wc, TRANSPORTS as Wi, fedimintRequestPayload as Wn, HANDOFF_TIMINGS as Wo, TYPING_REFRESH_MS as Wr, readDeviceInvite as Ws, didDhtDocument as Wt, mentionsMember as X, checkAppRevokeStatement as Xa, utf8Encode as Xc, peekDhtMailbox as Xi, INVITE_TAKEN as Xn, handoffBusyFrame as Xo, mayPin as Xr, inviteOwnership as Xs, ETHEREUM_USDT as Xt, parseCommunityEdit as Y, appRef as Ya, utf8Decode as Yc, DHT_MESSAGE_TTL as Yi, GhostLink as Yn, handoffAttemptSucceeded as Yo, PIN_LIMITS as Yr, inviteLink as Ys, signDidDhtPacket as Yt, groupName as Z, isAppKey as Za, DHT_FILE_CAPABILITY as Zi, LIVENESS_PING_MS as Zn, handoffCancelFrame as Zo, nextPinNumber as Zr, DEVICES_CAPABILITY as Zs, EVM_TEST_CHAINS as Zt, readAppBundle as _, forwardedAgain as _a, readTurnPacket as _c, HOLD_CAPABILITY as _i, isPubkyProofFolder as _n, parseLocalTarget as _o, groupSignalFrame as _r, readHandoffPake as _s, isAtprotoDid as _t, planAppUpdate as a, PLAYABLE_AUDIO as aa, newDeviceSetSecret as ac, parseWakeFrame as ai, assertTokenUnits as an, MAX_GROUP_CHAIN as ao, fetchesByItself as ar, handoffOfferFrame as as, verifySshSignature as at, groupTypingEpoch as b, GROUP_REACTION_FRAME as ba, turnKeys as bc, HoldKeys as bi, pubkyProofCapability as bn, decodeControl as bo, mayBeHub as br, sealHandoffPiece as bs, shortAtprotoDid as bt, signAppStore as c, fileMessageText as ca, TURN_PUT_WINDOW_MS as cc, checkPushEndpoint as ci, validatePaymentTarget as cn, verifyCommitSignature as co, readGroupFileMeta as cr, handoffRetryAfter as cs, decodeAnyBitcoinAddress as ct, APP_ICON_PATH as d, parseVideoMeta as da, classifyTurnRead as dc, isAppVersion as di, SPARK_PROVIDER as dn, DHT_POLL_INTERVALS as do, readGroupWantNo as dr, newHandoffId as ds, ATPROTO_PROOF_COLLECTION as dt, readImageMeta as ea, deviceEchoFrame as ec, readPin as ei, SEPOLIA_TEST_USDT as en, signAppObject as eo, engineText as er, handoffDataFrame as es, SshSigError as et, appDigest as f, EDIT_BUFFER_MS as fa, turnPutSummary as fc, APPS_CAPABILITY as fi, sparkAddressKind as fn, EXPECT_PEER_MS as fo, CarriedTransport as fr, readHandoffAck as fs, ATPROTO_PROOF_SCOPE as ft, isAppUrl as g, parseLinkPreview as ga, nextTurnPosition as gc, UPGRADE_CAPABILITY as gi, isPubkyKey as gn, formatLocalTarget as go, SIGNAL_REPLY_MS as gr, readHandoffHello as gs, checkAtprotoProofRecord as gt, checkAppManifest as h, dhtEditId as ha, firstDeviceSetSecret as hc, STATUS_CARD_CAPABILITY as hi, PUBKY_PROOF_MAX_BYTES as hn, GhostlyHttpError as ho, SIGNAL_HOLD_MS as hr, readHandoffHave as hs, atprotoProofRkey as ht, checkAppBeforeRun as i, sanitizeMime as ia, devicePingFrame as ic, newWakeToken as ii, WALLET_NETWORKS as in, GROUP_MEMBER_CAP as io, GROUP_WANT_NO_FRAME as ir, handoffManifestFrames as is, sshSignCommand as it, encodeGroupEntryLink as j, readReply as ja, PacketTooLargeError as jc, IdentityCheckUnavailable as ji, isPublicAddress as jn, emptyLinkRecords as jo, mergeLobby as jr, ENROLL_PROOF as js, didSignedMessages as jt, KNOCK_TTL_MS as k, wireReaction as ka, isDiscoveryBudgetError as kc, IDENTITY_MAX_VALIDITY as ki, domainWellKnownFile as kn, parseCallSignal as ko, lobbyRecords as kr, ENROLL_GRANT as ks, didGhostlyServiceEntry as kt, APP_BUNDLE_LIMITS as l, parseVoiceMeta as la, TURN_RAISE_READ_TIMEOUT_MS as lc, vapidKeysMatch as li, walletNetworkOf as ln, edgeParams as lo, readGroupHave as lr, handoffStreamKey as ls, ATPROTO_DECLARED_SCOPE as lt, assembleAppBundle as m, RateWindow as ma, TURN_MAX as mc, EDIT_CAPABILITY as mi, isBitcoinAddress as mn, presenceSeenAt as mo, SIGNAL_FRESH_MS as mr, readHandoffDone as ms, atprotoProofRecord as mt, appStoreDecision as n, safeBlobType as na, deviceKeyZ32 as nc, WakeLimiter as ni, TEST_USDT_FAUCET_AMOUNT as nn, canonicalJson as no, GROUP_HAVE_FRAME as nr, handoffEphemeral as ns, parseSshSignature as nt, checkAppStoreIndex as o, VOICE_LIMITS as oa, seedSigner as oc, relayRequest as oi, assertWholeSats as on, MEMBER_KEY as oo, groupFileFallback as or, handoffPakeFrame as os, verifyBitcoinMessage as ot, appViewOf as p, EDIT_RATE_WINDOW_MS as pa, TURN_LAST_SEQUENCE as pc, BUTTONS_CAPABILITY as pi, sparkInvoiceDetails as pn, RELAY_POLL_INTERVALS as po, GROUP_SIGNAL_FRAME as pr, readHandoffCancel as ps, atprotoDidMethod as pt, GROUP_EDIT_TEXT_BYTES as q, APP_STATEMENT_LIMITS as qa, toBase64Url as qc, irohRelayUrl as qi, parseCashuRequestPayload as qn, handoffAttemptAllowed as qo, typingActivity as qr, decodeInviteCode as qs, encodeDidDhtPacket as qt, appUpdateDecision as r, sanitizeFileName as ra, deviceLinkPairing as rc, deviceWakeRequest as ri, USDT_PUBLIC_RPC as rn, canonicalJsonBytes as ro, GROUP_WANT_FRAME as rr, handoffHelloFrame as rs, sameSshKey as rt, readAppStore as s, baseMime as sa, webCryptoSigner as sc, wakeRequest as si, formatPaymentAmount as sn, rosterHas as so, isGroupMessageId as sr, handoffReleaseFrame as ss, BitcoinAddressError as st, appRevocationFor as t, formatFileSize as ta, deviceEchoNonce as tc, WAKE_CALL_INTERVAL_MS as ti, SEPOLIA_TEST_USDT_FAUCET as tn, signAppRevocation as to, GROUP_FILE_LIMITS as tr, handoffDigest as ts, parseSshPublicKey as tt, APP_BUNDLE_MAGIC as u, voicePeaksOf as ua, TURN_SETTLE_MS as uc, chatAppId as ui, SPARK_NETWORKS as un, DirectPathWatch as uo, readGroupWant as ur, handoffVersions as us, ATPROTO_FULL_SCOPE as ut, GROUP_TYPING_FRAME as v, readForwarded as va, signTurnPacket as vc, HOLD_DATED_BACK_MS as vi, newPubkyProofFolder as vn, webLocalFetch as vo, readGroupSignal as vr, readHandoffTurnFrame as vs, normalizeAtprotoHandle as vt, CommunitySession as w, queueReaction as wa, currentRelays as wc, CapsExchange as wi, pubkyHomeserverOf as wn, answersOffer as wo, beaconKeys as wr, setAckFrame as ws, DID_FILE_MAX_BYTES as wt, openGroupTyping as x, REACTION_LIMITS as xa, turnName as xc, HoldRefusedError as xi, pubkyProofFile as xn, encodeControl as xo, meshHubs as xr, SET_ACK as xs, AtprotoProofError as xt, GroupTypingBoard as y, COMMUNITY_REACTION_FRAME as ya, signTurnRelease as yc, HOLD_LIMITS as yi, normalizePubkyKey as yn, LIMITS as yo, MESH_HUBS as yr, readHandoffVerified as ys, parseAtprotoDidDocument as yt, groupWakeFrame as z, sameButtonText as za, identityFromSeedB64 as zc, revokesIdentity as zi, requestLnurlInvoice as zn, HANDOFF_HELLO as zo, ChatFiles as zr, readEnrollGrant as zs, parseDidDocument as zt };
|
|
29078
29604
|
|
|
29079
|
-
//# sourceMappingURL=src-
|
|
29605
|
+
//# sourceMappingURL=src-BMUcYQ7b.js.map
|