@ghostlytools/cli 1.1.3 → 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 (29) hide show
  1. package/dist/assets/argon2id-088uQfnH.js.map +1 -1
  2. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  3. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  4. package/dist/assets/breez_sdk_spark_wasm_bg.wasm +0 -0
  5. package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  6. package/dist/assets/{hyperdhtRelay-CHMS7_KO.js → hyperdhtRelay-BoxPHp7v.js} +3 -3
  7. package/dist/assets/{hyperdhtRelay-CHMS7_KO.js.map → hyperdhtRelay-BoxPHp7v.js.map} +1 -1
  8. package/dist/assets/legacy_ciphers-JJOu0JXL.js.map +1 -1
  9. package/dist/assets/{mainline-B0UTU0-4.js → mainline-dkA94eh2.js} +2 -2
  10. package/dist/assets/{mainline-B0UTU0-4.js.map → mainline-dkA94eh2.js.map} +1 -1
  11. package/dist/assets/nacl-fast-CDhNCYHs.js.map +1 -1
  12. package/dist/assets/noble_curves-DDWn5AH0.js.map +1 -1
  13. package/dist/assets/noble_hashes-B7AZyFyS.js.map +1 -1
  14. package/dist/assets/openpgp-BBFr-zHQ.js.map +1 -1
  15. package/dist/assets/{server-9AM0ta6q.js → server-CXFTDnFI.js} +204 -30
  16. package/dist/assets/server-CXFTDnFI.js.map +1 -0
  17. package/dist/assets/sha512-BYId-G-T.js.map +1 -1
  18. package/dist/assets/{src-C_E99PGx.js → src-K3NijMq-.js} +414 -46
  19. package/dist/assets/src-K3NijMq-.js.map +1 -0
  20. package/dist/assets/storage-Bya5b2Xt.js.map +1 -1
  21. package/dist/assets/tree-store-N362nuzZ.js.map +1 -1
  22. package/dist/assets/unbzip2-stream-B1C-JX9L.js.map +1 -1
  23. package/dist/assets/{web-OUQl4Q7L.js → web-DwgdhfcY.js} +16 -16
  24. package/dist/assets/{web-OUQl4Q7L.js.map → web-DwgdhfcY.js.map} +1 -1
  25. package/dist/ghostly.mjs +28 -13
  26. package/dist/ghostly.mjs.map +1 -1
  27. package/package.json +7 -7
  28. package/dist/assets/server-9AM0ta6q.js.map +0 -1
  29. package/dist/assets/src-C_E99PGx.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);
@@ -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,
@@ -12449,6 +12566,14 @@ var STATUS_CARD_CAPABILITY = "status-card/1";
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;
@@ -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
  /**
@@ -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;
@@ -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();
@@ -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-C_E99PGx.js.map
26944
+ //# sourceMappingURL=src-K3NijMq-.js.map