@lydianpay/lydianconnect 1.2.1 → 1.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
@@ -66,6 +66,14 @@ var ConnectManager = class {
66
66
  get(walletId) {
67
67
  return this.connections.get(walletId);
68
68
  }
69
+ /**
70
+ * Fire a wallet's deep link. Goes straight to the fallback: it is the only
71
+ * connector that owns a link table, and the only one that emits the
72
+ * `display_uri` a host is responding to when it calls this.
73
+ */
74
+ openWallet(input) {
75
+ return this.fallback.openWallet?.(input) ?? false;
76
+ }
69
77
  async disconnect(walletId) {
70
78
  if (walletId) {
71
79
  await this.connections.get(walletId)?.disconnect().catch(() => {
@@ -238,10 +246,15 @@ var WALLET_CATALOG = WALLETS.map((w) => ({
238
246
  }));
239
247
 
240
248
  // src/core/connector.ts
249
+ function requestedChain(req) {
250
+ return req.namespace.value.chains?.[0];
251
+ }
241
252
  var BaseConnector = class {
242
253
  constructor() {
243
254
  this.walletIds = [];
244
255
  this.handlers = /* @__PURE__ */ new Set();
256
+ /** Depth of in-flight connects; see {@link duringConnect}. */
257
+ this.connecting = 0;
245
258
  }
246
259
  servesNamespace(_namespaceName) {
247
260
  return true;
@@ -255,7 +268,28 @@ var BaseConnector = class {
255
268
  this.handlers.add(handler);
256
269
  return () => this.handlers.delete(handler);
257
270
  }
271
+ /**
272
+ * Run a connect with its handshake events suppressed.
273
+ *
274
+ * A connector that binds provider listeners before requesting accounts sees the
275
+ * wallet's own setup traffic — an initial `chainChanged`, and another when
276
+ * `ensureChain` switches. Forwarding those makes the host believe the user moved
277
+ * networks mid-connect. Nothing is lost by dropping them: connect() reads the
278
+ * authoritative account and chain when it settles, and only changes made after
279
+ * that are the user's.
280
+ */
281
+ async duringConnect(run) {
282
+ this.connecting++;
283
+ try {
284
+ return await run();
285
+ } finally {
286
+ this.connecting--;
287
+ }
288
+ }
258
289
  emit(event) {
290
+ if (this.connecting > 0 && (event.type === "chainChanged" || event.type === "accountsChanged")) {
291
+ return;
292
+ }
259
293
  for (const handler of this.handlers) handler(event);
260
294
  }
261
295
  };
@@ -265,6 +299,80 @@ function deriveUrl() {
265
299
  return typeof window !== "undefined" ? window.location.origin : "";
266
300
  }
267
301
 
302
+ // src/connectors/eip155/chain.ts
303
+ var CHAIN_SETTLE = { timeoutMs: 4e3, intervalMs: 100 };
304
+ function toCaip(chainId) {
305
+ if (!chainId) return "eip155:1";
306
+ const num = chainId.startsWith("0x") ? parseInt(chainId, 16) : Number(chainId);
307
+ return `eip155:${Number.isFinite(num) ? num : 1}`;
308
+ }
309
+ async function currentChain(rpc2) {
310
+ const raw = await rpc2({ method: "eth_chainId" }).catch(() => null);
311
+ return toCaip(typeof raw === "string" ? raw : null);
312
+ }
313
+ async function settleChain({
314
+ rpc: rpc2,
315
+ chainId,
316
+ timeoutMs,
317
+ intervalMs
318
+ }) {
319
+ const deadline = Date.now() + timeoutMs;
320
+ for (; ; ) {
321
+ const actual = await currentChain(rpc2);
322
+ if (actual === chainId || Date.now() >= deadline) return;
323
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
324
+ }
325
+ }
326
+ async function ensureChain({
327
+ rpc: rpc2,
328
+ chainId,
329
+ timeoutMs = CHAIN_SETTLE.timeoutMs,
330
+ intervalMs = CHAIN_SETTLE.intervalMs
331
+ }) {
332
+ const chainNumber = chainId ? Number(chainId.split(":")[1]) : NaN;
333
+ if (!Number.isFinite(chainNumber) || chainNumber <= 0) return;
334
+ const requested = `eip155:${chainNumber}`;
335
+ if (await currentChain(rpc2) === requested) return;
336
+ const switched = await rpc2({
337
+ method: "wallet_switchEthereumChain",
338
+ params: [{ chainId: `0x${chainNumber.toString(16)}` }]
339
+ }).then(
340
+ () => true,
341
+ // Chain not added to the wallet, or the user declined — the caller's
342
+ // chainChanged will reflect reality. No settle: the chain isn't moving.
343
+ () => false
344
+ );
345
+ if (switched) {
346
+ await settleChain({ rpc: rpc2, chainId: requested, timeoutMs, intervalMs });
347
+ }
348
+ }
349
+
350
+ // src/core/abort.ts
351
+ function withSignal(promise, signal, onAbort) {
352
+ if (!signal) return promise;
353
+ if (signal.aborted) {
354
+ onAbort?.();
355
+ return Promise.reject(new DOMException("Aborted", "AbortError"));
356
+ }
357
+ return new Promise((resolve, reject) => {
358
+ const abortHandler = () => {
359
+ onAbort?.();
360
+ reject(new DOMException("Aborted", "AbortError"));
361
+ };
362
+ signal.addEventListener("abort", abortHandler, { once: true });
363
+ promise.then(
364
+ (value) => {
365
+ signal.removeEventListener("abort", abortHandler);
366
+ resolve(value);
367
+ },
368
+ (error) => {
369
+ signal.removeEventListener("abort", abortHandler);
370
+ reject(error);
371
+ }
372
+ );
373
+ });
374
+ }
375
+
268
376
  // src/connectors/metamask/connector.ts
269
377
  var WALLET_ID = "metamask";
270
378
  var MetaMaskConnector = class extends BaseConnector {
@@ -322,17 +430,23 @@ var MetaMaskConnector = class extends BaseConnector {
322
430
  );
323
431
  }
324
432
  async connect(req) {
325
- const provider = await this.getProvider();
326
- const accounts = await provider.request({
327
- method: "eth_requestAccounts"
433
+ return this.duringConnect(async () => {
434
+ const provider = await this.getProvider();
435
+ const accounts = await withSignal(
436
+ provider.request({ method: "eth_requestAccounts" }),
437
+ req.signal
438
+ );
439
+ const account = accounts?.[0];
440
+ if (!account) throw new Error("MetaMask returned no account");
441
+ await ensureChain({
442
+ rpc: rpcFor(provider),
443
+ chainId: requestedChain(req)
444
+ });
445
+ const chainId = toCaip(
446
+ await provider.request({ method: "eth_chainId" })
447
+ );
448
+ return this.toConnection(provider, account, chainId);
328
449
  });
329
- const account = accounts?.[0];
330
- if (!account) throw new Error("MetaMask returned no account");
331
- await this.ensureChain(provider, req);
332
- const chainId = toCaip(
333
- await provider.request({ method: "eth_chainId" })
334
- );
335
- return this.toConnection(provider, account, chainId);
336
450
  }
337
451
  async restore() {
338
452
  const provider = await this.getProvider().catch(() => null);
@@ -350,22 +464,6 @@ var MetaMaskConnector = class extends BaseConnector {
350
464
  this.provider = null;
351
465
  this.listenersBound = false;
352
466
  }
353
- /** Best-effort switch to the requested chain; leaves the wallet as-is if it's unknown to it. */
354
- async ensureChain(provider, req) {
355
- const wanted = req.namespace.value.chains?.[0];
356
- const wantedNum = wanted ? Number(wanted.split(":")[1]) : NaN;
357
- if (!Number.isFinite(wantedNum) || wantedNum <= 0) return;
358
- const current = parseInt(
359
- await provider.request({ method: "eth_chainId" }).catch(() => "0x0") ?? "0x0",
360
- 16
361
- );
362
- if (current === wantedNum) return;
363
- await provider.request({
364
- method: "wallet_switchEthereumChain",
365
- params: [{ chainId: `0x${wantedNum.toString(16)}` }]
366
- }).catch(() => {
367
- });
368
- }
369
467
  toConnection(provider, account, chainId) {
370
468
  return {
371
469
  walletId: WALLET_ID,
@@ -387,20 +485,18 @@ var MetaMaskConnector = class extends BaseConnector {
387
485
  };
388
486
  }
389
487
  };
390
- function toCaip(hexChainId) {
391
- if (!hexChainId) return "eip155:1";
392
- const num = hexChainId.startsWith("0x") ? parseInt(hexChainId, 16) : Number(hexChainId);
393
- return `eip155:${Number.isFinite(num) ? num : 1}`;
488
+ function rpcFor(provider) {
489
+ return (args) => provider.request(args);
394
490
  }
395
491
 
396
492
  // src/connectors/coinbase/connector.ts
493
+ var WALLET_ID2 = "coinbase";
397
494
  function rpc(provider, method, params) {
398
495
  return provider.request({
399
496
  method,
400
497
  params
401
498
  });
402
499
  }
403
- var WALLET_ID2 = "coinbase";
404
500
  var CoinbaseConnector = class extends BaseConnector {
405
501
  constructor(app) {
406
502
  super();
@@ -437,7 +533,7 @@ var CoinbaseConnector = class extends BaseConnector {
437
533
  this.emit({
438
534
  type: "chainChanged",
439
535
  walletId: WALLET_ID2,
440
- chainId: toCaip2(String(hex))
536
+ chainId: toCaip(String(hex))
441
537
  });
442
538
  });
443
539
  provider.on("accountsChanged", (accounts) => {
@@ -452,13 +548,18 @@ var CoinbaseConnector = class extends BaseConnector {
452
548
  );
453
549
  }
454
550
  async connect(req) {
455
- const provider = await this.getProvider(evmChainIds(req));
456
- const accounts = await rpc(provider, "eth_requestAccounts");
457
- const account = accounts?.[0];
458
- if (!account) throw new Error("Coinbase Wallet returned no account");
459
- await this.ensureChain(provider, req);
460
- const chainId = toCaip2(await rpc(provider, "eth_chainId"));
461
- return this.toConnection(provider, account, chainId);
551
+ return this.duringConnect(async () => {
552
+ const provider = await this.getProvider(evmChainIds(req));
553
+ const accounts = await rpc(provider, "eth_requestAccounts");
554
+ const account = accounts?.[0];
555
+ if (!account) throw new Error("Coinbase Wallet returned no account");
556
+ await ensureChain({
557
+ rpc: rpcFor2(provider),
558
+ chainId: requestedChain(req)
559
+ });
560
+ const chainId = toCaip(await rpc(provider, "eth_chainId"));
561
+ return this.toConnection(provider, account, chainId);
562
+ });
462
563
  }
463
564
  async restore() {
464
565
  const provider = await this.getProvider().catch(() => null);
@@ -468,7 +569,7 @@ var CoinbaseConnector = class extends BaseConnector {
468
569
  );
469
570
  const account = accounts?.[0];
470
571
  if (!account) return [];
471
- const chainId = toCaip2(
572
+ const chainId = toCaip(
472
573
  await rpc(provider, "eth_chainId").catch(() => "0x1")
473
574
  );
474
575
  return [this.toConnection(provider, account, chainId)];
@@ -479,20 +580,6 @@ var CoinbaseConnector = class extends BaseConnector {
479
580
  this.provider = null;
480
581
  this.listenersBound = false;
481
582
  }
482
- async ensureChain(provider, req) {
483
- const wanted = req.namespace.value.chains?.[0];
484
- const wantedNum = wanted ? Number(wanted.split(":")[1]) : NaN;
485
- if (!Number.isFinite(wantedNum) || wantedNum <= 0) return;
486
- const current = parseInt(
487
- await rpc(provider, "eth_chainId").catch(() => "0x0") ?? "0x0",
488
- 16
489
- );
490
- if (current === wantedNum) return;
491
- await rpc(provider, "wallet_switchEthereumChain", [
492
- { chainId: `0x${wantedNum.toString(16)}` }
493
- ]).catch(() => {
494
- });
495
- }
496
583
  toConnection(provider, account, chainId) {
497
584
  return {
498
585
  walletId: WALLET_ID2,
@@ -512,10 +599,8 @@ var CoinbaseConnector = class extends BaseConnector {
512
599
  };
513
600
  }
514
601
  };
515
- function toCaip2(hexChainId) {
516
- if (!hexChainId) return "eip155:1";
517
- const num = hexChainId.startsWith("0x") ? parseInt(hexChainId, 16) : Number(hexChainId);
518
- return `eip155:${Number.isFinite(num) ? num : 1}`;
602
+ function rpcFor2(provider) {
603
+ return ({ method, params }) => rpc(provider, method, params);
519
604
  }
520
605
  function evmChainIds(req) {
521
606
  const wanted = req.namespace.value.chains?.[0];
@@ -579,8 +664,8 @@ var InjectedConnector = class extends BaseConnector {
579
664
  const account = accounts?.[0];
580
665
  if (!account)
581
666
  throw new Error(`${req.walletId} extension returned no account`);
582
- await this.ensureChain(provider, req);
583
- const chainId = toCaip3(
667
+ await ensureChain({ rpc: rpcFor3(provider), chainId: requestedChain(req) });
668
+ const chainId = toCaip(
584
669
  await provider.request({ method: "eth_chainId" })
585
670
  );
586
671
  this.bindEvents(provider, req.walletId);
@@ -599,7 +684,7 @@ var InjectedConnector = class extends BaseConnector {
599
684
  const accounts = await provider.request({ method: "eth_accounts" }).catch(() => []);
600
685
  const account = accounts?.[0];
601
686
  if (!account) continue;
602
- const chainId = toCaip3(
687
+ const chainId = toCaip(
603
688
  await provider.request({ method: "eth_chainId" }).catch(() => "0x1")
604
689
  );
605
690
  this.bindEvents(provider, walletId);
@@ -618,7 +703,7 @@ var InjectedConnector = class extends BaseConnector {
618
703
  this.emit({
619
704
  type: "chainChanged",
620
705
  walletId,
621
- chainId: toCaip3(String(hex))
706
+ chainId: toCaip(String(hex))
622
707
  });
623
708
  });
624
709
  provider.on("accountsChanged", (accounts) => {
@@ -631,22 +716,6 @@ var InjectedConnector = class extends BaseConnector {
631
716
  () => this.emit({ type: "disconnect", walletId })
632
717
  );
633
718
  }
634
- /** Best-effort switch to the requested chain; leaves the wallet as-is if it's unknown to it. */
635
- async ensureChain(provider, req) {
636
- const wanted = req.namespace.value.chains?.[0];
637
- const wantedNum = wanted ? Number(wanted.split(":")[1]) : NaN;
638
- if (!Number.isFinite(wantedNum) || wantedNum <= 0) return;
639
- const current = parseInt(
640
- await provider.request({ method: "eth_chainId" }).catch(() => "0x0") ?? "0x0",
641
- 16
642
- );
643
- if (current === wantedNum) return;
644
- await provider.request({
645
- method: "wallet_switchEthereumChain",
646
- params: [{ chainId: `0x${wantedNum.toString(16)}` }]
647
- }).catch(() => {
648
- });
649
- }
650
719
  toConnection(provider, walletId, account, chainId) {
651
720
  return {
652
721
  walletId,
@@ -670,29 +739,8 @@ var InjectedConnector = class extends BaseConnector {
670
739
  };
671
740
  }
672
741
  };
673
- function toCaip3(hexChainId) {
674
- if (!hexChainId) return "eip155:1";
675
- const num = hexChainId.startsWith("0x") ? parseInt(hexChainId, 16) : Number(hexChainId);
676
- return `eip155:${Number.isFinite(num) ? num : 1}`;
677
- }
678
- function withSignal(promise, signal) {
679
- if (!signal) return promise;
680
- if (signal.aborted)
681
- return Promise.reject(new DOMException("Aborted", "AbortError"));
682
- return new Promise((resolve, reject) => {
683
- const onAbort = () => reject(new DOMException("Aborted", "AbortError"));
684
- signal.addEventListener("abort", onAbort, { once: true });
685
- promise.then(
686
- (value) => {
687
- signal.removeEventListener("abort", onAbort);
688
- resolve(value);
689
- },
690
- (error) => {
691
- signal.removeEventListener("abort", onAbort);
692
- reject(error);
693
- }
694
- );
695
- });
742
+ function rpcFor3(provider) {
743
+ return (args) => provider.request(args);
696
744
  }
697
745
 
698
746
  // src/connectors/walletconnect/wallet-links.ts
@@ -1142,13 +1190,15 @@ function buildLink(base, uri) {
1142
1190
  const sep = base.includes("?") ? "&" : "?";
1143
1191
  return `${base}${sep}uri=${encodeURIComponent(uri)}`;
1144
1192
  }
1145
- function openWallet(link, uri) {
1146
- if (typeof document === "undefined") return;
1147
- if (link.scheme) {
1148
- openHref(buildLink(link.scheme, uri));
1149
- } else if (link.universal) {
1150
- openHref(buildLink(link.universal, uri));
1151
- }
1193
+ function canOpen(link) {
1194
+ return !!(link?.scheme || link?.universal);
1195
+ }
1196
+ function openWallet(link, uri, via = "scheme") {
1197
+ if (typeof document === "undefined") return false;
1198
+ const base = via === "universal" ? link.universal : link.scheme ?? link.universal;
1199
+ if (!base) return false;
1200
+ openHref(buildLink(base, uri));
1201
+ return true;
1152
1202
  }
1153
1203
  function openWalletApp(link) {
1154
1204
  if (typeof document === "undefined") return;
@@ -1187,6 +1237,7 @@ function detectAppOpen(timeoutMs) {
1187
1237
 
1188
1238
  // src/connectors/walletconnect/connector.ts
1189
1239
  var STORAGE_KEY = "lydianconnect.wc.topics";
1240
+ var PING_TIMEOUT_MS = 3e3;
1190
1241
  var WalletConnectConnector = class extends BaseConnector {
1191
1242
  constructor(app, options) {
1192
1243
  super();
@@ -1197,6 +1248,9 @@ var WalletConnectConnector = class extends BaseConnector {
1197
1248
  // claims any wallet not served by an SDK connector
1198
1249
  this.client = null;
1199
1250
  this.topics = /* @__PURE__ */ new Map();
1251
+ /** walletId -> pairing URI of the connect currently awaiting approval, so a
1252
+ * host-driven open button can fire without the host tracking the URI itself. */
1253
+ this.pending = /* @__PURE__ */ new Map();
1200
1254
  this.links = { ...DEFAULT_WALLET_LINKS, ...options.walletLinks ?? {} };
1201
1255
  }
1202
1256
  isAvailable() {
@@ -1240,35 +1294,37 @@ var WalletConnectConnector = class extends BaseConnector {
1240
1294
  }
1241
1295
  async connect(req) {
1242
1296
  const client = await this.getClient();
1243
- const reused = this.tryReuse(client, req);
1297
+ const reused = await this.tryReuse(client, req);
1244
1298
  if (reused) return reused;
1245
1299
  const { uri, approval } = await client.connect({
1246
1300
  requiredNamespaces: { [req.namespace.name]: req.namespace.value }
1247
1301
  });
1248
1302
  if (!uri) throw new Error("WalletConnect did not return a pairing URI");
1249
- this.emit({ type: "display_uri", walletId: req.walletId, uri });
1250
- if (isMobile()) {
1251
- const link = this.links[req.walletId];
1252
- if (link) {
1253
- openWallet(link, uri);
1254
- void detectAppOpen(this.options.openTimeoutMs ?? 2e3).then(
1255
- (opened) => {
1256
- if (!opened)
1257
- this.emit({
1258
- type: "wallet_open_failed",
1259
- walletId: req.walletId,
1260
- uri,
1261
- store: link.store
1262
- });
1263
- }
1264
- );
1265
- }
1303
+ const link = isMobile() ? this.links[req.walletId] : void 0;
1304
+ this.emit({
1305
+ type: "display_uri",
1306
+ walletId: req.walletId,
1307
+ uri,
1308
+ deepLink: canOpen(link)
1309
+ });
1310
+ if (link && canOpen(link)) {
1311
+ this.pending.set(req.walletId, uri);
1312
+ openWallet(link, uri);
1313
+ void detectAppOpen(this.options.openTimeoutMs ?? 2e3).then((opened) => {
1314
+ if (!opened)
1315
+ this.emit({
1316
+ type: "wallet_open_failed",
1317
+ walletId: req.walletId,
1318
+ uri,
1319
+ store: link.store
1320
+ });
1321
+ });
1266
1322
  }
1267
1323
  const pairingTopic = parseUri(uri).topic;
1268
- const session = await withSignal2(approval(), req.signal, () => {
1324
+ const session = await withSignal(approval(), req.signal, () => {
1269
1325
  void client.core.pairing.disconnect({ topic: pairingTopic }).catch(() => {
1270
1326
  });
1271
- });
1327
+ }).finally(() => this.pending.delete(req.walletId));
1272
1328
  const connection = this.toConnection(
1273
1329
  client,
1274
1330
  req.walletId,
@@ -1279,11 +1335,30 @@ var WalletConnectConnector = class extends BaseConnector {
1279
1335
  this.persist();
1280
1336
  return connection;
1281
1337
  }
1338
+ /**
1339
+ * Fire a wallet's deep link on demand. The host calls this from a real click,
1340
+ * which is the one context a mobile browser reliably allows a scheme
1341
+ * navigation from — see the note on {@link openWallet}.
1342
+ */
1343
+ openWallet(input) {
1344
+ const link = this.links[input.walletId];
1345
+ if (!link) return false;
1346
+ const uri = input.uri ?? this.pending.get(input.walletId);
1347
+ if (!uri) {
1348
+ openWalletApp(link);
1349
+ return !!link.native;
1350
+ }
1351
+ return openWallet(link, uri, input.via ?? "scheme");
1352
+ }
1282
1353
  async restore() {
1283
1354
  const client = await this.getClient();
1355
+ const checked = await Promise.all(
1356
+ [...this.topics].map(
1357
+ async ([walletId, topic]) => [walletId, await this.liveSession(client, topic)]
1358
+ )
1359
+ );
1284
1360
  const out = [];
1285
- for (const [walletId, topic] of [...this.topics]) {
1286
- const session = this.liveSession(client, topic);
1361
+ for (const [walletId, session] of checked) {
1287
1362
  if (!session) {
1288
1363
  this.topics.delete(walletId);
1289
1364
  continue;
@@ -1307,15 +1382,17 @@ var WalletConnectConnector = class extends BaseConnector {
1307
1382
  );
1308
1383
  }
1309
1384
  this.topics.clear();
1385
+ this.pending.clear();
1310
1386
  this.persist();
1311
1387
  }
1312
1388
  // --- helpers ---
1313
- tryReuse(client, req) {
1389
+ async tryReuse(client, req) {
1314
1390
  const topic = this.topics.get(req.walletId);
1315
1391
  if (!topic) return null;
1316
- const session = this.liveSession(client, topic);
1392
+ const session = await this.liveSession(client, topic);
1317
1393
  if (!session) {
1318
1394
  this.topics.delete(req.walletId);
1395
+ this.persist();
1319
1396
  return null;
1320
1397
  }
1321
1398
  const ns = session.namespaces[req.namespace.name];
@@ -1326,7 +1403,9 @@ var WalletConnectConnector = class extends BaseConnector {
1326
1403
  toConnection(client, walletId, session, namespace) {
1327
1404
  const ns = session.namespaces[namespace.name];
1328
1405
  const account = (ns?.accounts ?? []).map((a) => a.split(":")[2]).find((a) => !!a) ?? "";
1329
- const chainId = ns?.chains?.[0] ?? (ns?.accounts?.[0] ? ns.accounts[0].split(":").slice(0, 2).join(":") : `${namespace.name}:1`);
1406
+ const approved = ns?.chains?.length ? ns.chains : (ns?.accounts ?? []).map((a) => a.split(":").slice(0, 2).join(":"));
1407
+ const wanted = namespace.value.chains?.[0];
1408
+ const chainId = wanted && approved.includes(wanted) ? wanted : approved[0] ?? `${namespace.name}:1`;
1330
1409
  const topic = session.topic;
1331
1410
  return {
1332
1411
  walletId,
@@ -1351,12 +1430,27 @@ var WalletConnectConnector = class extends BaseConnector {
1351
1430
  }
1352
1431
  };
1353
1432
  }
1354
- liveSession(client, topic) {
1433
+ /**
1434
+ * A stored session the wallet is still on the other end of. The local record
1435
+ * is not evidence — it survives the user clearing the dApp inside their
1436
+ * wallet — and reusing a dead one returns a Connection that looks healthy
1437
+ * while every request hangs. So the peer has to answer for it.
1438
+ */
1439
+ async liveSession(client, topic) {
1440
+ let session;
1355
1441
  try {
1356
- return client.session.get(topic);
1442
+ session = client.session.get(topic);
1357
1443
  } catch {
1358
1444
  return void 0;
1359
1445
  }
1446
+ let timer;
1447
+ const alive = await Promise.race([
1448
+ client.ping({ topic }).then(() => true).catch(() => false),
1449
+ new Promise((resolve) => {
1450
+ timer = setTimeout(() => resolve(false), PING_TIMEOUT_MS);
1451
+ })
1452
+ ]).finally(() => clearTimeout(timer));
1453
+ return alive ? session : void 0;
1360
1454
  }
1361
1455
  walletForTopic(topic) {
1362
1456
  for (const [walletId, t] of this.topics) if (t === topic) return walletId;
@@ -1387,30 +1481,6 @@ function firstNamespace(session) {
1387
1481
  value: { chains: v.chains, methods: v.methods, events: v.events }
1388
1482
  };
1389
1483
  }
1390
- function withSignal2(promise, signal, onAbort) {
1391
- if (!signal) return promise;
1392
- if (signal.aborted) {
1393
- onAbort();
1394
- return Promise.reject(new DOMException("Aborted", "AbortError"));
1395
- }
1396
- return new Promise((resolve, reject) => {
1397
- const abortHandler = () => {
1398
- onAbort();
1399
- reject(new DOMException("Aborted", "AbortError"));
1400
- };
1401
- signal.addEventListener("abort", abortHandler, { once: true });
1402
- promise.then(
1403
- (v) => {
1404
- signal.removeEventListener("abort", abortHandler);
1405
- resolve(v);
1406
- },
1407
- (e) => {
1408
- signal.removeEventListener("abort", abortHandler);
1409
- reject(e);
1410
- }
1411
- );
1412
- });
1413
- }
1414
1484
 
1415
1485
  // src/core/lydian-connect.ts
1416
1486
  function injectedRdnsMap(sdkServed) {
@@ -1456,6 +1526,20 @@ var LydianConnect = class {
1456
1526
  signal: input.signal
1457
1527
  });
1458
1528
  }
1529
+ /**
1530
+ * Open a wallet app, optionally handing it the pairing URI from `display_uri`.
1531
+ *
1532
+ * Call this from a real click handler. A mobile browser will suppress the same
1533
+ * navigation issued automatically after an async step, which is why the
1534
+ * `display_uri` event reports `deepLink` — that is the library asking the host
1535
+ * for a button to put this behind.
1536
+ *
1537
+ * Returns whether a link was fired. `false` means nothing is known for this
1538
+ * wallet on this device; show the QR code instead.
1539
+ */
1540
+ openWallet(input) {
1541
+ return this.manager.openWallet(input);
1542
+ }
1459
1543
  restore() {
1460
1544
  return this.manager.restore();
1461
1545
  }