@ghostlytools/cli 1.1.5 → 1.1.6
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 +1 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/{endpoint-CaSbSFDw.js → endpoint-D_3bYafG.js} +76 -3
- package/dist/assets/endpoint-D_3bYafG.js.map +1 -0
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
- package/dist/assets/{hyperdhtRelay-w9cwVqGZ.js → hyperdhtRelay-CnEkFl8R.js} +3 -3
- package/dist/assets/{hyperdhtRelay-w9cwVqGZ.js.map → hyperdhtRelay-CnEkFl8R.js.map} +1 -1
- package/dist/assets/idb-BIdus0WX.js.map +1 -1
- package/dist/assets/{mainline-BabMhm8L.js → mainline-DQxDSWXm.js} +2 -2
- package/dist/assets/{mainline-BabMhm8L.js.map → mainline-DQxDSWXm.js.map} +1 -1
- package/dist/assets/{server-TamKsfmC.js → server-HMzdsyn_.js} +324 -53
- package/dist/assets/server-HMzdsyn_.js.map +1 -0
- package/dist/assets/{src-C6qQYrAH.js → src-DtxVo2Rm.js} +589 -69
- package/dist/assets/src-DtxVo2Rm.js.map +1 -0
- package/dist/ghostly.mjs +1068 -49
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/assets/endpoint-CaSbSFDw.js.map +0 -1
- package/dist/assets/server-TamKsfmC.js.map +0 -1
- package/dist/assets/src-C6qQYrAH.js.map +0 -1
|
@@ -897,6 +897,8 @@ function normalizeRelayUrl(input) {
|
|
|
897
897
|
return null;
|
|
898
898
|
}
|
|
899
899
|
}
|
|
900
|
+
/** A relay the breaker leaves alone for failing (`RelayBreaker`): nothing was asked of it now. */
|
|
901
|
+
var RelayLeftAloneError = class extends Error {};
|
|
900
902
|
var RelayTransport = class {
|
|
901
903
|
relays;
|
|
902
904
|
timeoutMs;
|
|
@@ -1185,8 +1187,11 @@ var RelayTransport = class {
|
|
|
1185
1187
|
return;
|
|
1186
1188
|
}
|
|
1187
1189
|
const held = results.map((r) => r.status === "rejected" && isDiscoveryBudgetError(r.reason) ? r.reason : null);
|
|
1190
|
+
const leftAlone = (r) => r.status === "rejected" && r.reason instanceof RelayLeftAloneError;
|
|
1191
|
+
const waits = held.filter((e) => e !== null);
|
|
1188
1192
|
const reasons = results.map((r) => r.status === "rejected" ? String(r.reason) : "").join("; ");
|
|
1189
|
-
if (
|
|
1193
|
+
if (waits.length === results.length) reject(new DiscoveryBudgetError(Math.min(...waits.map((e) => e.retryInMs)), `Publish held back on every relay: ${reasons}`));
|
|
1194
|
+
else if (waits.length && results.every((r, i) => held[i] !== null || leftAlone(r))) reject(new DiscoveryBudgetError(Math.min(...waits.map((e) => e.retryInMs)), `Publish held back on every relay not left alone: ${reasons}`));
|
|
1190
1195
|
else reject(/* @__PURE__ */ new Error(`Publish failed on every relay: ${reasons}`));
|
|
1191
1196
|
});
|
|
1192
1197
|
});
|
|
@@ -1484,7 +1489,7 @@ var RelayTransport = class {
|
|
|
1484
1489
|
const blocked = probe ? 0 : this.breaker.blockedFor(relay);
|
|
1485
1490
|
if (blocked > 0) {
|
|
1486
1491
|
if (this.breaker.blockedKind(relay) === "throttled") throw new DiscoveryBudgetError(blocked, "Discovery relay is throttling this address; retry shortly");
|
|
1487
|
-
throw new
|
|
1492
|
+
throw new RelayLeftAloneError(`${relay} is left alone after failing; asked again in ${Math.max(1, Math.ceil(this.breaker.askedAgainIn(relay, this.relays) / 1e3))} s`);
|
|
1488
1493
|
}
|
|
1489
1494
|
if (!this.take(relay, who, pubKeyZ32)) throw new DiscoveryBudgetError(this.heldFor(relay, who));
|
|
1490
1495
|
if (probe) this.breaker.beginAllDown(relay);
|
|
@@ -3358,7 +3363,7 @@ var HANDOFF_CANCEL_REASONS = [
|
|
|
3358
3363
|
"stalled"
|
|
3359
3364
|
];
|
|
3360
3365
|
var KEY$3 = /^[A-Za-z0-9_-]{43}$/;
|
|
3361
|
-
var DIGEST$
|
|
3366
|
+
var DIGEST$2 = KEY$3;
|
|
3362
3367
|
var SIGNATURE = /^[A-Za-z0-9_-]{86}$/;
|
|
3363
3368
|
var ID$5 = /^[A-Za-z0-9_-]{22}$/;
|
|
3364
3369
|
var B64$4 = /^[A-Za-z0-9_-]*$/;
|
|
@@ -3374,9 +3379,9 @@ var isHandoffPartName = (name) => typeof name === "string" && PART.test(name);
|
|
|
3374
3379
|
/** The part a file is: `file/<its SHA-256, base64url>`. */
|
|
3375
3380
|
var filePartName = (sha256B64) => `file/${sha256B64}`;
|
|
3376
3381
|
/** The digest a file part names, or null for any other part. */
|
|
3377
|
-
var filePartDigest = (name) => name.startsWith("file/") && DIGEST$
|
|
3382
|
+
var filePartDigest = (name) => name.startsWith("file/") && DIGEST$2.test(name.slice(5)) ? name.slice(5) : null;
|
|
3378
3383
|
function isPart(value) {
|
|
3379
|
-
return Array.isArray(value) && value.length === 3 && isHandoffPartName(value[0]) && isCount$1(value[1], MAX_PART) && isText$2(value[2], DIGEST$
|
|
3384
|
+
return Array.isArray(value) && value.length === 3 && isHandoffPartName(value[0]) && isCount$1(value[1], MAX_PART) && isText$2(value[2], DIGEST$2) && (filePartDigest(value[0]) === null || filePartDigest(value[0]) === value[2]);
|
|
3380
3385
|
}
|
|
3381
3386
|
/** Names compared as UTF-8 byte strings, as `H` sorts them. */
|
|
3382
3387
|
function compareNames(a, b) {
|
|
@@ -3567,7 +3572,7 @@ function readHandoffPake(frame) {
|
|
|
3567
3572
|
};
|
|
3568
3573
|
}
|
|
3569
3574
|
function readHandoffHave(frame) {
|
|
3570
|
-
if (frame.t !== "handoff-have" || !Array.isArray(frame.d) || frame.d.length > 300 || !frame.d.every((d) => isText$2(d, DIGEST$
|
|
3575
|
+
if (frame.t !== "handoff-have" || !Array.isArray(frame.d) || frame.d.length > 300 || !frame.d.every((d) => isText$2(d, DIGEST$2))) return null;
|
|
3571
3576
|
const p = {};
|
|
3572
3577
|
if (frame.p !== void 0) {
|
|
3573
3578
|
if (!frame.p || typeof frame.p !== "object" || Array.isArray(frame.p) || Object.keys(frame.p).length > 300) return null;
|
|
@@ -3623,7 +3628,7 @@ function readHandoffAck(frame) {
|
|
|
3623
3628
|
}
|
|
3624
3629
|
/** `H` of a `handoff-verified` that B's key signed for this turn address and turn, or null. */
|
|
3625
3630
|
function readHandoffVerified(frame, turnAddress, turn, takerKey) {
|
|
3626
|
-
if (frame.t !== "handoff-verified" || !isText$2(frame.h, DIGEST$
|
|
3631
|
+
if (frame.t !== "handoff-verified" || !isText$2(frame.h, DIGEST$2) || !isText$2(frame.s, SIGNATURE)) return null;
|
|
3627
3632
|
const h = fromBase64Url(frame.h);
|
|
3628
3633
|
try {
|
|
3629
3634
|
return verify(fromBase64Url(frame.s), handoffVerifiedMessage(turnAddress, turn, h), takerKey) ? h : null;
|
|
@@ -7159,7 +7164,13 @@ var STATUS_CARD_LIMITS = {
|
|
|
7159
7164
|
buttonLabel: 40,
|
|
7160
7165
|
/** An app card's title and version, in characters (WISP 405 § An app): the manifest's bounds. */
|
|
7161
7166
|
appTitle: 40,
|
|
7162
|
-
appVersion: 32
|
|
7167
|
+
appVersion: 32,
|
|
7168
|
+
/** A usage card's label ("Claude") and account, in characters (WISP 405 § Usage). */
|
|
7169
|
+
usageLabel: 24,
|
|
7170
|
+
/** A usage window's name ("5 h", "week"), in characters. */
|
|
7171
|
+
usageWindow: 16,
|
|
7172
|
+
/** Windows beside a usage card's own, shown in its details. */
|
|
7173
|
+
usageWindows: 3
|
|
7163
7174
|
};
|
|
7164
7175
|
/** How a button looks: `primary` is the one highlighted, `danger` warns; `neutral` when none is said. */
|
|
7165
7176
|
var BUTTON_STYLES = [
|
|
@@ -7311,6 +7322,52 @@ function readRun(raw, now) {
|
|
|
7311
7322
|
...summary && { summary }
|
|
7312
7323
|
};
|
|
7313
7324
|
}
|
|
7325
|
+
/** A percent as a reader keeps it: a finite number, rounded and clamped into 0 to 100. */
|
|
7326
|
+
var percent = (value) => typeof value === "number" && Number.isFinite(value) ? Math.round(Math.min(100, Math.max(0, value))) : void 0;
|
|
7327
|
+
/** A time ahead of the reader (a reset, a next run) as it keeps it: whole, positive, at most a year ahead. */
|
|
7328
|
+
var ahead = (value, now) => typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= now + STATUS_CARD_LIMITS.nextRunMs ? value : void 0;
|
|
7329
|
+
/** A usage card as a reader keeps it (WISP 405 § Usage): dropped without a percent left or a used of limit. */
|
|
7330
|
+
function readUsage(raw, id, now) {
|
|
7331
|
+
const left = percent(raw.left);
|
|
7332
|
+
let used = count(raw.used), limit = count(raw.limit, 1);
|
|
7333
|
+
if (used === void 0 || limit === void 0 || used > limit) used = limit = void 0;
|
|
7334
|
+
if (left === void 0 && limit === void 0) return void 0;
|
|
7335
|
+
const label = cardLine(raw.label, STATUS_CARD_LIMITS.usageLabel), account = cardLine(raw.account, STATUS_CARD_LIMITS.usageLabel);
|
|
7336
|
+
const window = cardLine(raw.window, STATUS_CARD_LIMITS.usageWindow);
|
|
7337
|
+
const resetsAt = ahead(raw.resetsAt, now), updatedAt = time(raw.updatedAt, now + MESSAGE_CLOCK_SKEW_MS);
|
|
7338
|
+
const windows = [];
|
|
7339
|
+
if (Array.isArray(raw.windows)) for (const entry of raw.windows.slice(0, STATUS_CARD_LIMITS.usageWindows)) {
|
|
7340
|
+
if (!isObject$4(entry)) continue;
|
|
7341
|
+
const name = cardLine(entry.window, STATUS_CARD_LIMITS.usageWindow), share = percent(entry.left);
|
|
7342
|
+
if (!name || share === void 0) continue;
|
|
7343
|
+
const at = ahead(entry.resetsAt, now);
|
|
7344
|
+
windows.push({
|
|
7345
|
+
window: name,
|
|
7346
|
+
left: share,
|
|
7347
|
+
...at && { resetsAt: at }
|
|
7348
|
+
});
|
|
7349
|
+
}
|
|
7350
|
+
return {
|
|
7351
|
+
kind: "usage",
|
|
7352
|
+
id,
|
|
7353
|
+
...left !== void 0 && { left },
|
|
7354
|
+
...used !== void 0 && limit !== void 0 && {
|
|
7355
|
+
used,
|
|
7356
|
+
limit
|
|
7357
|
+
},
|
|
7358
|
+
...label && { label },
|
|
7359
|
+
...account && { account },
|
|
7360
|
+
...window && { window },
|
|
7361
|
+
...resetsAt && { resetsAt },
|
|
7362
|
+
...updatedAt && { updatedAt },
|
|
7363
|
+
...windows.length && { windows }
|
|
7364
|
+
};
|
|
7365
|
+
}
|
|
7366
|
+
/** How much of a usage card's quota is left, 0 to 100: its own percent, else worked out from used of limit. */
|
|
7367
|
+
function usageLeft(card) {
|
|
7368
|
+
if (card.left !== void 0) return card.left;
|
|
7369
|
+
return card.used !== void 0 && card.limit ? Math.round((card.limit - card.used) / card.limit * 100) : 0;
|
|
7370
|
+
}
|
|
7314
7371
|
/** The bytes a card takes on the wire: what `STATUS_CARD_LIMITS.bytes` bounds. */
|
|
7315
7372
|
function statusCardBytes(card) {
|
|
7316
7373
|
try {
|
|
@@ -7347,6 +7404,7 @@ function readStatusCard(raw, now = Date.now()) {
|
|
|
7347
7404
|
};
|
|
7348
7405
|
}
|
|
7349
7406
|
const latest = now + MESSAGE_CLOCK_SKEW_MS;
|
|
7407
|
+
if (raw.kind === "usage") return readUsage(raw, id, now);
|
|
7350
7408
|
const links = readLinks(raw.links);
|
|
7351
7409
|
if (raw.kind === "task") {
|
|
7352
7410
|
const title = cardLine(raw.title, STATUS_CARD_LIMITS.title), status = oneOf(TASK_STATUSES, raw.status);
|
|
@@ -7465,7 +7523,7 @@ function checkStatusCard(raw, now = Date.now()) {
|
|
|
7465
7523
|
if (!isObject$4(raw)) return { error: "A card is an object" };
|
|
7466
7524
|
const bytes = statusCardBytes(raw);
|
|
7467
7525
|
if (bytes > STATUS_CARD_LIMITS.bytes) return { error: `The card takes ${bytes} bytes; at most ${STATUS_CARD_LIMITS.bytes}` };
|
|
7468
|
-
if (raw.kind !== "task" && raw.kind !== "routine" && raw.kind !== "buttons" && raw.kind !== "app") return { error: "kind is task, routine, buttons or
|
|
7526
|
+
if (raw.kind !== "task" && raw.kind !== "routine" && raw.kind !== "buttons" && raw.kind !== "app" && raw.kind !== "usage") return { error: "kind is task, routine, buttons, app or usage" };
|
|
7469
7527
|
if (typeof raw.id !== "string" || !ID$4.test(raw.id)) return { error: `id is 1 to ${STATUS_CARD_LIMITS.id} of A-Z a-z 0-9 _ . : - and does not start with -` };
|
|
7470
7528
|
const line = (field, value, max, required = false) => {
|
|
7471
7529
|
if (value === void 0 && !required) return null;
|
|
@@ -7486,7 +7544,31 @@ function checkStatusCard(raw, now = Date.now()) {
|
|
|
7486
7544
|
errors.push(url(`links[${i}].url`, link.url), line(`links[${i}].label`, link.label, STATUS_CARD_LIMITS.linkLabel));
|
|
7487
7545
|
}
|
|
7488
7546
|
}
|
|
7489
|
-
|
|
7547
|
+
const share = (field, value) => value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100 ? null : `${field} is a number from 0 to 100`;
|
|
7548
|
+
const reset = (field, value) => value === void 0 || ahead(value, now) !== void 0 ? null : `${field} is a time in milliseconds, within a year`;
|
|
7549
|
+
if (raw.kind === "usage") {
|
|
7550
|
+
if (raw.links !== void 0) errors.push("a usage card takes no links");
|
|
7551
|
+
if (raw.left === void 0 && raw.used === void 0) errors.push("left (a percent) or used and limit");
|
|
7552
|
+
errors.push(share("left", raw.left));
|
|
7553
|
+
if (raw.used === void 0 !== (raw.limit === void 0)) errors.push("used and limit go together");
|
|
7554
|
+
else if (raw.used !== void 0) {
|
|
7555
|
+
errors.push(whole("used", raw.used), whole("limit", raw.limit, 1));
|
|
7556
|
+
if (typeof raw.used === "number" && typeof raw.limit === "number" && raw.used > raw.limit) errors.push("used is at most limit");
|
|
7557
|
+
}
|
|
7558
|
+
errors.push(line("label", raw.label, STATUS_CARD_LIMITS.usageLabel), line("account", raw.account, STATUS_CARD_LIMITS.usageLabel), line("window", raw.window, STATUS_CARD_LIMITS.usageWindow), reset("resetsAt", raw.resetsAt));
|
|
7559
|
+
if (raw.updatedAt !== void 0 && !(typeof raw.updatedAt === "number" && Number.isSafeInteger(raw.updatedAt) && raw.updatedAt > 0 && raw.updatedAt <= now + 3e5)) errors.push("updatedAt is a time in milliseconds, not in the future");
|
|
7560
|
+
if (raw.windows !== void 0) {
|
|
7561
|
+
if (!Array.isArray(raw.windows) || raw.windows.length > STATUS_CARD_LIMITS.usageWindows) errors.push(`windows is a list of at most ${STATUS_CARD_LIMITS.usageWindows}`);
|
|
7562
|
+
else for (const [i, entry] of raw.windows.entries()) {
|
|
7563
|
+
if (!isObject$4(entry)) {
|
|
7564
|
+
errors.push(`windows[${i}] is an object`);
|
|
7565
|
+
continue;
|
|
7566
|
+
}
|
|
7567
|
+
errors.push(line(`windows[${i}].window`, entry.window, STATUS_CARD_LIMITS.usageWindow, true));
|
|
7568
|
+
errors.push(entry.left === void 0 ? `windows[${i}].left is a number from 0 to 100` : share(`windows[${i}].left`, entry.left), reset(`windows[${i}].resetsAt`, entry.resetsAt));
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
} else if (raw.kind === "app") {
|
|
7490
7572
|
if (raw.links !== void 0) errors.push("an app card takes no links");
|
|
7491
7573
|
if (!isAppRef(raw.ref)) errors.push("ref is <publisher key in z-base32>/<name>");
|
|
7492
7574
|
else if (raw.id !== appCardId(raw.ref)) errors.push(`id is ${appCardId(raw.ref)}, made from ref`);
|
|
@@ -7629,6 +7711,17 @@ function statusCardText(card) {
|
|
|
7629
7711
|
const named = `${card.title}${card.version ? ` ${card.version}` : ""}`;
|
|
7630
7712
|
return [card.opened ? `🧩 Opened ${named} in this chat (Ghostly app)` : `🧩 ${named} (Ghostly app)`, ...card.url ? [card.url] : []].join("\n");
|
|
7631
7713
|
}
|
|
7714
|
+
if (card.kind === "usage") {
|
|
7715
|
+
const left = `${usageLeft(card)}% left${card.window ? ` (${card.window})` : ""}`;
|
|
7716
|
+
return [
|
|
7717
|
+
`📊 ${card.label ?? "Usage"}`,
|
|
7718
|
+
...card.account ? [card.account] : [],
|
|
7719
|
+
left,
|
|
7720
|
+
...card.used !== void 0 && card.limit !== void 0 ? [`${card.used} of ${card.limit} used`] : [],
|
|
7721
|
+
...card.resetsAt ? [`resets ${cardTimeUtc(card.resetsAt)}`] : [],
|
|
7722
|
+
...(card.windows ?? []).map((w) => `${w.window} ${w.left}%`)
|
|
7723
|
+
].join(" · ");
|
|
7724
|
+
}
|
|
7632
7725
|
if (card.kind === "buttons") return `Reply: ${card.buttons.map((b) => b.label).join(" / ")}`;
|
|
7633
7726
|
if (card.kind === "task") {
|
|
7634
7727
|
const progress = taskProgress(card);
|
|
@@ -10171,6 +10264,12 @@ var TransportSwitch = class {
|
|
|
10171
10264
|
preparing = false;
|
|
10172
10265
|
/** The coordinator's dial for the pending plan has started (`dialing`): from then on a newer choice waits for it. */
|
|
10173
10266
|
dialStarted = false;
|
|
10267
|
+
/**
|
|
10268
|
+
* The contact's `paired-policy` says `replaces`: its app reads a `paired-switch-keep` for a plan a newer choice
|
|
10269
|
+
* replaced as that (`supersede`). An app before 1.1.5 reads it as its target missed and says the chat was kept on its
|
|
10270
|
+
* transport; 1.1.5 reads it right but does not say so.
|
|
10271
|
+
*/
|
|
10272
|
+
remoteReplaces = false;
|
|
10174
10273
|
/** This side chose while no session was open: the next one begins with it as a choice. */
|
|
10175
10274
|
apart = false;
|
|
10176
10275
|
constructor(options) {
|
|
@@ -10226,10 +10325,12 @@ var TransportSwitch = class {
|
|
|
10226
10325
|
context: this.context
|
|
10227
10326
|
});
|
|
10228
10327
|
}
|
|
10328
|
+
/** Older apps read the policy's own fields only, and drop `replaces`. */
|
|
10229
10329
|
announce() {
|
|
10230
10330
|
this.send({
|
|
10231
10331
|
t: "paired-policy",
|
|
10232
|
-
policy: this.local()
|
|
10332
|
+
policy: this.local(),
|
|
10333
|
+
replaces: true
|
|
10233
10334
|
});
|
|
10234
10335
|
}
|
|
10235
10336
|
/**
|
|
@@ -10246,6 +10347,7 @@ var TransportSwitch = class {
|
|
|
10246
10347
|
if (!migrated) {
|
|
10247
10348
|
this.failed = "";
|
|
10248
10349
|
this.remote = null;
|
|
10350
|
+
this.remoteReplaces = false;
|
|
10249
10351
|
}
|
|
10250
10352
|
if (this.apart) {
|
|
10251
10353
|
this.intent = Math.max(this.intent, this.lastRemote?.intent ?? 0) + 1;
|
|
@@ -10330,6 +10432,7 @@ var TransportSwitch = class {
|
|
|
10330
10432
|
this.clearPlan();
|
|
10331
10433
|
this.context = "";
|
|
10332
10434
|
this.remote = null;
|
|
10435
|
+
this.remoteReplaces = false;
|
|
10333
10436
|
}
|
|
10334
10437
|
clearPlan() {
|
|
10335
10438
|
if (this.timer) clearTimeout(this.timer);
|
|
@@ -10347,11 +10450,12 @@ var TransportSwitch = class {
|
|
|
10347
10450
|
* move back to WebRTC waits for the WebRTC session it left to retire (`SWITCH_RETIRE_MS`), and nothing has been tried
|
|
10348
10451
|
* yet. The coordinator drops that plan, so the chat does not move there only to move again; the other side is told
|
|
10349
10452
|
* with `paired-switch-keep`, and whatever the newer policies call for is planned next. A plan already dialling goes
|
|
10350
|
-
* on: a choice made then is planned once it ended (`replanIfMoved`).
|
|
10453
|
+
* on: a choice made then is planned once it ended (`replanIfMoved`). So does a plan the contact's app would take
|
|
10454
|
+
* that keep for a target missed (its policy did not say `replaces`): the newer choice is planned once it landed.
|
|
10351
10455
|
*/
|
|
10352
10456
|
supersede() {
|
|
10353
10457
|
const plan = this.plan;
|
|
10354
|
-
if (!plan || !this.preparing || this.dialStarted || !this.context || !this.remote || this.options.key > this.options.peerKey) return;
|
|
10458
|
+
if (!plan || !this.preparing || this.dialStarted || !this.context || !this.remote || this.options.key > this.options.peerKey || !this.remoteReplaces) return;
|
|
10355
10459
|
if (this.choices(this.local(), this.remote)[0] === plan.choices[0]) return;
|
|
10356
10460
|
this.send({
|
|
10357
10461
|
t: "paired-switch-keep",
|
|
@@ -10481,6 +10585,7 @@ var TransportSwitch = class {
|
|
|
10481
10585
|
if (!policy || this.remote && (policy.revision < this.remote.revision || policy.revision === this.remote.revision && JSON.stringify(policy) !== JSON.stringify(this.remote))) return true;
|
|
10482
10586
|
const changed = !this.remote || policy.revision !== this.remote.revision;
|
|
10483
10587
|
this.remote = this.lastRemote = policy;
|
|
10588
|
+
this.remoteReplaces = frame.replaces === true;
|
|
10484
10589
|
this.options.peer(policy);
|
|
10485
10590
|
if (changed && this.plan && !this.preparing) this.clearPlan();
|
|
10486
10591
|
else if (changed) this.supersede();
|
|
@@ -12567,6 +12672,18 @@ function readManifest(meta) {
|
|
|
12567
12672
|
*/
|
|
12568
12673
|
var OFFER_EXTENSIONS = ["ping/1"];
|
|
12569
12674
|
var MAX_HANDSHAKE_BYTES = 4096;
|
|
12675
|
+
/**
|
|
12676
|
+
* Frames received and not handled yet, at most: their count and their size together. Frames are handled one at a time,
|
|
12677
|
+
* each awaited (a group frame is decrypted, checked and stored before the next), and the peer sends at the network's
|
|
12678
|
+
* pace. A member back in a group answers a sync with a burst: a commit per epoch, its kept messages, the ones it hands
|
|
12679
|
+
* on, edits, reactions. With a bound of 64 frames a phone handling them a few milliseconds each failed its session
|
|
12680
|
+
* mid catch-up ("Session receive limit exceeded" then, "Session receive queue full" now, 2026-10-07). The size bound keeps the old worst case in memory
|
|
12681
|
+
* (64 frames of 60 KiB); the count only bounds a flood of tiny frames.
|
|
12682
|
+
*/
|
|
12683
|
+
var SESSION_RECEIVE_PENDING = {
|
|
12684
|
+
frames: 1024,
|
|
12685
|
+
bytes: 3932160
|
|
12686
|
+
};
|
|
12570
12687
|
var KEY = /^[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/;
|
|
12571
12688
|
var NONCE$1 = /^[A-Za-z0-9_-]{43}$/;
|
|
12572
12689
|
var SIG$2 = /^[A-Za-z0-9_-]{86}$/;
|
|
@@ -12598,6 +12715,11 @@ var PairedSession = class {
|
|
|
12598
12715
|
state = { status: "negotiating" };
|
|
12599
12716
|
/** The peer said nothing more within `authTimeoutMs`: the connection carried nothing, which proves nothing about the peer. */
|
|
12600
12717
|
authTimedOut = false;
|
|
12718
|
+
/**
|
|
12719
|
+
* The peer sent faster than this side handles, past `SESSION_RECEIVE_PENDING`: the session ends, which is no proof
|
|
12720
|
+
* of anything wrong with the peer (a burst of catch-up on a slow device), so the chat dials again.
|
|
12721
|
+
*/
|
|
12722
|
+
overloaded = false;
|
|
12601
12723
|
/** The seed's identity; null when a signer signs. */
|
|
12602
12724
|
identity;
|
|
12603
12725
|
signer;
|
|
@@ -12614,6 +12736,7 @@ var PairedSession = class {
|
|
|
12614
12736
|
confirming = false;
|
|
12615
12737
|
admission = null;
|
|
12616
12738
|
pending = 0;
|
|
12739
|
+
pendingBytes = 0;
|
|
12617
12740
|
queue = Promise.resolve();
|
|
12618
12741
|
timer = null;
|
|
12619
12742
|
constructor(channel, options) {
|
|
@@ -12701,7 +12824,12 @@ var PairedSession = class {
|
|
|
12701
12824
|
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) => !KEY.test(k))) return this.fail("Invalid connection binding or unavailable transport");
|
|
12702
12825
|
this.channel.onMessage = (data) => {
|
|
12703
12826
|
if (this.stopped) return;
|
|
12704
|
-
|
|
12827
|
+
const size = data.length;
|
|
12828
|
+
if (size > 61440) return this.fail("Session frame too large");
|
|
12829
|
+
if (++this.pending > SESSION_RECEIVE_PENDING.frames || (this.pendingBytes += size) > SESSION_RECEIVE_PENDING.bytes) {
|
|
12830
|
+
this.overloaded = true;
|
|
12831
|
+
return this.fail("Session receive queue full");
|
|
12832
|
+
}
|
|
12705
12833
|
this.queue = this.queue.then(async () => {
|
|
12706
12834
|
if (this.stopped) return;
|
|
12707
12835
|
if (typeof data === "string" && data.startsWith("{\"t\":\"pair-")) {
|
|
@@ -12710,6 +12838,7 @@ var PairedSession = class {
|
|
|
12710
12838
|
} else if (this.state.status === "ready") await this.options.onApplication(data);
|
|
12711
12839
|
}).catch(() => this.fail("Invalid session negotiation")).finally(() => {
|
|
12712
12840
|
this.pending--;
|
|
12841
|
+
this.pendingBytes -= size;
|
|
12713
12842
|
});
|
|
12714
12843
|
};
|
|
12715
12844
|
this.timer = setTimeout(() => {
|
|
@@ -13950,7 +14079,7 @@ var FILE_LIMITS = {
|
|
|
13950
14079
|
sendRetryMs: 250
|
|
13951
14080
|
};
|
|
13952
14081
|
var ID = /^[A-Za-z0-9_-]{8,64}$/;
|
|
13953
|
-
var DIGEST = /^[A-Za-z0-9_-]{43}$/;
|
|
14082
|
+
var DIGEST$1 = /^[A-Za-z0-9_-]{43}$/;
|
|
13954
14083
|
var DATA = /^[A-Za-z0-9_-]+$/;
|
|
13955
14084
|
var FILE_FRAMES = /* @__PURE__ */ new Set([
|
|
13956
14085
|
"pf-offer",
|
|
@@ -14694,7 +14823,7 @@ var ChatFiles = class {
|
|
|
14694
14823
|
return;
|
|
14695
14824
|
}
|
|
14696
14825
|
if (record.state !== "active" || incoming.verifying || !incoming.target) return;
|
|
14697
|
-
if (frame.size !== record.file.size || typeof frame.digest !== "string" || !DIGEST.test(frame.digest)) return;
|
|
14826
|
+
if (frame.size !== record.file.size || typeof frame.digest !== "string" || !DIGEST$1.test(frame.digest)) return;
|
|
14698
14827
|
const digest = frame.digest;
|
|
14699
14828
|
await incoming.writing;
|
|
14700
14829
|
if (transferEnded(record) || record.state !== "active") return;
|
|
@@ -15707,6 +15836,142 @@ var CarriedTransport = class {
|
|
|
15707
15836
|
}
|
|
15708
15837
|
};
|
|
15709
15838
|
//#endregion
|
|
15839
|
+
//#region ../core/src/groupFiles.ts
|
|
15840
|
+
/**
|
|
15841
|
+
* Files and voice messages in groups (WISP 503 · Group Files). A file is announced as a group message carrying its
|
|
15842
|
+
* description; its bytes go over `files/3` between two members, from a member that holds it to one that asks for it.
|
|
15843
|
+
* The limits below are the profile's first numbers, kept in one place so they are easy to change.
|
|
15844
|
+
*/
|
|
15845
|
+
var GROUP_FILE_LIMITS = {
|
|
15846
|
+
/** The largest file a group takes. */
|
|
15847
|
+
maxBytes: 104857600,
|
|
15848
|
+
/** A file this size or smaller is fetched without asking (voice messages always are, up to `VOICE_LIMITS`). */
|
|
15849
|
+
autoBytes: 8388608,
|
|
15850
|
+
/** What a device keeps of a group's files it fetched without asking; past it, every file is asked about. */
|
|
15851
|
+
autoBytesPerGroup: 268435456,
|
|
15852
|
+
/** Bytes of group files a device serves to others in a day, when serving is on. */
|
|
15853
|
+
serveBytesPerDay: 1073741824,
|
|
15854
|
+
/** Transfers a holder serves at once, all groups together. */
|
|
15855
|
+
serveAtOnce: 3,
|
|
15856
|
+
/** File announcements a member sends to one group in a minute. */
|
|
15857
|
+
announcePerMinute: 8,
|
|
15858
|
+
/** File announcements taken from one member of a group in `announceWindowMs`; past it, shown as text, unfetched. */
|
|
15859
|
+
announceTaken: 30,
|
|
15860
|
+
announceWindowMs: 6e5,
|
|
15861
|
+
/** Holders remembered per file. */
|
|
15862
|
+
holders: 8,
|
|
15863
|
+
/**
|
|
15864
|
+
* Message ids a `group-have` named before their message came (a member back from away hears who holds what as its
|
|
15865
|
+
* catch-up begins), remembered with their holders for when it does.
|
|
15866
|
+
*/
|
|
15867
|
+
heardAhead: 256,
|
|
15868
|
+
/** Message ids in one `group-have`. */
|
|
15869
|
+
haveIds: 32,
|
|
15870
|
+
/** A private group's `group-have` goes on an edge at most this often; a community's, from a member, every `communityHaveEveryMs`. */
|
|
15871
|
+
haveEveryMs: 1e4,
|
|
15872
|
+
communityHaveEveryMs: 6e4,
|
|
15873
|
+
/**
|
|
15874
|
+
* How long a want waits for an offer before the next holder is asked; a transfer that does not move for as long is
|
|
15875
|
+
* left for the next holder too.
|
|
15876
|
+
*/
|
|
15877
|
+
wantWaitMs: 3e4,
|
|
15878
|
+
/** A file session closes this long after its last transfer ends. */
|
|
15879
|
+
sessionIdleMs: 6e4,
|
|
15880
|
+
/** File sessions opening at once, and open at once, all groups together. */
|
|
15881
|
+
sessionsOpening: 2,
|
|
15882
|
+
sessionsOpen: 4,
|
|
15883
|
+
/** The longest file name and type kept from a description. */
|
|
15884
|
+
nameChars: 255,
|
|
15885
|
+
mimeChars: 255
|
|
15886
|
+
};
|
|
15887
|
+
/**
|
|
15888
|
+
* The frames of WISP 503 § Getting the bytes, on an edge between two apps that announced `GROUP_VERSION_FILES`:
|
|
15889
|
+
* `group-want {g, id}` asks a holder for the file of message `id`; `group-want-no {g, id, why}` is a holder's no;
|
|
15890
|
+
* `group-have {g, ids}` says which files a member keeps (in a community, an application frame `{t, ids}` instead).
|
|
15891
|
+
*/
|
|
15892
|
+
var GROUP_WANT_FRAME = "group-want";
|
|
15893
|
+
var GROUP_WANT_NO_FRAME = "group-want-no";
|
|
15894
|
+
var GROUP_HAVE_FRAME = "group-have";
|
|
15895
|
+
var REFUSALS = [
|
|
15896
|
+
"gone",
|
|
15897
|
+
"busy",
|
|
15898
|
+
"refused"
|
|
15899
|
+
];
|
|
15900
|
+
/** A group message's id, as a private group (`<s>:<e>:<n>`) or a community (`<s>:<e>:<h>:<n>`) names it. */
|
|
15901
|
+
var MESSAGE_ID = /^[a-z0-9]{52}:\d{1,15}(:[A-Za-z0-9_-]{1,64})?:\d{1,15}$/;
|
|
15902
|
+
var isGroupMessageId = (value) => typeof value === "string" && MESSAGE_ID.test(value);
|
|
15903
|
+
function frameOf(value, t, groupId) {
|
|
15904
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
15905
|
+
const f = value;
|
|
15906
|
+
return f.t === t && (groupId === void 0 || f.g === groupId) ? f : null;
|
|
15907
|
+
}
|
|
15908
|
+
/** A `group-want` of group `groupId`: the message whose file is asked for, or null. */
|
|
15909
|
+
function readGroupWant(value, groupId) {
|
|
15910
|
+
const f = frameOf(value, GROUP_WANT_FRAME, groupId);
|
|
15911
|
+
return f && isGroupMessageId(f.id) ? { id: f.id } : null;
|
|
15912
|
+
}
|
|
15913
|
+
/** A holder's `group-want-no` of group `groupId`, or null. A reason this app does not know reads as `busy`. */
|
|
15914
|
+
function readGroupWantNo(value, groupId) {
|
|
15915
|
+
const f = frameOf(value, GROUP_WANT_NO_FRAME, groupId);
|
|
15916
|
+
if (!f || !isGroupMessageId(f.id)) return null;
|
|
15917
|
+
return {
|
|
15918
|
+
id: f.id,
|
|
15919
|
+
why: typeof f.why === "string" && REFUSALS.includes(f.why) ? f.why : "busy"
|
|
15920
|
+
};
|
|
15921
|
+
}
|
|
15922
|
+
/**
|
|
15923
|
+
* The message ids of a `group-have`: of group `groupId` on an edge, or (no `groupId`) a community's application frame.
|
|
15924
|
+
* At most `haveIds` message ids; a frame with more, or anything else, is null.
|
|
15925
|
+
*/
|
|
15926
|
+
function readGroupHave(value, groupId) {
|
|
15927
|
+
const f = frameOf(value, GROUP_HAVE_FRAME, groupId);
|
|
15928
|
+
if (!f || !Array.isArray(f.ids) || !f.ids.length || f.ids.length > GROUP_FILE_LIMITS.haveIds || !f.ids.every(isGroupMessageId)) return null;
|
|
15929
|
+
return [...new Set(f.ids)];
|
|
15930
|
+
}
|
|
15931
|
+
var DIGEST = /^[A-Za-z0-9_-]{43}$/;
|
|
15932
|
+
/**
|
|
15933
|
+
* A description read from the wire, cleaned: the name made a display name (never a path), the type untrusted, the
|
|
15934
|
+
* size within the group limit, the digest the right shape. A malformed voice, video or picture description is dropped
|
|
15935
|
+
* on its own and the file is still a file; anything else malformed is no file at all (null), and the text shows.
|
|
15936
|
+
*/
|
|
15937
|
+
function readGroupFileMeta(value) {
|
|
15938
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
15939
|
+
const v = value;
|
|
15940
|
+
if (typeof v.name !== "string" || !v.name.trim() || [...v.name].length > GROUP_FILE_LIMITS.nameChars) return null;
|
|
15941
|
+
if (typeof v.mime !== "string" || v.mime.length > GROUP_FILE_LIMITS.mimeChars) return null;
|
|
15942
|
+
if (!Number.isSafeInteger(v.size) || v.size < 1 || v.size > GROUP_FILE_LIMITS.maxBytes) return null;
|
|
15943
|
+
if (typeof v.d !== "string" || !DIGEST.test(v.d)) return null;
|
|
15944
|
+
const mime = sanitizeMime(v.mime);
|
|
15945
|
+
const voice = v.voice === void 0 ? void 0 : parseVoiceMeta(v.voice, mime);
|
|
15946
|
+
const video = v.video === void 0 ? void 0 : parseVideoMeta(v.video, mime);
|
|
15947
|
+
const image = v.image === void 0 ? void 0 : parseImageMeta(v.image, mime);
|
|
15948
|
+
return {
|
|
15949
|
+
name: sanitizeFileName(v.name),
|
|
15950
|
+
mime,
|
|
15951
|
+
size: v.size,
|
|
15952
|
+
d: v.d,
|
|
15953
|
+
...voice ? { voice } : {},
|
|
15954
|
+
...video ? { video } : {},
|
|
15955
|
+
...image ? { image } : {}
|
|
15956
|
+
};
|
|
15957
|
+
}
|
|
15958
|
+
/**
|
|
15959
|
+
* The text an announcement carries when its author wrote no caption: what an app without group files shows instead
|
|
15960
|
+
* of the file. English, as wire text is; an app that knows the file shows the file, not this line.
|
|
15961
|
+
*/
|
|
15962
|
+
function groupFileFallback(file) {
|
|
15963
|
+
if (file.voice) return `🎤 Voice message (${formatVoiceDuration(file.voice.duration)}): update Ghostly to hear it`;
|
|
15964
|
+
return `${fileMessageText(file)} (${formatFileSize(file.size)}): update Ghostly to get files in groups`;
|
|
15965
|
+
}
|
|
15966
|
+
/**
|
|
15967
|
+
* Whether a device fetches a file by itself (WISP 503 § Automatic and asked downloads): a voice message always, any
|
|
15968
|
+
* other file up to `autoBytes` while what it took that way from the group stays within `autoBytesPerGroup`.
|
|
15969
|
+
*/
|
|
15970
|
+
function fetchesByItself(file, keptAutomatically, limits = GROUP_FILE_LIMITS) {
|
|
15971
|
+
if (file.voice) return true;
|
|
15972
|
+
return file.size <= limits.autoBytes && keptAutomatically + file.size <= limits.autoBytesPerGroup;
|
|
15973
|
+
}
|
|
15974
|
+
//#endregion
|
|
15710
15975
|
//#region ../core/src/pairingProgress.ts
|
|
15711
15976
|
var FIRST_STAGE = {
|
|
15712
15977
|
inviter: "publishing",
|
|
@@ -15993,6 +16258,8 @@ var QUIET_FRAMES = /* @__PURE__ */ new Set([
|
|
|
15993
16258
|
* `disconnected`) otherwise went on until its consent checks gave up, 27 s after the crash (CI run 36741701666).
|
|
15994
16259
|
*/
|
|
15995
16260
|
var PONG_WAIT_MS = 4e3;
|
|
16261
|
+
/** How long `handled` waits for the contact to answer its ping before it says it cannot tell. */
|
|
16262
|
+
var HANDLED_WAIT_MS = 1e4;
|
|
15996
16263
|
/**
|
|
15997
16264
|
* Back in front after an absence (`wake`), a live session that ends this soon after it was lost while the app was
|
|
15998
16265
|
* away (a phone that put it to sleep): the chat resumes as after a restart, not as a session the contact closed.
|
|
@@ -16182,6 +16449,16 @@ var GhostLink = class {
|
|
|
16182
16449
|
/** The contact's transport policy as last seen on this session, to tell its explicit choices apart. */
|
|
16183
16450
|
peerPolicySeen = null;
|
|
16184
16451
|
unansweredPings = 0;
|
|
16452
|
+
/**
|
|
16453
|
+
* Pings sent on this session and pongs heard on it, counted from its start: the contact answers each ping in the order
|
|
16454
|
+
* frames come, so the pong to ping n says it handled everything sent before it (`handled`).
|
|
16455
|
+
*/
|
|
16456
|
+
pingsSent = 0;
|
|
16457
|
+
pongsHeard = 0;
|
|
16458
|
+
/** The liveness ping whose pong measures the round trip: its number in `pingsSent`. */
|
|
16459
|
+
rttPing = 0;
|
|
16460
|
+
/** `handled` callers, each waiting for the pong to its ping. */
|
|
16461
|
+
handledWaiters = [];
|
|
16185
16462
|
/** The last ping's `PONG_WAIT_MS`: running until something comes back from the contact. */
|
|
16186
16463
|
pongWait = null;
|
|
16187
16464
|
/** The last ping's wait ran out with nothing back, and nothing came since (`restartedRedial`). */
|
|
@@ -17904,6 +18181,16 @@ var GhostLink = class {
|
|
|
17904
18181
|
dialsPastRtc() {
|
|
17905
18182
|
return (this.demotedUntil.get("webrtc/1") ?? 0) > Date.now() && this.fallback && this.peerFallback && TRANSPORTS.some((t) => this.canDial(t) && !!this.peerTransports?.includes(t) && this.transportOffer().includes(t));
|
|
17906
18183
|
}
|
|
18184
|
+
/**
|
|
18185
|
+
* A first pairing's inviter has no offer of the joiner's to wait for: WebRTC is missing here, or the joiner's record
|
|
18186
|
+
* says its app has none (a Linux Desktop's WebKitGTK), so its first packet carries none. A native transport the record
|
|
18187
|
+
* describes is dialled now. Waiting out `INVITER_DIAL_GRACE_MS` left two Linux Desktops 2.5 s more apart, and the
|
|
18188
|
+
* joiner, still reading, often went on without dialling for seconds (dht-direct on CI, 2026-10-07). Should the joiner
|
|
18189
|
+
* dial too, the two dials cross and the lower key's carries the chat (`parkCrossed`, `attachReplacement`).
|
|
18190
|
+
*/
|
|
18191
|
+
nothingToAwait() {
|
|
18192
|
+
return !(this.availableTransports.includes("webrtc/1") && (!this.peerTransports || this.peerTransports.includes("webrtc/1"))) && TRANSPORTS.some((t) => this.canDial(t) && !!this.peerTransports?.includes(t) && this.transportOffer().includes(t));
|
|
18193
|
+
}
|
|
17907
18194
|
/** Whether a native dial of `transport` can go now: this side's endpoint is up and the contact's descriptor known. */
|
|
17908
18195
|
canDial(transport) {
|
|
17909
18196
|
return transport !== "webrtc/1" && this.endpoints.has(transport) && !!this.peerDescriptors[transport];
|
|
@@ -18187,8 +18474,8 @@ var GhostLink = class {
|
|
|
18187
18474
|
* Only the lower key offers, so two peers coming online together do not collide. A first pairing is
|
|
18188
18475
|
* the exception: the joiner offers, whatever its key, in the very packet that says it is here (one hop
|
|
18189
18476
|
* less than presence, then the inviter's offer), and the inviter waits for that offer. An inviter that
|
|
18190
|
-
* sees the joiner but no offer for `INVITER_DIAL_GRACE_MS` (an app from before this rule) offers itself
|
|
18191
|
-
* the lower-key rule settles any collision, on both sides.
|
|
18477
|
+
* sees the joiner but no offer for `INVITER_DIAL_GRACE_MS` (an app from before this rule) offers itself,
|
|
18478
|
+
* and dials at once when no offer can come (`nothingToAwait`); the lower-key rule settles any collision, on both sides.
|
|
18192
18479
|
*/
|
|
18193
18480
|
maybeAutoConnect(presence) {
|
|
18194
18481
|
if (this.stopped || this.leaving || this.yielding || this.streamBlocked || this.keyStopped || !this.options.autoConnect || !presence.online || this.channel || this.dialing || this.dataLink.state !== "idle") return;
|
|
@@ -18200,7 +18487,7 @@ var GhostLink = class {
|
|
|
18200
18487
|
const role = !!this.tracker && !this.tracker.done && this.tracker.progress.stage !== "on-dht" ? this.options.pairingProgress?.role : void 0;
|
|
18201
18488
|
if (role === "inviter") {
|
|
18202
18489
|
this.peerSeenAt ||= Date.now();
|
|
18203
|
-
if (Date.now() - this.peerSeenAt < 2500) {
|
|
18490
|
+
if (Date.now() - this.peerSeenAt < 2500 && !this.nothingToAwait()) {
|
|
18204
18491
|
this.session.expectPeer();
|
|
18205
18492
|
return;
|
|
18206
18493
|
}
|
|
@@ -18877,7 +19164,8 @@ var GhostLink = class {
|
|
|
18877
19164
|
2,
|
|
18878
19165
|
3,
|
|
18879
19166
|
4,
|
|
18880
|
-
5
|
|
19167
|
+
5,
|
|
19168
|
+
6
|
|
18881
19169
|
]
|
|
18882
19170
|
}));
|
|
18883
19171
|
} catch {}
|
|
@@ -19157,9 +19445,10 @@ var GhostLink = class {
|
|
|
19157
19445
|
},
|
|
19158
19446
|
onFailure: () => {
|
|
19159
19447
|
if (paired.state.keyMismatch) this.options.events?.onPeerKeyRefused?.();
|
|
19160
|
-
if (this.unproven(channel) || paired.authTimedOut) {
|
|
19448
|
+
if (this.unproven(channel) || paired.authTimedOut || paired.overloaded) {
|
|
19161
19449
|
if (paired.state.keyMismatch) this.dht?.foreignKeySeen("stream");
|
|
19162
19450
|
if (this.unproven(channel)) traceLink(this.myPubKeyZ32, "dialed-in-refused", { keyMismatch: !!paired.state.keyMismatch });
|
|
19451
|
+
else if (paired.overloaded) traceLink(this.myPubKeyZ32, "receive-overload", { transport: binding?.transport ?? "webrtc/1" });
|
|
19163
19452
|
else traceLink(this.myPubKeyZ32, "auth-timeout", { transport: binding?.transport ?? "webrtc/1" });
|
|
19164
19453
|
migration?.reject(new Error(paired.state.error ?? "Candidate authentication failed"));
|
|
19165
19454
|
channel.close();
|
|
@@ -19296,11 +19585,13 @@ var GhostLink = class {
|
|
|
19296
19585
|
}
|
|
19297
19586
|
if (frame?.t === "paired-pong") {
|
|
19298
19587
|
this.peerAnswersPings = true;
|
|
19299
|
-
|
|
19588
|
+
const answered = ++this.pongsHeard;
|
|
19589
|
+
if (this.pingSentAt && answered >= this.rttPing) {
|
|
19300
19590
|
this.rtt = Date.now() - this.pingSentAt;
|
|
19301
19591
|
this.pingSentAt = 0;
|
|
19302
19592
|
this.options.events?.onRtt?.(this.rtt);
|
|
19303
19593
|
}
|
|
19594
|
+
for (const waiter of this.handledWaiters.filter((w) => w.ping <= answered)) waiter.done(true);
|
|
19304
19595
|
return;
|
|
19305
19596
|
}
|
|
19306
19597
|
if (frame?.t === "paired-bye") {
|
|
@@ -19623,8 +19914,7 @@ var GhostLink = class {
|
|
|
19623
19914
|
this.stopLiveness();
|
|
19624
19915
|
this.peerAnswersPings = peerAnswersPings;
|
|
19625
19916
|
const ping = () => {
|
|
19626
|
-
this.
|
|
19627
|
-
channel.send(JSON.stringify({ t: "paired-ping" }));
|
|
19917
|
+
this.sendPing(channel, true);
|
|
19628
19918
|
if (!this.peerAnswersPings || this.pongWait) return;
|
|
19629
19919
|
this.pongWait = setTimeout(() => {
|
|
19630
19920
|
this.pongWait = null;
|
|
@@ -19662,6 +19952,43 @@ var GhostLink = class {
|
|
|
19662
19952
|
this.peerAnswersPings = false;
|
|
19663
19953
|
this.pingSentAt = 0;
|
|
19664
19954
|
this.rtt = void 0;
|
|
19955
|
+
this.pingsSent = this.pongsHeard = this.rttPing = 0;
|
|
19956
|
+
for (const waiter of [...this.handledWaiters]) waiter.done(false);
|
|
19957
|
+
}
|
|
19958
|
+
/** `live`: a liveness ping, whose pong measures the round trip. */
|
|
19959
|
+
sendPing(channel, live) {
|
|
19960
|
+
if (live) this.pingSentAt = Date.now();
|
|
19961
|
+
channel.send(JSON.stringify({ t: "paired-ping" }));
|
|
19962
|
+
this.pingsSent++;
|
|
19963
|
+
if (live) this.rttPing = this.pingsSent;
|
|
19964
|
+
}
|
|
19965
|
+
/**
|
|
19966
|
+
* Resolves once the contact's app handled every frame sent on this session so far: it answers a ping in the order
|
|
19967
|
+
* frames come (every app since 1.0), so the pong to one sent now says so, however slowly it handles them. A group
|
|
19968
|
+
* member's catch-up answer goes a slice at a time on it (`GroupSessionHooks.handled`). `false` when it cannot tell:
|
|
19969
|
+
* no session, a contact that answers no ping, or no answer within `HANDLED_WAIT_MS`.
|
|
19970
|
+
*/
|
|
19971
|
+
handled() {
|
|
19972
|
+
const channel = this.channel;
|
|
19973
|
+
if (!channel || !this.peerAnswersPings || this.paired?.state.status !== "ready") return Promise.resolve(false);
|
|
19974
|
+
try {
|
|
19975
|
+
this.sendPing(channel, false);
|
|
19976
|
+
} catch {
|
|
19977
|
+
return Promise.resolve(false);
|
|
19978
|
+
}
|
|
19979
|
+
return new Promise((resolve) => {
|
|
19980
|
+
const waiter = {
|
|
19981
|
+
ping: this.pingsSent,
|
|
19982
|
+
done: (exact) => {
|
|
19983
|
+
clearTimeout(timer);
|
|
19984
|
+
const at = this.handledWaiters.indexOf(waiter);
|
|
19985
|
+
if (at >= 0) this.handledWaiters.splice(at, 1);
|
|
19986
|
+
resolve(exact);
|
|
19987
|
+
}
|
|
19988
|
+
};
|
|
19989
|
+
const timer = setTimeout(() => waiter.done(false), HANDLED_WAIT_MS);
|
|
19990
|
+
this.handledWaiters.push(waiter);
|
|
19991
|
+
});
|
|
19665
19992
|
}
|
|
19666
19993
|
/** Something came back on the session: the ping's wait is over. */
|
|
19667
19994
|
heardFromPeer() {
|
|
@@ -19824,8 +20151,7 @@ var GhostLink = class {
|
|
|
19824
20151
|
if (!this.peerAnswersPings || this.paired?.state.status !== "ready") return;
|
|
19825
20152
|
this.unansweredPings++;
|
|
19826
20153
|
try {
|
|
19827
|
-
this.
|
|
19828
|
-
channel.send(JSON.stringify({ t: "paired-ping" }));
|
|
20154
|
+
this.sendPing(channel, true);
|
|
19829
20155
|
} catch {
|
|
19830
20156
|
this.dropDeadSession(channel);
|
|
19831
20157
|
return;
|
|
@@ -24036,6 +24362,64 @@ function indexOf(points, words, from) {
|
|
|
24036
24362
|
}
|
|
24037
24363
|
return -1;
|
|
24038
24364
|
}
|
|
24365
|
+
/**
|
|
24366
|
+
* Catch-up answers, a slice at a time (`CATCH_UP_SLICE`), with two slices at most not handled yet by the member's app
|
|
24367
|
+
* (`hooks.handled`): the next once it handled the one before the last, so the slices follow each other without a round
|
|
24368
|
+
* trip between them. All at once, a member back in a busy group was handed over a hundred frames on one session, and an
|
|
24369
|
+
* app that holds 64 waiting ended that session mid catch-up ("Session receive limit exceeded", 2026-10-07). The frames
|
|
24370
|
+
* and their order are what they were: only when they go changes.
|
|
24371
|
+
*/
|
|
24372
|
+
var CatchUpAnswers = class {
|
|
24373
|
+
hooks;
|
|
24374
|
+
/** Answers still going out, per member: what is left of each. */
|
|
24375
|
+
going = /* @__PURE__ */ new Map();
|
|
24376
|
+
constructor(hooks) {
|
|
24377
|
+
this.hooks = hooks;
|
|
24378
|
+
}
|
|
24379
|
+
/**
|
|
24380
|
+
* The first two slices go now, the rest without holding the group's other frames back. A newer answer to the same
|
|
24381
|
+
* member takes the place of what is left of the last; a frame the edge did not take ends it (the member asks again
|
|
24382
|
+
* when its edge opens).
|
|
24383
|
+
*/
|
|
24384
|
+
handOut(to, frames) {
|
|
24385
|
+
const handled = this.hooks.handled;
|
|
24386
|
+
if (!handled) {
|
|
24387
|
+
for (const frame of frames) this.hooks.send(to, frame);
|
|
24388
|
+
return;
|
|
24389
|
+
}
|
|
24390
|
+
const going = this.going.get(to);
|
|
24391
|
+
if (going) {
|
|
24392
|
+
going.splice(0, going.length, ...frames);
|
|
24393
|
+
return;
|
|
24394
|
+
}
|
|
24395
|
+
const rest = [...frames], told = [];
|
|
24396
|
+
const next = () => {
|
|
24397
|
+
if (!this.sendSlice(to, rest)) return false;
|
|
24398
|
+
if (rest.length) told.push(handled(to).catch(() => false));
|
|
24399
|
+
return true;
|
|
24400
|
+
};
|
|
24401
|
+
if (!next() || !next() || !rest.length) return;
|
|
24402
|
+
this.going.set(to, rest);
|
|
24403
|
+
(async () => {
|
|
24404
|
+
try {
|
|
24405
|
+
while (rest.length) {
|
|
24406
|
+
if (!await told.shift()) await new Promise((resolve) => setTimeout(resolve, 500));
|
|
24407
|
+
if (!this.hooks.active() || !next()) break;
|
|
24408
|
+
}
|
|
24409
|
+
} finally {
|
|
24410
|
+
this.going.delete(to);
|
|
24411
|
+
}
|
|
24412
|
+
})();
|
|
24413
|
+
}
|
|
24414
|
+
/** The next slice of `rest` to `to`: false, and nothing left, when the edge did not take one. */
|
|
24415
|
+
sendSlice(to, rest) {
|
|
24416
|
+
for (const frame of rest.splice(0, 16)) if (this.hooks.send(to, frame) === false) {
|
|
24417
|
+
rest.length = 0;
|
|
24418
|
+
return false;
|
|
24419
|
+
}
|
|
24420
|
+
return true;
|
|
24421
|
+
}
|
|
24422
|
+
};
|
|
24039
24423
|
//#endregion
|
|
24040
24424
|
//#region ../core/src/groupSession.ts
|
|
24041
24425
|
/**
|
|
@@ -24087,7 +24471,15 @@ var GROUP_LIMITS = {
|
|
|
24087
24471
|
syncAnswers: 8,
|
|
24088
24472
|
syncWindowMs: 6e4,
|
|
24089
24473
|
/** Messages delivered from a copy that was not whole, remembered until a whole copy completes them. */
|
|
24090
|
-
provisional: 256
|
|
24474
|
+
provisional: 256,
|
|
24475
|
+
/**
|
|
24476
|
+
* Frames of one catch-up answer in a slice (`GroupSessionHooks.handled`), two slices at most not handled yet by the
|
|
24477
|
+
* member's app: the next goes once it handled the one before the last. An app holds 64 frames waiting at most before
|
|
24478
|
+
* 2026-10-07 (more ends the session), and a member back in a busy group was handed over a hundred at once.
|
|
24479
|
+
*/
|
|
24480
|
+
catchUpSlice: 16,
|
|
24481
|
+
/** Between two slices, when the member's app cannot say it handled one (`handled` resolves `false`). */
|
|
24482
|
+
catchUpPauseMs: 500
|
|
24091
24483
|
};
|
|
24092
24484
|
/** What every member should know about who can read what, in the words the apps show. */
|
|
24093
24485
|
var GROUP_READ_NOTE = `Everyone in the group can read everything sent while they are a member. Someone removed keeps what they already received and cannot read what comes after; someone who joins later cannot read what came before. Messages go directly to each member; whoever was away gets what they missed from any member who has it (the group's last ${GROUP_LIMITS.relay} messages) when they meet again.`;
|
|
@@ -24126,6 +24518,17 @@ var cardAad = (f) => JSON.stringify([
|
|
|
24126
24518
|
f.ts
|
|
24127
24519
|
]);
|
|
24128
24520
|
var isCardBox = isSealedBox(MAX_CARD_BOX);
|
|
24521
|
+
/** A file's description as JSON: a name and a type of 255 characters each, 128 voice bars or a 12 KiB poster, sealed. */
|
|
24522
|
+
var MAX_FILE_BOX = Math.ceil((GROUP_FILE_LIMITS.nameChars * 4 + GROUP_FILE_LIMITS.mimeChars + 17e3) * 4 / 3) + 4;
|
|
24523
|
+
var fileAad = (f) => JSON.stringify([
|
|
24524
|
+
"ghostly-group/1 file",
|
|
24525
|
+
f.g,
|
|
24526
|
+
f.e,
|
|
24527
|
+
f.s,
|
|
24528
|
+
f.n,
|
|
24529
|
+
f.ts
|
|
24530
|
+
]);
|
|
24531
|
+
var isFileBox = isSealedBox(MAX_FILE_BOX);
|
|
24129
24532
|
var B64$1 = /^[A-Za-z0-9_-]*$/;
|
|
24130
24533
|
var secretAad$1 = (g, e, member) => JSON.stringify([
|
|
24131
24534
|
"ghostly-group/1 secret",
|
|
@@ -24174,6 +24577,11 @@ var messageSignedWhole = (f) => utf8Encode(JSON.stringify([
|
|
|
24174
24577
|
f.sc.n,
|
|
24175
24578
|
f.sc.c
|
|
24176
24579
|
] : [],
|
|
24580
|
+
...f.fl ? [
|
|
24581
|
+
"fl",
|
|
24582
|
+
f.fl.n,
|
|
24583
|
+
f.fl.c
|
|
24584
|
+
] : [],
|
|
24177
24585
|
...f.o ? [
|
|
24178
24586
|
"o",
|
|
24179
24587
|
f.o.e,
|
|
@@ -24252,7 +24660,7 @@ function readFirstPlace(o, epoch) {
|
|
|
24252
24660
|
/** One of my own frames, among mine: `e:n`. */
|
|
24253
24661
|
var ownKey = (f) => `${f.e}:${f.n}`;
|
|
24254
24662
|
/** What a frame weighs in a log, roughly: its boxes plus the fixed fields around them. */
|
|
24255
|
-
var frameBytes = (f) => f.c.length + (f.m?.c.length ?? 0) + (f.r?.c.length ?? 0) + (f.sc?.c.length ?? 0) + 400;
|
|
24663
|
+
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;
|
|
24256
24664
|
/** Only the fields an edit frame has, as `clean` does for a message. */
|
|
24257
24665
|
var cleanEdit = (f) => ({
|
|
24258
24666
|
t: GROUP_EDIT_FRAME,
|
|
@@ -24291,6 +24699,10 @@ function clean(f) {
|
|
|
24291
24699
|
n: f.sc.n,
|
|
24292
24700
|
c: f.sc.c
|
|
24293
24701
|
} } : {},
|
|
24702
|
+
...isFileBox(f.fl) ? { fl: {
|
|
24703
|
+
n: f.fl.n,
|
|
24704
|
+
c: f.fl.c
|
|
24705
|
+
} } : {},
|
|
24294
24706
|
...readFirstPlace(f.o, f.e) ? { o: readFirstPlace(f.o, f.e) } : {},
|
|
24295
24707
|
...typeof f.xs === "string" && f.xs.length === 86 && B64$1.test(f.xs) ? { xs: f.xs } : {}
|
|
24296
24708
|
};
|
|
@@ -24367,6 +24779,8 @@ var GroupSession = class {
|
|
|
24367
24779
|
passOn = null;
|
|
24368
24780
|
/** Syncs answered per member, a few a minute (`GROUP_LIMITS.syncAnswers`). In memory only. */
|
|
24369
24781
|
syncsAnswered = /* @__PURE__ */ new Map();
|
|
24782
|
+
/** Catch-up answers still going out, a slice at a time (`handOut`). */
|
|
24783
|
+
answers;
|
|
24370
24784
|
/**
|
|
24371
24785
|
* Messages delivered from a copy that was not whole (handed on without its author's whole signature): not seen, so
|
|
24372
24786
|
* a sync still asks for them and a whole copy completes them. Oldest first, saved as `GroupState.provisional`, so a
|
|
@@ -24379,6 +24793,11 @@ var GroupSession = class {
|
|
|
24379
24793
|
this.hooks = hooks;
|
|
24380
24794
|
this.state = state;
|
|
24381
24795
|
this.identity = identityFromSeedB64(state.seedB64);
|
|
24796
|
+
this.answers = new CatchUpAnswers({
|
|
24797
|
+
send: (to, frame) => hooks.send(to, frame),
|
|
24798
|
+
...hooks.handled && { handled: (to) => hooks.handled(to) },
|
|
24799
|
+
active: () => this.state.status === "active"
|
|
24800
|
+
});
|
|
24382
24801
|
this.provisional = new Set((state.provisional ?? []).slice(-GROUP_LIMITS.provisional));
|
|
24383
24802
|
this.chainMovedAt = this.clock();
|
|
24384
24803
|
}
|
|
@@ -24503,6 +24922,15 @@ var GroupSession = class {
|
|
|
24503
24922
|
get others() {
|
|
24504
24923
|
return this.roster.map(([k]) => k).filter((k) => k !== this.myKey);
|
|
24505
24924
|
}
|
|
24925
|
+
/**
|
|
24926
|
+
* Whether `key` could read message `messageId` (`<s>:<e>:<n>`) and still can: in the roster of its epoch, and in the
|
|
24927
|
+
* roster now. A holder serves a group file only to such a member (WISP 503 § Asking).
|
|
24928
|
+
*/
|
|
24929
|
+
couldRead(key, messageId) {
|
|
24930
|
+
const e = Number(messageId.split(":")[1]);
|
|
24931
|
+
const then = this.state.chain.find((c) => c.e === e);
|
|
24932
|
+
return !!then && rosterHas(then.m, key) && rosterHas(this.roster, key);
|
|
24933
|
+
}
|
|
24506
24934
|
role(key) {
|
|
24507
24935
|
return this.roster.find(([k]) => k === key)?.[1];
|
|
24508
24936
|
}
|
|
@@ -24751,14 +25179,18 @@ var GroupSession = class {
|
|
|
24751
25179
|
* text that name members (everyone: the admin only); what does not hold is left out. `reply`: the message it
|
|
24752
25180
|
* answers, sealed apart from the text (`r`) like the mentions, so an older app still reads the text. `forwarded`: the
|
|
24753
25181
|
* hop count of a forwarded text (`f`, WISP 902 § Forwards). `card`: a status card, checked by the caller, sealed in a
|
|
24754
|
-
* box of its own (`sc`, WISP 405 · Status Cards); the text is its fallback.
|
|
25182
|
+
* box of its own (`sc`, WISP 405 · Status Cards); the text is its fallback. `file`: a file or voice message the text
|
|
25183
|
+
* announces (`fl`, WISP 503 · Group Files), its description checked by the caller; the text is its caption, or the
|
|
25184
|
+
* line an older app shows.
|
|
24755
25185
|
*/
|
|
24756
|
-
sendText(text, now = Date.now(), mentions = [], reply, forwarded, card) {
|
|
25186
|
+
sendText(text, now = Date.now(), mentions = [], reply, forwarded, card, file) {
|
|
24757
25187
|
return this.serialize(async () => {
|
|
24758
25188
|
if (this.state.status !== "active") return { error: this.state.statusReason ?? "You are no longer in this group" };
|
|
24759
25189
|
const trimmed = text.trim();
|
|
24760
25190
|
if (!trimmed) return { error: "Nothing to send" };
|
|
24761
25191
|
if (utf8Encode(trimmed).length > GROUP_LIMITS.textBytes) return { error: "Message exceeds 16 KiB" };
|
|
25192
|
+
const described = file === void 0 ? void 0 : readGroupFileMeta(file);
|
|
25193
|
+
if (file !== void 0 && !described) return { error: "That file cannot go to a group" };
|
|
24762
25194
|
const epoch = this.epoch;
|
|
24763
25195
|
if (!this.secret(epoch)) return { error: "This epoch's key has not arrived yet. Wait for a member to catch you up." };
|
|
24764
25196
|
const n = this.nextSeq(epoch);
|
|
@@ -24769,7 +25201,8 @@ var GroupSession = class {
|
|
|
24769
25201
|
mentions: named,
|
|
24770
25202
|
reply: answers || void 0,
|
|
24771
25203
|
forwarded: fw,
|
|
24772
|
-
card
|
|
25204
|
+
card,
|
|
25205
|
+
file: described ?? void 0
|
|
24773
25206
|
});
|
|
24774
25207
|
this.logOwn(frame);
|
|
24775
25208
|
const id = groupMessageId(this.myKey, epoch, n);
|
|
@@ -24783,7 +25216,8 @@ var GroupSession = class {
|
|
|
24783
25216
|
...named.length ? { mentions: named } : {},
|
|
24784
25217
|
...answers ? { reply: answers } : {},
|
|
24785
25218
|
...fw ? { forwarded: fw } : {},
|
|
24786
|
-
...card ? { card } : {}
|
|
25219
|
+
...card ? { card } : {},
|
|
25220
|
+
...described ? { file: described } : {}
|
|
24787
25221
|
});
|
|
24788
25222
|
await this.say(frame);
|
|
24789
25223
|
return { id };
|
|
@@ -24824,6 +25258,7 @@ var GroupSession = class {
|
|
|
24824
25258
|
...extra.reply ? { r: encryptText(key, replyAad(header), JSON.stringify(extra.reply)) } : {},
|
|
24825
25259
|
...extra.forwarded ? { f: extra.forwarded } : {},
|
|
24826
25260
|
...extra.card ? { sc: encryptText(key, cardAad(header), JSON.stringify(extra.card)) } : {},
|
|
25261
|
+
...extra.file ? { fl: encryptText(key, fileAad(header), JSON.stringify(extra.file)) } : {},
|
|
24827
25262
|
...o ? { o } : {}
|
|
24828
25263
|
};
|
|
24829
25264
|
return {
|
|
@@ -24893,7 +25328,8 @@ var GroupSession = class {
|
|
|
24893
25328
|
mentions,
|
|
24894
25329
|
reply: this.openReply(key, f),
|
|
24895
25330
|
forwarded: readForwarded(f.f) ? f.f : void 0,
|
|
24896
|
-
card: this.openCard(key, f)
|
|
25331
|
+
card: this.openCard(key, f),
|
|
25332
|
+
file: this.openFile(key, f)
|
|
24897
25333
|
}, readFirstPlace(f.o, f.e) ?? {
|
|
24898
25334
|
e: f.e,
|
|
24899
25335
|
n: f.n
|
|
@@ -25228,6 +25664,7 @@ var GroupSession = class {
|
|
|
25228
25664
|
delete raw.r;
|
|
25229
25665
|
delete raw.f;
|
|
25230
25666
|
delete raw.sc;
|
|
25667
|
+
delete raw.fl;
|
|
25231
25668
|
delete raw.o;
|
|
25232
25669
|
delete raw.xs;
|
|
25233
25670
|
}
|
|
@@ -25258,6 +25695,7 @@ var GroupSession = class {
|
|
|
25258
25695
|
const reply = this.openReply(key, raw);
|
|
25259
25696
|
const forwarded = readForwarded(raw.f);
|
|
25260
25697
|
const card = this.openCard(key, raw);
|
|
25698
|
+
const file = this.openFile(key, raw);
|
|
25261
25699
|
const completes = whole && this.provisional.delete(id);
|
|
25262
25700
|
await this.hooks.message({
|
|
25263
25701
|
id,
|
|
@@ -25270,6 +25708,7 @@ var GroupSession = class {
|
|
|
25270
25708
|
...reply ? { reply } : {},
|
|
25271
25709
|
...forwarded ? { forwarded } : {},
|
|
25272
25710
|
...card ? { card } : {},
|
|
25711
|
+
...file ? { file } : {},
|
|
25273
25712
|
...completes ? { completes: true } : {}
|
|
25274
25713
|
});
|
|
25275
25714
|
if (!whole) {
|
|
@@ -25371,6 +25810,17 @@ var GroupSession = class {
|
|
|
25371
25810
|
return;
|
|
25372
25811
|
}
|
|
25373
25812
|
}
|
|
25813
|
+
/** The file a message announces (WISP 503); a box that does not open, or a description that does not hold, is no file. */
|
|
25814
|
+
openFile(key, raw) {
|
|
25815
|
+
if (!isFileBox(raw.fl)) return void 0;
|
|
25816
|
+
const plain = decryptText(key, fileAad(raw), raw.fl.n, raw.fl.c);
|
|
25817
|
+
if (plain === null) return void 0;
|
|
25818
|
+
try {
|
|
25819
|
+
return readGroupFileMeta(JSON.parse(plain)) ?? void 0;
|
|
25820
|
+
} catch {
|
|
25821
|
+
return;
|
|
25822
|
+
}
|
|
25823
|
+
}
|
|
25374
25824
|
/** The message a text answers; a box that does not open, or does not hold one, is no reply, not no message. */
|
|
25375
25825
|
openReply(key, raw) {
|
|
25376
25826
|
if (!isReplyBox(raw.r)) return void 0;
|
|
@@ -25524,9 +25974,10 @@ var GroupSession = class {
|
|
|
25524
25974
|
});
|
|
25525
25975
|
return;
|
|
25526
25976
|
}
|
|
25977
|
+
const answer = [];
|
|
25527
25978
|
for (let e = frame.e + 1; e <= this.epoch; e++) {
|
|
25528
25979
|
const commit = this.state.chain[e], secret = this.secret(e);
|
|
25529
|
-
|
|
25980
|
+
answer.push({
|
|
25530
25981
|
t: "group-commit",
|
|
25531
25982
|
g: this.id,
|
|
25532
25983
|
commit,
|
|
@@ -25538,7 +25989,7 @@ var GroupSession = class {
|
|
|
25538
25989
|
e,
|
|
25539
25990
|
s: sealSecret(from, this.secret(e), secretAad$1(this.id, e, from))
|
|
25540
25991
|
}));
|
|
25541
|
-
for (let i = 0; i < secrets.length; i += GROUP_LIMITS.secretsPerFrame)
|
|
25992
|
+
for (let i = 0; i < secrets.length; i += GROUP_LIMITS.secretsPerFrame) answer.push({
|
|
25542
25993
|
t: "group-secrets",
|
|
25543
25994
|
g: this.id,
|
|
25544
25995
|
secrets: secrets.slice(i, i + GROUP_LIMITS.secretsPerFrame)
|
|
@@ -25546,12 +25997,24 @@ var GroupSession = class {
|
|
|
25546
25997
|
const lacks = missingIn(frame, from);
|
|
25547
25998
|
const asked = new Set(Array.isArray(frame.ask) ? frame.ask.filter((k) => typeof k === "string" && k !== from && k !== this.myKey && rosterHas(this.roster, k)) : []);
|
|
25548
25999
|
const kept = asked.size ? (this.state.relay ?? []).filter((f) => asked.has(f.s) && this.state.chain[f.e] && rosterHas(this.state.chain[f.e].m, from) && lacks(f)) : [];
|
|
25549
|
-
|
|
26000
|
+
answer.push(...asWritten(this.ownLacked(from, frame), kept));
|
|
25550
26001
|
for (const author of asked) {
|
|
25551
26002
|
const edits = (this.state.relayEdits ?? []).filter((f) => f.s === author && this.state.chain[f.e] && rosterHas(this.state.chain[f.e].m, from));
|
|
25552
|
-
|
|
26003
|
+
answer.push(...edits.slice(-GROUP_LIMITS.handOnEdits));
|
|
25553
26004
|
}
|
|
25554
|
-
this.
|
|
26005
|
+
const meta = this.metaFor(from, frame.mt);
|
|
26006
|
+
if (meta) answer.push(meta);
|
|
26007
|
+
this.handOut(from, answer);
|
|
26008
|
+
}
|
|
26009
|
+
/**
|
|
26010
|
+
* A catch-up answer, a slice at a time (`GROUP_LIMITS.catchUpSlice`), with two slices at most not handled yet by the
|
|
26011
|
+
* member's app (`hooks.handled`), as a community's goes (`CatchUpAnswers`). All at once, a member back in a busy group
|
|
26012
|
+
* was handed over a hundred frames on one session, and an app that holds 64 waiting ended that session mid catch-up
|
|
26013
|
+
* ("Session receive limit exceeded", 2026-10-07). A newer answer to the same member takes the place of what is left of
|
|
26014
|
+
* the last; a frame the edge did not take ends it (the member asks again when its edge opens).
|
|
26015
|
+
*/
|
|
26016
|
+
handOut(to, frames) {
|
|
26017
|
+
this.answers.handOut(to, frames);
|
|
25555
26018
|
}
|
|
25556
26019
|
/** My own messages a member's sync says it lacks, from my bounded log, for the epochs it was a member of. */
|
|
25557
26020
|
handOwn(to, frame) {
|
|
@@ -25630,10 +26093,14 @@ var GroupSession = class {
|
|
|
25630
26093
|
}
|
|
25631
26094
|
/** My statement, to a member whose sync says it holds an older one (or none); nothing to an app that says nothing. */
|
|
25632
26095
|
offerMeta(to, theirTag) {
|
|
26096
|
+
const frame = this.metaFor(to, theirTag);
|
|
26097
|
+
if (frame) this.hooks.send(to, frame);
|
|
26098
|
+
}
|
|
26099
|
+
metaFor(to, theirTag) {
|
|
25633
26100
|
const meta = this.state.meta, theirs = parseGroupMetaTag(theirTag);
|
|
25634
|
-
if (!meta || theirs === null || !groupMetaNewer(meta, theirs) || !rosterHas(this.roster, to)) return;
|
|
26101
|
+
if (!meta || theirs === null || !groupMetaNewer(meta, theirs) || !rosterHas(this.roster, to)) return null;
|
|
25635
26102
|
const secret = this.secret(this.epoch);
|
|
25636
|
-
|
|
26103
|
+
return secret ? wrapGroupMeta(meta, this.epoch, epochKeys(secret, this.id, this.epoch).message) : null;
|
|
25637
26104
|
}
|
|
25638
26105
|
/**
|
|
25639
26106
|
* A metadata statement: kept when it is newer than mine, its commit is on my chain, its signer was
|
|
@@ -25917,7 +26384,15 @@ var COMMUNITY_LIMITS = {
|
|
|
25917
26384
|
* waits for the window.
|
|
25918
26385
|
*/
|
|
25919
26386
|
syncAnswers: 16,
|
|
25920
|
-
syncWindowMs: 6e4
|
|
26387
|
+
syncWindowMs: 6e4,
|
|
26388
|
+
/**
|
|
26389
|
+
* Frames of one catch-up answer in a slice (`CommunitySessionHooks.handled`), two slices at most not handled yet by
|
|
26390
|
+
* the member's app: a member back after the store filled was handed 258 at once, and an app holds 64 waiting at most
|
|
26391
|
+
* before 2026-10-07 (more ends the session). As a private group's (`GROUP_LIMITS.catchUpSlice`).
|
|
26392
|
+
*/
|
|
26393
|
+
catchUpSlice: 16,
|
|
26394
|
+
/** Between two slices, when the member's app cannot say it handled one (`handled` resolves `false`). */
|
|
26395
|
+
catchUpPauseMs: 500
|
|
25921
26396
|
};
|
|
25922
26397
|
/** What every member should know about who can read what, and what the link does, in the words the apps show. */
|
|
25923
26398
|
var GROUP_READ_NOTE_COMMUNITY = `Anyone with the group's link can join, and any member can let people in. Everyone in the group can read everything sent while they are a member; someone removed cannot read what comes after, someone who joins later cannot read what came before. Members who were away get the last ${COMMUNITY_LIMITS.store} messages from whoever is there when they return.`;
|
|
@@ -26227,6 +26702,8 @@ var CommunitySession = class {
|
|
|
26227
26702
|
asked = /* @__PURE__ */ new Map();
|
|
26228
26703
|
/** Syncs answered per member, a few a minute (`COMMUNITY_LIMITS.syncAnswers`). In memory only. */
|
|
26229
26704
|
syncsAnswered = /* @__PURE__ */ new Map();
|
|
26705
|
+
/** Catch-up answers still going out, a slice at a time (`receiveSync`). */
|
|
26706
|
+
answers;
|
|
26230
26707
|
queue = Promise.resolve();
|
|
26231
26708
|
/** When the commit I follow last changed (`adopt`), by `hooks.clock`. */
|
|
26232
26709
|
chainMovedAt = 0;
|
|
@@ -26239,6 +26716,11 @@ var CommunitySession = class {
|
|
|
26239
26716
|
this.hooks = hooks;
|
|
26240
26717
|
this.state = state;
|
|
26241
26718
|
this.identity = identityFromSeedB64(state.seedB64);
|
|
26719
|
+
this.answers = new CatchUpAnswers({
|
|
26720
|
+
send: (to, frame) => hooks.direct(to, frame),
|
|
26721
|
+
...hooks.handled && { handled: (to) => hooks.handled(to) },
|
|
26722
|
+
active: () => this.state.status === "active"
|
|
26723
|
+
});
|
|
26242
26724
|
this.rebuild();
|
|
26243
26725
|
if ((state.status === "active" || state.status === "lost") && this.bestBranch().tip !== this.topHash) this.serialize(() => this.chooseBranch());
|
|
26244
26726
|
}
|
|
@@ -26412,6 +26894,15 @@ var CommunitySession = class {
|
|
|
26412
26894
|
for (const roster of this.rosters.values()) if (rosterHas(roster, key)) return true;
|
|
26413
26895
|
return false;
|
|
26414
26896
|
}
|
|
26897
|
+
/**
|
|
26898
|
+
* Whether `key` could read message `messageId` (`<s>:<e>:<h>:<n>`) and still can: a member of the roster it was sealed
|
|
26899
|
+
* for, and of the roster now. A holder serves a group file only to such a member (WISP 503 § Asking).
|
|
26900
|
+
*/
|
|
26901
|
+
couldRead(key, messageId) {
|
|
26902
|
+
const [, e, h] = messageId.split(":");
|
|
26903
|
+
const found = h === void 0 ? void 0 : this.commitByShort(Number(e), h);
|
|
26904
|
+
return !!found && rosterHas(this.rosterAt(found.hash) ?? [], key) && rosterHas(this.roster, key);
|
|
26905
|
+
}
|
|
26415
26906
|
/** Someone the chain took out (removed or left) and who is not back in. */
|
|
26416
26907
|
wasRemoved(key) {
|
|
26417
26908
|
return this.outIndex(key) >= 0;
|
|
@@ -27029,23 +27520,29 @@ var CommunitySession = class {
|
|
|
27029
27520
|
* count against the text's 16 KiB, so the box stays within what older apps accept. `reply`: the message it
|
|
27030
27521
|
* answers (`r`), counted the same way. `forwarded`: a forwarded text's hop count (`fw`, WISP 903 § Forwards). `card`:
|
|
27031
27522
|
* a status card, checked by the caller (`sc`, WISP 405 · Status Cards), counted the same way; the text is its fallback.
|
|
27523
|
+
* `file`: a file or voice message the text announces (`fl`, WISP 503 · Group Files), counted the same way; the text is
|
|
27524
|
+
* its caption, or the line an older app shows.
|
|
27032
27525
|
*/
|
|
27033
|
-
sendText(text, nick, now = Date.now(), mentions = [], reply, forwarded, card) {
|
|
27526
|
+
sendText(text, nick, now = Date.now(), mentions = [], reply, forwarded, card, file) {
|
|
27034
27527
|
return this.serialize(async () => {
|
|
27035
27528
|
if (!this.isMember) return { error: this.state.statusReason ?? "You are not in this group" };
|
|
27036
27529
|
const trimmed = text.trim();
|
|
27037
27530
|
if (!trimmed) return { error: "Nothing to send" };
|
|
27531
|
+
const described = file === void 0 ? void 0 : readGroupFileMeta(file);
|
|
27532
|
+
if (file !== void 0 && !described) return { error: "That file cannot go to a group" };
|
|
27038
27533
|
const named = validMentions(wireMentions(mentions), trimmed, false);
|
|
27039
27534
|
const answers = reply && readReply(wireReply(reply), groupReplyAuthor);
|
|
27040
27535
|
const replyBytes = answers ? utf8Encode(JSON.stringify(answers)).length : 0;
|
|
27536
|
+
const fileBytes = described ? utf8Encode(JSON.stringify(described)).length + 8 : 0;
|
|
27041
27537
|
const hops = readForwarded(forwarded);
|
|
27042
|
-
if (utf8Encode(trimmed).length + mentionsBytes(named) + replyBytes + (hops ? 16 : 0) + (card ? statusCardBytes(card) + 8 : 0) > COMMUNITY_LIMITS.textBytes) return { error: "Message exceeds 16 KiB" };
|
|
27538
|
+
if (utf8Encode(trimmed).length + mentionsBytes(named) + replyBytes + fileBytes + (hops ? 16 : 0) + (card ? statusCardBytes(card) + 8 : 0) > COMMUNITY_LIMITS.textBytes) return { error: "Message exceeds 16 KiB" };
|
|
27043
27539
|
const sent = await this.sendPayload(() => ({
|
|
27044
27540
|
text: trimmed,
|
|
27045
27541
|
...named.length ? { m: named } : {},
|
|
27046
27542
|
...answers ? { r: answers } : {},
|
|
27047
27543
|
...hops ? { fw: hops } : {},
|
|
27048
|
-
...card ? { sc: card } : {}
|
|
27544
|
+
...card ? { sc: card } : {},
|
|
27545
|
+
...described ? { fl: described } : {}
|
|
27049
27546
|
}), nick, now);
|
|
27050
27547
|
if ("error" in sent) return sent;
|
|
27051
27548
|
await this.hooks.message({
|
|
@@ -27058,7 +27555,8 @@ var CommunitySession = class {
|
|
|
27058
27555
|
...named.length ? { mentions: named } : {},
|
|
27059
27556
|
...answers ? { reply: answers } : {},
|
|
27060
27557
|
...hops ? { forwarded: hops } : {},
|
|
27061
|
-
...card ? { card } : {}
|
|
27558
|
+
...card ? { card } : {},
|
|
27559
|
+
...described ? { file: described } : {}
|
|
27062
27560
|
});
|
|
27063
27561
|
await this.say(sent.frame);
|
|
27064
27562
|
return { id: sent.id };
|
|
@@ -27414,6 +27912,7 @@ var CommunitySession = class {
|
|
|
27414
27912
|
const reply = parsed.r === void 0 ? void 0 : readReply(parsed.r, groupReplyAuthor);
|
|
27415
27913
|
const forwarded = readForwarded(parsed.fw);
|
|
27416
27914
|
const card = parsed.sc === void 0 ? void 0 : readStatusCard(parsed.sc);
|
|
27915
|
+
const file = parsed.fl === void 0 ? void 0 : readGroupFileMeta(parsed.fl) ?? void 0;
|
|
27417
27916
|
await this.hooks.message({
|
|
27418
27917
|
id,
|
|
27419
27918
|
sender: raw.s,
|
|
@@ -27424,7 +27923,8 @@ var CommunitySession = class {
|
|
|
27424
27923
|
...mentions.length ? { mentions } : {},
|
|
27425
27924
|
...reply ? { reply } : {},
|
|
27426
27925
|
...forwarded ? { forwarded } : {},
|
|
27427
|
-
...card ? { card } : {}
|
|
27926
|
+
...card ? { card } : {},
|
|
27927
|
+
...file ? { file } : {}
|
|
27428
27928
|
});
|
|
27429
27929
|
} else if (isObject$2(parsed.x)) await this.hooks.app?.({
|
|
27430
27930
|
id,
|
|
@@ -27516,10 +28016,15 @@ var CommunitySession = class {
|
|
|
27516
28016
|
this.ask(to);
|
|
27517
28017
|
}
|
|
27518
28018
|
ask(from) {
|
|
28019
|
+
const sync = this.question(from);
|
|
28020
|
+
if (sync) this.hooks.direct(from, sync);
|
|
28021
|
+
}
|
|
28022
|
+
/** My sync for a member, unless I asked it a moment ago. */
|
|
28023
|
+
question(from) {
|
|
27519
28024
|
const now = this.hooks.clock?.() ?? Date.now();
|
|
27520
|
-
if (now - (this.asked.get(from) ?? 0) < 5e3) return;
|
|
28025
|
+
if (now - (this.asked.get(from) ?? 0) < 5e3) return null;
|
|
27521
28026
|
this.asked.set(from, now);
|
|
27522
|
-
|
|
28027
|
+
return this.syncFrame();
|
|
27523
28028
|
}
|
|
27524
28029
|
async replayWaiting() {
|
|
27525
28030
|
await this.metaFollowsChain();
|
|
@@ -27537,6 +28042,11 @@ var CommunitySession = class {
|
|
|
27537
28042
|
let answered = this.syncsAnswered.get(from);
|
|
27538
28043
|
if (!answered) this.syncsAnswered.set(from, answered = new RateWindow(COMMUNITY_LIMITS.syncAnswers, COMMUNITY_LIMITS.syncWindowMs, () => this.hooks.clock?.() ?? Date.now()));
|
|
27539
28044
|
if (!answered.take()) return;
|
|
28045
|
+
const answer = [];
|
|
28046
|
+
const ask = () => {
|
|
28047
|
+
const sync = this.question(from);
|
|
28048
|
+
if (sync) answer.push(sync);
|
|
28049
|
+
};
|
|
27540
28050
|
const theirs = this.known.get(frame.h);
|
|
27541
28051
|
let start;
|
|
27542
28052
|
if (theirs && this.mainIndex.has(frame.h)) start = this.mainIndex.get(frame.h) + 1;
|
|
@@ -27546,18 +28056,18 @@ var CommunitySession = class {
|
|
|
27546
28056
|
} else {
|
|
27547
28057
|
const common = (Array.isArray(frame.loc) ? frame.loc : []).find((x) => typeof x === "string" && this.mainIndex.has(x));
|
|
27548
28058
|
start = common !== void 0 ? this.mainIndex.get(common) + 1 : Math.max(0, this.state.chain.length - COMMUNITY_LIMITS.side);
|
|
27549
|
-
|
|
28059
|
+
ask();
|
|
27550
28060
|
}
|
|
27551
28061
|
for (let i = start; i < this.state.chain.length; i++) {
|
|
27552
28062
|
const commit = this.state.chain[i], h = communityCommitHash(commit);
|
|
27553
|
-
|
|
28063
|
+
answer.push({
|
|
27554
28064
|
t: "group-commit",
|
|
27555
28065
|
v: 2,
|
|
27556
28066
|
g: this.id,
|
|
27557
28067
|
commit
|
|
27558
28068
|
});
|
|
27559
28069
|
const secret = this.state.secrets[h];
|
|
27560
|
-
if (secret && rosterHas(this.rosterAt(h) ?? [], from) && FRESH.has(commit.k))
|
|
28070
|
+
if (secret && rosterHas(this.rosterAt(h) ?? [], from) && FRESH.has(commit.k)) answer.push({
|
|
27561
28071
|
t: "group-secret",
|
|
27562
28072
|
v: 2,
|
|
27563
28073
|
g: this.id,
|
|
@@ -27571,7 +28081,7 @@ var CommunitySession = class {
|
|
|
27571
28081
|
h,
|
|
27572
28082
|
s: sealSecret(from, fromBase64Url(this.state.secrets[h]), secretAad(this.id, h, from))
|
|
27573
28083
|
}));
|
|
27574
|
-
if (secrets.length)
|
|
28084
|
+
if (secrets.length) answer.push({
|
|
27575
28085
|
t: "group-secrets",
|
|
27576
28086
|
v: 2,
|
|
27577
28087
|
g: this.id,
|
|
@@ -27588,8 +28098,8 @@ var CommunitySession = class {
|
|
|
27588
28098
|
if (!reads && !(mine && rosterHas(this.roster, from))) continue;
|
|
27589
28099
|
const high = have[stored.s]?.[seenKey(stored.e, stored.h)];
|
|
27590
28100
|
if (Number.isSafeInteger(high) && high >= stored.n) continue;
|
|
27591
|
-
if (!this.mainIndex.has(found.hash)) this.handSide(from, found.hash, handed);
|
|
27592
|
-
|
|
28101
|
+
if (!this.mainIndex.has(found.hash)) this.handSide(from, found.hash, handed, answer);
|
|
28102
|
+
answer.push(stored);
|
|
27593
28103
|
if (mine && reads) unheard.delete(frameKey(stored));
|
|
27594
28104
|
}
|
|
27595
28105
|
if (unheard.size !== (this.state.unheard?.length ?? 0)) {
|
|
@@ -27598,30 +28108,32 @@ var CommunitySession = class {
|
|
|
27598
28108
|
}
|
|
27599
28109
|
if (rosterHas(this.roster, from)) {
|
|
27600
28110
|
const entry = this.entryFrame(from);
|
|
27601
|
-
if (entry)
|
|
28111
|
+
if (entry) answer.push(entry);
|
|
27602
28112
|
}
|
|
27603
|
-
for (const request of this.state.pendingLeaves)
|
|
28113
|
+
for (const request of this.state.pendingLeaves) answer.push({
|
|
27604
28114
|
t: "group-leave",
|
|
27605
28115
|
v: 2,
|
|
27606
28116
|
g: this.id,
|
|
27607
28117
|
...request
|
|
27608
28118
|
});
|
|
27609
|
-
this.
|
|
27610
|
-
|
|
28119
|
+
const meta = this.metaFor(from, frame.mt);
|
|
28120
|
+
if (meta) answer.push(meta);
|
|
28121
|
+
ask();
|
|
28122
|
+
this.answers.handOut(from, answer);
|
|
27611
28123
|
}
|
|
27612
|
-
/** The commits of a branch I do not follow, from where it leaves mine, and the secrets I hold of those `to` was in
|
|
27613
|
-
handSide(to, tip, handed) {
|
|
28124
|
+
/** The commits of a branch I do not follow, from where it leaves mine, and the secrets I hold of those `to` was in, onto `answer`. */
|
|
28125
|
+
handSide(to, tip, handed, answer) {
|
|
27614
28126
|
for (const h of this.pathToMain(tip) ?? []) {
|
|
27615
28127
|
if (handed.has(h)) continue;
|
|
27616
28128
|
handed.add(h);
|
|
27617
|
-
|
|
28129
|
+
answer.push({
|
|
27618
28130
|
t: "group-commit",
|
|
27619
28131
|
v: 2,
|
|
27620
28132
|
g: this.id,
|
|
27621
28133
|
commit: this.known.get(h)
|
|
27622
28134
|
});
|
|
27623
28135
|
const secret = this.state.secrets[h];
|
|
27624
|
-
if (secret && rosterHas(this.rosterAt(h) ?? [], to))
|
|
28136
|
+
if (secret && rosterHas(this.rosterAt(h) ?? [], to)) answer.push({
|
|
27625
28137
|
t: "group-secret",
|
|
27626
28138
|
v: 2,
|
|
27627
28139
|
g: this.id,
|
|
@@ -27720,12 +28232,11 @@ var CommunitySession = class {
|
|
|
27720
28232
|
const meta = this.state.meta, secret = this.state.secrets[this.topHash];
|
|
27721
28233
|
return meta && secret ? wrapGroupMeta(meta, this.topHash, epochKeys(fromBase64Url(secret), this.id, this.epoch).message, true) : null;
|
|
27722
28234
|
}
|
|
27723
|
-
/** My statement,
|
|
27724
|
-
|
|
28235
|
+
/** My statement, for a member whose sync says it holds an older one (or none); none for an app that says nothing. */
|
|
28236
|
+
metaFor(to, theirTag) {
|
|
27725
28237
|
const meta = this.state.meta, theirs = parseGroupMetaTag(theirTag);
|
|
27726
|
-
if (!meta || theirs === null || !groupMetaNewer(meta, theirs) || !this.isMember || !rosterHas(this.roster, to)) return;
|
|
27727
|
-
|
|
27728
|
-
if (frame) this.hooks.direct(to, frame);
|
|
28238
|
+
if (!meta || theirs === null || !groupMetaNewer(meta, theirs) || !this.isMember || !rosterHas(this.roster, to)) return null;
|
|
28239
|
+
return this.metaFrame();
|
|
27729
28240
|
}
|
|
27730
28241
|
/**
|
|
27731
28242
|
* A metadata statement (from its author's edge or a hub's): kept when it is newer than mine, its
|
|
@@ -27936,6 +28447,13 @@ var APP_CLIENTS = [
|
|
|
27936
28447
|
"desktop",
|
|
27937
28448
|
"extension"
|
|
27938
28449
|
];
|
|
28450
|
+
/**
|
|
28451
|
+
* Where an app shows (WISP 1200 · Manifest, `view`). `chat`, the default when the manifest has none: inside a 1:1 chat
|
|
28452
|
+
* only, beside it on a wide screen. `full`: full screen, alone or from a chat, never beside one.
|
|
28453
|
+
*/
|
|
28454
|
+
var APP_VIEWS = ["chat", "full"];
|
|
28455
|
+
/** The view of a manifest: `chat` when it names none. */
|
|
28456
|
+
var appViewOf = (manifest) => manifest.view ?? "chat";
|
|
27939
28457
|
var REQUIRED = [
|
|
27940
28458
|
"ghostlyApp",
|
|
27941
28459
|
"publisher",
|
|
@@ -27953,6 +28471,7 @@ var REQUIRED = [
|
|
|
27953
28471
|
];
|
|
27954
28472
|
var OPTIONAL = [
|
|
27955
28473
|
"description",
|
|
28474
|
+
"view",
|
|
27956
28475
|
"sources",
|
|
27957
28476
|
"proofs",
|
|
27958
28477
|
"homepage",
|
|
@@ -28081,6 +28600,7 @@ function checkAppManifest(value) {
|
|
|
28081
28600
|
if (m.releaseNotes !== void 0 && !isLine$1(m.releaseNotes, APP_BUNDLE_LIMITS.releaseNotes)) return bad("releaseNotes");
|
|
28082
28601
|
const permissions = m.permissions;
|
|
28083
28602
|
if (!Array.isArray(permissions) || new Set(permissions).size !== permissions.length || !permissions.every((p) => APP_PERMISSIONS.includes(p))) return bad("permissions");
|
|
28603
|
+
if (m.view !== void 0 && !APP_VIEWS.includes(m.view)) return bad("view", `view is "chat" or "full"`);
|
|
28084
28604
|
const runtime = m.runtime;
|
|
28085
28605
|
if (!isObject$1(runtime) || Object.keys(runtime).length !== 2 || typeof runtime.host !== "string" || !HOST.test(runtime.host)) return bad("runtime");
|
|
28086
28606
|
const clients = runtime.clients;
|
|
@@ -28554,6 +29074,6 @@ function appStoreDecision(held, read) {
|
|
|
28554
29074
|
return read.digest === held.digest ? "same" : "equivocation";
|
|
28555
29075
|
}
|
|
28556
29076
|
//#endregion
|
|
28557
|
-
export { parseSshSignature as $, parseLocalTarget as $a, parseLinkPreview as $i, GROUP_SIGNAL_FRAME as $n, readHandoffPake as $o, EDIT_CAPABILITY as $r, readTurnPacket as $s, TEST_USDT_FAUCET_AMOUNT as $t, knockIdentity as A, APP_STATEMENT_LIMITS as Aa, toZ32 as Ac, irohDescriptor as Ai, normalizeDomain as An, handoffAttemptFailed as Ao, TYPING_REFRESH_MS as Ar, encodeInviteCode as As, didWebFile as At, GROUP_READ_NOTE as B, canonicalJsonBytes as Ba, sanitizeFileName as Bi, decodeBolt11 as Bn, handoffManifestFrames as Bo, readPin as Br, devicePingFrame as Bs, strictBase64Url as Bt, communityHasFrame as C, statusCardText as Ca, verify as Cc, revokesIdentity as Ci, decodeDnsResponse as Cn, HANDOFF_PAKE as Co, readLobby as Cr, verifyEnrollProof as Cs, DidError as Ct, decodeGroupEntryLink as D, receivedTimestamp as Da, fromBase64Url as Dc, validExternalKey as Di, encodeDnsQuery as Dn, HANDOFF_VERIFIED as Do, transferEnded as Dr, OwnInviteError as Ds, didJwsAlg as Dt, KNOCK_TTL_MS as E, heardTime as Ea, bytesEqual as Ec, emptyProofLedger as Ei, domainWellKnownFile as En, HANDOFF_TIMINGS as Eo, FILE_LIMITS as Er, readDeviceInvite as Es, didGhostlyServiceEntry as Et, GroupWakeLimiter as F, isAppRef as Fa, DHT_FILE_CAPABILITY as Fi, wellKnownUrl as Fn, handoffContextProof as Fo, PIN_LIMITS as Fr, HANDOFF_CAPABILITY as Fs, parseDid as Ft, carryMentions as G, verifyCommitSignature as Ga, fileMessageText as Gi, isFederationId as Gn, handoffStreamKey as Go, parseWakeFrame as Gr, TURN_RAISE_READ_TIMEOUT_MS as Gs, resolveDidDht as Gt, COMMUNITY_EDIT_FRAME as H, MAX_GROUP_CHAIN as Ha, PLAYABLE_AUDIO as Hi, cashuRequestPayload as Hn, handoffPakeFrame as Ho, WakeLimiter as Hr, seedSigner as Hs, didDhtFromPublicKey as Ht, groupWakeFrame as I, readAppRevocations as Ia, dhtFileId as Ii, requestLnurlInvoice as In, handoffDataFrame as Io, mayPin as Ir, deviceEchoFrame as Is, parseDidDocument as It, groupName as J, EXPECT_PEER_MS as Ja, parseVideoMeta as Ji, GhostLink as Jn, readHandoffAck as Jo, checkPushEndpoint as Jr, turnPutSummary as Js, EVM_TEST_CHAINS as Jt, parseCommunityEdit as K, edgeParams as Ka, parseVoiceMeta as Ki, parseCashuRequestPayload as Kn, handoffVersions as Ko, relayRequest as Kr, TURN_SETTLE_MS as Ks, signDidDhtPacket as Kt, groupWakes as L, signAppObject as La, parseImageMeta as Li, resolveLightningDestination as Ln, handoffDigest as Lo, nextPinNumber as Lr, deviceEchoNonce as Ls, parseDidSignaturePaste as Lt, mergeKnocks as M, appRef as Ma, utf8Encode as Mc, DHT_FILE_TOO_LARGE as Mi, recordsFromWellKnown as Mn, handoffBusyFrame as Mo, sanitizeTypingStatus as Mr, inviteOwnership as Ms, didWebFileUrl as Mt, readKnocks as N, checkAppRevokeStatement as Na, DHT_MESSAGE_TTL as Ni, recordsName as Nn, handoffCancelFrame as No, typingActivity as Nr, DEVICES_CAPABILITY as Ns, didWebHost as Nt, encodeGroupEntryLink as O, shownTime as Oa, randomBytes as Oc, TRANSPORTS as Oi, isPublicAddress as On, filePartName as Oo, transferStalled as Or, createChatInvite as Os, didSignedMessages as Ot, GROUP_WAKE_FRAME as P, isAppKey as Pa, peekDhtMailbox as Pi, txtValue as Pn, handoffContext as Po, GROUP_PIN_FRAME as Pr, ENROLL_CAPABILITY as Ps, normalizeDid as Pt, parseSshPublicKey as Q, formatLocalTarget as Qa, dhtEditId as Qi, CarriedTransport as Qn, readHandoffHello as Qo, BUTTONS_CAPABILITY as Qr, nextTurnPosition as Qs, SEPOLIA_TEST_USDT_FAUCET as Qt, parseGroupWakeFrame as R, signAppRevocation as Ra, readImageMeta as Ri, engineError as Rn, handoffEphemeral as Ro, pinIsNewer as Rr, deviceKeyZ32 as Rs, signingKeys as Rt, GROUP_READ_NOTE_COMMUNITY as S, sameButtonText as Sa, publicKeyFromZ32 as Sc, receivedIdentityStatus as Si, DOMAIN_FILE_MAX_BYTES as Sn, HANDOFF_OFFER as So, readBeaconHead as Sr, readEnrollHello as Ss, DID_JWS_MAX_LENGTH as St, encodeCommunityLink as T, claimedTime as Ta, sanitizeAvatar as Tc, PeerProofs as Ti, domainTxtRecord as Tn, HANDOFF_REQUEST as To, ChatFiles as Tr, deviceInviteJoinerParams as Ts, checkDidSignature as Tt, GROUP_EDIT_FRAME as U, MEMBER_KEY as Ua, VOICE_LIMITS as Ui, fedimintRequestPayload as Un, handoffReleaseFrame as Uo, deviceWakeRequest as Ur, webCryptoSigner as Us, encodeDidDhtDeactivated as Ut, GroupSession as V, GROUP_MEMBER_CAP as Va, sanitizeMime as Vi, ENDPOINT as Vn, handoffOfferFrame as Vo, WAKE_CALL_INTERVAL_MS as Vr, newDeviceSetSecret as Vs, didDhtDocument as Vt, GROUP_EDIT_TEXT_BYTES as W, rosterHas as Wa, baseMime as Wi, findEndpoint as Wn, handoffRetryAfter as Wo, newWakeToken as Wr, TURN_PUT_WINDOW_MS as Ws, encodeDidDhtPacket as Wt, SSHSIG_NAMESPACE as X, presenceSeenAt as Xa, EDIT_RATE_WINDOW_MS as Xi, LIVENESS_PING_MS as Xn, readHandoffDone as Xo, chatAppId as Xr, TURN_MAX as Xs, PaymentPreflightError as Xt, SSHSIG_MAX_ARMOR as Y, RELAY_POLL_INTERVALS as Ya, EDIT_BUFFER_MS as Yi, INVITE_TAKEN as Yn, readHandoffCancel as Yo, vapidKeysMatch as Yr, TURN_LAST_SEQUENCE as Ys, ONCHAIN_PROVIDER as Yt, SshSigError as Z, GhostlyHttpError as Za, RateWindow as Zi, pairedMessageFrame as Zn, readHandoffHave as Zo, isAppVersion as Zr, firstDeviceSetSecret as Zs, SEPOLIA_TEST_USDT as Zt, groupTypingEpoch as _, ITEM_STATES as _a, openRelayPayload as _c, boundVerifiedIdentity as _i, pubkyProofCapability as _n, HANDOFF_ACK as _o, mergeLobby as _r, enrollDoneFrame as _s, shortAtprotoDid as _t, checkAppStoreIndex as a, ReactionWindow as aa, RelayTransport as ac, HoldRefusedError as ai, validatePaymentTarget as an, RTC_CONFIG as ao, MESH_HUBS as ar, readSetUpdate as as, decodeAnyBitcoinAddress as at, COMMUNITY_LIMITS as b, buttonLabelClash as ba, identityFromSeed as bc, identityStatement as bi, homeserverWebEndpoint as bn, HANDOFF_HAVE as bo, rankHubs as br, enrollProofFrame as bs, DID_DOCUMENT_MAX_BYTES as bt, APP_BUNDLE_LIMITS as c, reactionEmoji as ca, setLinkTraceSink as cc, CapsExchange as ci, SPARK_PROVIDER as cn, callRtcConfig as co, pickMeshHubs as cr, signTombstone as cs, ATPROTO_PROOF_COLLECTION as ct, assembleAppBundle as d, validReactionNumber as da, isDiscoveryBudgetError as dc, dialDescriptors as di, isBitcoinAddress as dn, parseCallSignal as do, beaconRecords as dr, ENROLL_GRANT as ds, atprotoProofRecord as dt, forwardedAgain as ea, signTurnPacket as ec, UPGRADE_CAPABILITY as ei, USDT_PUBLIC_RPC as en, webLocalFetch as eo, SIGNAL_FRESH_MS as er, readHandoffTurnFrame as es, sameSshKey as et, checkAppManifest as f, wireReaction as fa, withRequestOptions as fc, IDENTITY_MAX_EVIDENCE as fi, PUBKY_PROOF_MAX_BYTES as fn, signalHasVideo as fo, doorHubs as fr, ENROLL_HELLO as fs, atprotoProofRkey as ft, GroupTypingBoard as g, BUTTON_ID as ga, newerPacket as gc, IdentityExchange as gi, normalizePubkyKey as gn, serviceIdFromName as go, mergeBeacon as gr, enrollDigits as gs, parseAtprotoDidDocument as gt, GROUP_TYPING_FRAME as h, replySnippet as ha, createRelayPayload as hc, IdentityCheckUnavailable as hi, newPubkyProofFolder as hn, LEGACY_SERVICES as ho, lobbyRecords as hr, enrollCancelReason as hs, normalizeAtprotoHandle as ht, planAppUpdate as i, REACTION_LIMITS as ia, DEFAULT_RELAYS as ic, HoldKeys as ii, formatPaymentAmount as in, CALL_SIGNAL_MAX_AGE_MS as io, readGroupSignal as ir, SET_UPDATE as is, BitcoinAddressError as it, knockRecords as j, appFingerprint as ja, utf8Decode as jc, irohRelayUrl as ji, recordsFromTxt as jn, handoffAttemptSucceeded as jo, TypingSender as jr, inviteLink as js, didWebFileNames as jt, entryParams as k, APP_PREFIXES as ka, toBase64Url as kc, automaticTransport as ki, nip05RootKey as kn, handoffAttemptAllowed as ko, TYPING_KINDS as kr, decodeInviteCode as ks, didWebDocumentUrl as kt, APP_BUNDLE_MAGIC as l, reactionIsNewer as la, traceLink as lc, DHT_TEXT_CAPABILITY as li, sparkAddressKind as ln, callSignalHeardAt as lo, COMMUNITY_TOPOLOGY as lr, verifySetUpdate as ls, ATPROTO_PROOF_SCOPE as lt, readAppBundle as m, readReply as ma, RELAY_PAYLOAD_MAX_BYTES as mc, IDENTITY_REVOCATION_LABEL as mi, isPubkyProofFolder as mn, HTTP_SERVICE_PROTO as mo, lobbyKeys as mr, enrollCancelFrame as ms, isAtprotoDid as mt, appUpdateDecision as n, COMMUNITY_REACTION_FRAME as na, turnKeys as nc, HOLD_DATED_BACK_MS as ni, assertTokenUnits as nn, decodeControl as no, SIGNAL_REPLY_MS as nr, sealHandoffPiece as ns, verifySshSignature as nt, readAppStore as o, nextReactionNumber as oa, currentRelays as oc, newMailbox as oi, walletNetworkOf as on, answersOffer as oo, mayBeHub as or, setAckFrame as os, ATPROTO_DECLARED_SCOPE as ot, isAppUrl as p, pairedReplyAuthor as pa, PacketTooLargeError as pc, IDENTITY_MAX_VALIDITY as pi, isPubkyKey as pn, emptyLinkRecords as po, freshHubs as pr, ENROLL_PROOF as ps, checkAtprotoProofRecord as pt, mentionsMember as q, DirectPathWatch as qa, voicePeaksOf as qi, parseFedimintRequestPayload as qn, newHandoffId as qo, wakeRequest as qr, classifyTurnRead as qs, ETHEREUM_USDT as qt, checkAppBeforeRun as r, GROUP_REACTION_FRAME as ra, turnName as rc, HOLD_LIMITS as ri, assertWholeSats as rn, encodeControl as ro, groupSignalFrame as rr, SET_ACK as rs, verifyBitcoinMessage as rt, signAppStore as s, queueReaction as sa, normalizeRelayUrl as sc, readManifest as si, SPARK_NETWORKS as sn, buildSdpFromSignal as so, meshHubs as sr, setUpdateFrame as ss, ATPROTO_FULL_SCOPE as st, appStoreDecision as t, readForwarded as ta, signTurnRelease as tc, HOLD_CAPABILITY as ti, WALLET_NETWORKS as tn, LIMITS as to, SIGNAL_HOLD_MS as tr, readHandoffVerified as ts, sshSignCommand as tt, appDigest as u, readReaction as ua, budgetRetryMs as uc, capsDescriptors as ui, sparkInvoiceDetails as un, extractParamsFromSdp as uo, beaconKeys as ur, ENROLL_DONE as us, atprotoDidMethod as ut, openGroupTyping as v, RUN_RESULTS as va, parseRelayPayload as vc, emptyIdentityLedger as vi, pubkyProofFile as vn, HANDOFF_CANCEL as vo, newerHead as vr, enrollGrantFrame as vs, AtprotoProofError as vt, decodeCommunityLink as w, arrivalKey as wa, MAX_AVATAR_LENGTH as wc, lastSharedWithMe as wi, dnsProofName as wn, HANDOFF_PIECE_BYTES as wo, shouldBeHub as wr, createDeviceInvite as ws, checkDidJws as wt, CommunitySession as x, checkStatusCard as xa, identityFromSeedB64 as xc, newIdentityBinding as xi, pubkyHomeserverOf as xn, HANDOFF_HELLO as xo, readBeacon as xr, readEnrollGrant as xs, DID_FILE_MAX_BYTES as xt, sealGroupTyping as y, STATUS_CARD_LIMITS as ya, createIdentity as yc, identityRevocationValue as yi, pubkyProofPath as yn, HANDOFF_DONE as yo, pickHubs as yr, enrollHelloFrame as ys, verifyAtprotoRecordProof as yt, GROUP_REACTED_FRAME as z, canonicalJson as za, safeBlobType as zi, engineText as zn, handoffHelloFrame as zo, pinNumberHolds as zr, deviceLinkPairing as zs, staticDidDocument as zt };
|
|
29077
|
+
export { parseSshPublicKey as $, signAppRevocation as $a, formatFileSize as $i, GROUP_FILE_LIMITS as $n, handoffEphemeral as $o, WAKE_CALL_INTERVAL_MS as $r, deviceKeyZ32 as $s, SEPOLIA_TEST_USDT_FAUCET as $t, entryParams as A, replySnippet as Aa, createRelayPayload as Ac, IdentityExchange as Ai, nip05RootKey as An, LEGACY_SERVICES as Ao, newerHead as Ar, enrollCancelReason as As, didWebDocumentUrl as At, GROUP_REACTED_FRAME as B, claimedTime as Ba, sanitizeAvatar as Bc, emptyProofLedger as Bi, engineText as Bn, HANDOFF_REQUEST as Bo, transferStalled as Br, deviceInviteJoinerParams as Bs, staticDidDocument as Bt, GROUP_READ_NOTE_COMMUNITY as C, reactionEmoji as Ca, setLinkTraceSink as Cc, DHT_TEXT_CAPABILITY as Ci, DOMAIN_FILE_MAX_BYTES as Cn, callRtcConfig as Co, beaconRecords as Cr, signTombstone as Cs, DID_JWS_MAX_LENGTH as Ct, KNOCK_TTL_MS as D, wireReaction as Da, withRequestOptions as Dc, IDENTITY_MAX_VALIDITY as Di, domainWellKnownFile as Dn, signalHasVideo as Do, lobbyRecords as Dr, ENROLL_HELLO as Ds, didGhostlyServiceEntry as Dt, encodeCommunityLink as E, validReactionNumber as Ea, isDiscoveryBudgetError as Ec, IDENTITY_MAX_EVIDENCE as Ei, domainTxtRecord as En, parseCallSignal as Eo, lobbyKeys as Er, ENROLL_GRANT as Es, checkDidSignature as Et, GROUP_WAKE_FRAME as F, buttonLabelClash as Fa, identityFromSeed as Fc, newIdentityBinding as Fi, txtValue as Fn, HANDOFF_HAVE as Fo, readLobby as Fr, enrollProofFrame as Fs, normalizeDid as Ft, GROUP_EDIT_TEXT_BYTES as G, APP_STATEMENT_LIMITS as Ga, toZ32 as Gc, irohRelayUrl as Gi, findEndpoint as Gn, handoffAttemptFailed as Go, typingActivity as Gr, encodeInviteCode as Gs, encodeDidDhtPacket as Gt, GroupSession as H, receivedTimestamp as Ha, fromBase64Url as Hc, TRANSPORTS as Hi, ENDPOINT as Hn, HANDOFF_VERIFIED as Ho, TYPING_REFRESH_MS as Hr, OwnInviteError as Hs, didDhtDocument as Ht, GroupWakeLimiter as I, checkStatusCard as Ia, identityFromSeedB64 as Ic, receivedIdentityStatus as Ii, wellKnownUrl as In, HANDOFF_HELLO as Io, shouldBeHub as Ir, readEnrollGrant as Is, parseDid as It, mentionsMember as J, checkAppRevokeStatement as Ja, peekDhtMailbox as Ji, parseFedimintRequestPayload as Jn, handoffCancelFrame as Jo, mayPin as Jr, DEVICES_CAPABILITY as Js, ETHEREUM_USDT as Jt, carryMentions as K, appFingerprint as Ka, utf8Decode as Kc, DHT_FILE_TOO_LARGE as Ki, isFederationId as Kn, handoffAttemptSucceeded as Ko, GROUP_PIN_FRAME as Kr, inviteLink as Ks, resolveDidDht as Kt, groupWakeFrame as L, sameButtonText as La, publicKeyFromZ32 as Lc, revokesIdentity as Li, requestLnurlInvoice as Ln, HANDOFF_OFFER as Lo, ChatFiles as Lr, readEnrollHello as Ls, parseDidDocument as Lt, knockRecords as M, ITEM_STATES as Ma, openRelayPayload as Mc, emptyIdentityLedger as Mi, recordsFromTxt as Mn, HANDOFF_ACK as Mo, rankHubs as Mr, enrollDoneFrame as Ms, didWebFileNames as Mt, mergeKnocks as N, RUN_RESULTS as Na, parseRelayPayload as Nc, identityRevocationValue as Ni, recordsFromWellKnown as Nn, HANDOFF_CANCEL as No, readBeacon as Nr, enrollGrantFrame as Ns, didWebFileUrl as Nt, decodeGroupEntryLink as O, pairedReplyAuthor as Oa, PacketTooLargeError as Oc, IDENTITY_REVOCATION_LABEL as Oi, encodeDnsQuery as On, emptyLinkRecords as Oo, mergeBeacon as Or, ENROLL_PROOF as Os, didJwsAlg as Ot, readKnocks as P, STATUS_CARD_LIMITS as Pa, createIdentity as Pc, identityStatement as Pi, recordsName as Pn, HANDOFF_DONE as Po, readBeaconHead as Pr, enrollHelloFrame as Ps, didWebHost as Pt, SshSigError as Q, signAppObject as Qa, readImageMeta as Qi, pairedMessageFrame as Qn, handoffDigest as Qo, readPin as Qr, deviceEchoNonce as Qs, SEPOLIA_TEST_USDT as Qt, groupWakes as R, statusCardText as Ra, verify as Rc, lastSharedWithMe as Ri, resolveLightningDestination as Rn, HANDOFF_PAKE as Ro, FILE_LIMITS as Rr, verifyEnrollProof as Rs, parseDidSignaturePaste as Rt, CommunitySession as S, queueReaction as Sa, normalizeRelayUrl as Sc, CapsExchange as Si, pubkyHomeserverOf as Sn, buildSdpFromSignal as So, beaconKeys as Sr, setUpdateFrame as Ss, DID_FILE_MAX_BYTES as St, decodeCommunityLink as T, readReaction as Ta, budgetRetryMs as Tc, dialDescriptors as Ti, dnsProofName as Tn, extractParamsFromSdp as To, freshHubs as Tr, ENROLL_DONE as Ts, checkDidJws as Tt, COMMUNITY_EDIT_FRAME as U, shownTime as Ua, randomBytes as Uc, automaticTransport as Ui, cashuRequestPayload as Un, filePartName as Uo, TypingSender as Ur, createChatInvite as Us, didDhtFromPublicKey as Ut, GROUP_READ_NOTE as V, heardTime as Va, bytesEqual as Vc, validExternalKey as Vi, decodeBolt11 as Vn, HANDOFF_TIMINGS as Vo, TYPING_KINDS as Vr, readDeviceInvite as Vs, strictBase64Url as Vt, GROUP_EDIT_FRAME as W, APP_PREFIXES as Wa, toBase64Url as Wc, irohDescriptor as Wi, fedimintRequestPayload as Wn, handoffAttemptAllowed as Wo, sanitizeTypingStatus as Wr, decodeInviteCode as Ws, encodeDidDhtDeactivated as Wt, SSHSIG_MAX_ARMOR as X, isAppRef as Xa, dhtFileId as Xi, INVITE_TAKEN as Xn, handoffContextProof as Xo, pinIsNewer as Xr, HANDOFF_CAPABILITY as Xs, ONCHAIN_PROVIDER as Xt, groupName as Y, isAppKey as Ya, DHT_FILE_CAPABILITY as Yi, GhostLink as Yn, handoffContext as Yo, nextPinNumber as Yr, ENROLL_CAPABILITY as Ys, EVM_TEST_CHAINS as Yt, SSHSIG_NAMESPACE as Z, readAppRevocations as Za, parseImageMeta as Zi, LIVENESS_PING_MS as Zn, handoffDataFrame as Zo, pinNumberHolds as Zr, deviceEchoFrame as Zs, PaymentPreflightError as Zt, GroupTypingBoard as _, COMMUNITY_REACTION_FRAME as _a, turnKeys as _c, HOLD_LIMITS as _i, normalizePubkyKey as _n, decodeControl as _o, MESH_HUBS as _r, sealHandoffPiece as _s, parseAtprotoDidDocument as _t, checkAppStoreIndex as a, baseMime as aa, TURN_PUT_WINDOW_MS as ac, wakeRequest as ai, formatPaymentAmount as an, rosterHas as ao, isGroupMessageId as ar, handoffRetryAfter as as, BitcoinAddressError as at, sealGroupTyping as b, ReactionWindow as ba, RelayTransport as bc, newMailbox as bi, pubkyProofPath as bn, RTC_CONFIG as bo, pickMeshHubs as br, readSetUpdate as bs, verifyAtprotoRecordProof as bt, APP_BUNDLE_LIMITS as c, voicePeaksOf as ca, classifyTurnRead as cc, chatAppId as ci, SPARK_NETWORKS as cn, DirectPathWatch as co, readGroupWant as cr, newHandoffId as cs, ATPROTO_FULL_SCOPE as ct, appViewOf as d, EDIT_RATE_WINDOW_MS as da, TURN_MAX as dc, BUTTONS_CAPABILITY as di, sparkInvoiceDetails as dn, presenceSeenAt as do, GROUP_SIGNAL_FRAME as dr, readHandoffDone as ds, atprotoDidMethod as dt, safeBlobType as ea, deviceLinkPairing as ec, WakeLimiter as ei, TEST_USDT_FAUCET_AMOUNT as en, canonicalJson as eo, GROUP_HAVE_FRAME as er, handoffHelloFrame as es, parseSshSignature as et, assembleAppBundle as f, RateWindow as fa, firstDeviceSetSecret as fc, EDIT_CAPABILITY as fi, isBitcoinAddress as fn, GhostlyHttpError as fo, SIGNAL_FRESH_MS as fr, readHandoffHave as fs, atprotoProofRecord as ft, GROUP_TYPING_FRAME as g, readForwarded as ga, signTurnRelease as gc, HOLD_DATED_BACK_MS as gi, newPubkyProofFolder as gn, LIMITS as go, readGroupSignal as gr, readHandoffVerified as gs, normalizeAtprotoHandle as gt, readAppBundle as h, forwardedAgain as ha, signTurnPacket as hc, HOLD_CAPABILITY as hi, isPubkyProofFolder as hn, webLocalFetch as ho, groupSignalFrame as hr, readHandoffTurnFrame as hs, isAtprotoDid as ht, planAppUpdate as i, VOICE_LIMITS as ia, webCryptoSigner as ic, relayRequest as ii, assertWholeSats as in, MEMBER_KEY as io, groupFileFallback as ir, handoffReleaseFrame as is, verifyBitcoinMessage as it, knockIdentity as j, BUTTON_ID as ja, newerPacket as jc, boundVerifiedIdentity as ji, normalizeDomain as jn, serviceIdFromName as jo, pickHubs as jr, enrollDigits as js, didWebFile as jt, encodeGroupEntryLink as k, readReply as ka, RELAY_PAYLOAD_MAX_BYTES as kc, IdentityCheckUnavailable as ki, isPublicAddress as kn, HTTP_SERVICE_PROTO as ko, mergeLobby as kr, enrollCancelFrame as ks, didSignedMessages as kt, APP_BUNDLE_MAGIC as l, parseVideoMeta as la, turnPutSummary as lc, isAppVersion as li, SPARK_PROVIDER as ln, EXPECT_PEER_MS as lo, readGroupWantNo as lr, readHandoffAck as ls, ATPROTO_PROOF_COLLECTION as lt, isAppUrl as m, parseLinkPreview as ma, readTurnPacket as mc, UPGRADE_CAPABILITY as mi, isPubkyKey as mn, parseLocalTarget as mo, SIGNAL_REPLY_MS as mr, readHandoffPake as ms, checkAtprotoProofRecord as mt, appUpdateDecision as n, sanitizeMime as na, newDeviceSetSecret as nc, newWakeToken as ni, WALLET_NETWORKS as nn, GROUP_MEMBER_CAP as no, GROUP_WANT_NO_FRAME as nr, handoffOfferFrame as ns, sshSignCommand as nt, readAppStore as o, fileMessageText as oa, TURN_RAISE_READ_TIMEOUT_MS as oc, checkPushEndpoint as oi, validatePaymentTarget as on, verifyCommitSignature as oo, readGroupFileMeta as or, handoffStreamKey as os, decodeAnyBitcoinAddress as ot, checkAppManifest as p, dhtEditId as pa, nextTurnPosition as pc, STATUS_CARD_CAPABILITY as pi, PUBKY_PROOF_MAX_BYTES as pn, formatLocalTarget as po, SIGNAL_HOLD_MS as pr, readHandoffHello as ps, atprotoProofRkey as pt, parseCommunityEdit as q, appRef as qa, utf8Encode as qc, DHT_MESSAGE_TTL as qi, parseCashuRequestPayload as qn, handoffBusyFrame as qo, PIN_LIMITS as qr, inviteOwnership as qs, signDidDhtPacket as qt, checkAppBeforeRun as r, PLAYABLE_AUDIO as ra, seedSigner as rc, parseWakeFrame as ri, assertTokenUnits as rn, MAX_GROUP_CHAIN as ro, fetchesByItself as rr, handoffPakeFrame as rs, verifySshSignature as rt, signAppStore as s, parseVoiceMeta as sa, TURN_SETTLE_MS as sc, vapidKeysMatch as si, walletNetworkOf as sn, edgeParams as so, readGroupHave as sr, handoffVersions as ss, ATPROTO_DECLARED_SCOPE as st, appStoreDecision as t, sanitizeFileName as ta, devicePingFrame as tc, deviceWakeRequest as ti, USDT_PUBLIC_RPC as tn, canonicalJsonBytes as to, GROUP_WANT_FRAME as tr, handoffManifestFrames as ts, sameSshKey as tt, appDigest as u, EDIT_BUFFER_MS as ua, TURN_LAST_SEQUENCE as uc, APPS_CAPABILITY as ui, sparkAddressKind as un, RELAY_POLL_INTERVALS as uo, CarriedTransport as ur, readHandoffCancel as us, ATPROTO_PROOF_SCOPE as ut, groupTypingEpoch as v, GROUP_REACTION_FRAME as va, turnName as vc, HoldKeys as vi, pubkyProofCapability as vn, encodeControl as vo, mayBeHub as vr, SET_ACK as vs, shortAtprotoDid as vt, communityHasFrame as w, reactionIsNewer as wa, traceLink as wc, capsDescriptors as wi, decodeDnsResponse as wn, callSignalHeardAt as wo, doorHubs as wr, verifySetUpdate as ws, DidError as wt, COMMUNITY_LIMITS as x, nextReactionNumber as xa, currentRelays as xc, readManifest as xi, homeserverWebEndpoint as xn, answersOffer as xo, COMMUNITY_TOPOLOGY as xr, setAckFrame as xs, DID_DOCUMENT_MAX_BYTES as xt, openGroupTyping as y, REACTION_LIMITS as ya, DEFAULT_RELAYS as yc, HoldRefusedError as yi, pubkyProofFile as yn, CALL_SIGNAL_MAX_AGE_MS as yo, meshHubs as yr, SET_UPDATE as ys, AtprotoProofError as yt, parseGroupWakeFrame as z, arrivalKey as za, MAX_AVATAR_LENGTH as zc, PeerProofs as zi, engineError as zn, HANDOFF_PIECE_BYTES as zo, transferEnded as zr, createDeviceInvite as zs, signingKeys as zt };
|
|
28558
29078
|
|
|
28559
|
-
//# sourceMappingURL=src-
|
|
29079
|
+
//# sourceMappingURL=src-DtxVo2Rm.js.map
|