@qrlwallet/connect 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  SCHEME_VERSION_TYPED: () => SCHEME_VERSION_TYPED,
52
52
  SocketClient: () => SocketClient,
53
53
  UNRESTRICTED_METHODS: () => UNRESTRICTED_METHODS,
54
+ attemptWalletRedirect: () => attemptWalletRedirect,
54
55
  bytesToHex: () => bytesToHex,
55
56
  cidFromString: () => cidFromString,
56
57
  cidRandom: () => cidRandom,
@@ -630,6 +631,7 @@ var KeyExchange = class extends import_eventemitter3.default {
630
631
  var import_socket = require("socket.io-client");
631
632
  var import_eventemitter32 = __toESM(require("eventemitter3"));
632
633
  var PENDING_JOIN_TIMEOUT_MS = 2e4;
634
+ var SEND_FLUSH_TIMEOUT_MS = 600;
633
635
  var SocketClient = class extends import_eventemitter32.default {
634
636
  constructor(relayUrl, clientType) {
635
637
  super();
@@ -820,6 +822,48 @@ var SocketClient = class extends import_eventemitter32.default {
820
822
  );
821
823
  });
822
824
  }
825
+ /**
826
+ * Emit an event and resolve once the relay acks it, or after a bounded
827
+ * flush window. Lets a caller await transmission before tearing the
828
+ * socket down.
829
+ */
830
+ flushEmit(event, payload) {
831
+ return new Promise((resolve) => {
832
+ if (!this.socket?.connected) {
833
+ resolve();
834
+ return;
835
+ }
836
+ let settled = false;
837
+ const done = () => {
838
+ if (settled) return;
839
+ settled = true;
840
+ resolve();
841
+ };
842
+ const timer = setTimeout(done, SEND_FLUSH_TIMEOUT_MS);
843
+ this.socket.emit(event, payload, () => {
844
+ clearTimeout(timer);
845
+ done();
846
+ });
847
+ });
848
+ }
849
+ /**
850
+ * Explicitly terminate the channel on the relay (durable tombstone), as
851
+ * opposed to the transient leaveChannel(). Used when the session is dead
852
+ * in a way an encrypted TERMINATE cannot communicate (AEAD desync: the
853
+ * peer could not open it). Resolves once the close is flushed or the
854
+ * bounded window elapses, so the caller can safely disconnect afterwards.
855
+ */
856
+ closeChannel() {
857
+ const channelId = this.channelId;
858
+ this.channelId = null;
859
+ if (this.pendingJoin) {
860
+ clearTimeout(this.pendingJoin.watchdog);
861
+ this.pendingJoin.reject(new Error("Channel closed before join completed"));
862
+ this.pendingJoin = null;
863
+ }
864
+ if (!this.socket?.connected || !channelId) return Promise.resolve();
865
+ return this.flushEmit("close_channel", { channelId });
866
+ }
823
867
  /**
824
868
  * Leave the current channel.
825
869
  */
@@ -1120,11 +1164,13 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1120
1164
  this.connectedAccounts = [];
1121
1165
  this.unresponsiveTimer = null;
1122
1166
  this.reconnectProbeTimer = null;
1167
+ this.restoreInFlight = null;
1123
1168
  this.failedReconnects = 0;
1124
1169
  this.walletPresent = false;
1125
1170
  this.pendingRestore = null;
1126
1171
  this.messageQueue = Promise.resolve();
1127
1172
  this.outboundQueue = Promise.resolve();
1173
+ this.consecutiveDecryptFailures = 0;
1128
1174
  this.dappMetadata = options.dappMetadata;
1129
1175
  this.relayUrl = options.relayUrl ?? DEFAULT_RELAY_URL;
1130
1176
  this.chainId = options.chainId ?? "0x0";
@@ -1243,6 +1289,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1243
1289
  this.clearReconnectProbe();
1244
1290
  this.pendingRestore = null;
1245
1291
  this.walletPresent = false;
1292
+ this.consecutiveDecryptFailures = 0;
1246
1293
  if (this.keyExchange) {
1247
1294
  this.keyExchange.reset();
1248
1295
  } else {
@@ -1281,9 +1328,18 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1281
1328
  * Returns false if there is nothing to restore.
1282
1329
  */
1283
1330
  async reconnect() {
1331
+ if (this.restoreInFlight) return this.restoreInFlight;
1332
+ const task = this.reconnectNow().finally(() => {
1333
+ this.restoreInFlight = null;
1334
+ });
1335
+ this.restoreInFlight = task;
1336
+ return task;
1337
+ }
1338
+ async reconnectNow() {
1284
1339
  if (!this.pendingRestore) return false;
1285
1340
  this.setStatus("reconnecting" /* RECONNECTING */);
1286
1341
  this.walletPresent = false;
1342
+ this.consecutiveDecryptFailures = 0;
1287
1343
  try {
1288
1344
  const session = await KeyExchange.sessionFromPersisted(this.pendingRestore.keyExchange);
1289
1345
  this.keyExchange = new KeyExchange(true, session);
@@ -1359,15 +1415,53 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1359
1415
  }
1360
1416
  }
1361
1417
  }
1418
+ /**
1419
+ * Make sure the socket is open and joined to the paired session's channel
1420
+ * so an outbound request can be routed - or relay-buffered if the wallet is
1421
+ * currently absent (its socket dies within seconds of the app
1422
+ * backgrounding; the relay holds channel traffic for it). Revives the
1423
+ * socket after a probe-timeout teardown and hydrates a cold stored
1424
+ * session. Returns false when there is no session to revive (never paired,
1425
+ * explicit disconnect, or the channel was tombstoned).
1426
+ */
1427
+ async ensureChannelJoined() {
1428
+ if (this.keyExchange?.areKeysExchanged()) {
1429
+ if (this.socketClient.isConnected() && this.socketClient.getChannelId()) {
1430
+ return true;
1431
+ }
1432
+ this.setStatus("reconnecting" /* RECONNECTING */);
1433
+ this.walletPresent = false;
1434
+ return this.joinAndSettle();
1435
+ }
1436
+ if (this.pendingRestore) {
1437
+ return this.reconnect();
1438
+ }
1439
+ return false;
1440
+ }
1441
+ /**
1442
+ * True while a pairing exists (handshake complete, session keys in
1443
+ * memory), even when the wallet's socket is momentarily out of the
1444
+ * channel. Survives the reconnect-probe teardown; false after an explicit
1445
+ * disconnect or a relay tombstone.
1446
+ */
1447
+ isPaired() {
1448
+ return this.keyExchange?.areKeysExchanged() ?? false;
1449
+ }
1450
+ /** True while the relay roster last showed the wallet in the channel. */
1451
+ isWalletPresent() {
1452
+ return this.walletPresent;
1453
+ }
1362
1454
  /**
1363
1455
  * A session was terminated for good (wallet sent a relay 'close', or the
1364
1456
  * join ack reported a tombstone). Clear local state and surface
1365
1457
  * DISCONNECTED so the consumer drops to a fresh-pairing UI.
1366
1458
  */
1367
1459
  handleSessionTerminated() {
1460
+ this.emit("session_terminated");
1368
1461
  this.clearReconnectProbe();
1369
1462
  this.clearUnresponsiveTimer();
1370
1463
  this.walletPresent = false;
1464
+ this.consecutiveDecryptFailures = 0;
1371
1465
  this.pendingRestore = null;
1372
1466
  this.keyExchange = null;
1373
1467
  this.socketClient.leaveChannel();
@@ -1396,22 +1490,28 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1396
1490
  }
1397
1491
  }
1398
1492
  /**
1399
- * Send a JSON-RPC request to the wallet (async, fire-and-forget).
1493
+ * Send a JSON-RPC request to the wallet. Returns the outbound send promise
1494
+ * (settled on the relay's ack) so callers that must sequence on delivery -
1495
+ * e.g. the provider's wallet-wake redirect, which cannot navigate away
1496
+ * before the ciphertext reaches the relay - can await it. Callers may also
1497
+ * ignore it: failures are logged here either way.
1400
1498
  */
1401
1499
  sendJsonRpc(request) {
1402
1500
  if (!this.keyExchange?.areKeysExchanged()) {
1403
1501
  throw new Error("Not connected: key exchange not complete");
1404
1502
  }
1405
- this.sendEncrypted({
1503
+ const sent = this.sendEncrypted({
1406
1504
  type: "jsonrpc" /* JSONRPC */,
1407
1505
  jsonrpc: "2.0",
1408
1506
  id: request.id,
1409
1507
  method: request.method,
1410
1508
  params: request.params
1411
- }).catch((err) => {
1509
+ });
1510
+ sent.catch((err) => {
1412
1511
  error("ConnectionManager", "Failed to send JSON-RPC:", err);
1413
1512
  });
1414
1513
  this.startUnresponsiveTimer();
1514
+ return sent;
1415
1515
  }
1416
1516
  getStatus() {
1417
1517
  return this.status;
@@ -1525,8 +1625,19 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1525
1625
  }
1526
1626
  }
1527
1627
  if (typeof message === "string" && this.keyExchange?.areKeysExchanged()) {
1628
+ let decrypted;
1629
+ try {
1630
+ decrypted = await this.keyExchange.decryptMessage(message);
1631
+ } catch (err) {
1632
+ error("ConnectionManager", "Failed to decrypt message:", err);
1633
+ this.consecutiveDecryptFailures++;
1634
+ if (this.consecutiveDecryptFailures >= _ConnectionManager.MAX_DECRYPT_FAILURES) {
1635
+ await this.teardownDesyncedSession();
1636
+ }
1637
+ return;
1638
+ }
1639
+ this.consecutiveDecryptFailures = 0;
1528
1640
  try {
1529
- const decrypted = await this.keyExchange.decryptMessage(message);
1530
1641
  await this.persistSession();
1531
1642
  const parsed = JSON.parse(decrypted);
1532
1643
  if (isRecord(parsed)) {
@@ -1535,10 +1646,24 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1535
1646
  warn("ConnectionManager", "Dropping non-object decrypted payload");
1536
1647
  }
1537
1648
  } catch (err) {
1538
- error("ConnectionManager", "Failed to decrypt message:", err);
1649
+ error("ConnectionManager", "Failed to handle decrypted message:", err);
1539
1650
  }
1540
1651
  }
1541
1652
  }
1653
+ /**
1654
+ * The receive stream is cryptographically unrecoverable: the peer's send
1655
+ * counter is ahead of our recv counter for good (relay buffer TTL/cap
1656
+ * dropped a ciphertext), so every future message would fail its tag.
1657
+ * An encrypted TERMINATE cannot communicate this (the desynced peer could
1658
+ * not open it either); tombstone the channel on the relay instead so the
1659
+ * wallet learns the pairing is dead even if it only re-joins later, then
1660
+ * clear local state and surface the standard terminated teardown.
1661
+ */
1662
+ async teardownDesyncedSession() {
1663
+ warn("ConnectionManager", "AEAD stream desynced beyond recovery; terminating session");
1664
+ await this.socketClient.closeChannel();
1665
+ this.handleSessionTerminated();
1666
+ }
1542
1667
  async handleSynAck(msg) {
1543
1668
  if (!this.keyExchange) return;
1544
1669
  this.setStatus("key_exchange" /* KEY_EXCHANGE */);
@@ -1592,6 +1717,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1592
1717
  }
1593
1718
  case "terminate" /* TERMINATE */: {
1594
1719
  log("ConnectionManager", "Received terminate from wallet");
1720
+ this.emit("session_terminated");
1595
1721
  void this.disconnect();
1596
1722
  break;
1597
1723
  }
@@ -1711,6 +1837,12 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1711
1837
  }
1712
1838
  };
1713
1839
  _ConnectionManager.MAX_RECONNECT_FAILURES = 5;
1840
+ // Nonces derive from the recv counter and there is no gap tolerance, so a
1841
+ // genuinely desynced stream (peer advanced past us, e.g. the relay's
1842
+ // buffer TTL dropped a ciphertext) fails EVERY subsequent open. Two in a
1843
+ // row cannot happen on a healthy stream; requiring the second guards
1844
+ // against a one-off injected junk ciphertext killing a live session.
1845
+ _ConnectionManager.MAX_DECRYPT_FAILURES = 2;
1714
1846
  var ConnectionManager = _ConnectionManager;
1715
1847
 
1716
1848
  // src/utils/platform.ts
@@ -1724,6 +1856,41 @@ function getAppStoreUrl() {
1724
1856
  }
1725
1857
  return "https://apps.apple.com/app/myqrlwallet/id6742219498";
1726
1858
  }
1859
+ function attemptWalletRedirect(uri, timeoutMs = 1800) {
1860
+ if (typeof window === "undefined" || typeof document === "undefined") {
1861
+ return Promise.resolve(false);
1862
+ }
1863
+ if (!uri.trim().toLowerCase().startsWith("qrlconnect:")) {
1864
+ return Promise.resolve(false);
1865
+ }
1866
+ return new Promise((resolve) => {
1867
+ let settled = false;
1868
+ const finish = (opened) => {
1869
+ if (settled) return;
1870
+ settled = true;
1871
+ document.removeEventListener("visibilitychange", onVisibility);
1872
+ window.removeEventListener("pagehide", onHide);
1873
+ window.clearTimeout(timer);
1874
+ resolve(opened);
1875
+ };
1876
+ const onVisibility = () => {
1877
+ if (document.visibilityState === "hidden") finish(true);
1878
+ };
1879
+ const onHide = () => {
1880
+ finish(true);
1881
+ };
1882
+ document.addEventListener("visibilitychange", onVisibility);
1883
+ window.addEventListener("pagehide", onHide);
1884
+ const timer = window.setTimeout(() => {
1885
+ finish(false);
1886
+ }, timeoutMs);
1887
+ try {
1888
+ window.location.href = uri;
1889
+ } catch {
1890
+ finish(false);
1891
+ }
1892
+ });
1893
+ }
1727
1894
 
1728
1895
  // src/QRLConnectProvider.ts
1729
1896
  var QRL_CONNECT_PROVIDER_INFO = {
@@ -1733,6 +1900,16 @@ var QRL_CONNECT_PROVIDER_INFO = {
1733
1900
  };
1734
1901
  var EIP6963_ANNOUNCE_EVENT = "eip6963:announceProvider";
1735
1902
  var EIP6963_REQUEST_EVENT = "eip6963:requestProvider";
1903
+ function isRecordObj(v) {
1904
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1905
+ }
1906
+ function parseInflightRecord(v) {
1907
+ if (!isRecordObj(v)) return null;
1908
+ const { id, method, ts } = v;
1909
+ if (typeof id !== "string" && typeof id !== "number") return null;
1910
+ if (typeof method !== "string" || typeof ts !== "number") return null;
1911
+ return { id, method, ts };
1912
+ }
1736
1913
  var QRLConnectProvider = class extends import_eventemitter34.default {
1737
1914
  constructor(options) {
1738
1915
  super();
@@ -1741,6 +1918,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1741
1918
  this.eip6963RequestListener = null;
1742
1919
  this.resumeListener = null;
1743
1920
  this.resumeDebounce = null;
1921
+ this.orphanedRequests = /* @__PURE__ */ new Map();
1744
1922
  // Random per-instance prefix keeps request ids unique across page loads.
1745
1923
  // A bare counter restarts at 1 on reload, and the relay buffers messages
1746
1924
  // for 5 minutes, so a stale buffered response could otherwise be matched
@@ -1749,6 +1927,10 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1749
1927
  this.requestCounter = 0;
1750
1928
  this.isQRLConnect = true;
1751
1929
  this.options = options;
1930
+ this.inflightKey = `${options.storageKey ?? `${STORAGE_KEY_PREFIX}:session`}:inflight`;
1931
+ for (const rec of this.readInflight()) {
1932
+ this.orphanedRequests.set(rec.id, { method: rec.method });
1933
+ }
1752
1934
  if (options.debug) {
1753
1935
  setDebug(true);
1754
1936
  }
@@ -1861,6 +2043,16 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1861
2043
  this.connectionManager.on("jsonrpc_response", (response) => {
1862
2044
  const pending = this.pendingRequests.get(response.id);
1863
2045
  if (!pending) {
2046
+ const orphan = this.orphanedRequests.get(response.id);
2047
+ if (orphan) {
2048
+ this.settleInflight(response.id);
2049
+ this.emit("late_response", {
2050
+ id: response.id,
2051
+ method: orphan.method,
2052
+ ...response.error ? { error: response.error } : { result: response.result }
2053
+ });
2054
+ return;
2055
+ }
1864
2056
  warn("Provider", `No pending request for id ${response.id}`);
1865
2057
  return;
1866
2058
  }
@@ -1889,6 +2081,13 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1889
2081
  }
1890
2082
  this.pendingRequests.clear();
1891
2083
  });
2084
+ this.connectionManager.on("session_terminated", () => {
2085
+ for (const [, pending] of this.pendingRequests) {
2086
+ pending.reject(new Error("Session terminated by wallet"));
2087
+ }
2088
+ this.pendingRequests.clear();
2089
+ this.clearAllInflight();
2090
+ });
1892
2091
  }
1893
2092
  /**
1894
2093
  * Generate a connection URI for QR code display or deep link redirect.
@@ -1925,7 +2124,13 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1925
2124
  throw new Error(`Unsupported method: ${method}`);
1926
2125
  }
1927
2126
  if (this.connectionManager.getStatus() !== "connected" /* CONNECTED */) {
1928
- throw new Error("Not connected to QRL Wallet");
2127
+ if (method === "qrl_requestAccounts" && this.connectionManager.isPaired() && this.connectionManager.getAccounts().length > 0) {
2128
+ return this.connectionManager.getAccounts();
2129
+ }
2130
+ const joined = await this.connectionManager.ensureChannelJoined();
2131
+ if (!joined) {
2132
+ throw new Error("Not connected to QRL Wallet");
2133
+ }
1929
2134
  }
1930
2135
  const id = `${this.requestIdPrefix}-${++this.requestCounter}`;
1931
2136
  return new Promise((resolve, reject) => {
@@ -1948,20 +2153,109 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1948
2153
  const originalReject = pending.reject;
1949
2154
  pending.resolve = (result) => {
1950
2155
  clearTimeout(timeout);
2156
+ this.settleInflight(id);
1951
2157
  originalResolve(result);
1952
2158
  };
1953
2159
  pending.reject = (error2) => {
1954
2160
  clearTimeout(timeout);
2161
+ this.settleInflight(id);
1955
2162
  originalReject(error2);
1956
2163
  };
1957
- this.connectionManager.sendJsonRpc({
2164
+ if (RESTRICTED_METHODS.has(method)) {
2165
+ this.persistInflight(id, method);
2166
+ }
2167
+ const sent = this.connectionManager.sendJsonRpc({
1958
2168
  jsonrpc: "2.0",
1959
2169
  id,
1960
2170
  method,
1961
2171
  params
1962
2172
  });
2173
+ sent.catch((err) => {
2174
+ const failed = this.pendingRequests.get(id);
2175
+ if (!failed) return;
2176
+ this.pendingRequests.delete(id);
2177
+ failed.reject(err instanceof Error ? err : new Error(String(err)));
2178
+ });
2179
+ if (this.shouldRedirectToWallet(method)) {
2180
+ void sent.then(
2181
+ () => attemptWalletRedirect(this.walletWakeUri()),
2182
+ () => void 0
2183
+ );
2184
+ }
1963
2185
  });
1964
2186
  }
2187
+ /**
2188
+ * Redirect only for calls the user must approve, on mobile, and only when
2189
+ * the wallet is actually absent from the channel: right after pairing its
2190
+ * socket lingers and a pointless app flash would be jarring.
2191
+ *
2192
+ * qrl_requestAccounts is excluded even though it is restricted: dApps call
2193
+ * it on page load to restore a session, so it can run with no user gesture
2194
+ * (an unsolicited "Open in MyQRLWallet?" sheet on load) and pairing UIs
2195
+ * already own their own deep-link step.
2196
+ */
2197
+ shouldRedirectToWallet(method) {
2198
+ return this.options.walletRedirectOnRequest !== false && RESTRICTED_METHODS.has(method) && method !== "qrl_requestAccounts" && isMobileBrowser() && !this.connectionManager.isWalletPresent();
2199
+ }
2200
+ // ── In-flight persistence (survive the return-redirect reload) ──────
2201
+ readInflight() {
2202
+ if (typeof localStorage === "undefined") return [];
2203
+ try {
2204
+ const raw = localStorage.getItem(this.inflightKey);
2205
+ if (!raw) return [];
2206
+ const parsed = JSON.parse(raw);
2207
+ if (!Array.isArray(parsed)) return [];
2208
+ const now = Date.now();
2209
+ const records = [];
2210
+ for (const v of parsed) {
2211
+ const rec = parseInflightRecord(v);
2212
+ if (rec && now - rec.ts <= REQUEST_TIMEOUT_MS) records.push(rec);
2213
+ }
2214
+ return records;
2215
+ } catch {
2216
+ return [];
2217
+ }
2218
+ }
2219
+ writeInflight(records) {
2220
+ if (typeof localStorage === "undefined") return;
2221
+ try {
2222
+ if (records.length === 0) {
2223
+ localStorage.removeItem(this.inflightKey);
2224
+ } else {
2225
+ localStorage.setItem(this.inflightKey, JSON.stringify(records));
2226
+ }
2227
+ } catch {
2228
+ }
2229
+ }
2230
+ persistInflight(id, method) {
2231
+ const records = this.readInflight().filter((r) => r.id !== id);
2232
+ records.push({ id, method, ts: Date.now() });
2233
+ this.writeInflight(records);
2234
+ }
2235
+ settleInflight(id) {
2236
+ this.orphanedRequests.delete(id);
2237
+ this.writeInflight(this.readInflight().filter((r) => r.id !== id));
2238
+ }
2239
+ clearAllInflight() {
2240
+ this.orphanedRequests.clear();
2241
+ this.writeInflight([]);
2242
+ }
2243
+ /**
2244
+ * Wake URI for the wallet app. Carries no pairing payload: the app
2245
+ * foregrounds on any `qrlconnect:` URL and re-joins its sessions (draining
2246
+ * the buffered request); the cid lets a future wallet jump straight to the
2247
+ * right approval.
2248
+ *
2249
+ * MUST stay query-only (no host/path). The mobile app routes deep links
2250
+ * with expo-router, which maps anything after the scheme to an in-app
2251
+ * route: `qrlconnect://resume?...` lands on the "+not-found" screen and
2252
+ * unmounts the WebView, so the wake achieves nothing. The hostless form
2253
+ * routes to the WebView tab exactly like the pairing URIs
2254
+ * (`qrlconnect://?q=...`), which is the whole point of the wake.
2255
+ */
2256
+ walletWakeUri() {
2257
+ return `qrlconnect://?wake=${encodeURIComponent(this.connectionManager.getChannelId())}`;
2258
+ }
1965
2259
  /**
1966
2260
  * Get the current connection status.
1967
2261
  */
@@ -1992,6 +2286,19 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1992
2286
  hasStoredSession() {
1993
2287
  return this.connectionManager.hasStoredSession();
1994
2288
  }
2289
+ /**
2290
+ * True while a pairing exists (keys exchanged), even when the wallet app
2291
+ * is backgrounded and its socket is out of the channel. Combine with
2292
+ * getStatus() to tell "wallet away, session intact" (paired + WAITING)
2293
+ * apart from "waiting for a first scan" in pairing UIs.
2294
+ */
2295
+ isPaired() {
2296
+ return this.connectionManager.isPaired();
2297
+ }
2298
+ /** True while the wallet is actually present in the relay channel. */
2299
+ isWalletPresent() {
2300
+ return this.connectionManager.isWalletPresent();
2301
+ }
1995
2302
  /**
1996
2303
  * Reset the connection and start a fresh pairing with a new channel.
1997
2304
  * Use this when the user wants to create a new connection instead of
@@ -2002,6 +2309,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
2002
2309
  pending.reject(new Error("Connection reset"));
2003
2310
  }
2004
2311
  this.pendingRequests.clear();
2312
+ this.clearAllInflight();
2005
2313
  await this.connectionManager.resetForNewChannel();
2006
2314
  return this.getConnectionURI();
2007
2315
  }
@@ -2016,6 +2324,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
2016
2324
  pending.reject(new Error("Disconnected"));
2017
2325
  }
2018
2326
  this.pendingRequests.clear();
2327
+ this.clearAllInflight();
2019
2328
  this.teardownResumeListeners();
2020
2329
  await this.connectionManager.disconnect();
2021
2330
  }
@@ -2406,6 +2715,7 @@ function verifyTypedData({ signature, publicKey, payload }) {
2406
2715
  SCHEME_VERSION_TYPED,
2407
2716
  SocketClient,
2408
2717
  UNRESTRICTED_METHODS,
2718
+ attemptWalletRedirect,
2409
2719
  bytesToHex,
2410
2720
  cidFromString,
2411
2721
  cidRandom,