@ghostlytools/cli 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +6 -6
  2. package/SKILL.md +1 -1
  3. package/dist/assets/argon2id-088uQfnH.js.map +1 -1
  4. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  5. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  6. package/dist/assets/breez_sdk_spark_wasm_bg.wasm +0 -0
  7. package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  8. package/dist/assets/{hyperdhtRelay-CVLCuqVr.js → hyperdhtRelay-BoxPHp7v.js} +3 -3
  9. package/dist/assets/{hyperdhtRelay-CVLCuqVr.js.map → hyperdhtRelay-BoxPHp7v.js.map} +1 -1
  10. package/dist/assets/legacy_ciphers-JJOu0JXL.js.map +1 -1
  11. package/dist/assets/{mainline-CKFpwXcB.js → mainline-dkA94eh2.js} +2 -2
  12. package/dist/assets/{mainline-CKFpwXcB.js.map → mainline-dkA94eh2.js.map} +1 -1
  13. package/dist/assets/nacl-fast-CDhNCYHs.js.map +1 -1
  14. package/dist/assets/noble_curves-DDWn5AH0.js.map +1 -1
  15. package/dist/assets/noble_hashes-B7AZyFyS.js.map +1 -1
  16. package/dist/assets/openpgp-BBFr-zHQ.js.map +1 -1
  17. package/dist/assets/{server-vGoe9MET.js → server-CXFTDnFI.js} +285 -99
  18. package/dist/assets/server-CXFTDnFI.js.map +1 -0
  19. package/dist/assets/sha512-BYId-G-T.js.map +1 -1
  20. package/dist/assets/{src-CJdPTl7Q.js → src-K3NijMq-.js} +455 -87
  21. package/dist/assets/src-K3NijMq-.js.map +1 -0
  22. package/dist/assets/storage-Bya5b2Xt.js.map +1 -1
  23. package/dist/assets/tree-store-N362nuzZ.js.map +1 -1
  24. package/dist/assets/unbzip2-stream-B1C-JX9L.js.map +1 -1
  25. package/dist/assets/{web-OUQl4Q7L.js → web-DwgdhfcY.js} +16 -16
  26. package/dist/assets/{web-OUQl4Q7L.js.map → web-DwgdhfcY.js.map} +1 -1
  27. package/dist/ghostly.mjs +55 -40
  28. package/dist/ghostly.mjs.map +1 -1
  29. package/examples/call-echo.mjs +1 -1
  30. package/package.json +7 -7
  31. package/dist/assets/server-vGoe9MET.js.map +0 -1
  32. package/dist/assets/src-CJdPTl7Q.js.map +0 -1
@@ -4349,7 +4349,7 @@ function buildSdpFromSignal(signal) {
4349
4349
  function sdpHasCandidates(sdp) {
4350
4350
  return /^a=candidate:/m.test(sdp ?? "");
4351
4351
  }
4352
- /** How long to keep collecting after the candidate we were waiting for showed up. */
4352
+ /** How long to keep collecting after the candidate we were waiting for showed up (a call's; a chat's data link sets its own). */
4353
4353
  var ICE_SETTLE_MS = 400;
4354
4354
  /** IPv4 ranges that are not reachable from the internet: this network, private, shared (CGNAT), loopback, link-local, the documentation ones, benchmarking, multicast and up. */
4355
4355
  var NOT_GLOBAL_IPV4 = [
@@ -4393,7 +4393,7 @@ function isGlobalIpv4Host(candidate) {
4393
4393
  * (`isGlobalIpv4Host`): a device with no NAT gets none, and its offers waited
4394
4394
  * the whole timeout whenever gathering did not complete.
4395
4395
  */
4396
- function waitForIceGathering(pc, timeoutMs = 1e4, { stallMs, fresh = false } = {}) {
4396
+ function waitForIceGathering(pc, timeoutMs = 1e4, { stallMs, fresh = false, settleMs = ICE_SETTLE_MS, hostBoundMs } = {}) {
4397
4397
  return new Promise((resolve) => {
4398
4398
  if (pc.iceGatheringState === "complete" && (!fresh || sdpHasCandidates(pc.localDescription?.sdp))) {
4399
4399
  resolve();
@@ -4406,6 +4406,7 @@ function waitForIceGathering(pc, timeoutMs = 1e4, { stallMs, fresh = false } = {
4406
4406
  const finish = () => {
4407
4407
  clearTimeout(timeout);
4408
4408
  if (stalled) clearTimeout(stalled);
4409
+ if (bounded) clearTimeout(bounded);
4409
4410
  if (settle) clearTimeout(settle);
4410
4411
  pc.removeEventListener("icegatheringstatechange", onState);
4411
4412
  pc.removeEventListener("icecandidate", onCandidate);
@@ -4415,13 +4416,16 @@ function waitForIceGathering(pc, timeoutMs = 1e4, { stallMs, fresh = false } = {
4415
4416
  const stalled = stallMs === void 0 ? null : setTimeout(() => {
4416
4417
  if (!found && !sdpHasCandidates(pc.localDescription?.sdp)) finish();
4417
4418
  }, stallMs);
4419
+ const bounded = hostBoundMs === void 0 ? null : setTimeout(() => {
4420
+ if (found || sdpHasCandidates(pc.localDescription?.sdp)) finish();
4421
+ }, hostBoundMs);
4418
4422
  const onState = () => {
4419
4423
  if (pc.iceGatheringState === "complete" && (!fresh || sdpHasCandidates(pc.localDescription?.sdp))) finish();
4420
4424
  };
4421
4425
  const onCandidate = (event) => {
4422
4426
  if (event.candidate) found = true;
4423
4427
  const line = event.candidate?.candidate;
4424
- if (!settle && line && (line.includes(wanted) || !usesTurn && isGlobalIpv4Host(line))) settle = setTimeout(finish, ICE_SETTLE_MS);
4428
+ if (!settle && line && (line.includes(wanted) || !usesTurn && isGlobalIpv4Host(line))) settle = setTimeout(finish, settleMs);
4425
4429
  };
4426
4430
  pc.addEventListener("icegatheringstatechange", onState);
4427
4431
  pc.addEventListener("icecandidate", onCandidate);
@@ -6087,11 +6091,27 @@ var DirectPathWatch = class {
6087
6091
  this.onChange(blocked);
6088
6092
  }
6089
6093
  };
6094
+ //#endregion
6095
+ //#region ../core/src/datalink.ts
6096
+ /**
6097
+ * An attempt ended from outside (another transport went live first) that had both descriptions for this long and no
6098
+ * connection says what one that failed does: a path that exists connects within a second or two.
6099
+ */
6100
+ var STALLED_EVIDENCE_MS = 5e3;
6090
6101
  /** A candidate another network can reach: server reflexive (STUN answered), relayed (TURN), or learnt from the peer. */
6091
6102
  var PUBLIC_CANDIDATE = /^a=candidate:.* typ (srflx|relay|prflx)\b/m;
6092
6103
  var CONNECT_TIMEOUT_MS = 9e4;
6093
6104
  var ICE_GATHERING_TIMEOUT_MS = 5e3;
6094
6105
  /**
6106
+ * An offer with its host candidates waits this long at most for a server reflexive one (STUN answered), not
6107
+ * `ICE_GATHERING_TIMEOUT_MS`. Where the STUN servers answer, they do in some 50 ms (0.44 s an offer took here, with the
6108
+ * settle); where they do not, every offer of a first pairing waited the full 5 s, twice when the inviter offered too
6109
+ * (One's e2e runs: 13 offers of 89 at 5 s, 9 more at 1.3-3 s, 2026-10-05). An offer without one still connects to most
6110
+ * contacts (their checks learn this side's address from its own); behind a restricted NAT on the contact's side it may
6111
+ * not, and the next offer of the chat then waits as long as it takes (`fullGather`).
6112
+ */
6113
+ var OFFER_HOST_GATHER_MS = 2e3;
6114
+ /**
6095
6115
  * A connection with no candidate at all by then has stalled: Chromium, rarely and under load, gathers none, and an
6096
6116
  * offer or answer without one can never connect. It is made again in its place, up to `GATHER_ATTEMPTS` in all.
6097
6117
  */
@@ -6105,6 +6125,13 @@ var DataLink = class {
6105
6125
  state = "idle";
6106
6126
  pc = null;
6107
6127
  myOfferTs = 0;
6128
+ /**
6129
+ * An offer of this side's that went without a public candidate came to nothing (it was answered, and no path
6130
+ * connected): the next one waits for its STUN servers as long as it takes (`ICE_GATHERING_TIMEOUT_MS`).
6131
+ */
6132
+ fullGather = false;
6133
+ /** The offer out has no public candidate (as it went: the connection's description gains any that come later). */
6134
+ offerLacksPublic = false;
6108
6135
  lastSignalTs = 0;
6109
6136
  /** The peer's description as this connection was given it: Chrome shows it as `remoteDescription` only once applied. */
6110
6137
  remoteSdp = null;
@@ -6118,17 +6145,35 @@ var DataLink = class {
6118
6145
  this.options = options;
6119
6146
  }
6120
6147
  /** Offers a connection to the peer. Resolves once the offer is published. */
6121
- async connect() {
6148
+ /**
6149
+ * Makes an offer and publishes it. `gate`: gathered ahead, while the caller still finds out whether the peer is there
6150
+ * (a joiner reading the inviter's record): the link stays idle meanwhile, and the offer goes once the gate says yes.
6151
+ * On no, the connection is dropped, nothing having been said, and `"held"` is the answer.
6152
+ */
6153
+ async connect(gate) {
6122
6154
  if (this.state !== "idle") return;
6123
6155
  this.answered = null;
6124
- this.setState("offering");
6125
- this.options.setFastPoll(true, true);
6156
+ if (!gate) {
6157
+ this.setState("offering");
6158
+ this.options.setFastPoll(true, true);
6159
+ }
6126
6160
  try {
6127
6161
  const pc = await this.gathered(true, async (pc) => {
6128
6162
  await pc.setLocalDescription(await pc.createOffer());
6129
6163
  });
6130
6164
  if (!pc) return;
6165
+ if (gate) {
6166
+ const go = await gate;
6167
+ if (this.pc !== pc || this.state !== "idle") return;
6168
+ if (!go) {
6169
+ this.teardown();
6170
+ return "held";
6171
+ }
6172
+ this.setState("offering");
6173
+ this.options.setFastPoll(true, true);
6174
+ }
6131
6175
  this.myOfferTs = Date.now();
6176
+ this.offerLacksPublic = !PUBLIC_CANDIDATE.test(pc.localDescription?.sdp ?? "");
6132
6177
  const signal = {
6133
6178
  t: "o",
6134
6179
  ts: this.myOfferTs,
@@ -6221,11 +6266,17 @@ var DataLink = class {
6221
6266
  }
6222
6267
  }
6223
6268
  close() {
6224
- const evidence = Date.now() - this.connectingSince >= 5e3 ? this.directEvidence() : null;
6269
+ const stalled = Date.now() - this.connectingSince >= STALLED_EVIDENCE_MS;
6270
+ const evidence = stalled ? this.directEvidence() : null;
6271
+ this.noteNoPublic(stalled);
6225
6272
  this.answered = null;
6226
6273
  this.reset();
6227
6274
  if (evidence) this.options.onDirect?.(evidence);
6228
6275
  }
6276
+ /** This side's offer went with no public candidate, was answered, and came to nothing: the next offer gathers for as long as it takes. */
6277
+ noteNoPublic(ended) {
6278
+ if (ended && this.myOfferTs && this.offerLacksPublic && this.state === "connecting") this.fullGather = true;
6279
+ }
6229
6280
  /**
6230
6281
  * What this attempt says about direct connections if it ends now without opening: nothing unless both descriptions
6231
6282
  * were exchanged (this side's offer answered, or its answer out). Then, what this device gathered: no public
@@ -6245,6 +6296,7 @@ var DataLink = class {
6245
6296
  const answered = this.state === "connecting" ? this.answered : null;
6246
6297
  const left = answered ? CONNECT_TIMEOUT_MS - (Date.now() - answered.at) : 0;
6247
6298
  const evidence = this.directEvidence();
6299
+ this.noteNoPublic(true);
6248
6300
  if (answered && answered.again < 2 && left > REANSWER_MARGIN_MS && this.options.offerStanding?.(answered.offer.ts)) {
6249
6301
  answered.again++;
6250
6302
  traceLink(this.options.myPubKeyZ32, "reanswer", {
@@ -6296,7 +6348,11 @@ var DataLink = class {
6296
6348
  await describe(pc);
6297
6349
  if (this.pc !== pc) return null;
6298
6350
  const gathering = Date.now();
6299
- await waitForIceGathering(pc, ICE_GATHERING_TIMEOUT_MS, { stallMs: GATHER_STALL_MS });
6351
+ await waitForIceGathering(pc, ICE_GATHERING_TIMEOUT_MS, {
6352
+ stallMs: GATHER_STALL_MS,
6353
+ settleMs: 150,
6354
+ ...offer && !this.fullGather ? { hostBoundMs: OFFER_HOST_GATHER_MS } : {}
6355
+ });
6300
6356
  if (this.pc !== pc) return null;
6301
6357
  const ms = Date.now() - gathering;
6302
6358
  if (sdpHasCandidates(pc.localDescription?.sdp)) {
@@ -6332,6 +6388,7 @@ var DataLink = class {
6332
6388
  if (this.pc !== pc) return;
6333
6389
  this.clearTimers();
6334
6390
  this.answered = null;
6391
+ this.fullGather = false;
6335
6392
  this.setState("open");
6336
6393
  this.options.setFastPoll(false);
6337
6394
  this.options.publishSignal(null);
@@ -6413,7 +6470,7 @@ var DataLink = class {
6413
6470
  //#endregion
6414
6471
  //#region ../core/src/groupCrypto.ts
6415
6472
  /**
6416
- * The cryptography of the `group-mesh/1` profile (WISP 900 / 9xx group mesh).
6473
+ * The cryptography of the `group-mesh/1` profile (WISP 900 / 902 group mesh).
6417
6474
  *
6418
6475
  * One random 32-byte **epoch secret** per membership state. It never travels in
6419
6476
  * the clear: the committer seals it to each member of the new roster with an
@@ -6555,7 +6612,7 @@ function edgeParams(groupId, mySeed, myZ32, peerZ32) {
6555
6612
  }
6556
6613
  /**
6557
6614
  * The cap the chain rules and the engine apply: `MAX_GROUP_MEMBERS`, always, in the apps. Only the scale harness raises
6558
- * it, to measure what hubs would cost past 32 (WISP 9xx · Group Mesh § Cost per member); an app never does.
6615
+ * it, to measure what hubs would cost past 32 (WISP 902 · Group Mesh § Cost per member); an app never does.
6559
6616
  */
6560
6617
  var GROUP_MEMBER_CAP = { max: 32 };
6561
6618
  var MAX_GROUP_CHAIN = 1024;
@@ -6689,7 +6746,7 @@ function verifyChain(raw, groupId) {
6689
6746
  */
6690
6747
  var OWN_FRAME_LIMIT = 2 ** 32 - 2 ** 23;
6691
6748
  /**
6692
- * Frames said again (WISP 9xx, both profiles): a member's own frames that no edge took when it said them are
6749
+ * Frames said again (WISP 902, both profiles): a member's own frames that no edge took when it said them are
6693
6750
  * remembered (`kept`, the latest) and, once the chain it follows is newer than such a frame's and has not moved for
6694
6751
  * `settledMs`, looked at once for saying again where the members let in while it was behind can read them. A catch-up
6695
6752
  * brings commits one by one, and a frame said again at the first of them would still miss those let in by the next.
@@ -6770,7 +6827,7 @@ var STATUS_CARD_LIMITS = {
6770
6827
  /** Labels on a task, and a label's length in characters. */
6771
6828
  tags: 3,
6772
6829
  tag: 24,
6773
- /** Buttons on a message (WISP 4xx · Message Buttons). */
6830
+ /** Buttons on a message (WISP 406 · Message Buttons). */
6774
6831
  buttons: 6,
6775
6832
  /** A button's id: what a press names. */
6776
6833
  buttonId: 32,
@@ -6831,7 +6888,7 @@ var ID$4 = /^[A-Za-z0-9_.:][A-Za-z0-9_.:-]{0,63}$/;
6831
6888
  var BUTTON_ID = /^[A-Za-z0-9_.:][A-Za-z0-9_.:-]{0,31}$/;
6832
6889
  var CRON = /^[0-9A-Za-z*,/?#\- ]{1,64}$/;
6833
6890
  /**
6834
- * Whether a typed answer is a button's label or id (WISP 4xx · Message Buttons): ignoring case and spaces at the ends.
6891
+ * Whether a typed answer is a button's label or id (WISP 406 · Message Buttons): ignoring case and spaces at the ends.
6835
6892
  * Not the device's locale's case: the presser's and the author's apps must agree ("I" is not "ı" anywhere).
6836
6893
  */
6837
6894
  var sameButtonText = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
@@ -7315,13 +7372,13 @@ var wireReply = (reply) => ({
7315
7372
  * {"t":"paired-reacted","n":1790000000000}
7316
7373
  *
7317
7374
  * Off the live session they ride on the DHT envelopes (WISP 403 § Reactions). Groups carry them in their own frames
7318
- * (WISP 9xx § Reactions). Older apps drop all of these: a `t` they do not know, or a trailing element they skip.
7375
+ * (WISP 902 § Reactions). Older apps drop all of these: a `t` they do not know, or a trailing element they skip.
7319
7376
  */
7320
7377
  var REACTION_FRAME = "paired-reaction";
7321
7378
  var REACTED_FRAME = "paired-reacted";
7322
- /** A reaction in a private group, over the edge from the member who reacts (WISP 9xx group mesh § Reactions). */
7379
+ /** A reaction in a private group, over the edge from the member who reacts (WISP 902 group mesh § Reactions). */
7323
7380
  var GROUP_REACTION_FRAME = "group-react";
7324
- /** A reaction in a community, as an application frame sealed to the group (WISP 9xx group community § Reactions). */
7381
+ /** A reaction in a community, as an application frame sealed to the group (WISP 903 group community § Reactions). */
7325
7382
  var COMMUNITY_REACTION_FRAME = "reaction";
7326
7383
  var REACTION_LIMITS = {
7327
7384
  /** UTF-8 bytes of one emoji: a family or a subdivision flag fits, a run of emoji does not. */
@@ -7679,7 +7736,7 @@ function parseLinkPreview(raw, text) {
7679
7736
  * side: the frame comes on the session authenticated with the pinned contact, and the receiver looks the id up among
7680
7737
  * that contact's messages only. The highest `e` wins, whatever order frames arrive in; an edit that is not newer is
7681
7738
  * confirmed and changes nothing. A preview (`pv`, WISP 401 § Link previews) may come with the new text, as with a
7682
- * message. A status card (`sc`, WISP 4xx · Status Cards) may come with it too; a card message takes up to
7739
+ * message. A status card (`sc`, WISP 405 · Status Cards) may come with it too; a card message takes up to
7683
7740
  * `STATUS_CARD_LIMITS.edits` edits, a number past `MAX_EDITS_PER_MESSAGE` holding only with a card. Older apps drop
7684
7741
  * both frames: they have an id, but a `t` they do not know.
7685
7742
  */
@@ -7970,10 +8027,20 @@ var INVISIBLE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
7970
8027
  * "CON", "nul.txt", "COM1 .log" open a device there, not a file.
7971
8028
  */
7972
8029
  var WINDOWS_DEVICE = /^(CON|PRN|AUX|NUL|CONIN\$|CONOUT\$|COM[0-9¹²³]|LPT[0-9¹²³])\s*(\.|$)/i;
8030
+ /**
8031
+ * At most `MAX_NAME_LENGTH` characters. A longer name loses the end of its stem, not its extension (up to 15
8032
+ * characters after the last dot): "…report.pdf" stays a PDF to the system it is saved on.
8033
+ */
8034
+ function shorten(name) {
8035
+ const chars = [...name];
8036
+ if (chars.length <= MAX_NAME_LENGTH) return name;
8037
+ const extension = [.../\.[^.\s]{1,15}$/u.exec(name)?.[0] ?? ""];
8038
+ return chars.slice(0, MAX_NAME_LENGTH - extension.length).join("").trimEnd() + extension.join("");
8039
+ }
7973
8040
  /** A file name is display text and a download suggestion, never a path. */
7974
8041
  function sanitizeFileName(name) {
7975
- const clean = [...name.replace(INVISIBLE, "").replace(/[/\\:]/g, "").replace(/^[\s.]+/, "")].slice(0, MAX_NAME_LENGTH).join("").trim();
7976
- if (WINDOWS_DEVICE.test(clean)) return [..."_" + clean].slice(0, MAX_NAME_LENGTH).join("");
8042
+ const clean = shorten(name.replace(INVISIBLE, "").replace(/[/\\:]/g, "").replace(/^[\s.]+/, "").trim());
8043
+ if (WINDOWS_DEVICE.test(clean)) return shorten("_" + clean);
7977
8044
  return clean || "file";
7978
8045
  }
7979
8046
  function sanitizeMime(mime) {
@@ -8451,6 +8518,8 @@ var DhtDelivery = class DhtDelivery {
8451
8518
  /** Until when the relays' request budget holds publications back: what is due then goes at once. */
8452
8519
  budgetUntil = 0;
8453
8520
  errors = {};
8521
+ /** When the device's network last came back (`networkBack`): a read or publication started before failed for want of it. */
8522
+ networkBackAt = -Infinity;
8454
8523
  foreignKeySeenAt;
8455
8524
  /** When the pinned mailbox was last looked in while the contact had not said it uses it. */
8456
8525
  pinnedProbeAt = 0;
@@ -8640,6 +8709,18 @@ var DhtDelivery = class DhtDelivery {
8640
8709
  this.tick();
8641
8710
  }
8642
8711
  /**
8712
+ * The device's network came back: a read or publication error from while it was away says nothing now (No Pkarr
8713
+ * relay reachable). It goes at once, and the mailbox is read now: one that still fails says so again.
8714
+ */
8715
+ networkBack() {
8716
+ this.networkBackAt = Date.now();
8717
+ if (!this.errors.read && !this.errors.publish) return;
8718
+ delete this.errors.read;
8719
+ delete this.errors.publish;
8720
+ this.changed();
8721
+ this.tick();
8722
+ }
8723
+ /**
8643
8724
  * Reads the contact's mailbox at the signaling pace for a while (a fresh packet of a contact not pinned yet).
8644
8725
  * `signal`: the contact is leaving DHT only (a fresh packet on its link key, or its offer), and the live link waits
8645
8726
  * on a read to say so: the next read goes now, and it and the next ones while the contact still says DHT only
@@ -9286,18 +9367,20 @@ var DhtDelivery = class DhtDelivery {
9286
9367
  const signal = this.signalReads > 0;
9287
9368
  this.urgent = false;
9288
9369
  if (signal) this.signalReads--;
9370
+ const readAt = Date.now();
9289
9371
  try {
9290
9372
  await this.read(background, signal);
9291
9373
  if (!this.running) return;
9292
9374
  delete this.errors.read;
9293
9375
  } catch (error) {
9294
- if (!isDiscoveryBudgetError(error)) this.errors.read = `Could not read DHT delivery: ${error instanceof Error ? error.message : String(error)}`;
9376
+ if (!isDiscoveryBudgetError(error) && readAt >= this.networkBackAt) this.errors.read = `Could not read DHT delivery: ${error instanceof Error ? error.message : String(error)}`;
9295
9377
  }
9296
9378
  if (this.state.peerMode !== "dht") this.signalReads = 0;
9379
+ const publishAt = Date.now();
9297
9380
  try {
9298
9381
  await this.publish();
9299
9382
  } catch (error) {
9300
- if (!isDiscoveryBudgetError(error)) this.errors.publish = `Could not publish DHT delivery: ${error instanceof Error ? error.message : String(error)}`;
9383
+ if (!isDiscoveryBudgetError(error) && publishAt >= this.networkBackAt) this.errors.publish = `Could not publish DHT delivery: ${error instanceof Error ? error.message : String(error)}`;
9301
9384
  }
9302
9385
  this.changed();
9303
9386
  });
@@ -9795,7 +9878,10 @@ var TransportSwitch = class {
9795
9878
  begin(context, actual, migrated = false) {
9796
9879
  const landed = migrated ? this.plan : null;
9797
9880
  this.settled = landed ? this.signature(landed.local, landed.remote) : "";
9798
- if (!migrated) this.failed = "";
9881
+ if (!migrated) {
9882
+ this.failed = "";
9883
+ this.remote = null;
9884
+ }
9799
9885
  if (this.apart) {
9800
9886
  this.intent = Math.max(this.intent, this.lastRemote?.intent ?? 0) + 1;
9801
9887
  this.apart = false;
@@ -9846,7 +9932,7 @@ var TransportSwitch = class {
9846
9932
  keep(reason) {
9847
9933
  const target = this.plan?.choices[0];
9848
9934
  if (this.plan) this.settled = this.signature(this.plan.local, this.plan.remote);
9849
- if (target && target !== this.actual) this.options.kept?.(target, reason);
9935
+ if (target && target !== this.actual && !this.plan?.upgrade) this.options.kept?.(target, reason);
9850
9936
  this.send({
9851
9937
  t: "paired-switch-keep",
9852
9938
  id: this.plan?.id
@@ -9886,7 +9972,7 @@ var TransportSwitch = class {
9886
9972
  this.preparing = false;
9887
9973
  }
9888
9974
  fail(error) {
9889
- const target = this.plan?.choices[0];
9975
+ const target = this.plan?.choices[0], upgrade = !!this.plan?.upgrade;
9890
9976
  if (this.plan) this.failed = this.signature(this.plan.local, this.plan.remote);
9891
9977
  this.send({
9892
9978
  t: "paired-switch-failed",
@@ -9894,7 +9980,8 @@ var TransportSwitch = class {
9894
9980
  });
9895
9981
  this.clearPlan();
9896
9982
  this.options.cancel();
9897
- this.unreached(target, error, error);
9983
+ if (upgrade) this.options.state();
9984
+ else this.unreached(target, error, error);
9898
9985
  this.replanIfMoved();
9899
9986
  }
9900
9987
  /**
@@ -9916,13 +10003,29 @@ var TransportSwitch = class {
9916
10003
  const ranked = rankTransports(allowedTransports(local), allowedTransports(remote), relayed);
9917
10004
  if (!ranked.length) return [];
9918
10005
  const winner = this.winner(local, remote);
9919
- const target = winner.intent === 0 && this.actual && ranked.includes(this.actual) ? this.actual : ranked.includes(winner.preferred) ? winner.preferred : ranked[0];
10006
+ const target = this.upgrading(winner, ranked, relayed) ? ranked[0] : winner.intent === 0 && this.actual && ranked.includes(this.actual) ? this.actual : ranked.includes(winner.preferred) ? winner.preferred : ranked[0];
9920
10007
  return [target, ...local.fallback && remote.fallback ? ranked.filter((t) => t !== target) : []];
9921
10008
  }
10009
+ /** Nobody chose, the session is on a relayed transport, a direct one ranks first, and both sides move (`upgrade/1`). */
10010
+ upgrading(winner, ranked, relayed) {
10011
+ return winner.intent === 0 && !!this.actual && relayed.includes(this.actual) && ranked.includes(this.actual) && ranked[0] !== this.actual && !relayed.includes(ranked[0]) && !!this.options.upgrade?.();
10012
+ }
10013
+ isUpgrade(local, remote) {
10014
+ const relayed = relayedTransports(local.descriptors, remote.descriptors);
10015
+ return this.upgrading(this.winner(local, remote), rankTransports(allowedTransports(local), allowedTransports(remote), relayed), relayed);
10016
+ }
10017
+ /**
10018
+ * Something `upgrade` depends on changed (what the contact's app offers on this session, said once it is ready): plan
10019
+ * again. The session's first agreement, made before that, settled on the relay it is on; a move off it is not that.
10020
+ */
10021
+ reconsider() {
10022
+ if (this.context && this.remote && !this.plan && this.isUpgrade(this.local(), this.remote) && this.settled === this.signature(this.local(), this.remote)) this.settled = "";
10023
+ this.reconcile();
10024
+ }
9922
10025
  install(plan) {
9923
10026
  this.clearPlan();
9924
10027
  this.plan = plan;
9925
- this.options.state(void 0, plan.choices[0]);
10028
+ this.options.state(void 0, plan.upgrade ? void 0 : plan.choices[0]);
9926
10029
  this.timer = setTimeout(() => this.fail("Transport change timed out. Your previous connection is kept when available; retry or choose another transport."), this.options.timeoutMs ?? 3e4);
9927
10030
  }
9928
10031
  reconcile() {
@@ -9944,11 +10047,16 @@ var TransportSwitch = class {
9944
10047
  return;
9945
10048
  }
9946
10049
  if (signature === this.failed) return;
9947
- this.options.state(void 0, choices[0]);
10050
+ const upgrade = this.isUpgrade(local, remote);
10051
+ this.options.state(void 0, upgrade ? void 0 : choices[0]);
9948
10052
  if (this.options.key > this.options.peerKey) {
9949
10053
  if (!this.timer) this.timer = setTimeout(() => {
9950
10054
  this.failed = signature;
9951
10055
  this.clearPlan();
10056
+ if (upgrade) {
10057
+ this.options.state();
10058
+ return;
10059
+ }
9952
10060
  this.unreached(choices[0], "Your contact did not acknowledge the transport change. Retry when both peers are connected.", "Your contact did not answer");
9953
10061
  }, this.options.timeoutMs ?? 3e4);
9954
10062
  return;
@@ -9957,7 +10065,8 @@ var TransportSwitch = class {
9957
10065
  id: `${signature}:${choices[0]}`,
9958
10066
  local,
9959
10067
  remote,
9960
- choices
10068
+ choices,
10069
+ ...upgrade && { upgrade }
9961
10070
  };
9962
10071
  this.install(plan);
9963
10072
  this.send({
@@ -9998,7 +10107,8 @@ var TransportSwitch = class {
9998
10107
  id: String(frame.id),
9999
10108
  local,
10000
10109
  remote,
10001
- choices
10110
+ choices,
10111
+ ...this.isUpgrade(local, remote) && { upgrade: true }
10002
10112
  });
10003
10113
  this.options.prepare(this.plan, false);
10004
10114
  this.send({
@@ -10009,17 +10119,18 @@ var TransportSwitch = class {
10009
10119
  }
10010
10120
  if (!this.plan || frame.id !== this.plan.id) return true;
10011
10121
  if (frame.t === "paired-switch-failed") {
10012
- const target = this.plan.choices[0];
10122
+ const target = this.plan.choices[0], upgrade = !!this.plan.upgrade;
10013
10123
  this.failed = this.signature(this.plan.local, this.plan.remote);
10014
10124
  this.clearPlan();
10015
10125
  this.options.cancel();
10016
- this.unreached(target, "The transport change failed. Retry or choose another transport.");
10126
+ if (upgrade) this.options.state();
10127
+ else this.unreached(target, "The transport change failed. Retry or choose another transport.");
10017
10128
  this.replanIfMoved();
10018
10129
  return true;
10019
10130
  }
10020
10131
  if (frame.t === "paired-switch-keep") {
10021
10132
  if (this.actual && this.plan.choices.includes(this.actual)) {
10022
- if (this.plan.choices[0] !== this.actual) this.options.kept?.(this.plan.choices[0]);
10133
+ if (this.plan.choices[0] !== this.actual && !this.plan.upgrade) this.options.kept?.(this.plan.choices[0]);
10023
10134
  this.settled = this.signature(this.plan.local, this.plan.remote);
10024
10135
  this.clearPlan();
10025
10136
  this.options.state();
@@ -10438,7 +10549,7 @@ var lastOf = (timeline, side, proofs) => {
10438
10549
  /**
10439
10550
  * The timeline after one step. A share already on it is updated in place, never repeated: a request sent again at
10440
10551
  * a reconnect, a second share of a proof the contact already verified. A new attempt after a failure or a stop is
10441
- * a new entry. A stop is written once.
10552
+ * a new entry. A stop is written once; a withdrawal later found revoked becomes a revocation in place.
10442
10553
  */
10443
10554
  function applyIdentityEvent(timeline, event, at) {
10444
10555
  const out = [...timeline];
@@ -10498,7 +10609,13 @@ function applyIdentityEvent(timeline, event, at) {
10498
10609
  break;
10499
10610
  }
10500
10611
  case "stopped":
10501
- if (last?.kind === "stopped") break;
10612
+ if (last?.kind === "stopped") {
10613
+ if (last.reason === "withdrawn" && event.reason === "revoked") out[i] = {
10614
+ ...last,
10615
+ reason: "revoked"
10616
+ };
10617
+ break;
10618
+ }
10502
10619
  add({
10503
10620
  proof: event.proof,
10504
10621
  side: event.side,
@@ -11717,7 +11834,7 @@ async function fitSignedPairedSignalWith(signal, signer, from, to, fits) {
11717
11834
  //#endregion
11718
11835
  //#region ../core/src/storeForward.ts
11719
11836
  /**
11720
- * Store-and-forward for an away contact (WISP 4xx draft, `hold/1`).
11837
+ * Store-and-forward for an away contact (WISP 404 draft, `hold/1`).
11721
11838
  *
11722
11839
  * What a person sends while the contact is away is sealed into a *bundle* (signed by the sender's
11723
11840
  * participation key, then encrypted to a key only the two of them can derive), put in the sender's
@@ -12440,15 +12557,23 @@ var WAKE_SESSION_CAPABILITY = "wake/1";
12440
12557
  var PIN_CAPABILITY = "pin/1";
12441
12558
  /**
12442
12559
  * Status cards: this app shows a message's card (`sc`) and takes a card message's edits past the text's 100, which an
12443
- * older app would drop (WISP 4xx · Status Cards, `statusCards.ts`). A card itself needs nothing: older apps show its text.
12560
+ * older app would drop (WISP 405 · Status Cards, `statusCards.ts`). A card itself needs nothing: older apps show its text.
12444
12561
  */
12445
12562
  var STATUS_CARD_CAPABILITY = "status-card/1";
12446
12563
  /**
12447
12564
  * Message buttons: this app shows a message's buttons (a card of kind `buttons`) and presses them with a reply naming
12448
- * the button (WISP 4xx · Message Buttons). Nothing is gated on it: an older app shows the text and sends replies, and
12565
+ * the button (WISP 406 · Message Buttons). Nothing is gated on it: an older app shows the text and sends replies, and
12449
12566
  * a press reads as an ordinary reply there. It tells a bot whether its contact can press at all.
12450
12567
  */
12451
12568
  var BUTTONS_CAPABILITY = "buttons/1";
12569
+ /**
12570
+ * A session live on a relayed transport while a direct one ranks first, with nobody's choice behind it, moves to the
12571
+ * direct one once, in place, its signaling on the session (WISP 100 § Back to a quiet contact): what a chat that came
12572
+ * back over a relay at once (rather than wait for an offer's answer) needs to end where a direct path would have put
12573
+ * it. Both sides must say it: the switch is planned by one and checked by the other. Also in the capability record
12574
+ * (WISP 03), so an app back after an absence knows before any session whether a relay can carry it there at once.
12575
+ */
12576
+ var UPGRADE_CAPABILITY = "upgrade/1";
12452
12577
  /** Every capability this app knows on a session: what `receive` reports changes of. */
12453
12578
  var KNOWN_SESSION_CAPABILITIES = [
12454
12579
  CALLS_CAPABILITY,
@@ -12460,7 +12585,8 @@ var KNOWN_SESSION_CAPABILITIES = [
12460
12585
  WAKE_SESSION_CAPABILITY,
12461
12586
  PIN_CAPABILITY,
12462
12587
  STATUS_CARD_CAPABILITY,
12463
- BUTTONS_CAPABILITY
12588
+ BUTTONS_CAPABILITY,
12589
+ UPGRADE_CAPABILITY
12464
12590
  ];
12465
12591
  var SESSION_CAPABILITIES_FRAME = "paired-capabilities";
12466
12592
  var MAX_ENTRIES$1 = 32;
@@ -14275,7 +14401,7 @@ var PairedCalls = class {
14275
14401
  //#endregion
14276
14402
  //#region ../core/src/communityRendezvous.ts
14277
14403
  /**
14278
- * Where the members of a `group-community/1` group find each other (WISP 9xx · Group Community §
14404
+ * Where the members of a `group-community/1` group find each other (WISP 903 · Group Community §
14279
14405
  * Topology): a **beacon** listing the current hubs, and a **lobby** per hub where a member asks
14280
14406
  * that hub for an edge. Both are Pkarr records under identities every member derives from the
14281
14407
  * group's rendezvous secret, sealed with a key from the same secret: relays see a key and an
@@ -14376,7 +14502,7 @@ function unpack(bytes, withLoad, max) {
14376
14502
  return out;
14377
14503
  }
14378
14504
  /**
14379
- * The hubs that take turns at the door (WISP 9xx § Admission): those that said so lately and have
14505
+ * The hubs that take turns at the door (WISP 903 § Admission): those that said so lately and have
14380
14506
  * been hubs for a minute, so that every hub, having read the beacon at least once since, agrees on
14381
14507
  * the same set. Only when there is none (a group's first minute), the one that has been a hub longest. And when none said so lately, a hub asking (`me`)
14382
14508
  * that sees no other fresh hub in the beacon is the door alone, listed or not: its own entry may not have reached the
@@ -14504,7 +14630,7 @@ function shouldBeHub(me, hubs, now = Date.now()) {
14504
14630
  //#endregion
14505
14631
  //#region ../core/src/groupHubs.ts
14506
14632
  /**
14507
- * Hubs in a private (mesh) group (WISP 9xx · Group Mesh § Hubs). Up to `threshold` members a group is a full mesh, as
14633
+ * Hubs in a private (mesh) group (WISP 902 · Group Mesh § Hubs). Up to `threshold` members a group is a full mesh, as
14508
14634
  * before. Past it, members that stay online (the Desktop app and the CLI, or members the admin pins) become **hubs**:
14509
14635
  * they keep edges with every other hub and with the members that ask them, and pass on every frame they take. The
14510
14636
  * other members keep edges with `hubsPerMember` hubs only. Frames are author-signed and sealed under the epoch key, and
@@ -15041,6 +15167,12 @@ var DHT_PIN_GRACE_MS = 1e4;
15041
15167
  var RESUME_WAIT_MS = 1e4;
15042
15168
  /** Two dials crossing: how long the lower key's side waits for its own to connect before it takes the contact's. */
15043
15169
  var CROSSED_WAIT_MS = 3e3;
15170
+ /** A WebRTC attempt under way, not yet open: what a member's packet saying it has no WebRTC ends (`peerPacketTransports`). */
15171
+ var WEBRTC_ATTEMPT = /* @__PURE__ */ new Set([
15172
+ "offering",
15173
+ "answering",
15174
+ "connecting"
15175
+ ]);
15044
15176
  /** What a joiner is told when someone else used the invite first (`DhtDeliveryView.inviteTaken`). */
15045
15177
  var INVITE_TAKEN = "Someone else joined with this invite first. Ask your contact for a new one.";
15046
15178
  /**
@@ -15151,7 +15283,7 @@ var PONG_WAIT_MS = 4e3;
15151
15283
  * A `paired-message` frame. The preview (`pv`) is left out when the frame would pass `MAX_PAIRED_MESSAGE_FRAME`
15152
15284
  * with it: the text matters, the card does not. A reply (`r`, WISP 401 § Replies) is a few hundred bytes at most
15153
15285
  * and always goes, and so does a forwarded message's hop count (`fw`, WISP 401 § Forwards) and a status card (`sc`,
15154
- * WISP 4xx · Status Cards), whose 8 KiB with the text's 16 stay well inside the frame.
15286
+ * WISP 405 · Status Cards), whose 8 KiB with the text's 16 stay well inside the frame.
15155
15287
  */
15156
15288
  function pairedMessageFrame(id, ts, m, preview, reply, forwarded, card) {
15157
15289
  const r = {
@@ -15186,6 +15318,11 @@ function pairedMessageFrame(id, ts, m, preview, reply, forwarded, card) {
15186
15318
  * names the transport. One just read that names it with no descriptor says its endpoint is not up (WISP 03: still
15187
15319
  * starting, or no free listener there): the one known is not dialled meanwhile (none, `undefined`).
15188
15320
  */
15321
+ /** The endpoint a descriptor names (Iroh's endpoint id, HyperDHT's public key), whatever relay it is homed on. */
15322
+ function endpointId(transport, descriptor) {
15323
+ const d = descriptor;
15324
+ return transport === "iroh/1" ? d?.id : d?.publicKey;
15325
+ }
15189
15326
  function recordDescriptor(transport, known, record, newer, listed) {
15190
15327
  if (!record) return newer && listed ? void 0 : known;
15191
15328
  if (!known) return record;
@@ -15240,6 +15377,9 @@ var GhostLink = class {
15240
15377
  endpoints = /* @__PURE__ */ new Map();
15241
15378
  activeBinding;
15242
15379
  dialing = false;
15380
+ /** A joiner's dial made as the link starts (`dialEarly`): its offer goes once the first look finds the inviter. */
15381
+ dialGate;
15382
+ firstLookDone;
15243
15383
  dhtPinGrace = null;
15244
15384
  /** Native attempts that failed in a row, and transports demoted until when (WISP 100, demotion). */
15245
15385
  nativeFailures = /* @__PURE__ */ new Map();
@@ -15323,6 +15463,8 @@ var GhostLink = class {
15323
15463
  unansweredPings = 0;
15324
15464
  /** The last ping's `PONG_WAIT_MS`: running until something comes back from the contact. */
15325
15465
  pongWait = null;
15466
+ /** The last ping's wait ran out with nothing back, and nothing came since (`restartedRedial`). */
15467
+ pongLate = false;
15326
15468
  openWaiters = [];
15327
15469
  /** Set while a WebRTC attempt this side dialled is pending: the ranked transports to try if it fails. */
15328
15470
  afterRtc;
@@ -15330,8 +15472,14 @@ var GhostLink = class {
15330
15472
  raceTimer = null;
15331
15473
  /** Set while `race` dials: the offer ending meanwhile is followed up by the race. */
15332
15474
  racing = false;
15333
- /** The WebRTC offer this side's dial made last, with fallback allowed: what a transport that comes up meanwhile joins. */
15475
+ /**
15476
+ * The WebRTC offer this side's dial made last, with fallback allowed: what a transport that comes up meanwhile joins.
15477
+ * `resume`: the dial that made it is this side back after an absence, over WebRTC (`resuming`).
15478
+ */
15334
15479
  offered;
15480
+ /** The attempt (its epoch) whose direct transports were knocked on as its offer went out (`knockEarly`), and whether that is under way. */
15481
+ knockedEarly = -1;
15482
+ knockingEarly = false;
15335
15483
  /** When that offer's answer was applied (`RACE_ANSWERED_MS`); 0 while the data link is not connecting on it. */
15336
15484
  answeredAt = 0;
15337
15485
  /** This side's offer is out unanswered, or answered and not connected yet: what is ranked after WebRTC may race it. */
@@ -15344,6 +15492,13 @@ var GhostLink = class {
15344
15492
  autoConnectFailures = 0;
15345
15493
  /** Native transports a record just read listed with no way to dial them (`record-undescribed`), until one describes them again. */
15346
15494
  undescribed = /* @__PURE__ */ new Set();
15495
+ /**
15496
+ * The native dial under way (`dialNative`): its attempt, its transport, the endpoint it went to (`redescribedDial`),
15497
+ * and whether it started with this device offline (`networkBack`).
15498
+ */
15499
+ nativeDial = null;
15500
+ /** When the device's network last came back (`wake({ network: true })`): a dial under way across it is no failure. */
15501
+ networkBackAt = -Infinity;
15347
15502
  /** The peer's packet I last looked fast for its offer after (its timestamp): once per packet. */
15348
15503
  offerAwaitedFor = 0;
15349
15504
  /** Whether the stream was blocked (DHT-only on either side) when last looked at. */
@@ -15501,7 +15656,10 @@ var GhostLink = class {
15501
15656
  firstPublish: options.firstPublish ?? (this.tracker && options.pairingProgress?.role === "joiner" || this.resuming ? "after-first-poll" : "at-start"),
15502
15657
  events: {
15503
15658
  onFirstPoll: () => {
15504
- if (!this.dialing) this.session.ensureAdvertised();
15659
+ const online = this.session.peerPresence.online, early = this.firstLookDone;
15660
+ this.firstLookDone = void 0;
15661
+ early?.(online);
15662
+ if (!this.dialing || early && !online) this.session.ensureAdvertised();
15505
15663
  },
15506
15664
  onMessages: (messages, batch) => {
15507
15665
  if (options.params.profile) return;
@@ -15622,7 +15780,13 @@ var GhostLink = class {
15622
15780
  if (this.activeBinding) return;
15623
15781
  events.onDataLinkState?.(state);
15624
15782
  if (state === "open") this.afterRtc = void 0;
15625
- if (state === "answering") this.answeringEpoch = this.connectionEpoch;
15783
+ if (state === "answering") {
15784
+ this.answeringEpoch = this.connectionEpoch;
15785
+ if (this.resumingOverRtc) {
15786
+ const epoch = this.connectionEpoch;
15787
+ setTimeout(() => this.maybeKnockAnswering(epoch), 0);
15788
+ }
15789
+ }
15626
15790
  this.answeredAt = 0;
15627
15791
  if (state === "connecting" && was === "offering" && this.offered?.epoch === this.connectionEpoch) {
15628
15792
  this.answeredAt = Date.now();
@@ -15700,7 +15864,8 @@ var GhostLink = class {
15700
15864
  },
15701
15865
  unreached: (target, reason) => {
15702
15866
  this.unreached(target, reason);
15703
- }
15867
+ },
15868
+ upgrade: () => !!this.options.upgradeSupport && this.sessionCapabilities.agreed("upgrade/1")
15704
15869
  });
15705
15870
  if (options.native?.automatic === false) this.switcher.choseApart();
15706
15871
  }
@@ -15967,9 +16132,36 @@ var GhostLink = class {
15967
16132
  this.session.expectPeer();
15968
16133
  this.resumeWaitUntil = Date.now() + RESUME_WAIT_MS;
15969
16134
  }
15970
- if (this.tracker && !this.tracker.done && this.options.pairingProgress?.role === "joiner") this.dht?.expect();
16135
+ if (this.tracker && !this.tracker.done && this.options.pairingProgress?.role === "joiner") {
16136
+ this.dht?.expect();
16137
+ this.dialEarly();
16138
+ }
15971
16139
  this.dht?.start();
15972
16140
  }
16141
+ /**
16142
+ * A joiner's offer is gathered as the link starts, while its first look at the inviter's record is under way, and goes
16143
+ * once that look finds the inviter (`dialGate`): the two used to come one after the other, the read (0.15 to 0.8 s)
16144
+ * then the gathering (0.2 s to 2 s).
16145
+ */
16146
+ dialEarly() {
16147
+ if (!this.options.params.profile || !this.options.autoConnect || this.deliveryMode === "dht" || this.streamBlocked || this.dialing || this.channel) return;
16148
+ const startedAt = this.options.pairingProgress?.startedAt ?? 0;
16149
+ if (Date.now() - startedAt > 6e4) return;
16150
+ this.dialGate = new Promise((resolve) => {
16151
+ this.firstLookDone = (online) => {
16152
+ if (online) {
16153
+ this.lastAutoConnectAt = Date.now();
16154
+ this.autoConnectFailures++;
16155
+ }
16156
+ resolve(online);
16157
+ };
16158
+ });
16159
+ traceLink(this.myPubKeyZ32, "dial", { early: true });
16160
+ this.dial().catch((error) => {
16161
+ this.tracker?.failed("transport", true);
16162
+ this.dialFailed(error instanceof Error ? error.message : String(error));
16163
+ });
16164
+ }
15973
16165
  async stop(announce = true) {
15974
16166
  this.stopped = true;
15975
16167
  this.clearRace();
@@ -16021,18 +16213,32 @@ var GhostLink = class {
16021
16213
  });
16022
16214
  }
16023
16215
  /** Closes what a switch replaced once the contact has had time to move too. */
16024
- retire(old, also) {
16216
+ retire(old, also, rtc = false) {
16025
16217
  if (this.retiring.has(old)) return;
16218
+ let closed = () => {};
16219
+ const done = new Promise((resolve) => {
16220
+ closed = resolve;
16221
+ });
16026
16222
  const close = () => {
16027
16223
  this.retiring.delete(old);
16028
16224
  old.close();
16029
16225
  also();
16226
+ closed();
16030
16227
  };
16031
16228
  this.retiring.set(old, {
16032
16229
  close,
16033
- timer: setTimeout(close, SWITCH_RETIRE_MS)
16230
+ timer: setTimeout(close, SWITCH_RETIRE_MS),
16231
+ rtc,
16232
+ closed: done
16034
16233
  });
16035
16234
  }
16235
+ /**
16236
+ * The WebRTC session a switch moved off is still retiring (`SWITCH_RETIRE_MS`): its data link is open, and a new offer
16237
+ * cannot go until it closes. Resolves once it has (at once when none is).
16238
+ */
16239
+ rtcRetired() {
16240
+ return Promise.all([...this.retiring.values()].filter((entry) => entry.rtc).map((entry) => entry.closed)).then(() => {});
16241
+ }
16036
16242
  closeRetired() {
16037
16243
  for (const { timer, close } of [...this.retiring.values()]) {
16038
16244
  clearTimeout(timer);
@@ -16104,7 +16310,7 @@ var GhostLink = class {
16104
16310
  const listed = !!wanted && !!this.peerRecordTransports?.includes(wanted), dialable = !!wanted && !!this.peerDescriptors[wanted];
16105
16311
  this.peerRecordTransports = [...transports];
16106
16312
  let changed = false, redescribed = false, again = false, news = false;
16107
- const renamed = /* @__PURE__ */ new Set();
16313
+ const renamed = /* @__PURE__ */ new Set(), reidentified = /* @__PURE__ */ new Set();
16108
16314
  for (const t of ["iroh/1", "hyperdht/1"]) {
16109
16315
  const known = this.peerDescriptors[t], next = recordDescriptor(t, known, descriptors[t], fresh, transports.includes(t));
16110
16316
  if (next === known) continue;
@@ -16124,6 +16330,7 @@ var GhostLink = class {
16124
16330
  changed = true;
16125
16331
  redescribed ||= t === wanted;
16126
16332
  renamed.add(t);
16333
+ if (known && endpointId(t, known) !== endpointId(t, next)) reidentified.add(t);
16127
16334
  this.nativeFailures.delete(t);
16128
16335
  this.demotedUntil.delete(t);
16129
16336
  }
@@ -16149,6 +16356,8 @@ var GhostLink = class {
16149
16356
  this.redial();
16150
16357
  return;
16151
16358
  }
16359
+ if (held && heldOn && reidentified.has(heldOn) && this.restartedRedial(held, heldOn)) return;
16360
+ if (this.redescribedDial()) return;
16152
16361
  if (this.channel || this.dialing) return;
16153
16362
  if (news || !again) {
16154
16363
  this.autoConnectFailures = 0;
@@ -16427,7 +16636,7 @@ var GhostLink = class {
16427
16636
  if (!said) return;
16428
16637
  const transports = said.transports, descriptors = dialDescriptors(said.descriptors);
16429
16638
  traceLink(this.myPubKeyZ32, "packet-transports", { transports });
16430
- if (!transports.includes("webrtc/1") && !this.channel && this.dataLink.state === "offering") this.disconnect();
16639
+ if (!transports.includes("webrtc/1") && !this.channel && WEBRTC_ATTEMPT.has(this.dataLink.state)) this.disconnect();
16431
16640
  this.options.events?.onPacketTransports?.(transports, descriptors);
16432
16641
  this.learnPeerTransports(transports, descriptors, true);
16433
16642
  }
@@ -16649,6 +16858,7 @@ var GhostLink = class {
16649
16858
  const remembered = this.peerTransports && rankTransports(local, this.peerFallback ? this.peerTransports : this.peerTransports.slice(0, 1), relayed);
16650
16859
  const ranked = remembered?.length ? remembered : this.peerTransports ? rankTransports(local, this.peerTransports, relayed) : local.filter((t) => t === "webrtc/1");
16651
16860
  const chosen = this.switcher.chosenTarget ?? this.choiceApart?.transport ?? this.resuming;
16861
+ const resume = this.resuming === "webrtc/1";
16652
16862
  if (chosen && chosen === this.resuming && ranked.includes(chosen)) this.resuming = void 0;
16653
16863
  const choices = chosen && ranked.includes(chosen) ? [chosen, ...ranked.filter((t) => t !== chosen)] : ranked;
16654
16864
  if (!choices.length) {
@@ -16668,12 +16878,21 @@ var GhostLink = class {
16668
16878
  epoch,
16669
16879
  rest
16670
16880
  } : void 0;
16671
- const offeredAt = Date.now();
16672
- this.offered = fallback ? {
16881
+ const standing = this.offerOut && this.offered?.epoch === epoch ? this.offered : void 0;
16882
+ const offeredAt = standing?.at ?? Date.now();
16883
+ this.offered = fallback ? standing ?? {
16673
16884
  epoch,
16674
- at: offeredAt
16885
+ at: offeredAt,
16886
+ ...resume && { resume }
16675
16887
  } : void 0;
16676
- await this.dataLink.connect();
16888
+ const gate = this.dialGate;
16889
+ this.dialGate = void 0;
16890
+ if (await this.dataLink.connect(gate) === "held") {
16891
+ traceLink(this.myPubKeyZ32, "dial-held", {});
16892
+ this.afterRtc = void 0;
16893
+ this.offered = void 0;
16894
+ return;
16895
+ }
16677
16896
  if (this.dataLink.state !== "idle" || epoch !== this.connectionEpoch) {
16678
16897
  this.scheduleRace(epoch, offeredAt);
16679
16898
  return;
@@ -16695,6 +16914,7 @@ var GhostLink = class {
16695
16914
  if (epoch === this.connectionEpoch) this.attemptEnded(error instanceof Error ? error.message : String(error));
16696
16915
  throw error;
16697
16916
  } finally {
16917
+ this.dialGate = void 0;
16698
16918
  if (epoch === this.connectionEpoch) {
16699
16919
  this.dialing = false;
16700
16920
  if (!this.channel && !this.afterRtc && this.dataLink.state === "idle") this.attemptEnded();
@@ -16711,6 +16931,12 @@ var GhostLink = class {
16711
16931
  const descriptor = this.peerDescriptors[transport];
16712
16932
  if (!endpoint || !descriptor) return /* @__PURE__ */ new Error("Peer native address unavailable; reconnect WebRTC once to exchange endpoints");
16713
16933
  const started = Date.now();
16934
+ const dialed = this.nativeDial = {
16935
+ epoch,
16936
+ transport,
16937
+ endpoint: endpointId(transport, descriptor),
16938
+ offline: this.isOffline()
16939
+ };
16714
16940
  try {
16715
16941
  const { channel, binding } = await endpoint.connect(descriptor);
16716
16942
  this.nativeFailures.delete(transport);
@@ -16729,6 +16955,10 @@ var GhostLink = class {
16729
16955
  });
16730
16956
  if (epoch !== this.connectionEpoch) return true;
16731
16957
  this.attemptFailed(transport, error instanceof Error ? error.message : String(error));
16958
+ if (dialed.offline || this.isOffline() || this.networkBackAt >= started) {
16959
+ traceLink(this.myPubKeyZ32, "dial-failed-offline", { transport });
16960
+ return error instanceof Error ? error : new Error(String(error));
16961
+ }
16732
16962
  const failures = (this.nativeFailures.get(transport) ?? 0) + 1;
16733
16963
  if (failures >= 3) {
16734
16964
  this.nativeFailures.delete(transport);
@@ -16736,8 +16966,59 @@ var GhostLink = class {
16736
16966
  traceLink(this.myPubKeyZ32, "demote", { transport });
16737
16967
  } else this.nativeFailures.set(transport, failures);
16738
16968
  return error instanceof Error ? error : new Error(String(error));
16969
+ } finally {
16970
+ if (this.nativeDial === dialed) this.nativeDial = null;
16739
16971
  }
16740
16972
  }
16973
+ /**
16974
+ * The native dial under way went to an endpoint the contact's newer packet no longer names for that transport: its
16975
+ * app started again (a page reloading, as both devices of a handoff do), and the endpoint dialled went with the old
16976
+ * one. That dial would end only at its connect timeout (Iroh: 20 s, then the next attempt's wait): it is let go, and
16977
+ * the endpoint the packet names dialled now. Not while a WebRTC offer of this attempt stands: the race that follows
16978
+ * it keeps its own order.
16979
+ */
16980
+ redescribedDial() {
16981
+ const dial = this.nativeDial;
16982
+ if (!dial || dial.epoch !== this.connectionEpoch || !this.dialing || this.channel || this.dataLink.state !== "idle") return false;
16983
+ const now = this.peerDescriptors[dial.transport];
16984
+ if (!now || endpointId(dial.transport, now) === dial.endpoint) return false;
16985
+ traceLink(this.myPubKeyZ32, "redescribed-dial", { transport: dial.transport });
16986
+ this.nativeDial = null;
16987
+ this.connectionEpoch++;
16988
+ this.dialing = false;
16989
+ this.clearRace();
16990
+ this.afterRtc = void 0;
16991
+ this.autoConnectFailures = Math.max(0, this.autoConnectFailures - 1);
16992
+ this.redial();
16993
+ return true;
16994
+ }
16995
+ /** Whether this device has no network now (`GhostLinkOptions.offline`). */
16996
+ isOffline() {
16997
+ return this.options.offline ? this.options.offline() : globalThis.navigator?.onLine === false;
16998
+ }
16999
+ /**
17000
+ * The device's network came back. A native dial that started while it was away would end only at its connect timeout
17001
+ * (Iroh: 20 s), well past the return: it is let go, and the chat dialled again now. Its WebRTC offer, if one stands,
17002
+ * was made with no network too, and goes with it.
17003
+ */
17004
+ networkBack() {
17005
+ this.networkBackAt = Date.now();
17006
+ const dial = this.nativeDial;
17007
+ if (!dial?.offline || dial.epoch !== this.connectionEpoch || !this.dialing || this.channel) return;
17008
+ if (this.stopped || this.leaving || this.streamBlocked || this.keyStopped || !this.options.autoConnect) return;
17009
+ traceLink(this.myPubKeyZ32, "offline-dial", { transport: dial.transport });
17010
+ this.nativeDial = null;
17011
+ if (this.dataLink.state !== "idle") this.disconnect();
17012
+ else {
17013
+ this.connectionEpoch++;
17014
+ this.dialing = false;
17015
+ this.clearRace();
17016
+ this.afterRtc = void 0;
17017
+ }
17018
+ this.autoConnectFailures = 0;
17019
+ this.lastAutoConnectAt = 0;
17020
+ this.redial();
17021
+ }
16741
17022
  /** A WebRTC attempt this side dialled ended without opening: the next ranked transports, in order. */
16742
17023
  async dialAfterRtc(next) {
16743
17024
  this.dialing = true;
@@ -16772,12 +17053,65 @@ var GhostLink = class {
16772
17053
  const answered = this.answeredAt ? this.answeredAt + RACE_ANSWERED_MS : Infinity;
16773
17054
  const at = next.rest.filter((t) => t !== "webrtc/1").map((t) => Math.max(Math.min(offeredAt + (relayed.includes(t) ? RACE_RELAYED_MS : RACE_DIRECT_MS), answered), this.canDial(t) ? 0 : now + RACE_RETRY_MS));
16774
17055
  if (!at.length) return;
17056
+ if (this.offered?.resume && this.knockedEarly !== epoch) setTimeout(() => this.maybeKnockEarly(epoch), 0);
16775
17057
  this.raceTimer = setTimeout(() => {
16776
17058
  this.raceTimer = null;
16777
17059
  this.race(epoch, offeredAt);
16778
17060
  }, Math.max(0, Math.min(...at) - now));
16779
17061
  }
16780
17062
  /**
17063
+ * Back after an absence, this side answers an offer the contact made while it was away (it dials whatever its key):
17064
+ * the contact looks for that answer at the pace of an offer out a while (every 8 s on the relays), and its attempt may
17065
+ * end before it does. The direct transports both run are knocked on meanwhile, and the relayed ones where both apps
17066
+ * move off a relay once live (`upgrade/1`): the first live wins, and a knock that does not connect counts as nothing.
17067
+ */
17068
+ maybeKnockAnswering(epoch) {
17069
+ if (!this.resumingOverRtc || epoch !== this.connectionEpoch || this.knockedEarly === epoch || this.dataLink.state === "idle") return;
17070
+ if (this.channel || this.dialing || this.stopped || this.streamBlocked || this.keyStopped || !this.contactQuiet(1e4)) return;
17071
+ if (!this.fallback || !this.peerFallback) return;
17072
+ this.knockedEarly = epoch;
17073
+ if (this.resuming === "webrtc/1") this.resuming = void 0;
17074
+ const relayed = this.upgradesFromRelay() ? [] : this.relayedTransports;
17075
+ const offer = this.transportOffer();
17076
+ const direct = TRANSPORTS.filter((t) => t !== "webrtc/1" && offer.includes(t) && !!this.peerTransports?.includes(t) && !relayed.includes(t) && this.canDial(t));
17077
+ if (!direct.length) return;
17078
+ this.knockEarly(epoch, direct, () => this.dataLink.state === "answering" || this.dataLink.state === "connecting");
17079
+ }
17080
+ /** Back after an absence over WebRTC: not dialled yet, or this attempt's offer is the resume's (`offered.resume`). */
17081
+ get resumingOverRtc() {
17082
+ return this.resuming === "webrtc/1" || !!this.offered?.resume && this.offered.epoch === this.connectionEpoch;
17083
+ }
17084
+ /** Both apps move a chat off a relay once live (`upgrade/1`): this one, and the contact's by its capability record. */
17085
+ upgradesFromRelay() {
17086
+ return !!this.options.upgradeSupport && !!this.options.peerUpgrades?.();
17087
+ }
17088
+ /** The contact's newest packet is older than `EXPECT_PEER_MS`: it is not signalling, and reads at a slow pace. */
17089
+ contactQuiet(ms = EXPECT_PEER_MS) {
17090
+ return Date.now() - presenceSeenAt(this.session.peerPresence) >= ms;
17091
+ }
17092
+ /** The direct transports of an attempt that resumes to a quiet contact, knocked on while its offer stands (`scheduleRace`). */
17093
+ maybeKnockEarly(epoch) {
17094
+ const next = this.afterRtc;
17095
+ if (!this.offered?.resume || this.offered.epoch !== epoch || this.knockedEarly === epoch || next?.epoch !== epoch || epoch !== this.connectionEpoch) return;
17096
+ if (this.dialing || this.channel || this.stopped || !this.offerOut || !this.contactQuiet()) return;
17097
+ const relayed = this.upgradesFromRelay() ? [] : this.relayedTransports;
17098
+ const direct = next.rest.filter((t) => !relayed.includes(t) && this.canDial(t));
17099
+ if (!direct.length) return;
17100
+ this.knockedEarly = epoch;
17101
+ this.knockEarly(epoch, direct);
17102
+ }
17103
+ async knockEarly(epoch, transports, standing = () => this.offerOut) {
17104
+ this.knockingEarly = true;
17105
+ try {
17106
+ for (const transport of transports) {
17107
+ if (epoch !== this.connectionEpoch || this.channel || this.stopped || !standing()) return;
17108
+ await this.knock(transport);
17109
+ }
17110
+ } finally {
17111
+ this.knockingEarly = false;
17112
+ }
17113
+ }
17114
+ /**
16781
17115
  * A native endpoint that started while this side's offer is out unanswered: its transport was not up when the dial
16782
17116
  * ranked what to try, and joins the race (and what the offer ending goes on to) if both sides allow it.
16783
17117
  */
@@ -16822,6 +17156,13 @@ var GhostLink = class {
16822
17156
  */
16823
17157
  async race(epoch, offeredAt) {
16824
17158
  const next = this.afterRtc;
17159
+ if (this.knockingEarly && next?.epoch === epoch && epoch === this.connectionEpoch && !this.channel) {
17160
+ this.raceTimer = setTimeout(() => {
17161
+ this.raceTimer = null;
17162
+ this.race(epoch, offeredAt);
17163
+ }, RACE_RETRY_MS);
17164
+ return;
17165
+ }
16825
17166
  if (!next || next.epoch !== epoch || epoch !== this.connectionEpoch || this.channel || this.dialing || this.stopped || this.leaving || this.streamBlocked || this.keyStopped || !this.offerOut) return;
16826
17167
  const relayed = this.relayedTransports;
16827
17168
  const late = Date.now() - offeredAt >= 4e4 || this.answeredAt > 0 && Date.now() - this.answeredAt >= 6e3;
@@ -16913,7 +17254,7 @@ var GhostLink = class {
16913
17254
  * the DHT has no room for one, and the text goes without it. A `reply` (WISP 401 § Replies) goes whole on the
16914
17255
  * session; on the DHT only its id does, which the contact looks up in its own history. `forwarded`: the hop count
16915
17256
  * of a forwarded message (WISP 401 § Forwards), on both paths (the DHT drops it when the packet has no room). `card`:
16916
- * a status card (WISP 4xx · Status Cards), on the live session only; on the DHT the text, its fallback, goes alone.
17257
+ * a status card (WISP 405 · Status Cards), on the live session only; on the DHT the text, its fallback, goes alone.
16917
17258
  */
16918
17259
  async sendMessage(text, timestamp = Date.now(), stableId, preview, reply, forwarded, card) {
16919
17260
  const trimmed = text.trim();
@@ -17387,6 +17728,7 @@ var GhostLink = class {
17387
17728
  if (this.options.pinSupport) offered.push(PIN_CAPABILITY);
17388
17729
  if (this.options.statusCardSupport) offered.push(STATUS_CARD_CAPABILITY);
17389
17730
  if (this.options.buttonsSupport) offered.push(BUTTONS_CAPABILITY);
17731
+ if (this.options.upgradeSupport) offered.push(UPGRADE_CAPABILITY);
17390
17732
  if (this.options.deviceCapabilities) offered.push(...this.options.deviceCapabilities);
17391
17733
  return offered;
17392
17734
  }
@@ -17617,6 +17959,7 @@ var GhostLink = class {
17617
17959
  if (changed.includes("edit/1")) this.options.events?.onEditSupport?.(this.supportsEdits);
17618
17960
  if (changed.includes("pin/1")) this.options.events?.onPinSupport?.(this.supportsPins);
17619
17961
  if (changed.includes("wake/1")) this.options.events?.onWakeSupport?.(this.supportsWake);
17962
+ if (changed.includes("upgrade/1")) this.switcher.reconsider();
17620
17963
  const devices = this.options.deviceCapabilities;
17621
17964
  if (devices && changed.some((capability) => devices.includes(capability))) this.options.events?.onDeviceCapabilities?.(devices.filter((capability) => this.supportsDevice(capability)));
17622
17965
  this.emitPairingState();
@@ -17715,7 +18058,9 @@ var GhostLink = class {
17715
18058
  };
17716
18059
  if (transport === "webrtc/1") {
17717
18060
  this.rtcCandidateWaiter = done;
17718
- this.dataLink.connect().catch(done.reject);
18061
+ this.rtcRetired().then(() => {
18062
+ if (active) return this.dataLink.connect();
18063
+ }).catch(done.reject);
17719
18064
  } else {
17720
18065
  const endpoint = this.endpoints.get(transport), descriptor = plan.remote.descriptors[transport];
17721
18066
  if (!endpoint || !descriptor) {
@@ -17969,7 +18314,7 @@ var GhostLink = class {
17969
18314
  oldPaired?.stop();
17970
18315
  if (oldChannel) this.retire(oldChannel, () => {
17971
18316
  if (!oldBinding && this.activeBinding) this.dataLink.close();
17972
- });
18317
+ }, !oldBinding);
17973
18318
  this.session.setDataLinkOpen(true);
17974
18319
  if (migration.plan) switched = {
17975
18320
  from,
@@ -18388,6 +18733,7 @@ var GhostLink = class {
18388
18733
  this.pongWait = null;
18389
18734
  if (this.channel !== channel || this.stopped) return;
18390
18735
  traceLink(this.myPubKeyZ32, "pong-late", { unanswered: this.unansweredPings });
18736
+ this.pongLate = true;
18391
18737
  this.session.pollNow();
18392
18738
  }, PONG_WAIT_MS);
18393
18739
  };
@@ -18424,6 +18770,24 @@ var GhostLink = class {
18424
18770
  heardFromPeer() {
18425
18771
  if (this.pongWait) clearTimeout(this.pongWait);
18426
18772
  this.pongWait = null;
18773
+ this.pongLate = false;
18774
+ }
18775
+ /**
18776
+ * The session runs on a native transport, its last ping went unanswered, and the contact's newer packet names another
18777
+ * endpoint for that transport: the contact's app started again (a web page reloading, as a device that takes the
18778
+ * profile does twice), and the endpoint this session runs on went with it, with nothing said. A browser's relay-only
18779
+ * Iroh closes nothing as a page goes, and QUIC notices only at its idle timeout (30 s): until then this side held a
18780
+ * session nothing answered, and the contact, whose key does not dial, waited for it to dial (a Use here said hello
18781
+ * into that session and gave up "unreachable", Omarchy 2026-10-05). The session is let go, and the new endpoint
18782
+ * dialled now, as when the session closes.
18783
+ */
18784
+ restartedRedial(channel, transport) {
18785
+ if (!this.pongLate || this.channel !== channel || this.paired?.state.status !== "ready") return false;
18786
+ traceLink(this.myPubKeyZ32, "restarted-redial", { transport });
18787
+ this.stopLiveness();
18788
+ this.disconnect();
18789
+ this.peerLost("restarted");
18790
+ return true;
18427
18791
  }
18428
18792
  dropDeadSession(channel) {
18429
18793
  this.stopLiveness();
@@ -18503,9 +18867,13 @@ var GhostLink = class {
18503
18867
  }
18504
18868
  /**
18505
18869
  * Someone is here again (the chat was opened, the window came back): look now, and try at once rather
18506
- * than after the wait between failed attempts.
18870
+ * than after the wait between failed attempts. `network`: the device's network just came back.
18507
18871
  */
18508
- wake() {
18872
+ wake(params = {}) {
18873
+ if (params.network) {
18874
+ this.dht?.networkBack();
18875
+ this.networkBack();
18876
+ }
18509
18877
  this.autoConnectFailures = 0;
18510
18878
  this.lastAutoConnectAt = 0;
18511
18879
  this.session.pollNow();
@@ -19849,7 +20217,7 @@ function validatePaymentTarget(value, now = Date.now()) {
19849
20217
  * did:dht (the DIF method, https://github.com/decentralized-identity/did-dht/blob/main/spec/spec.md): a W3C DID document written as DNS records into the
19850
20218
  * Pkarr packet of an Ed25519 key, the BEP44 mutable item Ghostly already publishes chats under. This
19851
20219
  * module maps a document to that packet and back (strictly: a malformed packet is an error, never a
19852
- * guess) and resolves a did:dht through Pkarr relays. WISP 3xx-did-dht describes Ghostly's use.
20220
+ * guess) and resolves a did:dht through Pkarr relays. WISP 310-did-dht describes Ghostly's use.
19853
20221
  */
19854
20222
  var DID_DHT_PREFIX = "did:dht:";
19855
20223
  /** The TTL the spec recommends: Mainline's default record lifetime. */
@@ -22598,7 +22966,7 @@ var wireMentions = (mentions) => mentions.map(({ k, o, l }) => ({
22598
22966
  //#endregion
22599
22967
  //#region ../core/src/groupEdits.ts
22600
22968
  /**
22601
- * Editing a sent text in a group (WISP 9xx § Edits, both profiles). As in a 1:1 chat (WISP 401 § Edits), the author says
22969
+ * Editing a sent text in a group (WISP 902 § Edits, both profiles). As in a 1:1 chat (WISP 401 § Edits), the author says
22602
22970
  * the whole new text of one of its own messages with an edit number of its own per message, and the highest number
22603
22971
  * wins whatever order edits arrive in. A group has no receipts: the author says an edit to every member it can, and
22604
22972
  * again later (a private group: when an edge opens; a community: its members catch up from each other).
@@ -22610,7 +22978,7 @@ var wireMentions = (mentions) => mentions.map(({ k, o, l }) => ({
22610
22978
  *
22611
22979
  * { "x": { "t": "edit", "id": <message id>, "v": 3, "ts": 1790000000000, "text": "Done: 12 of 12", "m"?: [mentions], "sc"?: {card} } }
22612
22980
  *
22613
- * `sc` is the status card of this version (WISP 4xx · Status Cards); an edit with one may be numbered up to
22981
+ * `sc` is the status card of this version (WISP 405 · Status Cards); an edit with one may be numbered up to
22614
22982
  * `STATUS_CARD_LIMITS.edits`, one without only up to `MAX_EDITS_PER_MESSAGE`.
22615
22983
  *
22616
22984
  * Older apps drop both: a mesh session ignores a `t` it does not know, a community app the `x` frames it does not know.
@@ -22740,7 +23108,7 @@ var GROUP_LIMITS = {
22740
23108
  */
22741
23109
  relay: 256,
22742
23110
  relayBytes: 262144,
22743
- /** Other members' edits kept to hand on, the latest per message (WISP 9xx § Edits): count and ciphertext bytes. */
23111
+ /** Other members' edits kept to hand on, the latest per message (WISP 902 § Edits): count and ciphertext bytes. */
22744
23112
  relayEdits: 64,
22745
23113
  relayEditBytes: 524288,
22746
23114
  /** Edits of one member handed on for one sync that asks for them: fewer than a receiver takes in a window. */
@@ -22867,7 +23235,7 @@ var editAad = (f) => JSON.stringify([
22867
23235
  f.v,
22868
23236
  f.ts
22869
23237
  ]);
22870
- /** What a member signs of its reaction, so that a hub can pass it on (WISP 9xx · Group Mesh § Hubs). */
23238
+ /** What a member signs of its reaction, so that a hub can pass it on (WISP 902 · Group Mesh § Hubs). */
22871
23239
  var reactionSigned = (g, r) => utf8Encode(JSON.stringify([
22872
23240
  "ghostly-group/1 react",
22873
23241
  g,
@@ -22880,7 +23248,7 @@ var reactionSigned = (g, r) => utf8Encode(JSON.stringify([
22880
23248
  * on vouches for. A frame of its own, which apps from before hubs drop: they would take it as the hub's reaction.
22881
23249
  */
22882
23250
  var GROUP_REACTED_FRAME = "group-reacted";
22883
- /** What a member signs of its pin: any member passes it on as it is (WISP 9xx · Group Mesh § Pinned message). */
23251
+ /** What a member signs of its pin: any member passes it on as it is (WISP 902 · Group Mesh § Pinned message). */
22884
23252
  var pinSigned = (g, p) => utf8Encode(JSON.stringify([
22885
23253
  "ghostly-group/1 pin",
22886
23254
  g,
@@ -23148,7 +23516,7 @@ var GroupSession = class {
23148
23516
  get id() {
23149
23517
  return this.state.id;
23150
23518
  }
23151
- /** The name the admin gave the group, else the one it had when I got in (WISP 9xx § Metadata). */
23519
+ /** The name the admin gave the group, else the one it had when I got in (WISP 902 § Metadata). */
23152
23520
  get name() {
23153
23521
  return groupDisplayName(this.state.meta, this.state.name);
23154
23522
  }
@@ -23184,7 +23552,7 @@ var GroupSession = class {
23184
23552
  get picture() {
23185
23553
  return groupMetaPicture(this.state.meta);
23186
23554
  }
23187
- /** Whom the admin pinned as hubs and excluded (WISP 9xx · Group Mesh § Hubs), from the group's metadata. */
23555
+ /** Whom the admin pinned as hubs and excluded (WISP 902 · Group Mesh § Hubs), from the group's metadata. */
23188
23556
  get hubPolicy() {
23189
23557
  const meta = this.state.meta;
23190
23558
  if (!this.policyOf || this.policyOf.sig !== meta?.sig) this.policyOf = {
@@ -23357,7 +23725,7 @@ var GroupSession = class {
23357
23725
  this.hooks.changed();
23358
23726
  return { secret };
23359
23727
  }
23360
- /** Someone out of the roster is not handed on any more, whatever they sent while in it (WISP 9xx § Catch-up). */
23728
+ /** Someone out of the roster is not handed on any more, whatever they sent while in it (WISP 902 § Catch-up). */
23361
23729
  pruneRelay() {
23362
23730
  if (this.state.relay?.length) this.state.relay = this.state.relay.filter((f) => rosterHas(this.roster, f.s));
23363
23731
  if (this.state.relayEdits?.length) this.state.relayEdits = this.state.relayEdits.filter((f) => rosterHas(this.roster, f.s));
@@ -23424,8 +23792,8 @@ var GroupSession = class {
23424
23792
  * Encrypts and signs a text, keeps it for catch-up and sends it to every other member. `mentions`: places of the
23425
23793
  * text that name members (everyone: the admin only); what does not hold is left out. `reply`: the message it
23426
23794
  * answers, sealed apart from the text (`r`) like the mentions, so an older app still reads the text. `forwarded`: the
23427
- * hop count of a forwarded text (`f`, WISP 9xx § Forwards). `card`: a status card, checked by the caller, sealed in a
23428
- * box of its own (`sc`, WISP 4xx · Status Cards); the text is its fallback.
23795
+ * hop count of a forwarded text (`f`, WISP 902 § Forwards). `card`: a status card, checked by the caller, sealed in a
23796
+ * box of its own (`sc`, WISP 405 · Status Cards); the text is its fallback.
23429
23797
  */
23430
23798
  sendText(text, now = Date.now(), mentions = [], reply, forwarded, card) {
23431
23799
  return this.serialize(async () => {
@@ -23609,7 +23977,7 @@ var GroupSession = class {
23609
23977
  return first ? groupMessageId(f.s, first.e, first.n) : groupMessageId(f.s, f.e, f.n);
23610
23978
  }
23611
23979
  /**
23612
- * Says an edit of one of my messages (WISP 9xx § Edits): edit `v`, the whole new text and its mentions, sealed under
23980
+ * Says an edit of one of my messages (WISP 902 § Edits): edit `v`, the whole new text and its mentions, sealed under
23613
23981
  * the key of the message's epoch and signed, to every member of that epoch still in the group, or to `to` alone (an
23614
23982
  * edge that just opened). Someone admitted after the message cannot open it and is not sent it. The message's
23615
23983
  * epoch key must still be here: past `GROUP_LIMITS.secrets` epochs a message cannot be edited.
@@ -23710,7 +24078,7 @@ var GroupSession = class {
23710
24078
  }
23711
24079
  /**
23712
24080
  * A frame from an authenticated member over the pairwise edge. Anything malformed or unauthorized is dropped.
23713
- * Resolves to what it took that a hub passes on to its other edges (WISP 9xx · Group Mesh § Hubs): each message,
24081
+ * Resolves to what it took that a hub passes on to its other edges (WISP 902 · Group Mesh § Hubs): each message,
23714
24082
  * edit, commit (without the secret sealed for me), metadata statement and signed leave, the first time only, so a
23715
24083
  * flood among hubs stops at every member that already has it. Only what its author still in the roster signed.
23716
24084
  */
@@ -24260,7 +24628,7 @@ var GroupSession = class {
24260
24628
  }
24261
24629
  /**
24262
24630
  * Pins a member as a hub, excludes one from being a hub, or leaves it to the member's app (`null`): only the admin,
24263
- * in the group's metadata beside the picture (WISP 9xx · Group Mesh § Hubs).
24631
+ * in the group's metadata beside the picture (WISP 902 · Group Mesh § Hubs).
24264
24632
  */
24265
24633
  setHub(key, role, now = Date.now()) {
24266
24634
  return this.serialize(async () => {
@@ -24372,7 +24740,7 @@ var GroupSession = class {
24372
24740
  //#endregion
24373
24741
  //#region ../core/src/groupWake.ts
24374
24742
  /**
24375
- * Wake-up push in a private group (WISP 9xx · Group Mesh § Wake-up push): a member whose web app is closed is woken
24743
+ * Wake-up push in a private group (WISP 902 · Group Mesh § Wake-up push): a member whose web app is closed is woken
24376
24744
  * when a message names it. `wake/1` as a 1:1 chat has it (WISP 401 § Wake-up push), carried on the group's edges: each
24377
24745
  * member shares its subscription with each other member on their own edge, under a token of that edge alone, and
24378
24746
  * only in a group it has not muted:
@@ -24537,7 +24905,7 @@ function entryParams(link, mySeed, myZ32, peerZ32) {
24537
24905
  //#endregion
24538
24906
  //#region ../core/src/groupCommunity.ts
24539
24907
  /**
24540
- * `group-community/1` (WISP 9xx · Group Community): a group whose link is the way in, for hundreds
24908
+ * `group-community/1` (WISP 903 · Group Community): a group whose link is the way in, for hundreds
24541
24909
  * of members, where the admin need not be online for anyone to join.
24542
24910
  *
24543
24911
  * What differs from `group-mesh/1`:
@@ -24833,7 +25201,7 @@ var seenKey = (e, h) => `${e}:${h}`;
24833
25201
  /** One of a member's frames, among its own. */
24834
25202
  var frameKey = (f) => `${f.e}:${f.h}:${f.n}`;
24835
25203
  /**
24836
- * Where a frame said again under a newer commit was first said (`o`, WISP 9xx § Messages and catch-up): its commit's
25204
+ * Where a frame said again under a newer commit was first said (`o`, WISP 903 § Messages and catch-up): its commit's
24837
25205
  * epoch and short hash and its number there. Never the frame's own place, nor a later epoch than its own.
24838
25206
  */
24839
25207
  function readOriginal(o, frame) {
@@ -24855,7 +25223,7 @@ function seenIn(seen, f) {
24855
25223
  return f.n === entry.high || entry.window.includes(f.n);
24856
25224
  }
24857
25225
  /**
24858
- * Whether a stored community state holds the frame a beacon's head names (WISP 9xx § Head), read without running
25226
+ * Whether a stored community state holds the frame a beacon's head names (WISP 903 § Head), read without running
24859
25227
  * the group: its own frame, one it took, or one of an epoch so far behind its own that it would not be taken now.
24860
25228
  */
24861
25229
  function communityHasFrame(state, f) {
@@ -25033,7 +25401,7 @@ var CommunitySession = class {
25033
25401
  get id() {
25034
25402
  return this.state.id;
25035
25403
  }
25036
- /** The name the admin gave the group, else the one it had when I got in (WISP 9xx § Metadata). */
25404
+ /** The name the admin gave the group, else the one it had when I got in (WISP 903 § Metadata). */
25037
25405
  get name() {
25038
25406
  return groupDisplayName(this.state.meta, this.state.name);
25039
25407
  }
@@ -25104,7 +25472,7 @@ var CommunitySession = class {
25104
25472
  return -1;
25105
25473
  }
25106
25474
  /**
25107
- * What someone the chain took out is told when it comes back (WISP 9xx § Leaving and removal): the commits from where
25475
+ * What someone the chain took out is told when it comes back (WISP 903 § Leaving and removal): the commits from where
25108
25476
  * it says it is (`sync`: its tip, or its locator) up to the one that took it out, and nothing after. Later commits
25109
25477
  * are not its business (a `link` commit names the new entry key); no secret, no stored frame, no seed goes with them.
25110
25478
  * Empty when it is a member, was never one, or already holds that commit.
@@ -25701,8 +26069,8 @@ var CommunitySession = class {
25701
26069
  /**
25702
26070
  * `mentions`: places of the text that name members, sealed with it (`m`). Never everyone in a community. They
25703
26071
  * count against the text's 16 KiB, so the box stays within what older apps accept. `reply`: the message it
25704
- * answers (`r`), counted the same way. `forwarded`: a forwarded text's hop count (`fw`, WISP 9xx § Forwards). `card`:
25705
- * a status card, checked by the caller (`sc`, WISP 4xx · Status Cards), counted the same way; the text is its fallback.
26072
+ * answers (`r`), counted the same way. `forwarded`: a forwarded text's hop count (`fw`, WISP 903 § Forwards). `card`:
26073
+ * a status card, checked by the caller (`sc`, WISP 405 · Status Cards), counted the same way; the text is its fallback.
25706
26074
  */
25707
26075
  sendText(text, nick, now = Date.now(), mentions = [], reply, forwarded, card) {
25708
26076
  return this.serialize(async () => {
@@ -25754,7 +26122,7 @@ var CommunitySession = class {
25754
26122
  });
25755
26123
  }
25756
26124
  /**
25757
- * An edit of one of my messages (WISP 9xx · Group Community § Edits): an application frame like `sendApp`'s, sealed
26125
+ * An edit of one of my messages (WISP 903 · Group Community § Edits): an application frame like `sendApp`'s, sealed
25758
26126
  * to the current epoch, but with a text's room rather than an application frame's, since it carries a whole text.
25759
26127
  * The nick is left out when the text leaves no room for it.
25760
26128
  */
@@ -26467,7 +26835,7 @@ function signCommunity(commit, seed) {
26467
26835
  //#endregion
26468
26836
  //#region ../core/src/groupTyping.ts
26469
26837
  /**
26470
- * The typing indicator of a private group (WISP 9xx · Group Mesh § Typing): "Ana is typing", said on the group's
26838
+ * The typing indicator of a private group (WISP 902 · Group Mesh § Typing): "Ana is typing", said on the group's
26471
26839
  * edges only, the way a 1:1 chat says it on its session (WISP 401 § Typing), with the same kinds, limits and wording.
26472
26840
  *
26473
26841
  * {"t":"group-typing","g":"<group>","e":3,"n":"<nonce>","c":"<sealed>"}
@@ -26571,6 +26939,6 @@ var GroupTypingBoard = class {
26571
26939
  }
26572
26940
  };
26573
26941
  //#endregion
26574
- export { checkAtprotoProofRecord as $, filePartName as $a, BUTTON_ID as $i, freshHubs as $n, createChatInvite as $o, IdentityExchange as $r, randomBytes as $s, isPubkyKey as $t, GROUP_EDIT_TEXT_BYTES as A, RTC_CONFIG as Aa, voicePeaksOf as Ai, findEndpoint as An, readSetUpdate as Ao, newWakeToken as Ar, RelayTransport as As, encodeDidDhtPacket as At, sameSshKey as B, LEGACY_SERVICES as Ba, GROUP_REACTION_FRAME as Bi, SIGNAL_FRESH_MS as Bn, enrollCancelReason as Bo, HOLD_LIMITS as Br, createRelayPayload as Bs, USDT_PUBLIC_RPC as Bt, groupWakes as C, formatLocalTarget as Ca, sanitizeFileName as Ci, resolveLightningDestination as Cn, readHandoffHello as Co, nextPinNumber as Cr, nextTurnPosition as Cs, parseDidSignaturePaste as Ct, GroupSession as D, decodeControl as Da, baseMime as Di, ENDPOINT as Dn, sealHandoffPiece as Do, WAKE_CALL_INTERVAL_MS as Dr, turnKeys as Ds, didDhtDocument as Dt, GROUP_READ_NOTE as E, LIMITS as Ea, VOICE_LIMITS as Ei, decodeBolt11 as En, readHandoffVerified as Eo, readPin as Er, signTurnRelease as Es, strictBase64Url as Et, SSHSIG_MAX_ARMOR as F, extractParamsFromSdp as Fa, dhtEditId as Fi, INVITE_TAKEN as Fn, ENROLL_DONE as Fo, vapidKeysMatch as Fr, budgetRetryMs as Fs, ONCHAIN_PROVIDER as Ft, decodeAnyBitcoinAddress as G, HANDOFF_HAVE as Ga, reactionEmoji as Gi, MESH_HUBS as Gn, enrollProofFrame as Go, CapsExchange as Gr, identityFromSeed as Gs, validatePaymentTarget as Gt, verifySshSignature as H, HANDOFF_ACK as Ha, ReactionWindow as Hi, SIGNAL_REPLY_MS as Hn, enrollDoneFrame as Ho, HoldRefusedError as Hr, openRelayPayload as Hs, assertTokenUnits as Ht, SSHSIG_NAMESPACE as I, parseCallSignal as Ia, parseLinkPreview as Ii, LIVENESS_PING_MS as In, ENROLL_GRANT as Io, BUTTONS_CAPABILITY as Ir, isDiscoveryBudgetError as Is, PaymentPreflightError as It, ATPROTO_PROOF_COLLECTION as J, HANDOFF_PAKE as Ja, validReactionNumber as Ji, pickMeshHubs as Jn, verifyEnrollProof as Jo, dialDescriptors as Jr, verify as Js, SPARK_PROVIDER as Jt, ATPROTO_DECLARED_SCOPE as K, HANDOFF_HELLO as Ka, reactionIsNewer as Ki, mayBeHub as Kn, readEnrollGrant as Ko, DHT_TEXT_CAPABILITY as Kr, identityFromSeedB64 as Ks, walletNetworkOf as Kt, SshSigError as L, signalHasVideo as La, forwardedAgain as Li, pairedMessageFrame as Ln, ENROLL_HELLO as Lo, EDIT_CAPABILITY as Lr, withRequestOptions as Ls, SEPOLIA_TEST_USDT as Lt, parseCommunityEdit as M, buildSdpFromSignal as Ma, EDIT_BUFFER_MS as Mi, parseCashuRequestPayload as Mn, setUpdateFrame as Mo, relayRequest as Mr, normalizeRelayUrl as Ms, signDidDhtPacket as Mt, mentionsMember as N, callRtcConfig as Na, EDIT_RATE_WINDOW_MS as Ni, parseFedimintRequestPayload as Nn, signTombstone as No, wakeRequest as Nr, setLinkTraceSink as Ns, ETHEREUM_USDT as Nt, COMMUNITY_EDIT_FRAME as O, encodeControl as Oa, fileMessageText as Oi, cashuRequestPayload as On, SET_ACK as Oo, WakeLimiter as Or, turnName as Os, didDhtFromPublicKey as Ot, groupName as P, callSignalHeardAt as Pa, RateWindow as Pi, GhostLink as Pn, verifySetUpdate as Po, checkPushEndpoint as Pr, traceLink as Ps, EVM_TEST_CHAINS as Pt, atprotoProofRkey as Q, HANDOFF_VERIFIED as Qa, replySnippet as Qi, doorHubs as Qn, OwnInviteError as Qo, IdentityCheckUnavailable as Qr, fromBase64Url as Qs, PUBKY_PROOF_MAX_BYTES as Qt, parseSshPublicKey as R, emptyLinkRecords as Ra, readForwarded as Ri, CarriedTransport as Rn, ENROLL_PROOF as Ro, HOLD_CAPABILITY as Rr, PacketTooLargeError as Rs, SEPOLIA_TEST_USDT_FAUCET as Rt, groupWakeFrame as S, GhostlyHttpError as Sa, safeBlobType as Si, requestLnurlInvoice as Sn, readHandoffHave as So, mayPin as Sr, firstDeviceSetSecret as Ss, parseDidDocument as St, GROUP_REACTED_FRAME as T, webLocalFetch as Ta, PLAYABLE_AUDIO as Ti, engineText as Tn, readHandoffTurnFrame as To, pinNumberHolds as Tr, signTurnPacket as Ts, staticDidDocument as Tt, verifyBitcoinMessage as U, HANDOFF_CANCEL as Ua, nextReactionNumber as Ui, groupSignalFrame as Un, enrollGrantFrame as Uo, newMailbox as Ur, parseRelayPayload as Us, assertWholeSats as Ut, sshSignCommand as V, serviceIdFromName as Va, REACTION_LIMITS as Vi, SIGNAL_HOLD_MS as Vn, enrollDigits as Vo, HoldKeys as Vr, newerPacket as Vs, WALLET_NETWORKS as Vt, BitcoinAddressError as W, HANDOFF_DONE as Wa, queueReaction as Wi, readGroupSignal as Wn, enrollHelloFrame as Wo, readManifest as Wr, createIdentity as Ws, formatPaymentAmount as Wt, atprotoDidMethod as X, HANDOFF_REQUEST as Xa, pairedReplyAuthor as Xi, beaconKeys as Xn, deviceInviteJoinerParams as Xo, IDENTITY_MAX_VALIDITY as Xr, sanitizeAvatar as Xs, sparkInvoiceDetails as Xt, ATPROTO_PROOF_SCOPE as Y, HANDOFF_PIECE_BYTES as Ya, wireReaction as Yi, COMMUNITY_TOPOLOGY as Yn, createDeviceInvite as Yo, IDENTITY_MAX_EVIDENCE as Yr, MAX_AVATAR_LENGTH as Ys, sparkAddressKind as Yt, atprotoProofRecord as Z, HANDOFF_TIMINGS as Za, readReply as Zi, beaconRecords as Zn, readDeviceInvite as Zo, IDENTITY_REVOCATION_LABEL as Zr, bytesEqual as Zs, isBitcoinAddress as Zt, knockRecords as _, edgeParams as _a, peekDhtMailbox as _i, recordsFromTxt as _n, handoffVersions as _o, TypingSender as _r, TURN_SETTLE_MS as _s, didWebFileNames as _t, sealGroupTyping as a, sameButtonText as aa, receivedIdentityStatus as ai, pubkyProofPath as an, handoffContext as ao, pickHubs as ar, ENROLL_CAPABILITY as as, verifyAtprotoRecordProof as at, GROUP_WAKE_FRAME as b, RELAY_POLL_INTERVALS as ba, parseImageMeta as bi, txtValue as bn, readHandoffCancel as bo, GROUP_PIN_FRAME as br, TURN_LAST_SEQUENCE as bs, normalizeDid as bt, GROUP_READ_NOTE_COMMUNITY as c, claimedTime as ca, PeerProofs as ci, DOMAIN_FILE_MAX_BYTES as cn, handoffDigest as co, readBeaconHead as cr, deviceEchoNonce as cs, DID_JWS_MAX_LENGTH as ct, encodeCommunityLink as d, shownTime as da, TRANSPORTS as di, domainTxtRecord as dn, handoffManifestFrames as do, ChatFiles as dr, devicePingFrame as ds, checkDidSignature as dt, ITEM_STATES as ea, toBase64Url as ec, boundVerifiedIdentity as ei, isPubkyProofFolder as en, handoffAttemptAllowed as eo, lobbyKeys as er, decodeInviteCode as es, isAtprotoDid as et, KNOCK_TTL_MS as f, GROUP_MEMBER_CAP as fa, automaticTransport as fi, domainWellKnownFile as fn, handoffOfferFrame as fo, FILE_LIMITS as fr, newDeviceSetSecret as fs, didGhostlyServiceEntry as ft, knockIdentity as g, verifyCommitSignature as ga, DHT_MESSAGE_TTL as gi, normalizeDomain as gn, handoffStreamKey as go, TYPING_REFRESH_MS as gr, TURN_RAISE_READ_TIMEOUT_MS as gs, didWebFile as gt, entryParams as h, rosterHas as ha, DHT_FILE_TOO_LARGE as hi, nip05RootKey as hn, handoffRetryAfter as ho, TYPING_KINDS as hr, TURN_PUT_WINDOW_MS as hs, didWebDocumentUrl as ht, openGroupTyping as i, checkStatusCard as ia, newIdentityBinding as ii, pubkyProofFile as in, handoffCancelFrame as io, newerHead as ir, DEVICES_CAPABILITY as is, AtprotoProofError as it, carryMentions as j, answersOffer as ja, parseVideoMeta as ji, isFederationId as jn, setAckFrame as jo, parseWakeFrame as jr, currentRelays as js, resolveDidDht as jt, GROUP_EDIT_FRAME as k, CALL_SIGNAL_MAX_AGE_MS as ka, parseVoiceMeta as ki, fedimintRequestPayload as kn, SET_UPDATE as ko, deviceWakeRequest as kr, DEFAULT_RELAYS as ks, encodeDidDhtDeactivated as kt, communityHasFrame as l, heardTime as la, emptyProofLedger as li, decodeDnsResponse as ln, handoffEphemeral as lo, readLobby as lr, deviceKeyZ32 as ls, DidError as lt, encodeGroupEntryLink as m, MEMBER_KEY as ma, irohRelayUrl as mi, isPublicAddress as mn, handoffReleaseFrame as mo, transferStalled as mr, webCryptoSigner as ms, didSignedMessages as mt, GroupTypingBoard as n, STATUS_CARD_LIMITS as na, utf8Decode as nc, identityRevocationValue as ni, normalizePubkyKey as nn, handoffAttemptSucceeded as no, mergeBeacon as nr, inviteLink as ns, parseAtprotoDidDocument as nt, COMMUNITY_LIMITS as o, statusCardText as oa, revokesIdentity as oi, homeserverWebEndpoint as on, handoffContextProof as oo, rankHubs as or, HANDOFF_CAPABILITY as os, DID_DOCUMENT_MAX_BYTES as ot, decodeGroupEntryLink as p, MAX_GROUP_CHAIN as pa, irohDescriptor as pi, encodeDnsQuery as pn, handoffPakeFrame as po, transferEnded as pr, seedSigner as ps, didJwsAlg as pt, ATPROTO_FULL_SCOPE as q, HANDOFF_OFFER as qa, readReaction as qi, meshHubs as qn, readEnrollHello as qo, capsDescriptors as qr, publicKeyFromZ32 as qs, SPARK_NETWORKS as qt, groupTypingEpoch as r, buttonLabelClash as ra, utf8Encode as rc, identityStatement as ri, pubkyProofCapability as rn, handoffBusyFrame as ro, mergeLobby as rr, inviteOwnership as rs, shortAtprotoDid as rt, CommunitySession as s, arrivalKey as sa, lastSharedWithMe as si, pubkyHomeserverOf as sn, handoffDataFrame as so, readBeacon as sr, deviceEchoFrame as ss, DID_FILE_MAX_BYTES as st, GROUP_TYPING_FRAME as t, RUN_RESULTS as ta, toZ32 as tc, emptyIdentityLedger as ti, newPubkyProofFolder as tn, handoffAttemptFailed as to, lobbyRecords as tr, encodeInviteCode as ts, normalizeAtprotoHandle as tt, decodeCommunityLink as u, receivedTimestamp as ua, validExternalKey as ui, dnsProofName as un, handoffHelloFrame as uo, shouldBeHub as ur, deviceLinkPairing as us, checkDidJws as ut, mergeKnocks as v, DirectPathWatch as va, DHT_FILE_CAPABILITY as vi, recordsFromWellKnown as vn, newHandoffId as vo, sanitizeTypingStatus as vr, classifyTurnRead as vs, didWebFileUrl as vt, parseGroupWakeFrame as w, parseLocalTarget as wa, sanitizeMime as wi, engineError as wn, readHandoffPake as wo, pinIsNewer as wr, readTurnPacket as ws, signingKeys as wt, GroupWakeLimiter as x, presenceSeenAt as xa, readImageMeta as xi, wellKnownUrl as xn, readHandoffDone as xo, PIN_LIMITS as xr, TURN_MAX as xs, parseDid as xt, readKnocks as y, EXPECT_PEER_MS as ya, dhtFileId as yi, recordsName as yn, readHandoffAck as yo, typingActivity as yr, turnPutSummary as ys, didWebHost as yt, parseSshSignature as z, HTTP_SERVICE_PROTO as za, COMMUNITY_REACTION_FRAME as zi, GROUP_SIGNAL_FRAME as zn, enrollCancelFrame as zo, HOLD_DATED_BACK_MS as zr, RELAY_PAYLOAD_MAX_BYTES as zs, TEST_USDT_FAUCET_AMOUNT as zt };
26942
+ export { checkAtprotoProofRecord as $, HANDOFF_VERIFIED as $a, replySnippet as $i, freshHubs as $n, OwnInviteError as $o, IdentityCheckUnavailable as $r, fromBase64Url as $s, isPubkyKey as $t, GROUP_EDIT_TEXT_BYTES as A, CALL_SIGNAL_MAX_AGE_MS as Aa, parseVoiceMeta as Ai, findEndpoint as An, SET_UPDATE as Ao, newWakeToken as Ar, DEFAULT_RELAYS as As, encodeDidDhtPacket as At, sameSshKey as B, HTTP_SERVICE_PROTO as Ba, COMMUNITY_REACTION_FRAME as Bi, SIGNAL_FRESH_MS as Bn, enrollCancelFrame as Bo, HOLD_DATED_BACK_MS as Br, RELAY_PAYLOAD_MAX_BYTES as Bs, USDT_PUBLIC_RPC as Bt, groupWakes as C, GhostlyHttpError as Ca, safeBlobType as Ci, resolveLightningDestination as Cn, readHandoffHave as Co, nextPinNumber as Cr, firstDeviceSetSecret as Cs, parseDidSignaturePaste as Ct, GroupSession as D, LIMITS as Da, VOICE_LIMITS as Di, ENDPOINT as Dn, readHandoffVerified as Do, WAKE_CALL_INTERVAL_MS as Dr, signTurnRelease as Ds, didDhtDocument as Dt, GROUP_READ_NOTE as E, webLocalFetch as Ea, PLAYABLE_AUDIO as Ei, decodeBolt11 as En, readHandoffTurnFrame as Eo, readPin as Er, signTurnPacket as Es, strictBase64Url as Et, SSHSIG_MAX_ARMOR as F, callSignalHeardAt as Fa, RateWindow as Fi, INVITE_TAKEN as Fn, verifySetUpdate as Fo, vapidKeysMatch as Fr, traceLink as Fs, ONCHAIN_PROVIDER as Ft, decodeAnyBitcoinAddress as G, HANDOFF_DONE as Ga, queueReaction as Gi, MESH_HUBS as Gn, enrollHelloFrame as Go, readManifest as Gr, createIdentity as Gs, validatePaymentTarget as Gt, verifySshSignature as H, serviceIdFromName as Ha, REACTION_LIMITS as Hi, SIGNAL_REPLY_MS as Hn, enrollDigits as Ho, HoldKeys as Hr, newerPacket as Hs, assertTokenUnits as Ht, SSHSIG_NAMESPACE as I, extractParamsFromSdp as Ia, dhtEditId as Ii, LIVENESS_PING_MS as In, ENROLL_DONE as Io, BUTTONS_CAPABILITY as Ir, budgetRetryMs as Is, PaymentPreflightError as It, ATPROTO_PROOF_COLLECTION as J, HANDOFF_OFFER as Ja, readReaction as Ji, pickMeshHubs as Jn, readEnrollHello as Jo, capsDescriptors as Jr, publicKeyFromZ32 as Js, SPARK_PROVIDER as Jt, ATPROTO_DECLARED_SCOPE as K, HANDOFF_HAVE as Ka, reactionEmoji as Ki, mayBeHub as Kn, enrollProofFrame as Ko, CapsExchange as Kr, identityFromSeed as Ks, walletNetworkOf as Kt, SshSigError as L, parseCallSignal as La, parseLinkPreview as Li, pairedMessageFrame as Ln, ENROLL_GRANT as Lo, EDIT_CAPABILITY as Lr, isDiscoveryBudgetError as Ls, SEPOLIA_TEST_USDT as Lt, parseCommunityEdit as M, answersOffer as Ma, parseVideoMeta as Mi, parseCashuRequestPayload as Mn, setAckFrame as Mo, relayRequest as Mr, currentRelays as Ms, signDidDhtPacket as Mt, mentionsMember as N, buildSdpFromSignal as Na, EDIT_BUFFER_MS as Ni, parseFedimintRequestPayload as Nn, setUpdateFrame as No, wakeRequest as Nr, normalizeRelayUrl as Ns, ETHEREUM_USDT as Nt, COMMUNITY_EDIT_FRAME as O, decodeControl as Oa, baseMime as Oi, cashuRequestPayload as On, sealHandoffPiece as Oo, WakeLimiter as Or, turnKeys as Os, didDhtFromPublicKey as Ot, groupName as P, callRtcConfig as Pa, EDIT_RATE_WINDOW_MS as Pi, GhostLink as Pn, signTombstone as Po, checkPushEndpoint as Pr, setLinkTraceSink as Ps, EVM_TEST_CHAINS as Pt, atprotoProofRkey as Q, HANDOFF_TIMINGS as Qa, readReply as Qi, doorHubs as Qn, readDeviceInvite as Qo, IDENTITY_REVOCATION_LABEL as Qr, bytesEqual as Qs, PUBKY_PROOF_MAX_BYTES as Qt, parseSshPublicKey as R, signalHasVideo as Ra, forwardedAgain as Ri, CarriedTransport as Rn, ENROLL_HELLO as Ro, UPGRADE_CAPABILITY as Rr, withRequestOptions as Rs, SEPOLIA_TEST_USDT_FAUCET as Rt, groupWakeFrame as S, presenceSeenAt as Sa, readImageMeta as Si, requestLnurlInvoice as Sn, readHandoffDone as So, mayPin as Sr, TURN_MAX as Ss, parseDidDocument as St, GROUP_REACTED_FRAME as T, parseLocalTarget as Ta, sanitizeMime as Ti, engineText as Tn, readHandoffPake as To, pinNumberHolds as Tr, readTurnPacket as Ts, staticDidDocument as Tt, verifyBitcoinMessage as U, HANDOFF_ACK as Ua, ReactionWindow as Ui, groupSignalFrame as Un, enrollDoneFrame as Uo, HoldRefusedError as Ur, openRelayPayload as Us, assertWholeSats as Ut, sshSignCommand as V, LEGACY_SERVICES as Va, GROUP_REACTION_FRAME as Vi, SIGNAL_HOLD_MS as Vn, enrollCancelReason as Vo, HOLD_LIMITS as Vr, createRelayPayload as Vs, WALLET_NETWORKS as Vt, BitcoinAddressError as W, HANDOFF_CANCEL as Wa, nextReactionNumber as Wi, readGroupSignal as Wn, enrollGrantFrame as Wo, newMailbox as Wr, parseRelayPayload as Ws, formatPaymentAmount as Wt, atprotoDidMethod as X, HANDOFF_PIECE_BYTES as Xa, wireReaction as Xi, beaconKeys as Xn, createDeviceInvite as Xo, IDENTITY_MAX_EVIDENCE as Xr, MAX_AVATAR_LENGTH as Xs, sparkInvoiceDetails as Xt, ATPROTO_PROOF_SCOPE as Y, HANDOFF_PAKE as Ya, validReactionNumber as Yi, COMMUNITY_TOPOLOGY as Yn, verifyEnrollProof as Yo, dialDescriptors as Yr, verify as Ys, sparkAddressKind as Yt, atprotoProofRecord as Z, HANDOFF_REQUEST as Za, pairedReplyAuthor as Zi, beaconRecords as Zn, deviceInviteJoinerParams as Zo, IDENTITY_MAX_VALIDITY as Zr, sanitizeAvatar as Zs, isBitcoinAddress as Zt, knockRecords as _, verifyCommitSignature as _a, DHT_MESSAGE_TTL as _i, recordsFromTxt as _n, handoffStreamKey as _o, TypingSender as _r, TURN_RAISE_READ_TIMEOUT_MS as _s, didWebFileNames as _t, sealGroupTyping as a, checkStatusCard as aa, newIdentityBinding as ai, pubkyProofPath as an, handoffCancelFrame as ao, pickHubs as ar, DEVICES_CAPABILITY as as, verifyAtprotoRecordProof as at, GROUP_WAKE_FRAME as b, EXPECT_PEER_MS as ba, dhtFileId as bi, txtValue as bn, readHandoffAck as bo, GROUP_PIN_FRAME as br, turnPutSummary as bs, normalizeDid as bt, GROUP_READ_NOTE_COMMUNITY as c, arrivalKey as ca, lastSharedWithMe as ci, DOMAIN_FILE_MAX_BYTES as cn, handoffDataFrame as co, readBeaconHead as cr, deviceEchoFrame as cs, DID_JWS_MAX_LENGTH as ct, encodeCommunityLink as d, receivedTimestamp as da, validExternalKey as di, domainTxtRecord as dn, handoffHelloFrame as do, ChatFiles as dr, deviceLinkPairing as ds, checkDidSignature as dt, BUTTON_ID as ea, randomBytes as ec, IdentityExchange as ei, isPubkyProofFolder as en, filePartName as eo, lobbyKeys as er, createChatInvite as es, isAtprotoDid as et, KNOCK_TTL_MS as f, shownTime as fa, TRANSPORTS as fi, domainWellKnownFile as fn, handoffManifestFrames as fo, FILE_LIMITS as fr, devicePingFrame as fs, didGhostlyServiceEntry as ft, knockIdentity as g, rosterHas as ga, DHT_FILE_TOO_LARGE as gi, normalizeDomain as gn, handoffRetryAfter as go, TYPING_REFRESH_MS as gr, TURN_PUT_WINDOW_MS as gs, didWebFile as gt, entryParams as h, MEMBER_KEY as ha, irohRelayUrl as hi, nip05RootKey as hn, handoffReleaseFrame as ho, TYPING_KINDS as hr, webCryptoSigner as hs, didWebDocumentUrl as ht, openGroupTyping as i, buttonLabelClash as ia, utf8Encode as ic, identityStatement as ii, pubkyProofFile as in, handoffBusyFrame as io, newerHead as ir, inviteOwnership as is, AtprotoProofError as it, carryMentions as j, RTC_CONFIG as ja, voicePeaksOf as ji, isFederationId as jn, readSetUpdate as jo, parseWakeFrame as jr, RelayTransport as js, resolveDidDht as jt, GROUP_EDIT_FRAME as k, encodeControl as ka, fileMessageText as ki, fedimintRequestPayload as kn, SET_ACK as ko, deviceWakeRequest as kr, turnName as ks, encodeDidDhtDeactivated as kt, communityHasFrame as l, claimedTime as la, PeerProofs as li, decodeDnsResponse as ln, handoffDigest as lo, readLobby as lr, deviceEchoNonce as ls, DidError as lt, encodeGroupEntryLink as m, MAX_GROUP_CHAIN as ma, irohDescriptor as mi, isPublicAddress as mn, handoffPakeFrame as mo, transferStalled as mr, seedSigner as ms, didSignedMessages as mt, GroupTypingBoard as n, RUN_RESULTS as na, toZ32 as nc, emptyIdentityLedger as ni, normalizePubkyKey as nn, handoffAttemptFailed as no, mergeBeacon as nr, encodeInviteCode as ns, parseAtprotoDidDocument as nt, COMMUNITY_LIMITS as o, sameButtonText as oa, receivedIdentityStatus as oi, homeserverWebEndpoint as on, handoffContext as oo, rankHubs as or, ENROLL_CAPABILITY as os, DID_DOCUMENT_MAX_BYTES as ot, decodeGroupEntryLink as p, GROUP_MEMBER_CAP as pa, automaticTransport as pi, encodeDnsQuery as pn, handoffOfferFrame as po, transferEnded as pr, newDeviceSetSecret as ps, didJwsAlg as pt, ATPROTO_FULL_SCOPE as q, HANDOFF_HELLO as qa, reactionIsNewer as qi, meshHubs as qn, readEnrollGrant as qo, DHT_TEXT_CAPABILITY as qr, identityFromSeedB64 as qs, SPARK_NETWORKS as qt, groupTypingEpoch as r, STATUS_CARD_LIMITS as ra, utf8Decode as rc, identityRevocationValue as ri, pubkyProofCapability as rn, handoffAttemptSucceeded as ro, mergeLobby as rr, inviteLink as rs, shortAtprotoDid as rt, CommunitySession as s, statusCardText as sa, revokesIdentity as si, pubkyHomeserverOf as sn, handoffContextProof as so, readBeacon as sr, HANDOFF_CAPABILITY as ss, DID_FILE_MAX_BYTES as st, GROUP_TYPING_FRAME as t, ITEM_STATES as ta, toBase64Url as tc, boundVerifiedIdentity as ti, newPubkyProofFolder as tn, handoffAttemptAllowed as to, lobbyRecords as tr, decodeInviteCode as ts, normalizeAtprotoHandle as tt, decodeCommunityLink as u, heardTime as ua, emptyProofLedger as ui, dnsProofName as un, handoffEphemeral as uo, shouldBeHub as ur, deviceKeyZ32 as us, checkDidJws as ut, mergeKnocks as v, edgeParams as va, peekDhtMailbox as vi, recordsFromWellKnown as vn, handoffVersions as vo, sanitizeTypingStatus as vr, TURN_SETTLE_MS as vs, didWebFileUrl as vt, parseGroupWakeFrame as w, formatLocalTarget as wa, sanitizeFileName as wi, engineError as wn, readHandoffHello as wo, pinIsNewer as wr, nextTurnPosition as ws, signingKeys as wt, GroupWakeLimiter as x, RELAY_POLL_INTERVALS as xa, parseImageMeta as xi, wellKnownUrl as xn, readHandoffCancel as xo, PIN_LIMITS as xr, TURN_LAST_SEQUENCE as xs, parseDid as xt, readKnocks as y, DirectPathWatch as ya, DHT_FILE_CAPABILITY as yi, recordsName as yn, newHandoffId as yo, typingActivity as yr, classifyTurnRead as ys, didWebHost as yt, parseSshSignature as z, emptyLinkRecords as za, readForwarded as zi, GROUP_SIGNAL_FRAME as zn, ENROLL_PROOF as zo, HOLD_CAPABILITY as zr, PacketTooLargeError as zs, TEST_USDT_FAUCET_AMOUNT as zt };
26575
26943
 
26576
- //# sourceMappingURL=src-CJdPTl7Q.js.map
26944
+ //# sourceMappingURL=src-K3NijMq-.js.map