@bwalletx/connect 0.5.3 → 0.5.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.
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(src_exports, {
36
36
  AccessError: () => AccessError,
37
37
  BRC29_PROTOCOL: () => BRC29_PROTOCOL,
38
38
  BWALLETX_RDNS: () => BWALLETX_RDNS,
39
+ DEFAULT_DISCOVER_MS: () => DEFAULT_DISCOVER_MS,
39
40
  GET_URL: () => GET_URL,
40
41
  LOGIN_PROTOCOL: () => LOGIN_PROTOCOL,
41
42
  LOGO_URL: () => LOGO_URL,
@@ -49,6 +50,7 @@ __export(src_exports, {
49
50
  WEB_WALLET_URL: () => WEB_WALLET_URL,
50
51
  accessErrorMessage: () => accessErrorMessage,
51
52
  amountInSats: () => amountInSats,
53
+ bindPrewarm: () => bindPrewarm,
52
54
  brc100Option: () => brc100Option,
53
55
  buildAuthorizeUrl: () => buildAuthorizeUrl,
54
56
  buyToken: () => buyToken,
@@ -71,6 +73,7 @@ __export(src_exports, {
71
73
  pairingOption: () => pairingOption,
72
74
  pay: () => pay,
73
75
  pickWallet: () => pickWallet,
76
+ prewarmBwalletX: () => prewarmBwalletX,
74
77
  protocolMediaUrl: () => protocolMediaUrl,
75
78
  proveHoldings: () => proveHoldings,
76
79
  qrSvg: () => qrSvg,
@@ -91,13 +94,16 @@ var import_sdk = require("@bsv/sdk");
91
94
  var RDNS_EXTENSION = "com.bwalletx.extension";
92
95
  var RDNS_MOBILE = "space.bwallet.mobile";
93
96
  var BWALLETX_RDNS = [RDNS_EXTENSION, RDNS_MOBILE];
94
- function discoverBwalletX(ms = 1200) {
97
+ var DEFAULT_DISCOVER_MS = 300;
98
+ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
95
99
  if (typeof window === "undefined") return Promise.resolve(null);
96
100
  return new Promise((resolve) => {
97
101
  let done = false;
102
+ let timer;
98
103
  const finish = (r) => {
99
104
  if (done) return;
100
105
  done = true;
106
+ if (timer !== void 0) clearTimeout(timer);
101
107
  window.removeEventListener("brc100:announceWallet", onAnnounce);
102
108
  resolve(r);
103
109
  };
@@ -108,17 +114,25 @@ function discoverBwalletX(ms = 1200) {
108
114
  finish({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
109
115
  }
110
116
  };
117
+ const cwiNow = () => {
118
+ const cwi = window.CWI;
119
+ if (cwi) finish({ wallet: cwi, method: "in-app", rdns: null });
120
+ return !!cwi;
121
+ };
111
122
  window.addEventListener("brc100:announceWallet", onAnnounce);
112
123
  window.dispatchEvent(new Event("brc100:requestWallet"));
113
- setTimeout(() => {
114
- const cwi = window.CWI;
115
- if (cwi) return finish({ wallet: cwi, method: "in-app", rdns: null });
116
- if (!isFramed()) return finish(null);
117
- void probeXdm(ms).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
124
+ if (done || cwiNow()) return;
125
+ if (isFramed()) {
126
+ void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
127
+ return;
128
+ }
129
+ timer = setTimeout(() => {
130
+ timer = void 0;
131
+ if (!cwiNow()) finish(null);
118
132
  }, ms);
119
133
  });
120
134
  }
121
- async function findBwalletX(ms = 1200) {
135
+ async function findBwalletX(ms = DEFAULT_DISCOVER_MS) {
122
136
  return (await discoverBwalletX(ms))?.wallet ?? null;
123
137
  }
124
138
  var isFramed = () => {
@@ -173,6 +187,21 @@ async function probeXdm(ms = 1200, allowed = BWALLETX_PARENT_ORIGINS) {
173
187
  return null;
174
188
  }
175
189
  }
190
+ var warmedAt = 0;
191
+ function prewarmBwalletX() {
192
+ if (typeof window === "undefined" || Date.now() - warmedAt < 2e4) return;
193
+ warmedAt = Date.now();
194
+ void discoverBwalletX(0).then((f) => {
195
+ if (f && f.method === "extension") void f.wallet.isAuthenticated({}).catch(() => {
196
+ });
197
+ });
198
+ }
199
+ function bindPrewarm(el2) {
200
+ const on = () => prewarmBwalletX();
201
+ const evs = ["pointerenter", "focus", "touchstart"];
202
+ for (const e of evs) el2.addEventListener(e, on, { passive: true });
203
+ return () => evs.forEach((e) => el2.removeEventListener(e, on));
204
+ }
176
205
 
177
206
  // src/pair.ts
178
207
  var import_sdk3 = require("@bsv/sdk");
@@ -213,10 +242,22 @@ function pairUrl(link) {
213
242
  const q = new URLSearchParams({ v: link.v, r: link.r, c: link.c, k: link.k, o: link.o, e: String(link.e) });
214
243
  return `https://${PAIR_HOST}/pair?${q}`;
215
244
  }
216
- var relaySocketUrl = (relay, channel, role, expiry) => {
245
+ var newClaim = () => b64url(crypto.getRandomValues(new Uint8Array(16)));
246
+ var WALLET_CLAIM_INFO = "bwallet-pair-wallet-claim-v1";
247
+ var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
248
+ async function walletClaimFor(channel, sitePub) {
249
+ const d = await crypto.subtle.digest("SHA-256", enc.encode(`${WALLET_CLAIM_INFO}|${channel}|${sitePub}`));
250
+ return b64url(new Uint8Array(d));
251
+ }
252
+ async function claimDigest(claim) {
253
+ return hex(new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(claim))));
254
+ }
255
+ var relaySocketUrl = (relay, channel, role, expiry, claim, walletClaimHash) => {
217
256
  const local = /^(localhost|127\.0\.0\.1)(:\d+)?$/.test(relay);
218
257
  const q = new URLSearchParams({ role });
219
258
  if (expiry) q.set("e", String(expiry));
259
+ if (claim) q.set("claim", claim);
260
+ if (walletClaimHash && role === "site") q.set("wc", walletClaimHash);
220
261
  return `${local ? "ws" : "wss"}://${relay}/v1/c/${channel}?${q}`;
221
262
  };
222
263
  async function deriveSession(mine, theirs, channel) {
@@ -314,6 +355,7 @@ function parseStored(raw) {
314
355
  if (typeof p.s !== "string" || !/^[0-9a-f]{64}$/i.test(p.s)) return null;
315
356
  if (typeof p.k !== "string" || !/^0[23][0-9a-f]{64}$/i.test(p.k)) return null;
316
357
  if (!n(p.sent) || !n(p.lastSeen) || !n(p.pairedAt)) return null;
358
+ if (p.sc !== void 0 && (typeof p.sc !== "string" || !/^[A-Za-z0-9_-]{22,64}$/.test(p.sc))) return null;
317
359
  return p;
318
360
  } catch {
319
361
  return null;
@@ -371,7 +413,12 @@ var Link = class {
371
413
  await this.opening;
372
414
  }
373
415
  reopen() {
374
- const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, "site", Math.floor(Date.now() / 1e3) + QR_LIFETIME_S - 10));
416
+ if (!this.p.sc) {
417
+ this.p = { ...this.p, sc: newClaim() };
418
+ this.persist();
419
+ }
420
+ const e = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S - 10;
421
+ const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, "site", e, this.p.sc));
375
422
  return new Promise((resolve, reject) => {
376
423
  ws.onopen = () => {
377
424
  this.ws = ws;
@@ -465,11 +512,13 @@ function pairBwalletX(opts) {
465
512
  const siteKey = import_sdk3.PrivateKey.fromRandom();
466
513
  const channel = newChannel();
467
514
  const expiry = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S;
468
- return new Promise((resolve, reject) => {
515
+ const siteClaim = newClaim();
516
+ const sitePub = siteKey.toPublicKey().toString();
517
+ return walletClaimFor(channel, sitePub).then(claimDigest).then((wc) => new Promise((resolve, reject) => {
469
518
  if (opts.signal?.aborted) return reject(new Error("cancelled"));
470
519
  let ws;
471
520
  try {
472
- ws = socket(relaySocketUrl(relay, channel, "site", expiry));
521
+ ws = socket(relaySocketUrl(relay, channel, "site", expiry, siteClaim, wc));
473
522
  } catch (e) {
474
523
  return reject(e);
475
524
  }
@@ -508,7 +557,7 @@ function pairBwalletX(opts) {
508
557
  if (msg.t === "ready") {
509
558
  settled = true;
510
559
  clearTimeout(timer);
511
- const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now() };
560
+ const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now(), sc: siteClaim };
512
561
  await Promise.resolve(storage.setItem(key, JSON.stringify(p))).catch(() => {
513
562
  });
514
563
  const link = new Link(p, sealer, storage, key, socket, opts.callTimeoutMs ?? 12e4);
@@ -518,8 +567,8 @@ function pairBwalletX(opts) {
518
567
  fail(new Error("Pairing was declined in bWalletX"));
519
568
  }
520
569
  });
521
- opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: siteKey.toPublicKey().toString(), o: origin, e: expiry }));
522
- });
570
+ opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: sitePub, o: origin, e: expiry }));
571
+ }));
523
572
  }
524
573
  async function restorePairing(opts = {}) {
525
574
  const storage = opts.storage === void 0 ? defaultStorage() : opts.storage ?? memoryStorage();
@@ -540,16 +589,33 @@ async function clearPairing(opts = {}) {
540
589
  // src/signin.ts
541
590
  var LOGIN_PROTOCOL = [2, "bwallet sign in"];
542
591
  async function connectBwalletX(opts = {}) {
543
- if (opts.wallet) return { wallet: opts.wallet, method: "wallet" };
544
- const found = await discoverBwalletX(opts.discoverMs);
545
- if (found) return { wallet: found.wallet, method: found.method };
592
+ const status = (s) => {
593
+ try {
594
+ opts.onStatus?.(s);
595
+ } catch {
596
+ }
597
+ };
598
+ if (opts.wallet) {
599
+ status("opening");
600
+ return { wallet: opts.wallet, method: "wallet" };
601
+ }
602
+ status("finding");
603
+ const found = await discoverBwalletX(opts.discoverMs ?? DEFAULT_DISCOVER_MS);
604
+ if (found) {
605
+ status("opening");
606
+ return { wallet: found.wallet, method: found.method };
607
+ }
546
608
  if (opts.reusePairing !== false) {
547
609
  const restored = await restorePairing(opts.pairing ?? {});
548
- if (restored) return { wallet: restored, method: "pairing" };
610
+ if (restored) {
611
+ status("opening");
612
+ return { wallet: restored, method: "pairing" };
613
+ }
549
614
  }
550
615
  if (!opts.pairing) {
551
616
  throw Object.assign(new Error("bWalletX was not found in this browser"), { code: "NOT_FOUND" });
552
617
  }
618
+ status("pairing");
553
619
  return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
554
620
  }
555
621
  async function signInWithBwalletX(opts) {
@@ -557,6 +623,10 @@ async function signInWithBwalletX(opts) {
557
623
  if (method !== "pairing") await wallet.waitForAuthentication({}).catch(() => {
558
624
  });
559
625
  const { publicKey: identityKey } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
626
+ try {
627
+ opts.onStatus?.("signing");
628
+ } catch {
629
+ }
560
630
  const c = await atStage("challenge", () => opts.challenge(identityKey));
561
631
  const { signature } = await atStage(
562
632
  "signature",
@@ -1006,6 +1076,7 @@ function createClass() {
1006
1076
  const nf = this.#notFound ? `<p class="nf" part="not-found" role="status">bWalletX wasn't found in this browser. <a href="${esc(getUrl)}" target="_blank" rel="noopener">Get ${brand}</a> or <a href="${esc(webUrl)}" target="_blank" rel="noopener">use the web wallet</a>.</p>` : "";
1007
1077
  this.#root.innerHTML = `<style>${SIGNIN_CSS}</style><button type="button" part="button" aria-label="${esc(aria)}" title="${esc(subtitle ? `${text} (${subtitle})` : text)}"${disabled ? " disabled" : ""}${loading ? ' aria-busy="true"' : ""}><span class="mark" part="mark">${mark}</span>${label}</button>${loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : ""}${nf}`;
1008
1078
  this.button.addEventListener("click", () => void this.#click());
1079
+ bindPrewarm(this.button);
1009
1080
  }
1010
1081
  #emit(type, detail, cancelable = false) {
1011
1082
  return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
@@ -1032,7 +1103,7 @@ function createClass() {
1032
1103
  this.#notFound = false;
1033
1104
  this.#setBusy(true);
1034
1105
  try {
1035
- const ms = Number(this.#attr("discover-ms")) || 1200;
1106
+ const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
1036
1107
  const found = await discoverBwalletX(ms);
1037
1108
  if (!found) {
1038
1109
  this.#notFound = true;
@@ -1160,11 +1231,11 @@ async function resolvePaymailP2P(paymail, sats, f = fetch) {
1160
1231
  return {
1161
1232
  outputs: body.outputs.map((o) => ({ script: o.script, satoshis: o.satoshis })),
1162
1233
  reference: body.reference,
1163
- submit: receive ? async (hex) => {
1234
+ submit: receive ? async (hex2) => {
1164
1235
  await f(fill(receive), {
1165
1236
  method: "POST",
1166
1237
  headers: { "content-type": "application/json" },
1167
- body: JSON.stringify({ hex, reference: body.reference, metadata: {} })
1238
+ body: JSON.stringify({ hex: hex2, reference: body.reference, metadata: {} })
1168
1239
  });
1169
1240
  } : void 0
1170
1241
  };
@@ -1310,7 +1381,7 @@ async function probe(substrate, ms) {
1310
1381
  }
1311
1382
  async function discoverWallets(o = {}) {
1312
1383
  const out = [];
1313
- const ms = o.ms ?? 1200;
1384
+ const ms = o.ms ?? 300;
1314
1385
  if (typeof window !== "undefined") {
1315
1386
  const seen = /* @__PURE__ */ new Map();
1316
1387
  const onAnnounce = (e) => {
@@ -1324,7 +1395,7 @@ async function discoverWallets(o = {}) {
1324
1395
  window.addEventListener("brc100:announceWallet", onAnnounce);
1325
1396
  window.dispatchEvent(new Event("brc100:requestWallet"));
1326
1397
  const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
1327
- await new Promise((r) => setTimeout(r, ms));
1398
+ await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
1328
1399
  window.removeEventListener("brc100:announceWallet", onAnnounce);
1329
1400
  out.push(...seen.values());
1330
1401
  const cwi = window.CWI;
@@ -1516,6 +1587,7 @@ function declareWalletTheme(theme) {
1516
1587
  AccessError,
1517
1588
  BRC29_PROTOCOL,
1518
1589
  BWALLETX_RDNS,
1590
+ DEFAULT_DISCOVER_MS,
1519
1591
  GET_URL,
1520
1592
  LOGIN_PROTOCOL,
1521
1593
  LOGO_URL,
@@ -1529,6 +1601,7 @@ function declareWalletTheme(theme) {
1529
1601
  WEB_WALLET_URL,
1530
1602
  accessErrorMessage,
1531
1603
  amountInSats,
1604
+ bindPrewarm,
1532
1605
  brc100Option,
1533
1606
  buildAuthorizeUrl,
1534
1607
  buyToken,
@@ -1551,6 +1624,7 @@ function declareWalletTheme(theme) {
1551
1624
  pairingOption,
1552
1625
  pay,
1553
1626
  pickWallet,
1627
+ prewarmBwalletX,
1554
1628
  protocolMediaUrl,
1555
1629
  proveHoldings,
1556
1630
  qrSvg,