@ghostlytools/cli 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/assets/{api-Dq2QXQDk.js → api-B1fADtqd.js} +65 -20
- package/dist/assets/api-B1fADtqd.js.map +1 -0
- package/dist/assets/{api-TiB5QSG9.js → api-Bg7bh_6E.js} +2 -2
- package/dist/assets/{apiKit-CA2x5GJF.js → apiKit-BsUZPHLn.js} +44 -33
- package/dist/assets/apiKit-BsUZPHLn.js.map +1 -0
- package/dist/assets/{apps-BxV9i_Y4.js → apps-BaUNt-5c.js} +91 -24
- package/dist/assets/apps-BaUNt-5c.js.map +1 -0
- package/dist/assets/{apps-C-T8jeji.js → apps-DepomCiX.js} +1 -1
- package/dist/assets/{arkade-C_qDmHnm.js → arkade-CNnMaoEQ.js} +4 -4
- package/dist/assets/arkade-CNnMaoEQ.js.map +1 -0
- package/dist/assets/{backup-Bugnn3Ue.js → backup-B5Q1W-Ey.js} +1 -1
- package/dist/assets/{backup-CnMN2oUK.js → backup-Bbz3lyC5.js} +6 -6
- package/dist/assets/{backup-CnMN2oUK.js.map → backup-Bbz3lyC5.js.map} +1 -1
- package/dist/assets/buttons-CUd7j0ZA.js +2 -0
- package/dist/assets/{buttons-CvNBzh2S.js → buttons-DS1iURp8.js} +4 -4
- package/dist/assets/{buttons-CvNBzh2S.js.map → buttons-DS1iURp8.js.map} +1 -1
- package/dist/assets/{envelope-WjingYSc.js → envelope-fNC941z4.js} +2 -2
- package/dist/assets/{envelope-WjingYSc.js.map → envelope-fNC941z4.js.map} +1 -1
- package/dist/assets/{errors-DO5z6h_l.js → errors-B-zMxbcd.js} +6 -5
- package/dist/assets/{errors-DO5z6h_l.js.map → errors-B-zMxbcd.js.map} +1 -1
- package/dist/assets/{ghostly_iroh_web-Dvzh3Y6E.js → ghostly_iroh_web-BsAEFIjl.js} +30 -36
- package/dist/assets/ghostly_iroh_web-BsAEFIjl.js.map +1 -0
- package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
- package/dist/assets/{groupFiles-IEUW3DQz.js → groupFiles-6qnLCnOm.js} +82 -29
- package/dist/assets/groupFiles-6qnLCnOm.js.map +1 -0
- package/dist/assets/{host-CWt-jtqz.js → host-Dj3C1fn3.js} +305 -68
- package/dist/assets/host-Dj3C1fn3.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-CPYwSLLS.js → hyperdhtRelay-CrgYQN-g.js} +2 -2
- package/dist/assets/{hyperdhtRelay-CPYwSLLS.js.map → hyperdhtRelay-CrgYQN-g.js.map} +1 -1
- package/dist/assets/{irohWeb-4oChIOIp.js → irohWeb-DSyLp5Re.js} +3 -3
- package/dist/assets/{irohWeb-4oChIOIp.js.map → irohWeb-DSyLp5Re.js.map} +1 -1
- package/dist/assets/{main-DbqcdShN.js → main-BDd11Yd1.js} +159 -54
- package/dist/assets/main-BDd11Yd1.js.map +1 -0
- package/dist/assets/{mainline-ZkiOnevs.js → mainline-CLTo0nGa.js} +2 -2
- package/dist/assets/{mainline-ZkiOnevs.js.map → mainline-CLTo0nGa.js.map} +1 -1
- package/dist/assets/{networkReason-CetKkTPo.js → networkReason-DwcadZz5.js} +2 -2
- package/dist/assets/{networkReason-CetKkTPo.js.map → networkReason-DwcadZz5.js.map} +1 -1
- package/dist/assets/{nwc-CgkqyvsE.js → nwc-BNFyyECH.js} +3 -3
- package/dist/assets/{nwc-CgkqyvsE.js.map → nwc-BNFyyECH.js.map} +1 -1
- package/dist/assets/{nwcProvider-4eVz_8HP.js → nwcProvider-BbjJOfLD.js} +2 -2
- package/dist/assets/{nwcProvider-4eVz_8HP.js.map → nwcProvider-BbjJOfLD.js.map} +1 -1
- package/dist/assets/{profiles-BHc9pEYg.js → profiles-gPCjLOQq.js} +2 -2
- package/dist/assets/{profiles-BHc9pEYg.js.map → profiles-gPCjLOQq.js.map} +1 -1
- package/dist/assets/{registry-DlAKO0Fy.js → registry-CoLHpxkb.js} +17 -32
- package/dist/assets/registry-CoLHpxkb.js.map +1 -0
- package/dist/assets/{registry-OBJBnHVQ.js → registry-DoTb6uEr.js} +1 -1
- package/dist/assets/{resolve-CouHhCpJ.js → resolve-BIDJ-UUl.js} +18 -3
- package/dist/assets/resolve-BIDJ-UUl.js.map +1 -0
- package/dist/assets/{server-Ss0gJ-nw.js → server-Bx3RGkBC.js} +380 -116
- package/dist/assets/server-Bx3RGkBC.js.map +1 -0
- package/dist/assets/{src-DDzSERyc.js → src-D7gcJElT.js} +470 -242
- package/dist/assets/src-D7gcJElT.js.map +1 -0
- package/dist/assets/{turnRefs-B87dtrHM.js → turnRefs-PN644M9o.js} +2 -2
- package/dist/assets/{turnRefs-B87dtrHM.js.map → turnRefs-PN644M9o.js.map} +1 -1
- package/dist/assets/{usdt-Fj8CQO2z.js → usdt-B2TLRlYa.js} +3 -3
- package/dist/assets/{usdt-Fj8CQO2z.js.map → usdt-B2TLRlYa.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +1 -1
- package/dist/assets/api-Dq2QXQDk.js.map +0 -1
- package/dist/assets/apiKit-CA2x5GJF.js.map +0 -1
- package/dist/assets/apps-BxV9i_Y4.js.map +0 -1
- package/dist/assets/arkade-C_qDmHnm.js.map +0 -1
- package/dist/assets/buttons-Bj7Vf7Pn.js +0 -2
- package/dist/assets/ghostly_iroh_web-Dvzh3Y6E.js.map +0 -1
- package/dist/assets/groupFiles-IEUW3DQz.js.map +0 -1
- package/dist/assets/host-CWt-jtqz.js.map +0 -1
- package/dist/assets/main-DbqcdShN.js.map +0 -1
- package/dist/assets/registry-DlAKO0Fy.js.map +0 -1
- package/dist/assets/resolve-CouHhCpJ.js.map +0 -1
- package/dist/assets/server-Ss0gJ-nw.js.map +0 -1
- package/dist/assets/src-DDzSERyc.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { xsalsa20poly1305 } from "@noble/ciphers/salsa.js";
|
|
2
2
|
import { ed25519, x25519 } from "@noble/curves/ed25519.js";
|
|
3
|
-
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
4
3
|
import { sha256, sha512 } from "@noble/hashes/sha2.js";
|
|
4
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
5
5
|
import { base58, base64, base64urlnopad, bech32, bech32m, createBase58check } from "@scure/base";
|
|
6
6
|
import { hmac } from "@noble/hashes/hmac.js";
|
|
7
7
|
import { hexToBytes } from "@noble/hashes/utils.js";
|
|
@@ -123,6 +123,19 @@ function fromZ32(str) {
|
|
|
123
123
|
}
|
|
124
124
|
return out;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* A 32-byte key written as the one z-base-32 spelling `toZ32` produces: 52 characters that decode to 32 bytes and
|
|
128
|
+
* encode back to the same string. The last character carries four zero padding bits, so each key has exactly one
|
|
129
|
+
* spelling and keys may be compared as strings (WISP 02). Any other spelling is not a key here.
|
|
130
|
+
*/
|
|
131
|
+
function isZ32Key(value) {
|
|
132
|
+
if (typeof value !== "string" || value.length !== 52) return false;
|
|
133
|
+
try {
|
|
134
|
+
return toZ32(fromZ32(value)) === value;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
126
139
|
function randomBytes(length) {
|
|
127
140
|
const bytes = new Uint8Array(length);
|
|
128
141
|
crypto.getRandomValues(bytes);
|
|
@@ -255,11 +268,26 @@ function tryDecrypt(encoded, key) {
|
|
|
255
268
|
return null;
|
|
256
269
|
}
|
|
257
270
|
}
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Public keys already derived, by the SHA-256 of their seed, the most recently used last. A chat's start derives the
|
|
273
|
+
* same few seeds' keys again and again (its link, its delivery, its capability record): ~0.25 ms each in V8, ~3.4x
|
|
274
|
+
* that in JSC. The seed itself is never kept, so a seed its owner zeroes leaves nothing behind here.
|
|
275
|
+
*/
|
|
276
|
+
var publicKeys = /* @__PURE__ */ new Map();
|
|
277
|
+
function publicKeyOf(seed) {
|
|
278
|
+
const id = toBase64Url(sha256(seed));
|
|
279
|
+
let key = publicKeys.get(id);
|
|
280
|
+
if (key) publicKeys.delete(id);
|
|
281
|
+
else {
|
|
282
|
+
key = ed25519.getPublicKey(seed);
|
|
283
|
+
if (publicKeys.size >= 1024) publicKeys.delete(publicKeys.keys().next().value);
|
|
284
|
+
}
|
|
285
|
+
publicKeys.set(id, key);
|
|
286
|
+
return key.slice();
|
|
287
|
+
}
|
|
260
288
|
function identityFromSeed(seed) {
|
|
261
289
|
if (seed.length !== 32) throw new Error("Seed must be exactly 32 bytes");
|
|
262
|
-
const publicKey =
|
|
290
|
+
const publicKey = publicKeyOf(seed);
|
|
263
291
|
return {
|
|
264
292
|
seed,
|
|
265
293
|
seedB64: toBase64Url(seed),
|
|
@@ -276,7 +304,7 @@ function createIdentity() {
|
|
|
276
304
|
function publicKeyFromZ32(z32) {
|
|
277
305
|
if (z32.length !== 52) throw new Error("Invalid public key: expected 52 z-base-32 characters");
|
|
278
306
|
const bytes = fromZ32(z32);
|
|
279
|
-
if (bytes
|
|
307
|
+
if (toZ32(bytes) !== z32) throw new Error("Invalid public key: not canonical z-base-32");
|
|
280
308
|
return bytes;
|
|
281
309
|
}
|
|
282
310
|
function sign(message, seed) {
|
|
@@ -3016,7 +3044,6 @@ function readGhostly(code) {
|
|
|
3016
3044
|
};
|
|
3017
3045
|
}
|
|
3018
3046
|
var B64_KEY = /^[A-Za-z0-9_-]{43}$/;
|
|
3019
|
-
var Z32_KEY$1 = /^[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/;
|
|
3020
3047
|
function readSlashForm(code) {
|
|
3021
3048
|
const dht = code.startsWith("pair2d/");
|
|
3022
3049
|
const profile = dht || code.startsWith("pair1/") ? "paired-chat/1" : void 0;
|
|
@@ -3030,7 +3057,7 @@ function readSlashForm(code) {
|
|
|
3030
3057
|
reason: "not-ghostly"
|
|
3031
3058
|
};
|
|
3032
3059
|
const [seedB64, peerPubKeyZ32, encKeyB64] = parts;
|
|
3033
|
-
const bad = !B64_KEY.test(seedB64) ? "The seed" : !
|
|
3060
|
+
const bad = !B64_KEY.test(seedB64) ? "The seed" : !isZ32Key(peerPubKeyZ32) ? "The contact's key" : !B64_KEY.test(encKeyB64) ? "The shared key" : null;
|
|
3034
3061
|
if (bad) return {
|
|
3035
3062
|
ok: false,
|
|
3036
3063
|
reason: "damaged",
|
|
@@ -3313,7 +3340,7 @@ var ENROLL_CANCEL_REASONS = [
|
|
|
3313
3340
|
"full",
|
|
3314
3341
|
"failed"
|
|
3315
3342
|
];
|
|
3316
|
-
var KEY$
|
|
3343
|
+
var KEY$3 = /^[A-Za-z0-9_-]{43}$/;
|
|
3317
3344
|
var SIGNATURE$2 = /^[A-Za-z0-9_-]{86}$/;
|
|
3318
3345
|
var HASH$3 = /^[0-9a-f]{64}$/;
|
|
3319
3346
|
function checkKey(key, who) {
|
|
@@ -3363,7 +3390,7 @@ async function enrollHelloFrame(transcriptHash, inviterKey, joiner, about) {
|
|
|
3363
3390
|
* key's over this session's tuple. Null for anything else.
|
|
3364
3391
|
*/
|
|
3365
3392
|
function readEnrollHello(frame, transcriptHash, inviterKey, sessionKey) {
|
|
3366
|
-
if (frame.t !== "enroll-hello" || typeof frame.k !== "string" || !KEY$
|
|
3393
|
+
if (frame.t !== "enroll-hello" || typeof frame.k !== "string" || !KEY$3.test(frame.k) || typeof frame.s !== "string" || !SIGNATURE$2.test(frame.s)) return null;
|
|
3367
3394
|
if (typeof frame.name !== "string" || frame.name.length > 64 || typeof frame.app !== "string" || frame.app.length > 32) return null;
|
|
3368
3395
|
const key = fromBase64Url(frame.k);
|
|
3369
3396
|
if (key.length !== 32 || toBase64Url(key) !== toBase64Url(sessionKey)) return null;
|
|
@@ -3445,7 +3472,7 @@ function enrollGrantFrame(grant) {
|
|
|
3445
3472
|
* once and the inviter once, and a turn and `rev` an ordinary record can have. Null for anything else.
|
|
3446
3473
|
*/
|
|
3447
3474
|
function readEnrollGrant(frame, inviterKey, joinerKey) {
|
|
3448
|
-
if (frame.t !== "enroll-grant" || typeof frame.d !== "string" || !KEY$
|
|
3475
|
+
if (frame.t !== "enroll-grant" || typeof frame.d !== "string" || !KEY$3.test(frame.d) || !Array.isArray(frame.set) || frame.set.length > 4) return null;
|
|
3449
3476
|
if (!Number.isInteger(frame.turn) || frame.turn < 0 || frame.turn > TURN_MAX) return null;
|
|
3450
3477
|
if (!Number.isInteger(frame.rev) || frame.rev < 0 || frame.rev >= TURN_REV_LIMIT) return null;
|
|
3451
3478
|
const set = [];
|
|
@@ -3454,7 +3481,7 @@ function readEnrollGrant(frame, inviterKey, joinerKey) {
|
|
|
3454
3481
|
set.push(null);
|
|
3455
3482
|
continue;
|
|
3456
3483
|
}
|
|
3457
|
-
if (!Array.isArray(slot) || slot.length !== 2 || typeof slot[0] !== "string" || !KEY$
|
|
3484
|
+
if (!Array.isArray(slot) || slot.length !== 2 || typeof slot[0] !== "string" || !KEY$3.test(slot[0]) || typeof slot[1] !== "string" || slot[1].length > 64) return null;
|
|
3458
3485
|
set.push({
|
|
3459
3486
|
key: fromBase64Url(slot[0]),
|
|
3460
3487
|
name: turnName(slot[1])
|
|
@@ -3491,7 +3518,7 @@ function enrollCancelReason(frame) {
|
|
|
3491
3518
|
//#region ../core/src/setUpdate.ts
|
|
3492
3519
|
var SET_UPDATE = "set-update";
|
|
3493
3520
|
var SET_ACK = "set-ack";
|
|
3494
|
-
var KEY$
|
|
3521
|
+
var KEY$2 = /^[A-Za-z0-9_-]{43}$/;
|
|
3495
3522
|
var SIGNATURE$1 = /^[A-Za-z0-9_-]{86}$/;
|
|
3496
3523
|
/** A tombstone packet is a relay payload of 706 bytes (WISP 06 § Record); a little room above it, nothing more. */
|
|
3497
3524
|
var TOMB = /^[A-Za-z0-9_-]{100,1400}$/;
|
|
@@ -3543,7 +3570,7 @@ async function setUpdateFrame(oldAddress, update, signer) {
|
|
|
3543
3570
|
* here: that needs the old turn address (`verifySetUpdate`).
|
|
3544
3571
|
*/
|
|
3545
3572
|
function readSetUpdate(frame) {
|
|
3546
|
-
if (frame.t !== "set-update" || typeof frame.d !== "string" || !KEY$
|
|
3573
|
+
if (frame.t !== "set-update" || typeof frame.d !== "string" || !KEY$2.test(frame.d) || typeof frame.by !== "string" || !KEY$2.test(frame.by)) return null;
|
|
3547
3574
|
if (typeof frame.s !== "string" || !SIGNATURE$1.test(frame.s) || typeof frame.tomb !== "string" || !TOMB.test(frame.tomb)) return null;
|
|
3548
3575
|
if (!Array.isArray(frame.set) || frame.set.length > 4) return null;
|
|
3549
3576
|
if (frame.rec !== void 0 && (typeof frame.rec !== "string" || !TOMB.test(frame.rec))) return null;
|
|
@@ -3557,7 +3584,7 @@ function readSetUpdate(frame) {
|
|
|
3557
3584
|
signed.push(null);
|
|
3558
3585
|
continue;
|
|
3559
3586
|
}
|
|
3560
|
-
if (!Array.isArray(slot) || slot.length !== 2 || typeof slot[0] !== "string" || !KEY$
|
|
3587
|
+
if (!Array.isArray(slot) || slot.length !== 2 || typeof slot[0] !== "string" || !KEY$2.test(slot[0]) || typeof slot[1] !== "string" || slot[1].length > 64) return null;
|
|
3561
3588
|
if (utf8Encode(slot[1]).length > 16 || slot[1].includes("\0")) return null;
|
|
3562
3589
|
signed.push([slot[0], slot[1]]);
|
|
3563
3590
|
set.push({
|
|
@@ -3662,8 +3689,8 @@ var HANDOFF_CANCEL_REASONS = [
|
|
|
3662
3689
|
"password",
|
|
3663
3690
|
"stalled"
|
|
3664
3691
|
];
|
|
3665
|
-
var KEY$
|
|
3666
|
-
var DIGEST$2 = KEY$
|
|
3692
|
+
var KEY$1 = /^[A-Za-z0-9_-]{43}$/;
|
|
3693
|
+
var DIGEST$2 = KEY$1;
|
|
3667
3694
|
var SIGNATURE = /^[A-Za-z0-9_-]{86}$/;
|
|
3668
3695
|
var ID$5 = /^[A-Za-z0-9_-]{22}$/;
|
|
3669
3696
|
var B64$4 = /^[A-Za-z0-9_-]*$/;
|
|
@@ -3798,7 +3825,7 @@ function handoffHelloFrame(hello) {
|
|
|
3798
3825
|
}
|
|
3799
3826
|
function readHandoffHello(frame) {
|
|
3800
3827
|
if (frame.t !== "handoff-hello" || frame.v !== 1) return null;
|
|
3801
|
-
if (!isText$2(frame.e, KEY$
|
|
3828
|
+
if (!isText$2(frame.e, KEY$1) || fromBase64Url(frame.e).length !== 32) return null;
|
|
3802
3829
|
if (!isText$2(frame.app, VERSION_TEXT) || !isCount$1(frame.db, 1e6) || !isPins(frame.pins)) return null;
|
|
3803
3830
|
if (typeof frame.room !== "number" || !Number.isSafeInteger(frame.room) || frame.room < -1) return null;
|
|
3804
3831
|
if (typeof frame.metered !== "boolean") return null;
|
|
@@ -3864,7 +3891,7 @@ function readHandoffPake(frame) {
|
|
|
3864
3891
|
wrong: true
|
|
3865
3892
|
} : null;
|
|
3866
3893
|
if (!isText$2(frame.m, B64$4) || !frame.m.length || frame.m.length > 4096) return null;
|
|
3867
|
-
if (frame.n === 3 && !isText$2(frame.c, KEY$
|
|
3894
|
+
if (frame.n === 3 && !isText$2(frame.c, KEY$1)) return null;
|
|
3868
3895
|
return {
|
|
3869
3896
|
n: frame.n,
|
|
3870
3897
|
m: frame.m,
|
|
@@ -4656,8 +4683,12 @@ function buildSdpFromSignal(signal) {
|
|
|
4656
4683
|
function sdpHasCandidates(sdp) {
|
|
4657
4684
|
return /^a=candidate:/m.test(sdp ?? "");
|
|
4658
4685
|
}
|
|
4659
|
-
/**
|
|
4660
|
-
|
|
4686
|
+
/**
|
|
4687
|
+
* How long to keep collecting after the candidate we were waiting for showed up (a call's; a chat's data link sets its
|
|
4688
|
+
* own, the same). The host candidates are in before it, and a call's signal keeps the first reflexive one only
|
|
4689
|
+
* (`pickCallCandidates`): 400 ms held every call's offer, and its answer, back that long for nothing it carried.
|
|
4690
|
+
*/
|
|
4691
|
+
var ICE_SETTLE_MS = 150;
|
|
4661
4692
|
/** IPv4 ranges that are not reachable from the internet: this network, private, shared (CGNAT), loopback, link-local, the documentation ones, benchmarking, multicast and up. */
|
|
4662
4693
|
var NOT_GLOBAL_IPV4 = [
|
|
4663
4694
|
[0, 8],
|
|
@@ -7160,7 +7191,11 @@ function edgeParams(groupId, mySeed, myZ32, peerZ32) {
|
|
|
7160
7191
|
var GROUP_MEMBER_CAP = { max: 32 };
|
|
7161
7192
|
var MAX_GROUP_CHAIN = 1024;
|
|
7162
7193
|
var GROUP_ID = /^[A-Za-z0-9_-]{22}$/;
|
|
7163
|
-
|
|
7194
|
+
/**
|
|
7195
|
+
* A member's key, in its one canonical spelling (`isZ32Key`): a roster names members by string, so a key respelled in
|
|
7196
|
+
* its last character would be a second member for one key, and `publicKeyFromZ32` would throw on it past this check.
|
|
7197
|
+
*/
|
|
7198
|
+
var MEMBER_KEY = { test: isZ32Key };
|
|
7164
7199
|
var TAG$1 = /^[A-Za-z0-9_-]{43}$/;
|
|
7165
7200
|
var SIG$6 = /^[A-Za-z0-9_-]{86}$/;
|
|
7166
7201
|
var HASH$2 = /^[a-f0-9]{64}$/;
|
|
@@ -7226,6 +7261,8 @@ function parseCommit$1(raw) {
|
|
|
7226
7261
|
sig: c.sig
|
|
7227
7262
|
};
|
|
7228
7263
|
}
|
|
7264
|
+
/** Only shape, no signature or parent: a commit as kept, its fields alone (anything else it carried dropped); null when malformed. */
|
|
7265
|
+
var parseGroupCommit = parseCommit$1;
|
|
7229
7266
|
/** The roster a commit must carry, given the previous one; null when the change is not allowed. */
|
|
7230
7267
|
function expectedRoster(previous, kind, by, subject) {
|
|
7231
7268
|
if (kind === "create") return previous === null && !subject ? [[by, "admin"]] : null;
|
|
@@ -8854,6 +8891,147 @@ var RateWindow = class {
|
|
|
8854
8891
|
}
|
|
8855
8892
|
};
|
|
8856
8893
|
//#endregion
|
|
8894
|
+
//#region ../core/src/image.ts
|
|
8895
|
+
var IMAGE_LIMITS = {
|
|
8896
|
+
/** Pixels on either side: the most a JPEG can say, well past what anyone sends as a picture. */
|
|
8897
|
+
maxEdge: 65535,
|
|
8898
|
+
/** How much of a file is read for its size: past a JPEG's EXIF, thumbnails and colour profile. */
|
|
8899
|
+
headBytes: 262144,
|
|
8900
|
+
/**
|
|
8901
|
+
* The most pixels a picture drawn in a chat may have: a 50-megapixel phone photo (8160 × 6120), about 200 MB once
|
|
8902
|
+
* decoded. A few kilobytes can say far more (a PNG of 23000 × 23000 is 64 KB and 2 GB decoded): past this a picture
|
|
8903
|
+
* is kept and saved, not drawn.
|
|
8904
|
+
*/
|
|
8905
|
+
previewPixels: 5e7,
|
|
8906
|
+
/** The longest side a picture drawn in a chat may have: a wide panorama. */
|
|
8907
|
+
previewEdge: 16384
|
|
8908
|
+
};
|
|
8909
|
+
/** Whether a file is a picture, by its type. */
|
|
8910
|
+
function isImageMime(mime) {
|
|
8911
|
+
return mime.split(";")[0].trim().toLowerCase().startsWith("image/");
|
|
8912
|
+
}
|
|
8913
|
+
var edge = (n) => typeof n === "number" && Number.isSafeInteger(n) && n > 0 && n <= IMAGE_LIMITS.maxEdge;
|
|
8914
|
+
/** A peer's `image` description, or undefined when it is not one. It never refuses the file. */
|
|
8915
|
+
function parseImageMeta(value, mime) {
|
|
8916
|
+
if (mime !== void 0 && !isImageMime(mime)) return void 0;
|
|
8917
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
8918
|
+
const { width, height } = value;
|
|
8919
|
+
return edge(width) && edge(height) ? {
|
|
8920
|
+
width,
|
|
8921
|
+
height
|
|
8922
|
+
} : void 0;
|
|
8923
|
+
}
|
|
8924
|
+
var u16be = (b, at) => b[at] << 8 | b[at + 1];
|
|
8925
|
+
var u16le = (b, at) => b[at] | b[at + 1] << 8;
|
|
8926
|
+
var u24le = (b, at) => b[at] | b[at + 1] << 8 | b[at + 2] << 16;
|
|
8927
|
+
var u32be = (b, at) => (b[at] << 24 >>> 0) + (b[at + 1] << 16) + (b[at + 2] << 8) + b[at + 3];
|
|
8928
|
+
var ascii = (b, at, text) => [...text].every((c, i) => b[at + i] === c.charCodeAt(0));
|
|
8929
|
+
var sized = (width, height) => edge(width) && edge(height) ? {
|
|
8930
|
+
width,
|
|
8931
|
+
height
|
|
8932
|
+
} : void 0;
|
|
8933
|
+
/** SOFn markers: every frame type but DHT (C4), JPG (C8) and DAC (CC). */
|
|
8934
|
+
var JPEG_FRAME = /* @__PURE__ */ new Set([
|
|
8935
|
+
192,
|
|
8936
|
+
193,
|
|
8937
|
+
194,
|
|
8938
|
+
195,
|
|
8939
|
+
197,
|
|
8940
|
+
198,
|
|
8941
|
+
199,
|
|
8942
|
+
201,
|
|
8943
|
+
202,
|
|
8944
|
+
203,
|
|
8945
|
+
205,
|
|
8946
|
+
206,
|
|
8947
|
+
207
|
|
8948
|
+
]);
|
|
8949
|
+
/** The EXIF orientation in an APP1 segment's TIFF block (1 when it says none). */
|
|
8950
|
+
function exifOrientation(b, tiff, end) {
|
|
8951
|
+
if (tiff + 8 > end) return 1;
|
|
8952
|
+
const little = ascii(b, tiff, "II");
|
|
8953
|
+
if (!little && !ascii(b, tiff, "MM")) return 1;
|
|
8954
|
+
const u16 = (at) => little ? u16le(b, at) : u16be(b, at);
|
|
8955
|
+
const u32 = (at) => little ? u16le(b, at) + u16le(b, at + 2) * 65536 : u32be(b, at);
|
|
8956
|
+
if (u16(tiff + 2) !== 42) return 1;
|
|
8957
|
+
const ifd = tiff + u32(tiff + 4);
|
|
8958
|
+
if (ifd + 2 > end) return 1;
|
|
8959
|
+
const count = u16(ifd);
|
|
8960
|
+
for (let i = 0; i < count; i++) {
|
|
8961
|
+
const entry = ifd + 2 + i * 12;
|
|
8962
|
+
if (entry + 12 > end) return 1;
|
|
8963
|
+
if (u16(entry) === 274) {
|
|
8964
|
+
const value = u16(entry + 8);
|
|
8965
|
+
return value >= 1 && value <= 8 ? value : 1;
|
|
8966
|
+
}
|
|
8967
|
+
}
|
|
8968
|
+
return 1;
|
|
8969
|
+
}
|
|
8970
|
+
function jpegSize(b) {
|
|
8971
|
+
let at = 2, orientation = 1;
|
|
8972
|
+
while (at + 4 <= b.length) {
|
|
8973
|
+
if (b[at] !== 255) return void 0;
|
|
8974
|
+
const marker = b[at + 1];
|
|
8975
|
+
if (marker === 255) {
|
|
8976
|
+
at++;
|
|
8977
|
+
continue;
|
|
8978
|
+
}
|
|
8979
|
+
if (marker === 1 || marker >= 208 && marker <= 215) {
|
|
8980
|
+
at += 2;
|
|
8981
|
+
continue;
|
|
8982
|
+
}
|
|
8983
|
+
if (marker === 217 || marker === 218) return void 0;
|
|
8984
|
+
const length = u16be(b, at + 2);
|
|
8985
|
+
if (length < 2) return void 0;
|
|
8986
|
+
const end = Math.min(b.length, at + 2 + length);
|
|
8987
|
+
if (marker === 225 && ascii(b, at + 4, "Exif\0\0")) orientation = exifOrientation(b, at + 10, end);
|
|
8988
|
+
if (JPEG_FRAME.has(marker)) {
|
|
8989
|
+
if (at + 9 > b.length) return void 0;
|
|
8990
|
+
const height = u16be(b, at + 5), width = u16be(b, at + 7);
|
|
8991
|
+
return orientation >= 5 ? sized(height, width) : sized(width, height);
|
|
8992
|
+
}
|
|
8993
|
+
at += 2 + length;
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
function webpSize(b) {
|
|
8997
|
+
if (b.length < 30) return void 0;
|
|
8998
|
+
if (ascii(b, 12, "VP8 ")) {
|
|
8999
|
+
if (b[23] !== 157 || b[24] !== 1 || b[25] !== 42) return void 0;
|
|
9000
|
+
return sized(u16le(b, 26) & 16383, u16le(b, 28) & 16383);
|
|
9001
|
+
}
|
|
9002
|
+
if (ascii(b, 12, "VP8L")) {
|
|
9003
|
+
if (b[20] !== 47) return void 0;
|
|
9004
|
+
const bits = b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24;
|
|
9005
|
+
return sized((bits & 16383) + 1, (bits >>> 14 & 16383) + 1);
|
|
9006
|
+
}
|
|
9007
|
+
if (ascii(b, 12, "VP8X")) return sized(u24le(b, 24) + 1, u24le(b, 27) + 1);
|
|
9008
|
+
}
|
|
9009
|
+
/**
|
|
9010
|
+
* A picture's size as it is shown, read from its first bytes (PNG, JPEG, GIF, WebP), or undefined when they do not
|
|
9011
|
+
* say. Only the header is read: a JPEG whose frame header sits past `IMAGE_LIMITS.headBytes` goes without.
|
|
9012
|
+
*/
|
|
9013
|
+
function imageSize(head) {
|
|
9014
|
+
const b = head;
|
|
9015
|
+
try {
|
|
9016
|
+
if (b.length >= 24 && b[0] === 137 && ascii(b, 1, "PNG\r\n\n") && ascii(b, 12, "IHDR")) return sized(u32be(b, 16), u32be(b, 20));
|
|
9017
|
+
if (b.length >= 10 && (ascii(b, 0, "GIF87a") || ascii(b, 0, "GIF89a"))) return sized(u16le(b, 6), u16le(b, 8));
|
|
9018
|
+
if (b.length >= 4 && b[0] === 255 && b[1] === 216 && b[2] === 255) return jpegSize(b);
|
|
9019
|
+
if (b.length >= 16 && ascii(b, 0, "RIFF") && ascii(b, 8, "WEBP")) return webpSize(b);
|
|
9020
|
+
} catch {}
|
|
9021
|
+
}
|
|
9022
|
+
/**
|
|
9023
|
+
* The description a picture is sent with, read from the start of the file, or undefined when it is not a picture
|
|
9024
|
+
* or its first bytes do not say (it then goes without one).
|
|
9025
|
+
*/
|
|
9026
|
+
async function readImageMeta(source, mime) {
|
|
9027
|
+
if (!isImageMime(mime)) return void 0;
|
|
9028
|
+
try {
|
|
9029
|
+
return imageSize(new Uint8Array(await source.slice(0, IMAGE_LIMITS.headBytes).arrayBuffer()));
|
|
9030
|
+
} catch {
|
|
9031
|
+
return;
|
|
9032
|
+
}
|
|
9033
|
+
}
|
|
9034
|
+
//#endregion
|
|
8857
9035
|
//#region ../core/src/video.ts
|
|
8858
9036
|
var VIDEO_LIMITS = {
|
|
8859
9037
|
/** The poster's bytes: with the rest of an offer, a frame stays far below the 60 KiB a session carries. */
|
|
@@ -8877,7 +9055,8 @@ var JPEG_START = [
|
|
|
8877
9055
|
];
|
|
8878
9056
|
/**
|
|
8879
9057
|
* A peer's `video` description, or undefined when it is not one. It never refuses the file. A poster that is
|
|
8880
|
-
* too large
|
|
9058
|
+
* too large, not a JPEG, or says it is more pixels than a sender draws (`posterEdge`) is dropped on its own; the
|
|
9059
|
+
* length and size are kept. It is shown before the file is accepted, so a few bytes must not ask for a huge bitmap.
|
|
8881
9060
|
*/
|
|
8882
9061
|
function parseVideoMeta(value, mime) {
|
|
8883
9062
|
if (mime !== void 0 && !isVideoMime(mime)) return void 0;
|
|
@@ -8893,7 +9072,8 @@ function parseVideoMeta(value, mime) {
|
|
|
8893
9072
|
};
|
|
8894
9073
|
if (typeof poster === "string" && poster.length <= Math.ceil(VIDEO_LIMITS.maxPosterBytes * 4 / 3) && BASE64URL.test(poster)) try {
|
|
8895
9074
|
const bytes = fromBase64Url(poster);
|
|
8896
|
-
|
|
9075
|
+
const size = bytes.length <= VIDEO_LIMITS.maxPosterBytes && JPEG_START.every((b, i) => bytes[i] === b) ? imageSize(bytes) : void 0;
|
|
9076
|
+
if (size && Math.max(size.width, size.height) <= VIDEO_LIMITS.posterEdge) meta.poster = poster;
|
|
8897
9077
|
} catch {}
|
|
8898
9078
|
return meta;
|
|
8899
9079
|
}
|
|
@@ -9198,139 +9378,6 @@ var FileTransfers = class {
|
|
|
9198
9378
|
}
|
|
9199
9379
|
};
|
|
9200
9380
|
//#endregion
|
|
9201
|
-
//#region ../core/src/image.ts
|
|
9202
|
-
var IMAGE_LIMITS = {
|
|
9203
|
-
/** Pixels on either side: the most a JPEG can say, well past what anyone sends as a picture. */
|
|
9204
|
-
maxEdge: 65535,
|
|
9205
|
-
/** How much of a file is read for its size: past a JPEG's EXIF, thumbnails and colour profile. */
|
|
9206
|
-
headBytes: 262144
|
|
9207
|
-
};
|
|
9208
|
-
/** Whether a file is a picture, by its type. */
|
|
9209
|
-
function isImageMime(mime) {
|
|
9210
|
-
return mime.split(";")[0].trim().toLowerCase().startsWith("image/");
|
|
9211
|
-
}
|
|
9212
|
-
var edge = (n) => typeof n === "number" && Number.isSafeInteger(n) && n > 0 && n <= IMAGE_LIMITS.maxEdge;
|
|
9213
|
-
/** A peer's `image` description, or undefined when it is not one. It never refuses the file. */
|
|
9214
|
-
function parseImageMeta(value, mime) {
|
|
9215
|
-
if (mime !== void 0 && !isImageMime(mime)) return void 0;
|
|
9216
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
9217
|
-
const { width, height } = value;
|
|
9218
|
-
return edge(width) && edge(height) ? {
|
|
9219
|
-
width,
|
|
9220
|
-
height
|
|
9221
|
-
} : void 0;
|
|
9222
|
-
}
|
|
9223
|
-
var u16be = (b, at) => b[at] << 8 | b[at + 1];
|
|
9224
|
-
var u16le = (b, at) => b[at] | b[at + 1] << 8;
|
|
9225
|
-
var u24le = (b, at) => b[at] | b[at + 1] << 8 | b[at + 2] << 16;
|
|
9226
|
-
var u32be = (b, at) => (b[at] << 24 >>> 0) + (b[at + 1] << 16) + (b[at + 2] << 8) + b[at + 3];
|
|
9227
|
-
var ascii = (b, at, text) => [...text].every((c, i) => b[at + i] === c.charCodeAt(0));
|
|
9228
|
-
var sized = (width, height) => edge(width) && edge(height) ? {
|
|
9229
|
-
width,
|
|
9230
|
-
height
|
|
9231
|
-
} : void 0;
|
|
9232
|
-
/** SOFn markers: every frame type but DHT (C4), JPG (C8) and DAC (CC). */
|
|
9233
|
-
var JPEG_FRAME = /* @__PURE__ */ new Set([
|
|
9234
|
-
192,
|
|
9235
|
-
193,
|
|
9236
|
-
194,
|
|
9237
|
-
195,
|
|
9238
|
-
197,
|
|
9239
|
-
198,
|
|
9240
|
-
199,
|
|
9241
|
-
201,
|
|
9242
|
-
202,
|
|
9243
|
-
203,
|
|
9244
|
-
205,
|
|
9245
|
-
206,
|
|
9246
|
-
207
|
|
9247
|
-
]);
|
|
9248
|
-
/** The EXIF orientation in an APP1 segment's TIFF block (1 when it says none). */
|
|
9249
|
-
function exifOrientation(b, tiff, end) {
|
|
9250
|
-
if (tiff + 8 > end) return 1;
|
|
9251
|
-
const little = ascii(b, tiff, "II");
|
|
9252
|
-
if (!little && !ascii(b, tiff, "MM")) return 1;
|
|
9253
|
-
const u16 = (at) => little ? u16le(b, at) : u16be(b, at);
|
|
9254
|
-
const u32 = (at) => little ? u16le(b, at) + u16le(b, at + 2) * 65536 : u32be(b, at);
|
|
9255
|
-
if (u16(tiff + 2) !== 42) return 1;
|
|
9256
|
-
const ifd = tiff + u32(tiff + 4);
|
|
9257
|
-
if (ifd + 2 > end) return 1;
|
|
9258
|
-
const count = u16(ifd);
|
|
9259
|
-
for (let i = 0; i < count; i++) {
|
|
9260
|
-
const entry = ifd + 2 + i * 12;
|
|
9261
|
-
if (entry + 12 > end) return 1;
|
|
9262
|
-
if (u16(entry) === 274) {
|
|
9263
|
-
const value = u16(entry + 8);
|
|
9264
|
-
return value >= 1 && value <= 8 ? value : 1;
|
|
9265
|
-
}
|
|
9266
|
-
}
|
|
9267
|
-
return 1;
|
|
9268
|
-
}
|
|
9269
|
-
function jpegSize(b) {
|
|
9270
|
-
let at = 2, orientation = 1;
|
|
9271
|
-
while (at + 4 <= b.length) {
|
|
9272
|
-
if (b[at] !== 255) return void 0;
|
|
9273
|
-
const marker = b[at + 1];
|
|
9274
|
-
if (marker === 255) {
|
|
9275
|
-
at++;
|
|
9276
|
-
continue;
|
|
9277
|
-
}
|
|
9278
|
-
if (marker === 1 || marker >= 208 && marker <= 215) {
|
|
9279
|
-
at += 2;
|
|
9280
|
-
continue;
|
|
9281
|
-
}
|
|
9282
|
-
if (marker === 217 || marker === 218) return void 0;
|
|
9283
|
-
const length = u16be(b, at + 2);
|
|
9284
|
-
if (length < 2) return void 0;
|
|
9285
|
-
const end = Math.min(b.length, at + 2 + length);
|
|
9286
|
-
if (marker === 225 && ascii(b, at + 4, "Exif\0\0")) orientation = exifOrientation(b, at + 10, end);
|
|
9287
|
-
if (JPEG_FRAME.has(marker)) {
|
|
9288
|
-
if (at + 9 > b.length) return void 0;
|
|
9289
|
-
const height = u16be(b, at + 5), width = u16be(b, at + 7);
|
|
9290
|
-
return orientation >= 5 ? sized(height, width) : sized(width, height);
|
|
9291
|
-
}
|
|
9292
|
-
at += 2 + length;
|
|
9293
|
-
}
|
|
9294
|
-
}
|
|
9295
|
-
function webpSize(b) {
|
|
9296
|
-
if (b.length < 30) return void 0;
|
|
9297
|
-
if (ascii(b, 12, "VP8 ")) {
|
|
9298
|
-
if (b[23] !== 157 || b[24] !== 1 || b[25] !== 42) return void 0;
|
|
9299
|
-
return sized(u16le(b, 26) & 16383, u16le(b, 28) & 16383);
|
|
9300
|
-
}
|
|
9301
|
-
if (ascii(b, 12, "VP8L")) {
|
|
9302
|
-
if (b[20] !== 47) return void 0;
|
|
9303
|
-
const bits = b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24;
|
|
9304
|
-
return sized((bits & 16383) + 1, (bits >>> 14 & 16383) + 1);
|
|
9305
|
-
}
|
|
9306
|
-
if (ascii(b, 12, "VP8X")) return sized(u24le(b, 24) + 1, u24le(b, 27) + 1);
|
|
9307
|
-
}
|
|
9308
|
-
/**
|
|
9309
|
-
* A picture's size as it is shown, read from its first bytes (PNG, JPEG, GIF, WebP), or undefined when they do not
|
|
9310
|
-
* say. Only the header is read: a JPEG whose frame header sits past `IMAGE_LIMITS.headBytes` goes without.
|
|
9311
|
-
*/
|
|
9312
|
-
function imageSize(head) {
|
|
9313
|
-
const b = head;
|
|
9314
|
-
try {
|
|
9315
|
-
if (b.length >= 24 && b[0] === 137 && ascii(b, 1, "PNG\r\n\n") && ascii(b, 12, "IHDR")) return sized(u32be(b, 16), u32be(b, 20));
|
|
9316
|
-
if (b.length >= 10 && (ascii(b, 0, "GIF87a") || ascii(b, 0, "GIF89a"))) return sized(u16le(b, 6), u16le(b, 8));
|
|
9317
|
-
if (b.length >= 4 && b[0] === 255 && b[1] === 216 && b[2] === 255) return jpegSize(b);
|
|
9318
|
-
if (b.length >= 16 && ascii(b, 0, "RIFF") && ascii(b, 8, "WEBP")) return webpSize(b);
|
|
9319
|
-
} catch {}
|
|
9320
|
-
}
|
|
9321
|
-
/**
|
|
9322
|
-
* The description a picture is sent with, read from the start of the file, or undefined when it is not a picture
|
|
9323
|
-
* or its first bytes do not say (it then goes without one).
|
|
9324
|
-
*/
|
|
9325
|
-
async function readImageMeta(source, mime) {
|
|
9326
|
-
if (!isImageMime(mime)) return void 0;
|
|
9327
|
-
try {
|
|
9328
|
-
return imageSize(new Uint8Array(await source.slice(0, IMAGE_LIMITS.headBytes).arrayBuffer()));
|
|
9329
|
-
} catch {
|
|
9330
|
-
return;
|
|
9331
|
-
}
|
|
9332
|
-
}
|
|
9333
|
-
//#endregion
|
|
9334
9381
|
//#region ../core/src/dhtFiles.ts
|
|
9335
9382
|
/**
|
|
9336
9383
|
* A file's bubble on the DHT floor (WISP 403 § Files): while the contact is away, a file's offer (its name, size, type,
|
|
@@ -11807,10 +11854,10 @@ var IDENTITY_MAX_VALIDITY = 3456e4;
|
|
|
11807
11854
|
var IDENTITY_MAX_EVIDENCE = 16384;
|
|
11808
11855
|
/** Unanswered challenges a verifier keeps per conversation. */
|
|
11809
11856
|
var MAX_CHALLENGES = 8;
|
|
11857
|
+
var IDENTITY_FRAME_WINDOW_MS = 6e4;
|
|
11810
11858
|
var IDENTITY_PROVIDER_ID = /^[a-z][a-z0-9-]{0,31}$/;
|
|
11811
11859
|
/** Printable ASCII without spaces, so the statement can be built but never needs parsing. */
|
|
11812
11860
|
var IDENTITY_SUBJECT = /^[\x21-\x7e]{1,512}$/;
|
|
11813
|
-
var KEY$2 = /^[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/;
|
|
11814
11861
|
var BINDING_NONCE = /^[A-Za-z0-9_-]{22}$/;
|
|
11815
11862
|
var CHALLENGE_NONCE = /^[A-Za-z0-9_-]{43}$/;
|
|
11816
11863
|
var HEX64$2 = /^[a-f0-9]{64}$/;
|
|
@@ -11832,7 +11879,7 @@ var iso = (seconds) => (/* @__PURE__ */ new Date(seconds * 1e3)).toISOString().r
|
|
|
11832
11879
|
var isInt$2 = (v) => Number.isSafeInteger(v) && v > 0;
|
|
11833
11880
|
function isIdentityBinding(value) {
|
|
11834
11881
|
const b = value;
|
|
11835
|
-
return !!b && typeof b === "object" && !Array.isArray(b) && Object.keys(b).sort().join(",") === "expiresAt,issuedAt,key,nonce,provider,subject,v" && b.v === 1 && typeof b.provider === "string" && IDENTITY_PROVIDER_ID.test(b.provider) && typeof b.subject === "string" && IDENTITY_SUBJECT.test(b.subject) &&
|
|
11882
|
+
return !!b && typeof b === "object" && !Array.isArray(b) && Object.keys(b).sort().join(",") === "expiresAt,issuedAt,key,nonce,provider,subject,v" && b.v === 1 && typeof b.provider === "string" && IDENTITY_PROVIDER_ID.test(b.provider) && typeof b.subject === "string" && IDENTITY_SUBJECT.test(b.subject) && isZ32Key(b.key) && typeof b.nonce === "string" && BINDING_NONCE.test(b.nonce) && isInt$2(b.issuedAt) && isInt$2(b.expiresAt) && b.expiresAt > b.issuedAt && b.expiresAt - b.issuedAt <= 3456e4 && b.expiresAt < 253402300800;
|
|
11836
11883
|
}
|
|
11837
11884
|
/**
|
|
11838
11885
|
* The canonical statement, one line, so it survives every tool and clipboard (no line-ending
|
|
@@ -11892,7 +11939,7 @@ function signIdentityPresentation(seed, proofId, scope, nonce, issuedAt) {
|
|
|
11892
11939
|
return toBase64Url(sign(identityPresentationBytes(proofId, scope, nonce, issuedAt), seed));
|
|
11893
11940
|
}
|
|
11894
11941
|
function verifyIdentityPresentation(key, sig, proofId, scope, nonce, issuedAt) {
|
|
11895
|
-
if (typeof sig !== "string" || !SIG$5.test(sig) || !
|
|
11942
|
+
if (typeof sig !== "string" || !SIG$5.test(sig) || !isZ32Key(key)) return false;
|
|
11896
11943
|
return verify(fromBase64Url(sig), identityPresentationBytes(proofId, scope, nonce, issuedAt), publicKeyFromZ32(key));
|
|
11897
11944
|
}
|
|
11898
11945
|
var clean$1 = (value, max) => {
|
|
@@ -11945,6 +11992,18 @@ function receivedIdentityStatus(r, presenter, audience, now = nowSeconds()) {
|
|
|
11945
11992
|
if (Math.min(r.binding.expiresAt, r.verified.expiresAt ?? Infinity) <= now) return "expired";
|
|
11946
11993
|
return r.status === "unconfirmed" ? "unconfirmed" : "verified";
|
|
11947
11994
|
}
|
|
11995
|
+
var standing = (r, now) => r.status !== "withdrawn" && r.status !== "revoked" && Math.min(r.binding.expiresAt, r.verified.expiresAt ?? Infinity) > now;
|
|
11996
|
+
/**
|
|
11997
|
+
* `received` with room for one more proof: `replaced` (the older proof of the same identity) dropped, then the oldest ones
|
|
11998
|
+
* no longer standing; undefined when every place is still taken.
|
|
11999
|
+
*/
|
|
12000
|
+
function receivedWithRoom(received, id, replaced, now) {
|
|
12001
|
+
const kept = received.filter((r) => r.id !== id && !replaced(r));
|
|
12002
|
+
const gone = kept.filter((r) => !standing(r, now)).sort((a, b) => a.verifiedAt - b.verifiedAt);
|
|
12003
|
+
const drop = new Set(gone.slice(0, Math.max(0, kept.length - 16 + 1)));
|
|
12004
|
+
const out = kept.filter((r) => !drop.has(r));
|
|
12005
|
+
return out.length < 16 ? out : void 0;
|
|
12006
|
+
}
|
|
11948
12007
|
var shortError = (e) => clean$1(e instanceof Error ? e.message : String(e), 160) ?? "Invalid proof";
|
|
11949
12008
|
/**
|
|
11950
12009
|
* A check this side could not finish because of what its own network did: a host it refused to contact, a server
|
|
@@ -11960,6 +12019,7 @@ var IdentityCheckUnavailable = class extends Error {
|
|
|
11960
12019
|
};
|
|
11961
12020
|
/** What the contact is told when the check failed with an IdentityCheckUnavailable. */
|
|
11962
12021
|
var IDENTITY_CHECK_UNAVAILABLE = "Your contact's app could not check it. Try again later.";
|
|
12022
|
+
var TOO_MANY_RECEIVED = "Your contact keeps too many identities from you already";
|
|
11963
12023
|
var peerError = (e) => e instanceof IdentityCheckUnavailable ? IDENTITY_CHECK_UNAVAILABLE : shortError(e);
|
|
11964
12024
|
/**
|
|
11965
12025
|
* The `idp-*` exchange of one conversation. Frames (all JSON, additive, over the paired channel,
|
|
@@ -12184,31 +12244,29 @@ var IdentityExchange = class {
|
|
|
12184
12244
|
});
|
|
12185
12245
|
return;
|
|
12186
12246
|
}
|
|
12187
|
-
case "idp-withdraw":
|
|
12247
|
+
case "idp-withdraw": {
|
|
12188
12248
|
if (!id) return;
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
reason: "withdrawn"
|
|
12205
|
-
}) : next;
|
|
12206
|
-
});
|
|
12249
|
+
const r = this.options.storage.read().received.find((x) => x.id === id);
|
|
12250
|
+
if (r && r.status !== "withdrawn") await this.options.storage.update((l) => this.log({
|
|
12251
|
+
...l,
|
|
12252
|
+
received: l.received.map((x) => x.id === id ? {
|
|
12253
|
+
...x,
|
|
12254
|
+
status: "withdrawn"
|
|
12255
|
+
} : x)
|
|
12256
|
+
}, {
|
|
12257
|
+
type: "stopped",
|
|
12258
|
+
side: "theirs",
|
|
12259
|
+
proof: id,
|
|
12260
|
+
provider: r.binding.provider,
|
|
12261
|
+
subject: r.verified.subject,
|
|
12262
|
+
reason: "withdrawn"
|
|
12263
|
+
}));
|
|
12207
12264
|
this.send({
|
|
12208
12265
|
t: "idp-withdrawn",
|
|
12209
12266
|
id
|
|
12210
12267
|
});
|
|
12211
12268
|
return;
|
|
12269
|
+
}
|
|
12212
12270
|
case "idp-withdrawn": {
|
|
12213
12271
|
const s = id ? this.shared(id) : void 0;
|
|
12214
12272
|
if (s?.status === "withdrawal-pending") await this.setShared({
|
|
@@ -12226,25 +12284,11 @@ var IdentityExchange = class {
|
|
|
12226
12284
|
async onRequest(id, provider) {
|
|
12227
12285
|
if (!id) return;
|
|
12228
12286
|
if (typeof provider !== "string" || !this.options.providers().includes(provider)) {
|
|
12229
|
-
const error = "Your contact's app cannot verify this kind of identity yet";
|
|
12230
|
-
if (typeof provider === "string" && IDENTITY_PROVIDER_ID.test(provider)) await this.options.storage.update((l) => this.log(this.log(l, {
|
|
12231
|
-
type: "shared",
|
|
12232
|
-
side: "theirs",
|
|
12233
|
-
proof: id,
|
|
12234
|
-
provider
|
|
12235
|
-
}), {
|
|
12236
|
-
type: "result",
|
|
12237
|
-
side: "theirs",
|
|
12238
|
-
proof: id,
|
|
12239
|
-
provider,
|
|
12240
|
-
ok: false,
|
|
12241
|
-
error: "This app cannot verify this kind of identity yet"
|
|
12242
|
-
}));
|
|
12243
12287
|
this.send({
|
|
12244
12288
|
t: "idp-result",
|
|
12245
12289
|
id,
|
|
12246
12290
|
ok: false,
|
|
12247
|
-
error
|
|
12291
|
+
error: "Your contact's app cannot verify this kind of identity yet"
|
|
12248
12292
|
});
|
|
12249
12293
|
return;
|
|
12250
12294
|
}
|
|
@@ -12330,6 +12374,8 @@ var IdentityExchange = class {
|
|
|
12330
12374
|
/** The same, as this side's own timeline says it. */
|
|
12331
12375
|
let reason;
|
|
12332
12376
|
try {
|
|
12377
|
+
const mayReplace = (r) => r.binding.provider === binding.provider && (r.binding.subject === binding.subject || r.verified.subject === binding.subject);
|
|
12378
|
+
if (!receivedWithRoom(this.options.storage.read().received, id, mayReplace, now)) throw new Error(TOO_MANY_RECEIVED);
|
|
12333
12379
|
if (JSON.stringify(evidence ?? null).length > 16384) throw new Error("Evidence too large");
|
|
12334
12380
|
if (!verifyIdentityPresentation(binding.key, sig, id, scope, nonce, issuedAt)) throw new Error("The proof key did not sign this conversation's challenge");
|
|
12335
12381
|
if (binding.issuedAt > now + 300) throw new Error("The proof is dated in the future");
|
|
@@ -12345,6 +12391,12 @@ var IdentityExchange = class {
|
|
|
12345
12391
|
const challenge = challengeOf(l);
|
|
12346
12392
|
if (!challenge) throw new Error("Unknown or reused identity challenge");
|
|
12347
12393
|
const challenges = l.challenges.filter((c) => c.nonce !== nonce);
|
|
12394
|
+
const subject = verified?.subject;
|
|
12395
|
+
const received = verified && receivedWithRoom(l.received, id, (r) => r.binding.provider === binding.provider && r.verified.subject === subject, now);
|
|
12396
|
+
if (verified && !received) {
|
|
12397
|
+
verified = void 0;
|
|
12398
|
+
error = reason = TOO_MANY_RECEIVED;
|
|
12399
|
+
}
|
|
12348
12400
|
const logged = this.log(l, {
|
|
12349
12401
|
type: "result",
|
|
12350
12402
|
side: "theirs",
|
|
@@ -12355,7 +12407,7 @@ var IdentityExchange = class {
|
|
|
12355
12407
|
ok: !!verified,
|
|
12356
12408
|
...reason ? { error: reason } : {}
|
|
12357
12409
|
});
|
|
12358
|
-
if (!verified) return {
|
|
12410
|
+
if (!verified || !received) return {
|
|
12359
12411
|
...logged,
|
|
12360
12412
|
challenges
|
|
12361
12413
|
};
|
|
@@ -12371,7 +12423,6 @@ var IdentityExchange = class {
|
|
|
12371
12423
|
checkedAt: now,
|
|
12372
12424
|
status: "verified"
|
|
12373
12425
|
};
|
|
12374
|
-
const received = l.received.filter((r) => r.id !== id && !(r.binding.provider === binding.provider && r.verified.subject === verified.subject));
|
|
12375
12426
|
return {
|
|
12376
12427
|
...logged,
|
|
12377
12428
|
challenges,
|
|
@@ -12461,8 +12512,7 @@ var CapsRefusedError = class extends Error {
|
|
|
12461
12512
|
var IDENT = /^[a-z0-9][a-z0-9._-]*\/[0-9]+$/;
|
|
12462
12513
|
var HEX64$1 = /^[0-9a-f]{64}$/;
|
|
12463
12514
|
var SIG$4 = /^[A-Za-z0-9_-]{86}$/;
|
|
12464
|
-
var
|
|
12465
|
-
var KEY$1 = /^[A-Za-z0-9_-]{43}$/;
|
|
12515
|
+
var KEY = /^[A-Za-z0-9_-]{43}$/;
|
|
12466
12516
|
var hexToKey = (hex) => toBase64Url(Uint8Array.from(hex.match(/../g), (b) => parseInt(b, 16)));
|
|
12467
12517
|
var keyToHex = (key) => Array.from(fromBase64Url(key), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
12468
12518
|
/**
|
|
@@ -12514,14 +12564,14 @@ function parseDescriptors(value) {
|
|
|
12514
12564
|
const out = {};
|
|
12515
12565
|
const iroh = raw["iroh/1"], hyper = raw["hyperdht/1"];
|
|
12516
12566
|
if (iroh !== void 0) {
|
|
12517
|
-
if (!iroh || typeof iroh.id !== "string" || !KEY
|
|
12567
|
+
if (!iroh || typeof iroh.id !== "string" || !KEY.test(iroh.id) || iroh.relay !== void 0 && (typeof iroh.relay !== "string" || !RELAY_URL.test(iroh.relay))) return null;
|
|
12518
12568
|
out["iroh/1"] = {
|
|
12519
12569
|
id: iroh.id,
|
|
12520
12570
|
...iroh.relay !== void 0 ? { relay: iroh.relay } : {}
|
|
12521
12571
|
};
|
|
12522
12572
|
}
|
|
12523
12573
|
if (hyper !== void 0) {
|
|
12524
|
-
if (!hyper || typeof hyper.publicKey !== "string" || !KEY
|
|
12574
|
+
if (!hyper || typeof hyper.publicKey !== "string" || !KEY.test(hyper.publicKey) || hyper.relay !== void 0 && (typeof hyper.relay !== "string" || !RELAY_URL.test(hyper.relay))) return null;
|
|
12525
12575
|
out["hyperdht/1"] = {
|
|
12526
12576
|
publicKey: hyper.publicKey,
|
|
12527
12577
|
...hyper.relay !== void 0 ? { relay: hyper.relay } : {}
|
|
@@ -12665,7 +12715,7 @@ var CapsKeys = class {
|
|
|
12665
12715
|
if (body.length < 10 || body.length > 16) throw new CapsRefusedError("format", "Malformed capability record.");
|
|
12666
12716
|
const [version, rev, issued, author, versions, transports, capabilities, extensions, rawDescriptors, name, choice] = body;
|
|
12667
12717
|
const descriptors = parseDescriptors(rawDescriptors);
|
|
12668
|
-
if (version !== 1 || !Number.isSafeInteger(rev) || rev < 0 || !Number.isSafeInteger(issued) || issued <= 0 ||
|
|
12718
|
+
if (version !== 1 || !Number.isSafeInteger(rev) || rev < 0 || !Number.isSafeInteger(issued) || issued <= 0 || !isZ32Key(author) || !list$2(versions, CAPS_LIMITS.versions, (v) => Number.isSafeInteger(v) && v > 0) || !list$2(transports, CAPS_LIMITS.transports, ident) || transports.includes("dht/1") || !list$2(capabilities, CAPS_LIMITS.capabilities, ident) || !list$2(extensions, CAPS_LIMITS.extensions, ident) || !descriptors || typeof name !== "string" || utf8Encode(name).length > CAPS_LIMITS.nameBytes || choice !== void 0 && (!ident(choice) || choice === "dht/1")) throw new CapsRefusedError("format", "Malformed capability record.");
|
|
12669
12719
|
const signer = options.pinned ?? options.expected;
|
|
12670
12720
|
if (signer && author !== signer) throw new CapsRefusedError("author", "The capability record is signed by another participation key than this contact's.");
|
|
12671
12721
|
try {
|
|
@@ -13611,13 +13661,12 @@ var SESSION_RECEIVE_PENDING = {
|
|
|
13611
13661
|
frames: 1024,
|
|
13612
13662
|
bytes: 64 * SESSION_FRAME_MAX
|
|
13613
13663
|
};
|
|
13614
|
-
var KEY = /^[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/;
|
|
13615
13664
|
var NONCE$1 = /^[A-Za-z0-9_-]{43}$/;
|
|
13616
13665
|
var SIG$2 = /^[A-Za-z0-9_-]{86}$/;
|
|
13617
13666
|
var FINGERPRINT = /^[a-f0-9]{64}$/;
|
|
13618
13667
|
var isList = (v, maximum = 8) => Array.isArray(v) && v.length > 0 && v.length <= maximum && v.every((x) => typeof x === "string" && /^[a-z0-9/-]{1,40}$/.test(x)) && new Set(v).size === v.length;
|
|
13619
13668
|
function parseOffer(v) {
|
|
13620
|
-
if (!Array.isArray(v.versions) || v.versions.length === 0 || v.versions.length > 8 || !v.versions.every((n) => Number.isSafeInteger(n) && n > 0) || new Set(v.versions).size !== v.versions.length || !isList(v.transports) || !isList(v.capabilities, 32) ||
|
|
13669
|
+
if (!Array.isArray(v.versions) || v.versions.length === 0 || v.versions.length > 8 || !v.versions.every((n) => Number.isSafeInteger(n) && n > 0) || new Set(v.versions).size !== v.versions.length || !isList(v.transports) || !isList(v.capabilities, 32) || !isZ32Key(v.key) || typeof v.nonce !== "string" || !NONCE$1.test(v.nonce)) return null;
|
|
13621
13670
|
const extensions = isList(v.extensions, 32) ? v.extensions : void 0;
|
|
13622
13671
|
return {
|
|
13623
13672
|
t: "pair-offer",
|
|
@@ -13748,7 +13797,7 @@ var PairedSession = class {
|
|
|
13748
13797
|
}
|
|
13749
13798
|
start() {
|
|
13750
13799
|
const binding = this.options.binding;
|
|
13751
|
-
if (!(binding ? (binding.transport === "hyperdht/1" ? /^[a-f0-9]{128}$/.test(binding.context) : FINGERPRINT.test(binding.context)) && binding.identities.length === 2 && binding.identities.every((k) => FINGERPRINT.test(k)) && binding.identities[0] !== binding.identities[1] : this.options.fingerprints?.length === 2 && this.options.fingerprints.every((f) => FINGERPRINT.test(f))) || !this.offer.transports.includes(this.transport) || this.options.rendezvousKeys.some((k) => !
|
|
13800
|
+
if (!(binding ? (binding.transport === "hyperdht/1" ? /^[a-f0-9]{128}$/.test(binding.context) : FINGERPRINT.test(binding.context)) && binding.identities.length === 2 && binding.identities.every((k) => FINGERPRINT.test(k)) && binding.identities[0] !== binding.identities[1] : this.options.fingerprints?.length === 2 && this.options.fingerprints.every((f) => FINGERPRINT.test(f))) || !this.offer.transports.includes(this.transport) || this.options.rendezvousKeys.some((k) => !isZ32Key(k))) return this.fail("Invalid connection binding or unavailable transport");
|
|
13752
13801
|
this.channel.onMessage = (data) => {
|
|
13753
13802
|
if (this.stopped) return;
|
|
13754
13803
|
const size = data.length;
|
|
@@ -16930,7 +16979,7 @@ var CarriedTransport = class {
|
|
|
16930
16979
|
var GROUP_FILE_LIMITS = {
|
|
16931
16980
|
/** The largest file a group takes. */
|
|
16932
16981
|
maxBytes: 104857600,
|
|
16933
|
-
/** A file this size or smaller is fetched without asking
|
|
16982
|
+
/** A file this size or smaller is fetched without asking, a voice message too (one recorded is well inside it). */
|
|
16934
16983
|
autoBytes: 8388608,
|
|
16935
16984
|
/** What a device keeps of a group's files it fetched without asking; past it, every file is asked about. */
|
|
16936
16985
|
autoBytesPerGroup: 268435456,
|
|
@@ -16938,6 +16987,10 @@ var GROUP_FILE_LIMITS = {
|
|
|
16938
16987
|
serveBytesPerDay: 1073741824,
|
|
16939
16988
|
/** Transfers a holder serves at once, all groups together. */
|
|
16940
16989
|
serveAtOnce: 3,
|
|
16990
|
+
/** Bytes a served transfer gets confirmed (or the rest of its file) for it to count as moving in `wantWaitMs`. */
|
|
16991
|
+
serveMoveBytes: 16384,
|
|
16992
|
+
/** How long a served transfer keeps its place while all are taken and another member asks: past it, the place is theirs. */
|
|
16993
|
+
servePlaceMs: 3e5,
|
|
16941
16994
|
/** File announcements a member sends to one group in a minute. */
|
|
16942
16995
|
announcePerMinute: 8,
|
|
16943
16996
|
/** File announcements of one member of a group fetched by themselves in `announceWindowMs`; past it, kept for a Download. */
|
|
@@ -16964,11 +17017,21 @@ var GROUP_FILE_LIMITS = {
|
|
|
16964
17017
|
* left for the next holder too.
|
|
16965
17018
|
*/
|
|
16966
17019
|
wantWaitMs: 3e4,
|
|
17020
|
+
/**
|
|
17021
|
+
* How long a served transfer with all its bytes confirmed waits for the member's word that its check passed once the
|
|
17022
|
+
* member's next want has overtaken it.
|
|
17023
|
+
*/
|
|
17024
|
+
doneWaitMs: 5e3,
|
|
16967
17025
|
/** A file session closes this long after its last transfer ends. */
|
|
16968
17026
|
sessionIdleMs: 6e4,
|
|
16969
17027
|
/** File sessions opening at once, and open at once, all groups together. */
|
|
16970
17028
|
sessionsOpening: 2,
|
|
16971
17029
|
sessionsOpen: 4,
|
|
17030
|
+
/**
|
|
17031
|
+
* What a transfer must bring in `wantWaitMs` (or the rest of its file, when less) to count as moving: a holder that
|
|
17032
|
+
* sends a few bytes at a time is left as one that stalls.
|
|
17033
|
+
*/
|
|
17034
|
+
wantMoveBytes: 16384,
|
|
16972
17035
|
/** The longest file name and type kept from a description. */
|
|
16973
17036
|
nameChars: 255,
|
|
16974
17037
|
mimeChars: 255
|
|
@@ -17053,11 +17116,11 @@ function groupFileFallback(file) {
|
|
|
17053
17116
|
return `${fileMessageText(file)} (${formatFileSize(file.size)}): update Ghostly to get files in groups`;
|
|
17054
17117
|
}
|
|
17055
17118
|
/**
|
|
17056
|
-
* Whether a device fetches a file by itself (WISP 503 § Automatic and asked downloads):
|
|
17057
|
-
*
|
|
17119
|
+
* Whether a device fetches a file by itself (WISP 503 § Automatic and asked downloads): up to `autoBytes` while what
|
|
17120
|
+
* it took that way from the group stays within `autoBytesPerGroup`. A voice message too: one recorded (15 minutes at
|
|
17121
|
+
* most, about 3.6 MB) always fits, and a bigger file the author called a voice message is asked about like any other.
|
|
17058
17122
|
*/
|
|
17059
17123
|
function fetchesByItself(file, keptAutomatically, limits = GROUP_FILE_LIMITS) {
|
|
17060
|
-
if (file.voice) return true;
|
|
17061
17124
|
return file.size <= limits.autoBytes && keptAutomatically + file.size <= limits.autoBytesPerGroup;
|
|
17062
17125
|
}
|
|
17063
17126
|
//#endregion
|
|
@@ -17329,7 +17392,7 @@ var VALUE = {
|
|
|
17329
17392
|
max: "(\\d[\\d.,\\u00a0\\u202f' ]*?)"
|
|
17330
17393
|
};
|
|
17331
17394
|
var escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17332
|
-
Object.keys(ENGINE_ERRORS).map((code) => {
|
|
17395
|
+
var PATTERNS = Object.keys(ENGINE_ERRORS).map((code) => {
|
|
17333
17396
|
const names = [];
|
|
17334
17397
|
const source = ENGINE_ERRORS[code].split(/(\{\w+\})/).map((part) => {
|
|
17335
17398
|
const value = /^\{(\w+)\}$/.exec(part);
|
|
@@ -17343,6 +17406,18 @@ Object.keys(ENGINE_ERRORS).map((code) => {
|
|
|
17343
17406
|
pattern: new RegExp(`^${source}$`)
|
|
17344
17407
|
};
|
|
17345
17408
|
});
|
|
17409
|
+
/** The code and values of a text the engine wrote, or null for any other text (it is then shown as it is). */
|
|
17410
|
+
function parseEngineError(text) {
|
|
17411
|
+
const trimmed = text.trim();
|
|
17412
|
+
for (const { code, names, pattern } of PATTERNS) {
|
|
17413
|
+
const match = pattern.exec(trimmed);
|
|
17414
|
+
if (match) return {
|
|
17415
|
+
code,
|
|
17416
|
+
values: Object.fromEntries(names.map((name, i) => [name, match[i + 1]]))
|
|
17417
|
+
};
|
|
17418
|
+
}
|
|
17419
|
+
return null;
|
|
17420
|
+
}
|
|
17346
17421
|
//#endregion
|
|
17347
17422
|
//#region ../core/src/ghostlink.ts
|
|
17348
17423
|
/**
|
|
@@ -17699,6 +17774,24 @@ var GhostLink = class {
|
|
|
17699
17774
|
/** Edit frames the contact may send per window; the rest go unconfirmed and come again. */
|
|
17700
17775
|
editsReceived = new RateWindow(30, EDIT_RATE_WINDOW_MS);
|
|
17701
17776
|
wakeReceived = new RateWindow(6, 6e4);
|
|
17777
|
+
/**
|
|
17778
|
+
* The contact's name and picture, each saved when it changes: said once a session and when changed, so a handful a
|
|
17779
|
+
* minute each. Past that only the newest waits, read once the window has room again.
|
|
17780
|
+
*/
|
|
17781
|
+
peerSaid = {
|
|
17782
|
+
"paired-nick": {
|
|
17783
|
+
window: new RateWindow(6, 6e4),
|
|
17784
|
+
read: null,
|
|
17785
|
+
timer: null
|
|
17786
|
+
},
|
|
17787
|
+
"paired-avatar": {
|
|
17788
|
+
window: new RateWindow(6, 6e4),
|
|
17789
|
+
read: null,
|
|
17790
|
+
timer: null
|
|
17791
|
+
}
|
|
17792
|
+
};
|
|
17793
|
+
/** Identity-proof frames the contact may send per window; the rest are dropped before they are read. */
|
|
17794
|
+
identityReceived = new RateWindow(40, IDENTITY_FRAME_WINDOW_MS);
|
|
17702
17795
|
livenessTimer = null;
|
|
17703
17796
|
peerAnswersPings = false;
|
|
17704
17797
|
/** When the ping awaiting its pong went, and the round trip last measured on this session. */
|
|
@@ -19167,8 +19260,9 @@ var GhostLink = class {
|
|
|
19167
19260
|
}
|
|
19168
19261
|
/**
|
|
19169
19262
|
* Ends the idle live session on this transport and lets its listener go, for a chat in use that found every one
|
|
19170
|
-
* taken. The contact hears a goodbye
|
|
19171
|
-
*
|
|
19263
|
+
* taken. The contact hears a goodbye that says so (`yield`, `peerYielded`), rather than finding out when its pings go
|
|
19264
|
+
* unanswered, and does not watch for this app as for one that quit; the record then lists the transport undescribed,
|
|
19265
|
+
* so it stops dialling here. The chat goes on over the DHT (or WebRTC),
|
|
19172
19266
|
* and takes a listener again the same way once it is in use. False when the session may not end (see above).
|
|
19173
19267
|
*/
|
|
19174
19268
|
async yieldEndpoint(transport, idleMs) {
|
|
@@ -19180,7 +19274,10 @@ var GhostLink = class {
|
|
|
19180
19274
|
this.yielding = true;
|
|
19181
19275
|
try {
|
|
19182
19276
|
try {
|
|
19183
|
-
this.channel?.send(JSON.stringify({
|
|
19277
|
+
this.channel?.send(JSON.stringify({
|
|
19278
|
+
t: "paired-bye",
|
|
19279
|
+
yield: true
|
|
19280
|
+
}));
|
|
19184
19281
|
} catch {}
|
|
19185
19282
|
await new Promise((resolve) => setTimeout(resolve, YIELD_BYE_MS));
|
|
19186
19283
|
this.disconnect();
|
|
@@ -21230,7 +21327,10 @@ var GhostLink = class {
|
|
|
21230
21327
|
return;
|
|
21231
21328
|
}
|
|
21232
21329
|
if (frame?.t === "paired-bye") {
|
|
21233
|
-
if (paired.state.status === "ready")
|
|
21330
|
+
if (paired.state.status === "ready") {
|
|
21331
|
+
if (frame.yield === true) this.peerYielded(channel);
|
|
21332
|
+
else this.peerDeparted(channel);
|
|
21333
|
+
}
|
|
21234
21334
|
return;
|
|
21235
21335
|
}
|
|
21236
21336
|
if (!frame || typeof frame !== "object") return;
|
|
@@ -21309,25 +21409,15 @@ var GhostLink = class {
|
|
|
21309
21409
|
return;
|
|
21310
21410
|
}
|
|
21311
21411
|
if (typeof frame?.t === "string" && frame.t.startsWith("idp-") && this.identitySupport) {
|
|
21312
|
-
await this.options.events?.onIdentityProof?.(frame);
|
|
21412
|
+
if (this.identityReceived.take()) await this.options.events?.onIdentityProof?.(frame);
|
|
21313
21413
|
return;
|
|
21314
21414
|
}
|
|
21315
21415
|
if (typeof frame?.t === "string" && frame.t.startsWith("proof-") && this.peerProofSupport) {
|
|
21316
21416
|
await this.options.events?.onPeerProof?.(frame);
|
|
21317
21417
|
return;
|
|
21318
21418
|
}
|
|
21319
|
-
if (frame?.t === "paired-avatar") {
|
|
21320
|
-
|
|
21321
|
-
if (avatar !== void 0) this.options.events?.onPeerAvatar?.(avatar);
|
|
21322
|
-
return;
|
|
21323
|
-
}
|
|
21324
|
-
if (frame?.t === "paired-nick") {
|
|
21325
|
-
const nick = sanitizeNick(frame.n);
|
|
21326
|
-
if (typeof frame.n === "string") this.options.events?.onPeerNick?.(nick ?? null);
|
|
21327
|
-
if (nick !== this.peerNickOverride) {
|
|
21328
|
-
this.peerNickOverride = nick ?? null;
|
|
21329
|
-
this.options.events?.onPresence?.(this.presence);
|
|
21330
|
-
}
|
|
21419
|
+
if (frame?.t === "paired-avatar" || frame?.t === "paired-nick") {
|
|
21420
|
+
this.peerSays(channel, frame.t, frame);
|
|
21331
21421
|
return;
|
|
21332
21422
|
}
|
|
21333
21423
|
if (frame?.t === "paired-capabilities") {
|
|
@@ -21684,6 +21774,18 @@ var GhostLink = class {
|
|
|
21684
21774
|
this.peerLost("bye");
|
|
21685
21775
|
}
|
|
21686
21776
|
/**
|
|
21777
|
+
* The contact gave this session's listener up to another chat (`yieldEndpoint`): its app is still there, and its
|
|
21778
|
+
* record now lists the transport undescribed, so there is nothing to dial and no return to watch for. The session
|
|
21779
|
+
* ends now, and the chat reads the contact at its own pace; the contact dials once it takes a listener back. Taken
|
|
21780
|
+
* as a goodbye, it was watched as an app that quit: 89 reads of its key in the 2 minutes after a yield on the
|
|
21781
|
+
* Desktop, against 6 at the background pace (bug hunt, 2026-10-15).
|
|
21782
|
+
*/
|
|
21783
|
+
peerYielded(channel) {
|
|
21784
|
+
if (this.channel !== channel || this.stopped) return;
|
|
21785
|
+
traceLink(this.myPubKeyZ32, "peer-yielded", {});
|
|
21786
|
+
this.disconnect();
|
|
21787
|
+
}
|
|
21788
|
+
/**
|
|
21687
21789
|
* A packet of the contact's not seen before, fresh, while nothing is live, soon after a live session ended: it is back
|
|
21688
21790
|
* (its app restarted), so the wait that the attempts since built up is over, and the next dial goes now. Once per
|
|
21689
21791
|
* loss: a contact that is there but unreachable publishes often, and the wait between attempts is what spares the
|
|
@@ -21778,6 +21880,57 @@ var GhostLink = class {
|
|
|
21778
21880
|
offered.woke = true;
|
|
21779
21881
|
if (!this.dialing) setTimeout(() => this.maybeKnockEarly(offered.epoch), 0);
|
|
21780
21882
|
}
|
|
21883
|
+
/**
|
|
21884
|
+
* A name or picture the contact said, once it is one (reading it costs little; saving it is what counts): past the
|
|
21885
|
+
* window the newest waits for room on this session, so the last word still counts.
|
|
21886
|
+
*/
|
|
21887
|
+
peerSays(channel, kind, frame) {
|
|
21888
|
+
let read;
|
|
21889
|
+
if (kind === "paired-avatar") {
|
|
21890
|
+
const avatar = sanitizeAvatar(frame.a);
|
|
21891
|
+
if (avatar === void 0) return;
|
|
21892
|
+
read = () => this.options.events?.onPeerAvatar?.(avatar);
|
|
21893
|
+
} else {
|
|
21894
|
+
const nick = sanitizeNick(frame.n) ?? null, said = typeof frame.n === "string";
|
|
21895
|
+
read = () => {
|
|
21896
|
+
if (said) this.options.events?.onPeerNick?.(nick);
|
|
21897
|
+
if (nick !== this.peerNickOverride) {
|
|
21898
|
+
this.peerNickOverride = nick;
|
|
21899
|
+
this.options.events?.onPresence?.(this.presence);
|
|
21900
|
+
}
|
|
21901
|
+
};
|
|
21902
|
+
}
|
|
21903
|
+
const held = this.peerSaid[kind];
|
|
21904
|
+
if (!held.timer && held.window.take()) {
|
|
21905
|
+
read();
|
|
21906
|
+
return;
|
|
21907
|
+
}
|
|
21908
|
+
held.read = read;
|
|
21909
|
+
if (held.timer) return;
|
|
21910
|
+
const due = () => {
|
|
21911
|
+
held.timer = null;
|
|
21912
|
+
if (this.channel !== channel || !held.read) {
|
|
21913
|
+
held.read = null;
|
|
21914
|
+
return;
|
|
21915
|
+
}
|
|
21916
|
+
if (!held.window.take()) {
|
|
21917
|
+
held.timer = setTimeout(due, held.window.wait());
|
|
21918
|
+
return;
|
|
21919
|
+
}
|
|
21920
|
+
const next = held.read;
|
|
21921
|
+
held.read = null;
|
|
21922
|
+
next();
|
|
21923
|
+
};
|
|
21924
|
+
held.timer = setTimeout(due, held.window.wait());
|
|
21925
|
+
}
|
|
21926
|
+
/** What the contact said of itself and was still waiting goes with the session: the next one says it again. */
|
|
21927
|
+
dropPeerSaid() {
|
|
21928
|
+
for (const said of Object.values(this.peerSaid)) {
|
|
21929
|
+
if (said.timer) clearTimeout(said.timer);
|
|
21930
|
+
said.timer = null;
|
|
21931
|
+
said.read = null;
|
|
21932
|
+
}
|
|
21933
|
+
}
|
|
21781
21934
|
/** A ping on the session right after a wake, with `PONG_WAIT_MS` for its answer: none, and the session is dead. */
|
|
21782
21935
|
probeAfterWake(channel) {
|
|
21783
21936
|
if (!this.peerAnswersPings || this.paired?.state.status !== "ready") return;
|
|
@@ -21820,6 +21973,7 @@ var GhostLink = class {
|
|
|
21820
21973
|
this.channel = this.httpHost = this.httpClient = this.files = null;
|
|
21821
21974
|
this.peerServicesOverride = null;
|
|
21822
21975
|
this.peerNickOverride = null;
|
|
21976
|
+
this.dropPeerSaid();
|
|
21823
21977
|
this.sessionCapabilities.reset();
|
|
21824
21978
|
this.typingSender.reset();
|
|
21825
21979
|
this.typingReceiver.clear();
|
|
@@ -22304,6 +22458,10 @@ function parsePayParams(body, destination) {
|
|
|
22304
22458
|
if (body.tag !== "payRequest") throw new Error(`${destination.domain} answered with something other than a pay request${typeof body.tag === "string" ? ` (${reason(body.tag)})` : ""}`);
|
|
22305
22459
|
if (typeof body.callback !== "string") throw new Error("The pay request has no callback");
|
|
22306
22460
|
const callback = acceptUrl(body.callback);
|
|
22461
|
+
if (callback.host !== new URL(destination.url).host) {
|
|
22462
|
+
if (callback.protocol !== "https:") throw new Error("Lightning addresses and LNURLs must use HTTPS");
|
|
22463
|
+
if (isLocalHost(callback.hostname)) throw new Error(`${destination.domain} asks for the invoice at a local address`);
|
|
22464
|
+
}
|
|
22307
22465
|
const { minSendable, maxSendable, metadata, commentAllowed } = body;
|
|
22308
22466
|
if (!Number.isSafeInteger(minSendable) || !Number.isSafeInteger(maxSendable) || minSendable <= 0 || maxSendable < minSendable) throw new Error("The pay request has invalid amount limits");
|
|
22309
22467
|
const minSat = Math.ceil(minSendable / 1e3), maxSat = Math.floor(maxSendable / 1e3);
|
|
@@ -22442,10 +22600,14 @@ function decodeSvcbRecords(dnsPacket) {
|
|
|
22442
22600
|
}
|
|
22443
22601
|
return records;
|
|
22444
22602
|
}
|
|
22445
|
-
/**
|
|
22603
|
+
/**
|
|
22604
|
+
* The homeserver a Pubky key's packet names: the z-base-32 key its `_pubky` record points at, or undefined. The target
|
|
22605
|
+
* is read next as a key (`pubkyRecords` checks the homeserver's packet against it), so only a canonical one names a
|
|
22606
|
+
* homeserver; a respelled one is none, not a lookup that fails as "no records".
|
|
22607
|
+
*/
|
|
22446
22608
|
function pubkyHomeserverOf(dnsPacket, userZ32) {
|
|
22447
22609
|
const record = decodeSvcbRecords(dnsPacket).find((r) => r.name === `_pubky.${userZ32}` || r.name === "_pubky");
|
|
22448
|
-
return record &&
|
|
22610
|
+
return record && isZ32Key(record.target) ? record.target : void 0;
|
|
22449
22611
|
}
|
|
22450
22612
|
/**
|
|
22451
22613
|
* The homeserver's web endpoint from its own packet: of the HTTPS/SVCB records at its root, the lowest priority one
|
|
@@ -22722,6 +22884,8 @@ function assertTokenUnits(amount) {
|
|
|
22722
22884
|
function assertWholeSats(amount) {
|
|
22723
22885
|
if (!Number.isSafeInteger(amount) || amount <= 0 || amount > 0x775f05a074000) throw new Error("Enter a positive whole amount in sats");
|
|
22724
22886
|
}
|
|
22887
|
+
/** Arkade and Bark addresses are bech32m with `ark` on Bitcoin and `tark` on the test networks (one case only). */
|
|
22888
|
+
var arkAddressPrefix = (network) => network === "bitcoin" ? "ark1" : "tark1";
|
|
22725
22889
|
function validatePaymentTarget(value, now = Date.now()) {
|
|
22726
22890
|
if (!value || typeof value !== "object") throw new Error("Invalid payment target");
|
|
22727
22891
|
const t = value;
|
|
@@ -22760,6 +22924,7 @@ function validatePaymentTarget(value, now = Date.now()) {
|
|
|
22760
22924
|
"regtest"
|
|
22761
22925
|
].includes(t.network)) throw new Error("Unsupported Bark network");
|
|
22762
22926
|
if (typeof t.address !== "string" || !t.address || t.address.length > 4096 || typeof t.provider !== "string" || t.provider.length > 512) throw new Error("Invalid payment destination");
|
|
22927
|
+
if ((t.method === "arkade" || t.method === "bark") && (t.address !== t.address.toLowerCase() && t.address !== t.address.toUpperCase() || !t.address.toLowerCase().startsWith(arkAddressPrefix(t.network)))) throw new Error(`That is not an Ark address on ${t.network === "bitcoin" ? "Bitcoin" : t.network}`);
|
|
22763
22928
|
if (t.method !== "bitcoin" && t.method !== "fedimint" && t.method !== "spark") {
|
|
22764
22929
|
const url = new URL(t.provider);
|
|
22765
22930
|
if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && [
|
|
@@ -24402,7 +24567,7 @@ function shortAtprotoDid(did) {
|
|
|
24402
24567
|
}
|
|
24403
24568
|
/** The record key of a proof: its proof key (z-base-32, already a valid record key), so removal finds it. */
|
|
24404
24569
|
function atprotoProofRkey(proofKey) {
|
|
24405
|
-
if (
|
|
24570
|
+
if (!isZ32Key(proofKey)) throw new Error("Invalid proof key");
|
|
24406
24571
|
return proofKey;
|
|
24407
24572
|
}
|
|
24408
24573
|
var fragment = (id, did, name) => id === `#${name}` || id === `${did}#${name}`;
|
|
@@ -25824,6 +25989,28 @@ var fileAad = (f) => JSON.stringify([
|
|
|
25824
25989
|
f.ts
|
|
25825
25990
|
]);
|
|
25826
25991
|
var isFileBox = isSealedBox(MAX_FILE_BOX);
|
|
25992
|
+
/** The author's name (`MAX_NICK_LENGTH` characters of up to four bytes each), sealed; signed apart from `xs` (`ns`). */
|
|
25993
|
+
var MAX_NICK_BOX = Math.ceil(1088 / 3) + 4;
|
|
25994
|
+
var nickAad = (f) => JSON.stringify([
|
|
25995
|
+
"ghostly-group/1 nick",
|
|
25996
|
+
f.g,
|
|
25997
|
+
f.e,
|
|
25998
|
+
f.s,
|
|
25999
|
+
f.n,
|
|
26000
|
+
f.ts
|
|
26001
|
+
]);
|
|
26002
|
+
var nickSigned = (f, nk) => utf8Encode(JSON.stringify([
|
|
26003
|
+
"ghostly-group/1 nick",
|
|
26004
|
+
f.g,
|
|
26005
|
+
f.e,
|
|
26006
|
+
f.s,
|
|
26007
|
+
f.n,
|
|
26008
|
+
f.ts,
|
|
26009
|
+
nk.n,
|
|
26010
|
+
nk.c
|
|
26011
|
+
]));
|
|
26012
|
+
var isNickBox = isSealedBox(MAX_NICK_BOX);
|
|
26013
|
+
var isSignature = (v) => typeof v === "string" && v.length === 86 && B64$1.test(v);
|
|
25827
26014
|
var B64$1 = /^[A-Za-z0-9_-]*$/;
|
|
25828
26015
|
var secretAad$1 = (g, e, member) => JSON.stringify([
|
|
25829
26016
|
"ghostly-group/1 secret",
|
|
@@ -25963,7 +26150,7 @@ var ownKey = (f) => `${f.e}:${f.n}`;
|
|
|
25963
26150
|
/** What a frame weighs in a log, roughly: its boxes plus the fixed fields around them. */
|
|
25964
26151
|
var frameKey$1 = (f) => `${f.s}:${f.e}:${f.n}`;
|
|
25965
26152
|
/** What a kept frame counts for against `GROUP_LIMITS.relayBytes`. */
|
|
25966
|
-
var frameBytes = (f) => f.c.length + (f.m?.c.length ?? 0) + (f.r?.c.length ?? 0) + (f.sc?.c.length ?? 0) + (f.fl?.c.length ?? 0) + 400;
|
|
26153
|
+
var frameBytes = (f) => f.c.length + (f.m?.c.length ?? 0) + (f.r?.c.length ?? 0) + (f.sc?.c.length ?? 0) + (f.fl?.c.length ?? 0) + (f.nk?.c.length ?? 0) + 400;
|
|
25967
26154
|
/** Only the fields an edit frame has, as `clean` does for a message. */
|
|
25968
26155
|
var cleanEdit = (f) => ({
|
|
25969
26156
|
t: GROUP_EDIT_FRAME,
|
|
@@ -26007,7 +26194,14 @@ function clean(f) {
|
|
|
26007
26194
|
c: f.fl.c
|
|
26008
26195
|
} } : {},
|
|
26009
26196
|
...readFirstPlace(f.o, f.e) ? { o: readFirstPlace(f.o, f.e) } : {},
|
|
26010
|
-
...typeof f.xs === "string" && f.xs.length === 86 && B64$1.test(f.xs) ? { xs: f.xs } : {}
|
|
26197
|
+
...typeof f.xs === "string" && f.xs.length === 86 && B64$1.test(f.xs) ? { xs: f.xs } : {},
|
|
26198
|
+
...isNickBox(f.nk) && isSignature(f.ns) ? {
|
|
26199
|
+
nk: {
|
|
26200
|
+
n: f.nk.n,
|
|
26201
|
+
c: f.nk.c
|
|
26202
|
+
},
|
|
26203
|
+
ns: f.ns
|
|
26204
|
+
} : {}
|
|
26011
26205
|
};
|
|
26012
26206
|
}
|
|
26013
26207
|
/** Whether the member whose sync this is lacks a frame: above what it has from that sender, or one it names as missing. */
|
|
@@ -26077,6 +26271,8 @@ var GroupSession = class {
|
|
|
26077
26271
|
asked = /* @__PURE__ */ new Map();
|
|
26078
26272
|
/** The signed leave of each member I last passed on, kept for an admin that was away (`byesHeld`). In memory only. */
|
|
26079
26273
|
byesPassed = /* @__PURE__ */ new Map();
|
|
26274
|
+
/** Member → when the name I hold for it was said: its message's time, or the moment an edge said it (`heardName`). In memory only. */
|
|
26275
|
+
nickAt = /* @__PURE__ */ new Map();
|
|
26080
26276
|
queue = Promise.resolve();
|
|
26081
26277
|
/** One metadata frame that names a commit or an epoch I do not have yet: tried again when the chain moves. */
|
|
26082
26278
|
pendingMeta;
|
|
@@ -26435,7 +26631,7 @@ var GroupSession = class {
|
|
|
26435
26631
|
rewelcome(memberKey) {
|
|
26436
26632
|
return this.serialize(async () => {
|
|
26437
26633
|
this.requireAdmin();
|
|
26438
|
-
if (!rosterHas(this.roster, memberKey) || memberKey === this.myKey) throw new Error("Not a member");
|
|
26634
|
+
if (!rosterHas(this.roster, memberKey) || memberKey === this.myKey || !isZ32Key(memberKey)) throw new Error("Not a member");
|
|
26439
26635
|
const chain = this.state.chain;
|
|
26440
26636
|
let admitted = chain.length - 1;
|
|
26441
26637
|
while (admitted >= 0 && !(chain[admitted].k === "add" && chain[admitted].s === memberKey)) admitted--;
|
|
@@ -26517,7 +26713,7 @@ var GroupSession = class {
|
|
|
26517
26713
|
await this.persist();
|
|
26518
26714
|
for (const [key] of previous.m) {
|
|
26519
26715
|
if (key === this.myKey) continue;
|
|
26520
|
-
const member = rosterHas(roster, key);
|
|
26716
|
+
const member = rosterHas(roster, key) && isZ32Key(key);
|
|
26521
26717
|
this.hooks.send(key, {
|
|
26522
26718
|
t: "group-commit",
|
|
26523
26719
|
g: this.id,
|
|
@@ -26695,6 +26891,8 @@ var GroupSession = class {
|
|
|
26695
26891
|
...extra.file ? { fl: encryptText(key, fileAad(header), JSON.stringify(extra.file)) } : {},
|
|
26696
26892
|
...o ? { o } : {}
|
|
26697
26893
|
};
|
|
26894
|
+
const nick = sanitizeNick(this.hooks.nick?.());
|
|
26895
|
+
const nk = nick === void 0 ? void 0 : encryptText(key, nickAad(header), nick);
|
|
26698
26896
|
return {
|
|
26699
26897
|
t: "group-msg",
|
|
26700
26898
|
...unsigned,
|
|
@@ -26703,7 +26901,11 @@ var GroupSession = class {
|
|
|
26703
26901
|
xs: toBase64Url(sign(messageSignedWhole({
|
|
26704
26902
|
...unsigned,
|
|
26705
26903
|
...boxes
|
|
26706
|
-
}), this.identity.seed))
|
|
26904
|
+
}), this.identity.seed)),
|
|
26905
|
+
...nk ? {
|
|
26906
|
+
nk,
|
|
26907
|
+
ns: toBase64Url(sign(nickSigned(header, nk), this.identity.seed))
|
|
26908
|
+
} : {}
|
|
26707
26909
|
};
|
|
26708
26910
|
}
|
|
26709
26911
|
/** Into my bounded log, for members who missed it. */
|
|
@@ -26897,10 +27099,12 @@ var GroupSession = class {
|
|
|
26897
27099
|
setNick(key, nick) {
|
|
26898
27100
|
return this.serialize(async () => {
|
|
26899
27101
|
const clean = sanitizeNick(nick);
|
|
27102
|
+
this.nickAt.set(key, this.clock());
|
|
26900
27103
|
if ((this.state.nicks[key] ?? void 0) === clean) return;
|
|
26901
27104
|
if (clean) this.state.nicks[key] = clean;
|
|
26902
27105
|
else delete this.state.nicks[key];
|
|
26903
|
-
await this.persist();
|
|
27106
|
+
if (key === this.myKey) await this.persist();
|
|
27107
|
+
else this.persistSoon();
|
|
26904
27108
|
this.hooks.changed();
|
|
26905
27109
|
});
|
|
26906
27110
|
}
|
|
@@ -27132,6 +27336,7 @@ var GroupSession = class {
|
|
|
27132
27336
|
const key = epochKeys(secret, this.id, raw.e).message;
|
|
27133
27337
|
const text = decryptText(key, messageAad$1(raw), raw.nn, raw.c);
|
|
27134
27338
|
if (text === null) return;
|
|
27339
|
+
if (this.heardName(key, raw)) this.hooks.changed();
|
|
27135
27340
|
const firstFrame = first && {
|
|
27136
27341
|
...raw,
|
|
27137
27342
|
e: first.e,
|
|
@@ -27245,6 +27450,26 @@ var GroupSession = class {
|
|
|
27245
27450
|
return false;
|
|
27246
27451
|
}
|
|
27247
27452
|
}
|
|
27453
|
+
/**
|
|
27454
|
+
* The author's name in a message (`nk`), when the author's signature over it (`ns`) holds: any copy, handed on or
|
|
27455
|
+
* not, whole or not, since a member handing it on can only drop it. Taken only from a message newer than the one it
|
|
27456
|
+
* last came in, or than the moment an edge last said it (`setNick`), so a catch-up's older pages never bring back a
|
|
27457
|
+
* name the member changed since. Whether the name I hold changed.
|
|
27458
|
+
*/
|
|
27459
|
+
heardName(key, raw) {
|
|
27460
|
+
if (!isNickBox(raw.nk) || !isSignature(raw.ns) || raw.ts <= (this.nickAt.get(raw.s) ?? 0)) return false;
|
|
27461
|
+
try {
|
|
27462
|
+
if (!verify(fromBase64Url(raw.ns), nickSigned(raw, raw.nk), publicKeyFromZ32(raw.s))) return false;
|
|
27463
|
+
} catch {
|
|
27464
|
+
return false;
|
|
27465
|
+
}
|
|
27466
|
+
const nick = sanitizeNick(decryptText(key, nickAad(raw), raw.nk.n, raw.nk.c));
|
|
27467
|
+
if (nick === void 0) return false;
|
|
27468
|
+
this.nickAt.set(raw.s, raw.ts);
|
|
27469
|
+
if (this.state.nicks[raw.s] === nick) return false;
|
|
27470
|
+
this.state.nicks[raw.s] = nick;
|
|
27471
|
+
return true;
|
|
27472
|
+
}
|
|
27248
27473
|
openMentions(key, raw, text, everyone) {
|
|
27249
27474
|
if (!isMentionsBox(raw.m)) return [];
|
|
27250
27475
|
const plain = decryptText(key, mentionsAad(raw), raw.m.n, raw.m.c);
|
|
@@ -27436,7 +27661,7 @@ var GroupSession = class {
|
|
|
27436
27661
|
await this.replayWaiting();
|
|
27437
27662
|
}
|
|
27438
27663
|
async receiveSync(from, frame) {
|
|
27439
|
-
if (!rosterHas(this.roster, from) || !Number.isSafeInteger(frame.e) || frame.e < 0 || typeof frame.h !== "string") return;
|
|
27664
|
+
if (!rosterHas(this.roster, from) || !isZ32Key(from) || !Number.isSafeInteger(frame.e) || frame.e < 0 || typeof frame.h !== "string") return;
|
|
27440
27665
|
let answered = this.syncsAnswered.get(from);
|
|
27441
27666
|
if (!answered) this.syncsAnswered.set(from, answered = new RateWindow(GROUP_LIMITS.syncAnswers, GROUP_LIMITS.syncWindowMs, () => this.hooks.clock?.() ?? Date.now()));
|
|
27442
27667
|
if (!answered.take()) return;
|
|
@@ -28972,7 +29197,7 @@ var CommunitySession = class {
|
|
|
28972
29197
|
commit
|
|
28973
29198
|
});
|
|
28974
29199
|
if (FRESH.has(kind)) {
|
|
28975
|
-
for (const [key] of roster) if (key !== this.myKey) this.hooks.addressed(key, {
|
|
29200
|
+
for (const [key] of roster) if (key !== this.myKey && isZ32Key(key)) this.hooks.addressed(key, {
|
|
28976
29201
|
t: "group-secret",
|
|
28977
29202
|
v: 2,
|
|
28978
29203
|
g: this.id,
|
|
@@ -29019,7 +29244,7 @@ var CommunitySession = class {
|
|
|
29019
29244
|
rewelcome(memberKey) {
|
|
29020
29245
|
return this.serialize(async () => {
|
|
29021
29246
|
this.requireMember();
|
|
29022
|
-
if (!rosterHas(this.roster, memberKey) || memberKey === this.myKey) throw new Error("Not a member");
|
|
29247
|
+
if (!rosterHas(this.roster, memberKey) || memberKey === this.myKey || !isZ32Key(memberKey)) throw new Error("Not a member");
|
|
29023
29248
|
for (let i = this.state.chain.length - 1; i >= 0; i--) {
|
|
29024
29249
|
const c = this.state.chain[i];
|
|
29025
29250
|
if (c.k === "add" && c.s === memberKey) return this.welcomeFor(memberKey, communityCommitHash(c));
|
|
@@ -29085,7 +29310,10 @@ var CommunitySession = class {
|
|
|
29085
29310
|
seedB64: entry?.seedB64 ?? ""
|
|
29086
29311
|
};
|
|
29087
29312
|
await this.persist();
|
|
29088
|
-
if (entry) for (const key of this.others)
|
|
29313
|
+
if (entry) for (const key of this.others) {
|
|
29314
|
+
const frame = this.entryFrame(key);
|
|
29315
|
+
if (frame) this.hooks.addressed(key, frame);
|
|
29316
|
+
}
|
|
29089
29317
|
this.hooks.changed();
|
|
29090
29318
|
return this.state.entry.key;
|
|
29091
29319
|
});
|
|
@@ -29701,7 +29929,7 @@ var CommunitySession = class {
|
|
|
29701
29929
|
for (const { from, frame } of ready) await this.receiveMessage(from, frame);
|
|
29702
29930
|
}
|
|
29703
29931
|
async receiveSync(from, frame) {
|
|
29704
|
-
if (!this.isRecentMember(from) || !Number.isSafeInteger(frame.e) || typeof frame.h !== "string") return;
|
|
29932
|
+
if (!this.isRecentMember(from) || !isZ32Key(from) || !Number.isSafeInteger(frame.e) || typeof frame.h !== "string") return;
|
|
29705
29933
|
let answered = this.syncsAnswered.get(from);
|
|
29706
29934
|
if (!answered) this.syncsAnswered.set(from, answered = new RateWindow(COMMUNITY_LIMITS.syncAnswers, COMMUNITY_LIMITS.syncWindowMs, () => this.hooks.clock?.() ?? Date.now()));
|
|
29707
29935
|
if (!answered.take()) return;
|
|
@@ -29816,7 +30044,7 @@ var CommunitySession = class {
|
|
|
29816
30044
|
/** The current entry seed sealed to a member, when I hold it: sealed once per seed, not on every sync answer. */
|
|
29817
30045
|
entryFrame(to) {
|
|
29818
30046
|
const { key, seedB64 } = this.state.entry;
|
|
29819
|
-
if (!key || !seedB64) return null;
|
|
30047
|
+
if (!key || !seedB64 || !isZ32Key(to)) return null;
|
|
29820
30048
|
const sealed = this.sealedEntries.get(to);
|
|
29821
30049
|
if (sealed?.seedB64 === seedB64 && sealed.frame.x === key) return { ...sealed.frame };
|
|
29822
30050
|
const frame = {
|
|
@@ -30986,6 +31214,6 @@ function appStoreDecision(held, read) {
|
|
|
30986
31214
|
return read.digest === held.digest ? "same" : "equivocation";
|
|
30987
31215
|
}
|
|
30988
31216
|
//#endregion
|
|
30989
|
-
export { COMMUNITY_EDIT_FRAME as $, cardUrl as $a, budgetRetryMs as $c, PeerProofs as $i, GROUP_WANT_FRAME as $n, HTTP_SERVICE_PROTO as $o, deviceWakeRequest as $r, enrollCancelFrame as $s, didDhtFromPublicKey as $t, CommunitySession as A, COMMUNITY_REACTION_FRAME as Aa, TURN_PUT_WINDOW_MS as Ac, normalizeDomain as Ai, pubkyProofPath as An, DirectPathWatch as Ao, rankHubs as Ar, handoffStreamKey as As, verifyAtprotoRecordProof as At, knockRecords as B, wireReaction as Ba, signTurnPacket as Bc, IDENTITY_MAX_EVIDENCE as Bi, isFederationId as Bn, LIMITS as Bo, TYPING_REFRESH_MS as Br, readHandoffVerified as Bs, didWebDocumentUrl as Bt, sha256Paced as C, EDIT_BUFFER_MS as Ca, deviceEchoNonce as Cc, decodeDnsResponse as Ci, PUBKY_PROOF_MAX_BYTES as Cn, GROUP_MEMBER_CAP as Co, freshHubs as Cr, handoffEphemeral as Cs, atprotoProofRkey as Ct, openGroupTyping as D, parseLinkPreview as Da, newDeviceSetSecret as Dc, encodeDnsQuery as Di, normalizePubkyKey as Dn, rosterHas as Do, mergeLobby as Dr, handoffPakeFrame as Ds, parseAtprotoDidDocument as Dt, groupTypingEpoch as E, dhtEditId as Ea, devicePingFrame as Ec, domainWellKnownFile as Ei, newPubkyProofFolder as En, commitHash as Eo, mergeBeacon as Er, handoffOfferFrame as Es, normalizeAtprotoHandle as Et, KNOCK_TTL_MS as F, queueReaction as Fa, TURN_LAST_SEQUENCE as Fc, wellKnownUrl as Fi, decodeBolt11 as Fn, presenceSeenAt as Fo, ChatFiles as Fr, readHandoffDone as Fs, checkDidJws as Ft, groupWakeFrame as G, ITEM_STATES as Ga, turnStatedVerifier as Gc, boundVerifiedIdentity as Gi, LIVENESS_PING_MS as Gn, answersOffer as Go, PIN_LIMITS as Gr, setAckFrame as Gs, normalizeDid as Gt, readKnocks as H, readReply as Ha, turnInstance as Hc, IDENTITY_REVOCATION_LABEL as Hi, parseFedimintRequestPayload as Hn, encodeControl as Ho, sanitizeTypingStatus as Hr, SET_ACK as Hs, didWebFileNames as Ht, decodeGroupEntryLink as I, reactionEmoji as Ia, TURN_MAX as Ic, CapsExchange as Ii, ENDPOINT as In, GhostlyHttpError as Io, FILE_LIMITS as Ir, readHandoffHave as Is, checkDidSignature as It, GROUP_LIMITS as J, RUN_RESULTS as Ja, RelayTransport as Jc, identityStatement as Ji, pairedMessageFrame as Jn, callSignalHeardAt as Jo, pinIsNewer as Jr, verifySetUpdate as Js, parseDidSignaturePaste as Jt, groupWakes as K, PR_CHECKS as Ka, turnVerifierMark as Kc, emptyIdentityLedger as Ki, PAIRED_FRAME_TOO_LARGE as Kn, buildSdpFromSignal as Ko, mayPin as Kr, setUpdateFrame as Ks, parseDid as Kt, encodeGroupEntryLink as L, reactionIsNewer as La, firstDeviceSetSecret as Lc, DHT_TEXT_CAPABILITY as Li, cashuRequestPayload as Ln, formatLocalTarget as Lo, transferEnded as Lr, readHandoffHello as Ls, didGhostlyServiceEntry as Lt, communityHasFrame as M, REACTION_LIMITS as Ma, TURN_SETTLE_MS as Mc, recordsFromWellKnown as Mi, pubkyHomeserverOf as Mn, DHT_POLL_INTERVALS as Mo, readBeaconHead as Mr, newHandoffId as Ms, DID_FILE_MAX_BYTES as Mt, decodeCommunityLink as N, ReactionWindow as Na, classifyTurnRead as Nc, recordsName as Ni, requestLnurlInvoice as Nn, EXPECT_PEER_MS as No, readLobby as Nr, readHandoffAck as Ns, DID_JWS_MAX_LENGTH as Nt, sealGroupTyping as O, forwardedAgain as Oa, seedSigner as Oc, isPublicAddress as Oi, pubkyProofCapability as On, verifyCommitSignature as Oo, newerHead as Or, handoffReleaseFrame as Os, shortAtprotoDid as Ot, encodeCommunityLink as P, nextReactionNumber as Pa, turnPutSummary as Pc, txtValue as Pi, resolveLightningDestination as Pn, RELAY_POLL_INTERVALS as Po, shouldBeHub as Pr, readHandoffCancel as Ps, DidError as Pt, frameBytes as Q, cardLine as Qa, traceLink as Qc, lastSharedWithMe as Qi, GROUP_HAVE_FRAME as Qn, emptyLinkRecords as Qo, WakeLimiter as Qr, ENROLL_PROOF as Qs, didDhtDocument as Qt, entryParams as R, readReaction as Ra, nextTurnPosition as Rc, capsDescriptors as Ri, fedimintRequestPayload as Rn, parseLocalTarget as Ro, transferStalled as Rr, readHandoffPake as Rs, didJwsAlg as Rt, readAppBundleAsync as S, parseVideoMeta as Sa, deviceEchoFrame as Sc, DOMAIN_FILE_MAX_BYTES as Si, isBitcoinAddress as Sn, readCanonicalJson as So, doorHubs as Sr, handoffDigest as Ss, atprotoProofRecord as St, GroupTypingBoard as T, RateWindow as Ta, deviceLinkPairing as Tc, domainTxtRecord as Ti, isPubkyProofFolder as Tn, MEMBER_KEY as To, lobbyRecords as Tr, handoffManifestFrames as Ts, isAtprotoDid as Tt, GROUP_WAKE_FRAME as U, replySnippet as Ua, turnKeys as Uc, IdentityCheckUnavailable as Ui, GhostLink as Un, CALL_SIGNAL_MAX_AGE_MS as Uo, typingActivity as Ur, SET_UPDATE as Us, didWebFileUrl as Ut, mergeKnocks as V, pairedReplyAuthor as Va, signTurnRelease as Vc, IDENTITY_MAX_VALIDITY as Vi, parseCashuRequestPayload as Vn, decodeControl as Vo, TypingSender as Vr, sealHandoffPiece as Vs, didWebFile as Vt, GroupWakeLimiter as W, BUTTON_ID as Wa, turnName as Wc, IdentityExchange as Wi, INVITE_TAKEN as Wn, RTC_CONFIG as Wo, GROUP_PIN_FRAME as Wr, readSetUpdate as Ws, didWebHost as Wt, GROUP_READ_NOTE as X, appCardId as Xa, normalizeRelayUrl as Xc, receivedIdentityStatus as Xi, engineText as Xn, parseCallSignal as Xo, readPin as Xr, ENROLL_GRANT as Xs, staticDidDocument as Xt, GROUP_REACTED_FRAME as Y, STATUS_CARD_LIMITS as Ya, currentRelays as Yc, newIdentityBinding as Yi, engineError as Yn, extractParamsFromSdp as Yo, pinNumberHolds as Yr, ENROLL_DONE as Ys, signingKeys as Yt, GroupSession as Z, buttonLabelClash as Za, setLinkTraceSink as Zc, revokesIdentity as Zi, GROUP_FILE_LIMITS as Zn, signalHasVideo as Zo, WAKE_CALL_INTERVAL_MS as Zr, ENROLL_HELLO as Zs, strictBase64Url as Zt, assembleAppBundle as _, VOICE_LIMITS as _a, inviteOwnership as _c, HoldKeys as _i, randomBytes as _l, walletNetworkOf as _n, readAppRevocations as _o, meshHubs as _r, handoffBusyFrame as _s, ATPROTO_DECLARED_SCOPE as _t, planAppUpdate as a, irohRelayUrl as aa, enrollProofFrame as ac, vapidKeysMatch as ai, newerPacket as al, EVM_TEST_CHAINS as an, heardTime as ao, readGroupHave as ar, HANDOFF_HAVE as as, groupName as at, pacer as b, parseVoiceMeta as ba, ENROLL_CAPABILITY as bc, newMailbox as bi, utf8Decode as bl, sparkAddressKind as bn, canonicalJson as bo, beaconKeys as br, handoffContextProof as bs, ATPROTO_PROOF_SCOPE as bt, readAppStore as c, peekDhtMailbox as ca, verifyEnrollProof as cc, isAppVersion as ci, createIdentity as cl, SEPOLIA_TEST_USDT as cn, APP_PREFIXES as co, CarriedTransport as cr, HANDOFF_PAKE as cs, SshSigError as ct, APP_BUNDLE_LIMITS as d, parseImageMeta as da, readDeviceInvite as dc, EDIT_CAPABILITY as di, publicKeyFromZ32 as dl, USDT_PUBLIC_RPC as dn, appRef as do, SIGNAL_HOLD_MS as dr, HANDOFF_TIMINGS as ds, sameSshKey as dt, emptyProofLedger as ea, enrollCancelReason as ec, newWakeToken as ei, isDiscoveryBudgetError as el, encodeDidDhtDeactivated as en, checkStatusCard as eo, GROUP_WANT_NO_FRAME as er, LEGACY_SERVICES as es, GROUP_EDIT_FRAME as et, APP_BUNDLE_MAGIC as f, readImageMeta as fa, OwnInviteError as fc, STATUS_CARD_CAPABILITY as fi, verify as fl, WALLET_NETWORKS as fn, checkAppRevokeStatement as fo, SIGNAL_REPLY_MS as fr, HANDOFF_VERIFIED as fs, sshSignCommand as ft, appViewOf as g, PLAYABLE_AUDIO as ga, inviteLink as gc, HOLD_LIMITS as gi, fromBase64Url as gl, validatePaymentTarget as gn, readAppRevocation as go, mayBeHub as gr, handoffAttemptSucceeded as gs, decodeAnyBitcoinAddress as gt, appDigest as h, sanitizeMime as ha, encodeInviteCode as hc, HOLD_DATED_BACK_MS as hi, bytesEqual as hl, formatPaymentAmount as hn, isAppRef as ho, MESH_HUBS as hr, handoffAttemptFailed as hs, BitcoinAddressError as ht, checkAppBeforeRun as i, irohDescriptor as ia, enrollHelloFrame as ic, checkPushEndpoint as ii, createRelayPayload as il, ETHEREUM_USDT as in, claimedTime as io, readGroupFileMeta as ir, HANDOFF_DONE as is, mentionsMember as it, GROUP_READ_NOTE_COMMUNITY as j, GROUP_REACTION_FRAME as ja, TURN_RAISE_READ_TIMEOUT_MS as jc, recordsFromTxt as ji, homeserverWebEndpoint as jn, AWAITING_PEER_MS as jo, readBeacon as jr, handoffVersions as js, DID_DOCUMENT_MAX_BYTES as jt, COMMUNITY_LIMITS as k, readForwarded as ka, webCryptoSigner as kc, nip05RootKey as ki, pubkyProofFile as kn, edgeParams as ko, pickHubs as kr, handoffRetryAfter as ks, AtprotoProofError as kt, readAppStoreSteps as l, DHT_FILE_CAPABILITY as la, createDeviceInvite as lc, APPS_CAPABILITY as li, identityFromSeed as ll, SEPOLIA_TEST_USDT_FAUCET as ln, APP_STATEMENT_LIMITS as lo, GROUP_SIGNAL_FRAME as lr, HANDOFF_PIECE_BYTES as ls, parseSshPublicKey as lt, APP_PERMISSIONS as m, sanitizeFileName as ma, decodeInviteCode as mc, HOLD_CAPABILITY as mi, sanitizeAvatar as ml, assertWholeSats as mn, isAppKey as mo, readGroupSignal as mr, handoffAttemptAllowed as ms, verifyBitcoinMessage as mt, appStoreDecision as n, TRANSPORTS as na, enrollDoneFrame as nc, relayRequest as ni, PacketTooLargeError as nl, resolveDidDht as nn, statusCardText as no, groupFileFallback as nr, HANDOFF_ACK as ns, carryMentions as nt, APP_STORE_LIMITS as o, DHT_FILE_TOO_LARGE as oa, readEnrollGrant as oc, APP_DATA_MAX_BYTES as oi, openRelayPayload as ol, ONCHAIN_PROVIDER as on, receivedTimestamp as oo, readGroupWant as or, HANDOFF_HELLO as os, SSHSIG_MAX_ARMOR as ot, APP_ICON_PATH as p, formatFileSize as pa, createChatInvite as pc, UPGRADE_CAPABILITY as pi, MAX_AVATAR_LENGTH as pl, assertTokenUnits as pn, isAppHash as po, groupSignalFrame as pr, filePartName as ps, verifySshSignature as pt, parseGroupWakeFrame as q, PR_STATES as qa, DEFAULT_RELAYS as qc, identityRevocationValue as qi, pairedMessageFits as qn, callRtcConfig as qo, nextPinNumber as qr, signTombstone as qs, parseDidDocument as qt, appUpdateDecision as r, automaticTransport as ra, enrollGrantFrame as rc, wakeRequest as ri, RELAY_PAYLOAD_MAX_BYTES as rl, signDidDhtPacket as rn, arrivalKey as ro, isGroupMessageId as rr, HANDOFF_CANCEL as rs, parseCommunityEdit as rt, checkAppStoreIndex as s, DHT_MESSAGE_TTL as sa, readEnrollHello as sc, chatAppId as si, parseRelayPayload as sl, PaymentPreflightError as sn, shownTime as so, readGroupWantNo as sr, HANDOFF_OFFER as ss, SSHSIG_NAMESPACE as st, appRevocationFor as t, validExternalKey as ta, enrollDigits as tc, parseWakeFrame as ti, withRequestOptions as tl, encodeDidDhtPacket as tn, sameButtonText as to, fetchesByItself as tr, serviceIdFromName as ts, GROUP_EDIT_TEXT_BYTES as tt, signAppStore as u, dhtFileId as ua, deviceInviteJoinerParams as uc, BUTTONS_CAPABILITY as ui, identityFromSeedB64 as ul, TEST_USDT_FAUCET_AMOUNT as un, appFingerprint as uo, SIGNAL_FRESH_MS as ur, HANDOFF_REQUEST as us, parseSshSignature as ut, checkAppManifest as v, baseMime as va, ownInviteRefusal as vc, HoldRefusedError as vi, toBase64Url as vl, SPARK_NETWORKS as vn, signAppObject as vo, pickMeshHubs as vr, handoffCancelFrame as vs, ATPROTO_FULL_SCOPE as vt, GROUP_TYPING_FRAME as w, EDIT_RATE_WINDOW_MS as wa, deviceKeyZ32 as wc, dnsProofName as wi, isPubkyKey as wn, MAX_GROUP_CHAIN as wo, lobbyKeys as wr, handoffHelloFrame as ws, checkAtprotoProofRecord as wt, readAppBundle as x, voicePeaksOf as xa, HANDOFF_CAPABILITY as xc, readManifest as xi, utf8Encode as xl, sparkInvoiceDetails as xn, canonicalJsonBytes as xo, beaconRecords as xr, handoffDataFrame as xs, atprotoDidMethod as xt, isAppUrl as y, fileMessageText as ya, DEVICES_CAPABILITY as yc, isHoldUrl as yi, toZ32 as yl, SPARK_PROVIDER as yn, signAppRevocation as yo, COMMUNITY_TOPOLOGY as yr, handoffContext as ys, ATPROTO_PROOF_COLLECTION as yt, knockIdentity as z, validReactionNumber as za, readTurnPacket as zc, dialDescriptors as zi, findEndpoint as zn, webLocalFetch as zo, TYPING_KINDS as zr, readHandoffTurnFrame as zs, didSignedMessages as zt };
|
|
31217
|
+
export { COMMUNITY_EDIT_FRAME as $, buttonLabelClash as $a, currentRelays as $c, revokesIdentity as $i, GROUP_FILE_LIMITS as $n, extractParamsFromSdp as $o, WAKE_CALL_INTERVAL_MS as $r, ENROLL_DONE as $s, didDhtFromPublicKey as $t, CommunitySession as A, forwardedAgain as Aa, devicePingFrame as Ac, isPublicAddress as Ai, pubkyProofFile as An, rosterAdmin as Ao, newerHead as Ar, handoffOfferFrame as As, verifyAtprotoRecordProof as At, knockRecords as B, readReaction as Ba, TURN_MAX as Bc, capsDescriptors as Bi, findEndpoint as Bn, GhostlyHttpError as Bo, transferStalled as Br, readHandoffHave as Bs, didWebDocumentUrl as Bt, sha256Paced as C, parseImageMeta as Ca, DEVICES_CAPABILITY as Cc, readManifest as Ci, randomBytes as Cl, isBitcoinAddress as Cn, canonicalJsonBytes as Co, beaconRecords as Cr, handoffContext as Cs, atprotoProofRkey as Ct, openGroupTyping as D, RateWindow as Da, deviceEchoNonce as Dc, domainTxtRecord as Di, utf8Encode as Dl, newPubkyProofFolder as Dn, MEMBER_KEY as Do, lobbyRecords as Dr, handoffEphemeral as Ds, parseAtprotoDidDocument as Dt, groupTypingEpoch as E, EDIT_RATE_WINDOW_MS as Ea, deviceEchoFrame as Ec, dnsProofName as Ei, utf8Decode as El, isPubkyProofFolder as En, MAX_GROUP_CHAIN as Eo, lobbyKeys as Er, handoffDigest as Es, normalizeAtprotoHandle as Et, KNOCK_TTL_MS as F, ReactionWindow as Fa, TURN_RAISE_READ_TIMEOUT_MS as Fc, recordsName as Fi, resolveLightningDestination as Fn, AWAITING_PEER_MS as Fo, readLobby as Fr, handoffVersions as Fs, checkDidJws as Ft, groupWakeFrame as G, replySnippet as Ga, signTurnRelease as Gc, IdentityCheckUnavailable as Gi, INVITE_TAKEN as Gn, decodeControl as Go, typingActivity as Gr, sealHandoffPiece as Gs, normalizeDid as Gt, readKnocks as H, wireReaction as Ha, nextTurnPosition as Hc, IDENTITY_MAX_EVIDENCE as Hi, parseCashuRequestPayload as Hn, parseLocalTarget as Ho, TYPING_REFRESH_MS as Hr, readHandoffPake as Hs, didWebFileNames as Ht, decodeGroupEntryLink as I, nextReactionNumber as Ia, TURN_SETTLE_MS as Ic, txtValue as Ii, decodeBolt11 as In, DHT_POLL_INTERVALS as Io, shouldBeHub as Ir, newHandoffId as Is, checkDidSignature as It, GROUP_LIMITS as J, PR_CHECKS as Ja, turnName as Jc, emptyIdentityLedger as Ji, pairedMessageFits as Jn, RTC_CONFIG as Jo, mayPin as Jr, readSetUpdate as Js, parseDidSignaturePaste as Jt, groupWakes as K, BUTTON_ID as Ka, turnInstance as Kc, IdentityExchange as Ki, LIVENESS_PING_MS as Kn, encodeControl as Ko, GROUP_PIN_FRAME as Kr, SET_ACK as Ks, parseDid as Kt, encodeGroupEntryLink as L, queueReaction as La, classifyTurnRead as Lc, wellKnownUrl as Li, ENDPOINT as Ln, EXPECT_PEER_MS as Lo, ChatFiles as Lr, readHandoffAck as Ls, didGhostlyServiceEntry as Lt, communityHasFrame as M, COMMUNITY_REACTION_FRAME as Ma, seedSigner as Mc, normalizeDomain as Mi, homeserverWebEndpoint as Mn, verifyCommitSignature as Mo, rankHubs as Mr, handoffReleaseFrame as Ms, DID_FILE_MAX_BYTES as Mt, decodeCommunityLink as N, GROUP_REACTION_FRAME as Na, webCryptoSigner as Nc, recordsFromTxt as Ni, pubkyHomeserverOf as Nn, edgeParams as No, readBeacon as Nr, handoffRetryAfter as Ns, DID_JWS_MAX_LENGTH as Nt, sealGroupTyping as O, dhtEditId as Oa, deviceKeyZ32 as Oc, domainWellKnownFile as Oi, normalizePubkyKey as On, commitHash as Oo, mergeBeacon as Or, handoffHelloFrame as Os, shortAtprotoDid as Ot, encodeCommunityLink as P, REACTION_LIMITS as Pa, TURN_PUT_WINDOW_MS as Pc, recordsFromWellKnown as Pi, requestLnurlInvoice as Pn, DirectPathWatch as Po, readBeaconHead as Pr, handoffStreamKey as Ps, DidError as Pt, frameBytes as Q, appCardId as Qa, RelayTransport as Qc, receivedIdentityStatus as Qi, parseEngineError as Qn, callSignalHeardAt as Qo, readPin as Qr, verifySetUpdate as Qs, didDhtDocument as Qt, entryParams as R, reactionEmoji as Ra, turnPutSummary as Rc, CapsExchange as Ri, cashuRequestPayload as Rn, RELAY_POLL_INTERVALS as Ro, FILE_LIMITS as Rr, readHandoffCancel as Rs, didJwsAlg as Rt, readAppBundleAsync as S, parseVideoMeta as Sa, ownInviteRefusal as Sc, newMailbox as Si, isZ32Key as Sl, sparkInvoiceDetails as Sn, canonicalJson as So, beaconKeys as Sr, handoffCancelFrame as Ss, atprotoProofRecord as St, GroupTypingBoard as T, EDIT_BUFFER_MS as Ta, HANDOFF_CAPABILITY as Tc, decodeDnsResponse as Ti, toZ32 as Tl, isPubkyKey as Tn, GROUP_MEMBER_CAP as To, freshHubs as Tr, handoffDataFrame as Ts, isAtprotoDid as Tt, GROUP_WAKE_FRAME as U, pairedReplyAuthor as Ua, readTurnPacket as Uc, IDENTITY_MAX_VALIDITY as Ui, parseFedimintRequestPayload as Un, webLocalFetch as Uo, TypingSender as Ur, readHandoffTurnFrame as Us, didWebFileUrl as Ut, mergeKnocks as V, validReactionNumber as Va, firstDeviceSetSecret as Vc, dialDescriptors as Vi, isFederationId as Vn, formatLocalTarget as Vo, TYPING_KINDS as Vr, readHandoffHello as Vs, didWebFile as Vt, GroupWakeLimiter as W, readReply as Wa, signTurnPacket as Wc, IDENTITY_REVOCATION_LABEL as Wi, GhostLink as Wn, LIMITS as Wo, sanitizeTypingStatus as Wr, readHandoffVerified as Ws, didWebHost as Wt, GROUP_READ_NOTE as X, RUN_RESULTS as Xa, turnVerifierMark as Xc, identityStatement as Xi, engineError as Xn, buildSdpFromSignal as Xo, pinIsNewer as Xr, setUpdateFrame as Xs, staticDidDocument as Xt, GROUP_REACTED_FRAME as Y, PR_STATES as Ya, turnStatedVerifier as Yc, identityRevocationValue as Yi, pairedMessageFrame as Yn, answersOffer as Yo, nextPinNumber as Yr, setAckFrame as Ys, signingKeys as Yt, GroupSession as Z, STATUS_CARD_LIMITS as Za, DEFAULT_RELAYS as Zc, newIdentityBinding as Zi, engineText as Zn, callRtcConfig as Zo, pinNumberHolds as Zr, signTombstone as Zs, strictBase64Url as Zt, assembleAppBundle as _, VOICE_LIMITS as _a, createChatInvite as _c, HOLD_DATED_BACK_MS as _i, MAX_AVATAR_LENGTH as _l, validatePaymentTarget as _n, isAppRef as _o, MESH_HUBS as _r, filePartName as _s, ATPROTO_DECLARED_SCOPE as _t, planAppUpdate as a, automaticTransport as aa, enrollDigits as ac, wakeRequest as ai, withRequestOptions as al, EVM_TEST_CHAINS as an, arrivalKey as ao, isGroupMessageId as ar, serviceIdFromName as as, groupName as at, pacer as b, parseVoiceMeta as ba, inviteLink as bc, HoldRefusedError as bi, bytesEqual as bl, SPARK_PROVIDER as bn, signAppObject as bo, pickMeshHubs as br, handoffAttemptSucceeded as bs, ATPROTO_PROOF_SCOPE as bt, readAppStore as c, DHT_FILE_TOO_LARGE as ca, enrollHelloFrame as cc, APP_DATA_MAX_BYTES as ci, createRelayPayload as cl, SEPOLIA_TEST_USDT as cn, receivedTimestamp as co, readGroupWant as cr, HANDOFF_DONE as cs, SshSigError as ct, APP_BUNDLE_LIMITS as d, DHT_FILE_CAPABILITY as da, readEnrollHello as dc, APPS_CAPABILITY as di, parseRelayPayload as dl, USDT_PUBLIC_RPC as dn, APP_STATEMENT_LIMITS as do, GROUP_SIGNAL_FRAME as dr, HANDOFF_OFFER as ds, sameSshKey as dt, lastSharedWithMe as ea, ENROLL_GRANT as ec, WakeLimiter as ei, normalizeRelayUrl as el, encodeDidDhtDeactivated as en, cardLine as eo, GROUP_HAVE_FRAME as er, parseCallSignal as es, GROUP_EDIT_FRAME as et, APP_BUNDLE_MAGIC as f, dhtFileId as fa, verifyEnrollProof as fc, BUTTONS_CAPABILITY as fi, createIdentity as fl, WALLET_NETWORKS as fn, appFingerprint as fo, SIGNAL_FRESH_MS as fr, HANDOFF_PAKE as fs, sshSignCommand as ft, appViewOf as g, PLAYABLE_AUDIO as ga, OwnInviteError as gc, HOLD_CAPABILITY as gi, verify as gl, formatPaymentAmount as gn, isAppKey as go, readGroupSignal as gr, HANDOFF_VERIFIED as gs, decodeAnyBitcoinAddress as gt, appDigest as h, sanitizeMime as ha, readDeviceInvite as hc, UPGRADE_CAPABILITY as hi, publicKeyFromZ32 as hl, assertWholeSats as hn, isAppHash as ho, groupSignalFrame as hr, HANDOFF_TIMINGS as hs, BitcoinAddressError as ht, checkAppBeforeRun as i, TRANSPORTS as ia, enrollCancelReason as ic, relayRequest as ii, isDiscoveryBudgetError as il, ETHEREUM_USDT as in, statusCardText as io, groupFileFallback as ir, LEGACY_SERVICES as is, mentionsMember as it, GROUP_READ_NOTE_COMMUNITY as j, readForwarded as ja, newDeviceSetSecret as jc, nip05RootKey as ji, pubkyProofPath as jn, rosterHas as jo, pickHubs as jr, handoffPakeFrame as js, DID_DOCUMENT_MAX_BYTES as jt, COMMUNITY_LIMITS as k, parseLinkPreview as ka, deviceLinkPairing as kc, encodeDnsQuery as ki, pubkyProofCapability as kn, parseGroupCommit as ko, mergeLobby as kr, handoffManifestFrames as ks, AtprotoProofError as kt, readAppStoreSteps as l, DHT_MESSAGE_TTL as la, enrollProofFrame as lc, chatAppId as li, newerPacket as ll, SEPOLIA_TEST_USDT_FAUCET as ln, shownTime as lo, readGroupWantNo as lr, HANDOFF_HAVE as ls, parseSshPublicKey as lt, APP_PERMISSIONS as m, sanitizeFileName as ma, deviceInviteJoinerParams as mc, STATUS_CARD_CAPABILITY as mi, identityFromSeedB64 as ml, assertTokenUnits as mn, checkAppRevokeStatement as mo, SIGNAL_REPLY_MS as mr, HANDOFF_REQUEST as ms, verifyBitcoinMessage as mt, appStoreDecision as n, emptyProofLedger as na, ENROLL_PROOF as nc, newWakeToken as ni, traceLink as nl, resolveDidDht as nn, checkStatusCard as no, GROUP_WANT_NO_FRAME as nr, emptyLinkRecords as ns, carryMentions as nt, APP_STORE_LIMITS as o, irohDescriptor as oa, enrollDoneFrame as oc, checkPushEndpoint as oi, PacketTooLargeError as ol, ONCHAIN_PROVIDER as on, claimedTime as oo, readGroupFileMeta as or, HANDOFF_ACK as os, SSHSIG_MAX_ARMOR as ot, APP_ICON_PATH as p, formatFileSize as pa, createDeviceInvite as pc, EDIT_CAPABILITY as pi, identityFromSeed as pl, arkAddressPrefix as pn, appRef as po, SIGNAL_HOLD_MS as pr, HANDOFF_PIECE_BYTES as ps, verifySshSignature as pt, parseGroupWakeFrame as q, ITEM_STATES as qa, turnKeys as qc, boundVerifiedIdentity as qi, PAIRED_FRAME_TOO_LARGE as qn, CALL_SIGNAL_MAX_AGE_MS as qo, PIN_LIMITS as qr, SET_UPDATE as qs, parseDidDocument as qt, appUpdateDecision as r, validExternalKey as ra, enrollCancelFrame as rc, parseWakeFrame as ri, budgetRetryMs as rl, signDidDhtPacket as rn, sameButtonText as ro, fetchesByItself as rr, HTTP_SERVICE_PROTO as rs, parseCommunityEdit as rt, checkAppStoreIndex as s, irohRelayUrl as sa, enrollGrantFrame as sc, vapidKeysMatch as si, RELAY_PAYLOAD_MAX_BYTES as sl, PaymentPreflightError as sn, heardTime as so, readGroupHave as sr, HANDOFF_CANCEL as ss, SSHSIG_NAMESPACE as st, appRevocationFor as t, PeerProofs as ta, ENROLL_HELLO as tc, deviceWakeRequest as ti, setLinkTraceSink as tl, encodeDidDhtPacket as tn, cardUrl as to, GROUP_WANT_FRAME as tr, signalHasVideo as ts, GROUP_EDIT_TEXT_BYTES as tt, signAppStore as u, peekDhtMailbox as ua, readEnrollGrant as uc, isAppVersion as ui, openRelayPayload as ul, TEST_USDT_FAUCET_AMOUNT as un, APP_PREFIXES as uo, CarriedTransport as ur, HANDOFF_HELLO as us, parseSshSignature as ut, checkAppManifest as v, baseMime as va, decodeInviteCode as vc, HOLD_LIMITS as vi, sanitizeAvatar as vl, walletNetworkOf as vn, readAppRevocation as vo, mayBeHub as vr, handoffAttemptAllowed as vs, ATPROTO_FULL_SCOPE as vt, GROUP_TYPING_FRAME as w, readImageMeta as wa, ENROLL_CAPABILITY as wc, DOMAIN_FILE_MAX_BYTES as wi, toBase64Url as wl, PUBKY_PROOF_MAX_BYTES as wn, readCanonicalJson as wo, doorHubs as wr, handoffContextProof as ws, checkAtprotoProofRecord as wt, readAppBundle as x, voicePeaksOf as xa, inviteOwnership as xc, isHoldUrl as xi, fromBase64Url as xl, sparkAddressKind as xn, signAppRevocation as xo, COMMUNITY_TOPOLOGY as xr, handoffBusyFrame as xs, atprotoDidMethod as xt, isAppUrl as y, fileMessageText as ya, encodeInviteCode as yc, HoldKeys as yi, sanitizeDisplayText as yl, SPARK_NETWORKS as yn, readAppRevocations as yo, meshHubs as yr, handoffAttemptFailed as ys, ATPROTO_PROOF_COLLECTION as yt, knockIdentity as z, reactionIsNewer as za, TURN_LAST_SEQUENCE as zc, DHT_TEXT_CAPABILITY as zi, fedimintRequestPayload as zn, presenceSeenAt as zo, transferEnded as zr, readHandoffDone as zs, didSignedMessages as zt };
|
|
30990
31218
|
|
|
30991
|
-
//# sourceMappingURL=src-
|
|
31219
|
+
//# sourceMappingURL=src-D7gcJElT.js.map
|