@bwalletx/connect 0.5.3 → 0.5.6
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/CHANGELOG.md +35 -0
- package/README.md +80 -8
- package/dist/bwalletx-connect.global.js +92 -25
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{wallets-VOKIKBbO.d.ts → choice-DCh-tgpT.d.cts} +113 -5
- package/dist/{wallets-VOKIKBbO.d.cts → choice-DCh-tgpT.d.ts} +113 -5
- package/dist/chunk-34DKBQNN.js +222 -0
- package/dist/chunk-34DKBQNN.js.map +1 -0
- package/dist/{chunk-QRXFL66W.js → chunk-A2WLGS25.js} +6 -4
- package/dist/chunk-A2WLGS25.js.map +1 -0
- package/dist/chunk-IFGOAROM.js +255 -0
- package/dist/chunk-IFGOAROM.js.map +1 -0
- package/dist/chunk-LS6AYEBO.js +1886 -0
- package/dist/chunk-LS6AYEBO.js.map +1 -0
- package/dist/element-D9Zl-x12.d.cts +46 -0
- package/dist/element-D9Zl-x12.d.ts +46 -0
- package/dist/element.cjs +1221 -67
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +23 -28
- package/dist/element.d.ts +23 -28
- package/dist/element.js +29 -2
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +1539 -449
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -12
- package/dist/index.d.ts +185 -12
- package/dist/index.js +106 -12
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +5 -3
- package/dist/next.cjs.map +1 -1
- package/dist/next.js +1 -1
- package/dist/react.cjs +1073 -194
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +75 -8
- package/dist/react.d.ts +75 -8
- package/dist/react.js +121 -40
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +5 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +8 -3
- package/dist/server.d.ts +8 -3
- package/dist/server.js +1 -1
- package/dist/shell-react.cjs.map +1 -1
- package/dist/shell-react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-IUDD7YTQ.js +0 -839
- package/dist/chunk-IUDD7YTQ.js.map +0 -1
- package/dist/chunk-PI42H3XY.js +0 -459
- package/dist/chunk-PI42H3XY.js.map +0 -1
- package/dist/chunk-QRXFL66W.js.map +0 -1
package/dist/react.cjs
CHANGED
|
@@ -34,12 +34,16 @@ __export(react_exports, {
|
|
|
34
34
|
BwalletxProvider: () => BwalletxProvider,
|
|
35
35
|
BwalletxSignin: () => BwalletxSignin,
|
|
36
36
|
PaymentError: () => PaymentError,
|
|
37
|
+
accountFor: () => accountFor,
|
|
38
|
+
autoChoice: () => autoChoice,
|
|
37
39
|
brc100Option: () => brc100Option,
|
|
38
40
|
default: () => react_default,
|
|
39
41
|
discoverWallets: () => discoverWallets,
|
|
40
42
|
handcashAdapter: () => handcashAdapter,
|
|
41
43
|
pairingOption: () => pairingOption,
|
|
42
|
-
|
|
44
|
+
signinStateFor: () => signinStateFor,
|
|
45
|
+
useBwalletX: () => useBwalletX,
|
|
46
|
+
useBwalletXAccount: () => useBwalletXAccount
|
|
43
47
|
});
|
|
44
48
|
module.exports = __toCommonJS(react_exports);
|
|
45
49
|
var import_react = require("react");
|
|
@@ -83,10 +87,22 @@ function pairUrl(link) {
|
|
|
83
87
|
const q = new URLSearchParams({ v: link.v, r: link.r, c: link.c, k: link.k, o: link.o, e: String(link.e) });
|
|
84
88
|
return `https://${PAIR_HOST}/pair?${q}`;
|
|
85
89
|
}
|
|
86
|
-
var
|
|
90
|
+
var newClaim = () => b64url(crypto.getRandomValues(new Uint8Array(16)));
|
|
91
|
+
var WALLET_CLAIM_INFO = "bwallet-pair-wallet-claim-v1";
|
|
92
|
+
var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
93
|
+
async function walletClaimFor(channel, sitePub) {
|
|
94
|
+
const d = await crypto.subtle.digest("SHA-256", enc.encode(`${WALLET_CLAIM_INFO}|${channel}|${sitePub}`));
|
|
95
|
+
return b64url(new Uint8Array(d));
|
|
96
|
+
}
|
|
97
|
+
async function claimDigest(claim) {
|
|
98
|
+
return hex(new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(claim))));
|
|
99
|
+
}
|
|
100
|
+
var relaySocketUrl = (relay, channel, role, expiry, claim, walletClaimHash) => {
|
|
87
101
|
const local = /^(localhost|127\.0\.0\.1)(:\d+)?$/.test(relay);
|
|
88
102
|
const q = new URLSearchParams({ role });
|
|
89
103
|
if (expiry) q.set("e", String(expiry));
|
|
104
|
+
if (claim) q.set("claim", claim);
|
|
105
|
+
if (walletClaimHash && role === "site") q.set("wc", walletClaimHash);
|
|
90
106
|
return `${local ? "ws" : "wss"}://${relay}/v1/c/${channel}?${q}`;
|
|
91
107
|
};
|
|
92
108
|
async function deriveSession(mine, theirs, channel) {
|
|
@@ -184,6 +200,7 @@ function parseStored(raw) {
|
|
|
184
200
|
if (typeof p.s !== "string" || !/^[0-9a-f]{64}$/i.test(p.s)) return null;
|
|
185
201
|
if (typeof p.k !== "string" || !/^0[23][0-9a-f]{64}$/i.test(p.k)) return null;
|
|
186
202
|
if (!n(p.sent) || !n(p.lastSeen) || !n(p.pairedAt)) return null;
|
|
203
|
+
if (p.sc !== void 0 && (typeof p.sc !== "string" || !/^[A-Za-z0-9_-]{22,64}$/.test(p.sc))) return null;
|
|
187
204
|
return p;
|
|
188
205
|
} catch {
|
|
189
206
|
return null;
|
|
@@ -241,7 +258,12 @@ var Link = class {
|
|
|
241
258
|
await this.opening;
|
|
242
259
|
}
|
|
243
260
|
reopen() {
|
|
244
|
-
|
|
261
|
+
if (!this.p.sc) {
|
|
262
|
+
this.p = { ...this.p, sc: newClaim() };
|
|
263
|
+
this.persist();
|
|
264
|
+
}
|
|
265
|
+
const e = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S - 10;
|
|
266
|
+
const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, "site", e, this.p.sc));
|
|
245
267
|
return new Promise((resolve, reject) => {
|
|
246
268
|
ws.onopen = () => {
|
|
247
269
|
this.ws = ws;
|
|
@@ -335,11 +357,13 @@ function pairBwalletX(opts) {
|
|
|
335
357
|
const siteKey = import_sdk2.PrivateKey.fromRandom();
|
|
336
358
|
const channel = newChannel();
|
|
337
359
|
const expiry = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S;
|
|
338
|
-
|
|
360
|
+
const siteClaim = newClaim();
|
|
361
|
+
const sitePub = siteKey.toPublicKey().toString();
|
|
362
|
+
return walletClaimFor(channel, sitePub).then(claimDigest).then((wc) => new Promise((resolve, reject) => {
|
|
339
363
|
if (opts.signal?.aborted) return reject(new Error("cancelled"));
|
|
340
364
|
let ws;
|
|
341
365
|
try {
|
|
342
|
-
ws = socket(relaySocketUrl(relay, channel, "site", expiry));
|
|
366
|
+
ws = socket(relaySocketUrl(relay, channel, "site", expiry, siteClaim, wc));
|
|
343
367
|
} catch (e) {
|
|
344
368
|
return reject(e);
|
|
345
369
|
}
|
|
@@ -378,7 +402,7 @@ function pairBwalletX(opts) {
|
|
|
378
402
|
if (msg.t === "ready") {
|
|
379
403
|
settled = true;
|
|
380
404
|
clearTimeout(timer);
|
|
381
|
-
const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now() };
|
|
405
|
+
const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now(), sc: siteClaim };
|
|
382
406
|
await Promise.resolve(storage.setItem(key, JSON.stringify(p))).catch(() => {
|
|
383
407
|
});
|
|
384
408
|
const link = new Link(p, sealer, storage, key, socket, opts.callTimeoutMs ?? 12e4);
|
|
@@ -388,8 +412,8 @@ function pairBwalletX(opts) {
|
|
|
388
412
|
fail(new Error("Pairing was declined in bWalletX"));
|
|
389
413
|
}
|
|
390
414
|
});
|
|
391
|
-
opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k:
|
|
392
|
-
});
|
|
415
|
+
opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: sitePub, o: origin, e: expiry }));
|
|
416
|
+
}));
|
|
393
417
|
}
|
|
394
418
|
async function restorePairing(opts = {}) {
|
|
395
419
|
const storage = opts.storage === void 0 ? defaultStorage() : opts.storage ?? memoryStorage();
|
|
@@ -426,18 +450,88 @@ function declareWalletTheme(theme) {
|
|
|
426
450
|
if (meta.content !== id) meta.content = id;
|
|
427
451
|
}
|
|
428
452
|
|
|
453
|
+
// src/account.ts
|
|
454
|
+
var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
455
|
+
var BAVATAR_URL = "https://chat.bwalletx.com/api/bavatar/";
|
|
456
|
+
function shortKey(key) {
|
|
457
|
+
return key.length > 12 ? `${key.slice(0, 6)}\u2026${key.slice(-4)}` : key;
|
|
458
|
+
}
|
|
459
|
+
function accountLabel(a) {
|
|
460
|
+
if (a.paymail && /@bwalletx\.com$/i.test(a.paymail)) return "$" + a.paymail.split("@")[0];
|
|
461
|
+
if (a.handle) return a.handle;
|
|
462
|
+
if (a.paymail) return a.paymail;
|
|
463
|
+
return a.identityKey ? shortKey(a.identityKey) : "";
|
|
464
|
+
}
|
|
465
|
+
function avatarUrlFor(identityKey) {
|
|
466
|
+
return identityKey && /^0[23][0-9a-fA-F]{64}$/.test(identityKey) ? `${BAVATAR_URL}${identityKey.toLowerCase()}.png` : null;
|
|
467
|
+
}
|
|
468
|
+
var cache = /* @__PURE__ */ new Map();
|
|
469
|
+
function lookupPaymail(identityKey, url = PAYMAIL_LOOKUP_URL) {
|
|
470
|
+
if (url === "off" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
471
|
+
const k = `${url}|${identityKey}`;
|
|
472
|
+
let p = cache.get(k);
|
|
473
|
+
if (!p) {
|
|
474
|
+
p = fetch(url + encodeURIComponent(identityKey)).then((r) => r.ok ? r.json() : null).then((j) => j && typeof j.paymail === "string" ? j.paymail : null).catch(() => null);
|
|
475
|
+
cache.set(k, p);
|
|
476
|
+
}
|
|
477
|
+
return p;
|
|
478
|
+
}
|
|
479
|
+
function isUserRejection(e) {
|
|
480
|
+
const code = e?.code;
|
|
481
|
+
if (code === "ERR_PERMISSION_DENIED" || code === 4001) return true;
|
|
482
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
483
|
+
return /denied|reject|cancel|declin|refused|not now|user closed/i.test(msg);
|
|
484
|
+
}
|
|
485
|
+
function signInErrorText(e) {
|
|
486
|
+
if (isUserRejection(e)) return "Sign-in cancelled";
|
|
487
|
+
const code = e?.code;
|
|
488
|
+
if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
|
|
489
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
490
|
+
if (/lock/i.test(msg)) return "Your wallet is locked. Unlock it and try again";
|
|
491
|
+
if (/timeout|timed out|expired/i.test(msg)) return "The wallet did not answer in time";
|
|
492
|
+
if (/origin/i.test(msg)) return "This page is not on the site\u2019s configured origin";
|
|
493
|
+
return "Sign-in failed";
|
|
494
|
+
}
|
|
495
|
+
async function copyText(text) {
|
|
496
|
+
try {
|
|
497
|
+
await navigator.clipboard.writeText(text);
|
|
498
|
+
return true;
|
|
499
|
+
} catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
var IDENTITY_HINT_KEY = "bwx:identityKey";
|
|
504
|
+
function rememberedIdentity() {
|
|
505
|
+
try {
|
|
506
|
+
const k = globalThis.localStorage?.getItem(IDENTITY_HINT_KEY) ?? "";
|
|
507
|
+
return /^0[23][0-9a-f]{64}$/i.test(k) ? k : void 0;
|
|
508
|
+
} catch {
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function rememberIdentity(key) {
|
|
513
|
+
try {
|
|
514
|
+
if (key) globalThis.localStorage?.setItem(IDENTITY_HINT_KEY, key);
|
|
515
|
+
else globalThis.localStorage?.removeItem(IDENTITY_HINT_KEY);
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
429
520
|
// src/discover.ts
|
|
430
521
|
var import_sdk3 = require("@bsv/sdk");
|
|
431
522
|
var RDNS_EXTENSION = "com.bwalletx.extension";
|
|
432
523
|
var RDNS_MOBILE = "space.bwallet.mobile";
|
|
433
524
|
var BWALLETX_RDNS = [RDNS_EXTENSION, RDNS_MOBILE];
|
|
434
|
-
|
|
525
|
+
var DEFAULT_DISCOVER_MS = 300;
|
|
526
|
+
function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
435
527
|
if (typeof window === "undefined") return Promise.resolve(null);
|
|
436
528
|
return new Promise((resolve) => {
|
|
437
529
|
let done = false;
|
|
530
|
+
let timer;
|
|
438
531
|
const finish = (r) => {
|
|
439
532
|
if (done) return;
|
|
440
533
|
done = true;
|
|
534
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
441
535
|
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
442
536
|
resolve(r);
|
|
443
537
|
};
|
|
@@ -450,11 +544,14 @@ function discoverBwalletX(ms = 1200) {
|
|
|
450
544
|
};
|
|
451
545
|
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
452
546
|
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
547
|
+
if (done) return;
|
|
548
|
+
if (isFramed()) {
|
|
549
|
+
void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
timer = setTimeout(() => {
|
|
553
|
+
timer = void 0;
|
|
554
|
+
finish(null);
|
|
458
555
|
}, ms);
|
|
459
556
|
});
|
|
460
557
|
}
|
|
@@ -510,39 +607,116 @@ async function probeXdm(ms = 1200, allowed = BWALLETX_PARENT_ORIGINS) {
|
|
|
510
607
|
return null;
|
|
511
608
|
}
|
|
512
609
|
}
|
|
610
|
+
var warmedAt = 0;
|
|
611
|
+
function prewarmBwalletX() {
|
|
612
|
+
if (typeof window === "undefined" || Date.now() - warmedAt < 2e4) return;
|
|
613
|
+
warmedAt = Date.now();
|
|
614
|
+
void discoverBwalletX(0).then((f) => {
|
|
615
|
+
if (f && f.method === "extension") void f.wallet.isAuthenticated({}).catch(() => {
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function bindPrewarm(el2) {
|
|
620
|
+
const on = () => prewarmBwalletX();
|
|
621
|
+
const evs = ["pointerenter", "focus", "touchstart"];
|
|
622
|
+
for (const e of evs) el2.addEventListener(e, on, { passive: true });
|
|
623
|
+
return () => evs.forEach((e) => el2.removeEventListener(e, on));
|
|
624
|
+
}
|
|
513
625
|
|
|
514
626
|
// src/signin.ts
|
|
515
627
|
var LOGIN_PROTOCOL = [2, "bwallet sign in"];
|
|
516
628
|
async function connectBwalletX(opts = {}) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
629
|
+
const status = (s) => {
|
|
630
|
+
try {
|
|
631
|
+
opts.onStatus?.(s);
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
if (opts.wallet) {
|
|
636
|
+
status("opening");
|
|
637
|
+
return { wallet: opts.wallet, method: "wallet" };
|
|
638
|
+
}
|
|
639
|
+
status("finding");
|
|
640
|
+
const found = await discoverBwalletX(opts.discoverMs ?? DEFAULT_DISCOVER_MS);
|
|
641
|
+
if (found) {
|
|
642
|
+
status("opening");
|
|
643
|
+
return { wallet: found.wallet, method: found.method };
|
|
644
|
+
}
|
|
520
645
|
if (opts.reusePairing !== false) {
|
|
521
646
|
const restored = await restorePairing(opts.pairing ?? {});
|
|
522
|
-
if (restored)
|
|
647
|
+
if (restored) {
|
|
648
|
+
status("opening");
|
|
649
|
+
return { wallet: restored, method: "pairing" };
|
|
650
|
+
}
|
|
523
651
|
}
|
|
524
652
|
if (!opts.pairing) {
|
|
525
653
|
throw Object.assign(new Error("bWalletX was not found in this browser"), { code: "NOT_FOUND" });
|
|
526
654
|
}
|
|
655
|
+
status("pairing");
|
|
527
656
|
return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
|
|
528
657
|
}
|
|
658
|
+
async function waitIfLocked(wallet) {
|
|
659
|
+
let ok = false;
|
|
660
|
+
try {
|
|
661
|
+
ok = !!(await wallet.isAuthenticated({}))?.authenticated;
|
|
662
|
+
} catch {
|
|
663
|
+
}
|
|
664
|
+
if (!ok) await Promise.resolve().then(() => wallet.waitForAuthentication({})).catch(() => {
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
function challengeWantsIdentityKey(e) {
|
|
668
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
669
|
+
return /identityKey/i.test(msg);
|
|
670
|
+
}
|
|
529
671
|
async function signInWithBwalletX(opts) {
|
|
530
672
|
const { wallet, method } = await connectBwalletX(opts);
|
|
531
|
-
if (method !== "pairing") await wallet
|
|
673
|
+
if (method !== "pairing" && !opts.skipWaitForAuthentication) await waitIfLocked(wallet);
|
|
674
|
+
const status = () => {
|
|
675
|
+
try {
|
|
676
|
+
opts.onStatus?.("signing");
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const sign = (c2) => wallet.createSignature({
|
|
681
|
+
protocolID: c2.protocolID ?? LOGIN_PROTOCOL,
|
|
682
|
+
keyID: c2.keyID,
|
|
683
|
+
counterparty: "anyone",
|
|
684
|
+
data: Array.from(new TextEncoder().encode(c2.message))
|
|
532
685
|
});
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
"
|
|
537
|
-
() =>
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
686
|
+
const keyFirst = async () => {
|
|
687
|
+
const { publicKey: identityKey2 } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
|
|
688
|
+
status();
|
|
689
|
+
const c2 = await atStage("challenge", () => opts.challenge(identityKey2));
|
|
690
|
+
const { signature: signature2 } = await atStage("signature", () => sign(c2));
|
|
691
|
+
return { identityKey: identityKey2, signature: Array.from(signature2), nonce: c2.nonce, method, wallet };
|
|
692
|
+
};
|
|
693
|
+
if (opts.challengeNeedsIdentityKey && !opts.identityKey) return keyFirst();
|
|
694
|
+
let c;
|
|
695
|
+
try {
|
|
696
|
+
c = await atStage("challenge", () => opts.challenge(opts.identityKey));
|
|
697
|
+
} catch (e) {
|
|
698
|
+
if (!opts.identityKey && challengeWantsIdentityKey(e)) return keyFirst();
|
|
699
|
+
throw e;
|
|
700
|
+
}
|
|
701
|
+
status();
|
|
702
|
+
const [{ publicKey: identityKey }, { signature }] = await Promise.all([
|
|
703
|
+
opts.identityKey ? Promise.resolve({ publicKey: opts.identityKey }) : atStage("identity", () => wallet.getPublicKey({ identityKey: true })),
|
|
704
|
+
atStage("signature", () => sign(c))
|
|
705
|
+
]);
|
|
544
706
|
return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
|
|
545
707
|
}
|
|
708
|
+
async function signInAndVerify(opts) {
|
|
709
|
+
const hinted = !opts.identityKey && !!opts.identityKeyHint;
|
|
710
|
+
const r = await signInWithBwalletX({ ...opts, identityKey: opts.identityKey ?? opts.identityKeyHint });
|
|
711
|
+
try {
|
|
712
|
+
return { result: r, verified: await atStage("verify", () => opts.verify(r)) };
|
|
713
|
+
} catch (e) {
|
|
714
|
+
const status = e?.status;
|
|
715
|
+
if (!hinted || status !== 401) throw e;
|
|
716
|
+
const again = await signInWithBwalletX({ ...opts, wallet: r.wallet, identityKey: void 0, skipWaitForAuthentication: true });
|
|
717
|
+
return { result: again, verified: await atStage("verify", () => opts.verify(again)) };
|
|
718
|
+
}
|
|
719
|
+
}
|
|
546
720
|
async function atStage(stage, run) {
|
|
547
721
|
try {
|
|
548
722
|
return await run();
|
|
@@ -553,11 +727,272 @@ async function atStage(stage, run) {
|
|
|
553
727
|
}
|
|
554
728
|
}
|
|
555
729
|
|
|
730
|
+
// src/wallets.ts
|
|
731
|
+
var import_sdk4 = require("@bsv/sdk");
|
|
732
|
+
async function brc100Connected(wallet, o, c = {}) {
|
|
733
|
+
if (c.identity === false) return { wallet, payer: wallet, identityKey: null, optionId: o.id, kind: o.kind };
|
|
734
|
+
await waitIfLocked(wallet);
|
|
735
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
736
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
|
|
737
|
+
}
|
|
738
|
+
function brc100Option(wallet, info) {
|
|
739
|
+
const opt = {
|
|
740
|
+
id: info.id,
|
|
741
|
+
name: info.name,
|
|
742
|
+
icon: info.icon,
|
|
743
|
+
rdns: info.rdns,
|
|
744
|
+
kind: "brc100",
|
|
745
|
+
isBwalletX: info.isBwalletX ?? false,
|
|
746
|
+
substrate: info.substrate,
|
|
747
|
+
wallet,
|
|
748
|
+
connect: (c) => brc100Connected(wallet, opt, c)
|
|
749
|
+
};
|
|
750
|
+
return opt;
|
|
751
|
+
}
|
|
752
|
+
function pairingOption(pairing) {
|
|
753
|
+
const opt = {
|
|
754
|
+
id: "bwalletx-pairing",
|
|
755
|
+
name: "bWalletX on your phone",
|
|
756
|
+
kind: "pairing",
|
|
757
|
+
isBwalletX: true,
|
|
758
|
+
substrate: "pairing",
|
|
759
|
+
async connect(c) {
|
|
760
|
+
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
761
|
+
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
762
|
+
return brc100Connected(w, opt, c);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
return opt;
|
|
766
|
+
}
|
|
767
|
+
function handcashAdapter(o) {
|
|
768
|
+
const opt = {
|
|
769
|
+
id: "handcash",
|
|
770
|
+
name: "HandCash",
|
|
771
|
+
icon: o.icon,
|
|
772
|
+
kind: "handcash",
|
|
773
|
+
isBwalletX: false,
|
|
774
|
+
substrate: "adapter",
|
|
775
|
+
async connect() {
|
|
776
|
+
const p = await o.login();
|
|
777
|
+
return {
|
|
778
|
+
wallet: null,
|
|
779
|
+
payer: o.pay ? { pay: o.pay } : null,
|
|
780
|
+
identityKey: p.identityKey ?? null,
|
|
781
|
+
handle: p.handle,
|
|
782
|
+
paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
|
|
783
|
+
optionId: opt.id,
|
|
784
|
+
kind: "handcash",
|
|
785
|
+
transitional: true
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
return opt;
|
|
790
|
+
}
|
|
791
|
+
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
792
|
+
async function probe(substrate, ms) {
|
|
793
|
+
try {
|
|
794
|
+
const w = new import_sdk4.WalletClient(substrate);
|
|
795
|
+
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
796
|
+
return ok ? w : null;
|
|
797
|
+
} catch {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function cwiIsAnnounced(cwi, announced) {
|
|
802
|
+
return announced.some((o) => o.wallet === cwi || o.rdns === RDNS_MOBILE);
|
|
803
|
+
}
|
|
804
|
+
async function discoverWallets(o = {}) {
|
|
805
|
+
const out = [];
|
|
806
|
+
const ms = o.ms ?? 300;
|
|
807
|
+
if (typeof window !== "undefined") {
|
|
808
|
+
const seen = /* @__PURE__ */ new Map();
|
|
809
|
+
const onAnnounce = (e) => {
|
|
810
|
+
const d = e.detail;
|
|
811
|
+
if (!d?.wallet) return;
|
|
812
|
+
const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
|
|
813
|
+
if (seen.has(rdns)) return;
|
|
814
|
+
const isB = BWALLETX_RDNS.includes(rdns);
|
|
815
|
+
const substrate = rdns === RDNS_EXTENSION ? "extension" : rdns === RDNS_MOBILE ? "in-app" : "injected";
|
|
816
|
+
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB, substrate }));
|
|
817
|
+
};
|
|
818
|
+
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
819
|
+
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
820
|
+
const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
|
|
821
|
+
await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
|
|
822
|
+
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
823
|
+
out.push(...seen.values());
|
|
824
|
+
if (![...seen.values()].some((w) => w.isBwalletX) && isFramed()) {
|
|
825
|
+
const x = await probeXdm(Math.max(ms, 1200));
|
|
826
|
+
if (x) out.push(brc100Option(x, { id: "bwalletx-xdm", name: "bWalletX", isBwalletX: true, substrate: "xdm" }));
|
|
827
|
+
}
|
|
828
|
+
const cwi = window.CWI;
|
|
829
|
+
if (cwi && !cwiIsAnnounced(cwi, [...seen.values()])) {
|
|
830
|
+
out.push(brc100Option(cwi, { id: "window.CWI", name: seen.size ? "Another browser wallet" : "Browser wallet", isBwalletX: false, substrate: "window.CWI" }));
|
|
831
|
+
}
|
|
832
|
+
const [json, cicada] = await local;
|
|
833
|
+
if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)", substrate: "json-api" }));
|
|
834
|
+
if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)", substrate: "cicada" }));
|
|
835
|
+
}
|
|
836
|
+
if (o.pairing) out.push(pairingOption(o.pairing));
|
|
837
|
+
out.push(...o.extra ?? []);
|
|
838
|
+
const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
|
|
839
|
+
return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
|
|
840
|
+
}
|
|
841
|
+
var DESKTOP_OPTION_ID = "desktop";
|
|
842
|
+
function desktopOption(ms = 1500) {
|
|
843
|
+
const opt = {
|
|
844
|
+
id: DESKTOP_OPTION_ID,
|
|
845
|
+
name: "Desktop wallet",
|
|
846
|
+
kind: "brc100",
|
|
847
|
+
isBwalletX: false,
|
|
848
|
+
substrate: "json-api",
|
|
849
|
+
async connect(c) {
|
|
850
|
+
const [json, cicada] = await Promise.all([probe("json-api", ms), probe("Cicada", ms)]);
|
|
851
|
+
const w = json ?? cicada;
|
|
852
|
+
if (!w) throw Object.assign(new Error("No desktop wallet answered on this computer"), { code: "NOT_FOUND" });
|
|
853
|
+
opt.wallet = w;
|
|
854
|
+
opt.substrate = json ? "json-api" : "cicada";
|
|
855
|
+
return brc100Connected(w, opt, c);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
return opt;
|
|
859
|
+
}
|
|
860
|
+
function autoChoice(list, force = false) {
|
|
861
|
+
const detected = list.filter((w) => w.kind === "brc100");
|
|
862
|
+
const others = list.filter((w) => w.kind === "handcash" || w.kind === "custom");
|
|
863
|
+
if (force) return detected[0] ?? list.find((w) => w.kind !== "pairing") ?? list[0] ?? null;
|
|
864
|
+
if (!list.length) return null;
|
|
865
|
+
if (list.length === 1) return list[0];
|
|
866
|
+
if (detected.length === 1 && others.length === 0) return detected[0];
|
|
867
|
+
if (detected.length === 0 && others.length === 0) return list[0];
|
|
868
|
+
return void 0;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/choice.ts
|
|
872
|
+
var BWALLETX_TYPE = "bwalletx";
|
|
873
|
+
function substrateOf(o) {
|
|
874
|
+
if (o.substrate) return o.substrate;
|
|
875
|
+
if (o.kind === "pairing") return "pairing";
|
|
876
|
+
if (o.isBwalletX && o.kind === "brc100") return o.rdns === RDNS_MOBILE ? "in-app" : o.id === "bwalletx-xdm" ? "xdm" : "extension";
|
|
877
|
+
if (o.kind === "handcash" || o.kind === "custom") return "adapter";
|
|
878
|
+
if (o.rdns === RDNS_EXTENSION) return "extension";
|
|
879
|
+
if (o.rdns === RDNS_MOBILE) return "in-app";
|
|
880
|
+
if (o.id === "window.CWI") return "window.CWI";
|
|
881
|
+
if (o.id === "localhost:3321" || o.id === DESKTOP_OPTION_ID) return "json-api";
|
|
882
|
+
if (o.id === "localhost:3301") return "cicada";
|
|
883
|
+
return "injected";
|
|
884
|
+
}
|
|
885
|
+
function walletTypeOf(o) {
|
|
886
|
+
return o.isBwalletX ? BWALLETX_TYPE : o.rdns ?? o.id;
|
|
887
|
+
}
|
|
888
|
+
var OWN_MARKERS = ["extension", "in-app", "xdm"];
|
|
889
|
+
function isPresentBwalletX(o) {
|
|
890
|
+
return o.isBwalletX && o.kind === "brc100" && !!o.wallet && OWN_MARKERS.includes(substrateOf(o));
|
|
891
|
+
}
|
|
892
|
+
function hasBwalletX(list) {
|
|
893
|
+
return list.some(isPresentBwalletX);
|
|
894
|
+
}
|
|
895
|
+
function otherDetected(list) {
|
|
896
|
+
return list.filter((o) => !o.isBwalletX && o.kind === "brc100" && !!o.wallet);
|
|
897
|
+
}
|
|
898
|
+
function chooserOrder(list) {
|
|
899
|
+
const rank = (o) => isPresentBwalletX(o) ? 0 : o.isBwalletX ? 1 : o.kind === "brc100" ? 2 : o.kind === "custom" ? 3 : 4;
|
|
900
|
+
return list.map((o, i) => ({ o, i })).sort((a, b) => rank(a.o) - rank(b.o) || a.i - b.i).map((x) => x.o);
|
|
901
|
+
}
|
|
902
|
+
function decideWallet(list, o = {}) {
|
|
903
|
+
if (o.walletId) {
|
|
904
|
+
const w = list.find((x) => x.id === o.walletId);
|
|
905
|
+
if (w) return { option: w, reason: "requested" };
|
|
906
|
+
}
|
|
907
|
+
if (o.chooser) return { option: null, reason: list.length || o.picker !== false ? "chooser" : "none" };
|
|
908
|
+
if (o.fastPath === false) {
|
|
909
|
+
const a = autoChoice([...list], o.picker === false);
|
|
910
|
+
return a === void 0 ? { option: null, reason: "chooser" } : a === null ? { option: null, reason: "none" } : { option: a, reason: "legacy" };
|
|
911
|
+
}
|
|
912
|
+
const b = list.find(isPresentBwalletX);
|
|
913
|
+
if (b) return { option: b, reason: "bwalletx" };
|
|
914
|
+
if (o.picker === false) {
|
|
915
|
+
const phone = list.find((x) => x.isBwalletX);
|
|
916
|
+
return phone ? { option: phone, reason: "bwalletx-phone" } : { option: null, reason: "none" };
|
|
917
|
+
}
|
|
918
|
+
return list.length ? { option: null, reason: "chooser" } : { option: null, reason: "none" };
|
|
919
|
+
}
|
|
920
|
+
function outcomeOf(e) {
|
|
921
|
+
const err = e;
|
|
922
|
+
if (err?.code === "NOT_FOUND") return "not-found";
|
|
923
|
+
if (err?.name === "AbortError" || err?.message === "cancelled") return "cancelled";
|
|
924
|
+
if (isUserRejection(e)) return "rejected";
|
|
925
|
+
return "error";
|
|
926
|
+
}
|
|
927
|
+
function attemptFor(option, outcome, startedAt, now = Date.now()) {
|
|
928
|
+
return {
|
|
929
|
+
walletType: option ? walletTypeOf(option) : null,
|
|
930
|
+
substrate: option ? substrateOf(option) : null,
|
|
931
|
+
outcome,
|
|
932
|
+
ms: Math.max(0, Math.round(now - startedAt))
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function reportAttempt(hook, a) {
|
|
936
|
+
if (!hook) return;
|
|
937
|
+
try {
|
|
938
|
+
hook(Object.freeze({ walletType: a.walletType, substrate: a.substrate, outcome: a.outcome, ms: a.ms }));
|
|
939
|
+
} catch {
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/connect-flow.ts
|
|
944
|
+
async function runConnect(o) {
|
|
945
|
+
const started = Date.now();
|
|
946
|
+
const { list } = o;
|
|
947
|
+
let choice = null;
|
|
948
|
+
let reason = "none";
|
|
949
|
+
try {
|
|
950
|
+
const d = decideWallet(list, { walletId: o.walletId, chooser: o.chooser, picker: o.picker, fastPath: o.fastPath });
|
|
951
|
+
choice = d.option;
|
|
952
|
+
reason = d.reason;
|
|
953
|
+
if (!choice && d.reason === "chooser") choice = await o.pick(list);
|
|
954
|
+
if (!choice) {
|
|
955
|
+
if (d.reason === "none" || !list.length) throw Object.assign(new Error("No wallet found. Get bWalletX at bwalletx.com."), { code: "NOT_FOUND" });
|
|
956
|
+
reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
|
|
960
|
+
const cw = await choice.connect(signIn ? { identity: false } : void 0);
|
|
961
|
+
let handle = cw.handle ?? null;
|
|
962
|
+
let paymail = cw.paymail ?? null;
|
|
963
|
+
let identityKey = cw.identityKey;
|
|
964
|
+
let signedIn = false;
|
|
965
|
+
if (cw.wallet && signIn) {
|
|
966
|
+
const { result, verified } = await signInAndVerify({
|
|
967
|
+
wallet: cw.wallet,
|
|
968
|
+
identityKey: cw.identityKey ?? void 0,
|
|
969
|
+
// The key that signed in here last time: a repeat sign-in is then the signature alone.
|
|
970
|
+
identityKeyHint: rememberedIdentity(),
|
|
971
|
+
challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
|
|
972
|
+
challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
973
|
+
verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
974
|
+
});
|
|
975
|
+
rememberIdentity(result.identityKey);
|
|
976
|
+
identityKey = result.identityKey;
|
|
977
|
+
handle = verified.handle ?? handle;
|
|
978
|
+
paymail = verified.paymail ?? paymail;
|
|
979
|
+
signedIn = true;
|
|
980
|
+
} else if (cw.wallet && !identityKey) {
|
|
981
|
+
identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
|
|
982
|
+
}
|
|
983
|
+
reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
|
|
984
|
+
return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
|
|
985
|
+
} catch (e) {
|
|
986
|
+
reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
|
|
987
|
+
throw e;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
556
991
|
// src/modal.ts
|
|
557
992
|
var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
|
|
558
993
|
|
|
559
994
|
// src/access-client.ts
|
|
560
|
-
var
|
|
995
|
+
var import_sdk5 = require("@bsv/sdk");
|
|
561
996
|
|
|
562
997
|
// src/access.ts
|
|
563
998
|
var ACCESS_PROTOCOL = [2, "bwalletx access"];
|
|
@@ -695,7 +1130,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
|
|
|
695
1130
|
const counterparty = ci.counterparty ?? "self";
|
|
696
1131
|
for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
|
|
697
1132
|
const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
|
|
698
|
-
const h =
|
|
1133
|
+
const h = import_sdk5.Utils.toHex(import_sdk5.Hash.hash160(import_sdk5.PublicKey.fromString(publicKey).encode(true)));
|
|
699
1134
|
if (want && h !== want) continue;
|
|
700
1135
|
const existing = byKey.get(publicKey);
|
|
701
1136
|
if (existing) {
|
|
@@ -745,7 +1180,7 @@ async function requestAccess(o) {
|
|
|
745
1180
|
var SIGNIN_TAG = "bwalletx-signin";
|
|
746
1181
|
var GET_URL = "https://bwalletx.com/get";
|
|
747
1182
|
var WEB_WALLET_URL = "https://web.bwalletx.com";
|
|
748
|
-
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect" };
|
|
1183
|
+
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect", get: "Get", play: "Play with" };
|
|
749
1184
|
function signinText(label = "signin", brand = "bWalletX") {
|
|
750
1185
|
return `${VERBS[label] ?? VERBS.signin} ${brand}`;
|
|
751
1186
|
}
|
|
@@ -800,7 +1235,50 @@ button[aria-busy=true]{cursor:wait}
|
|
|
800
1235
|
.nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
801
1236
|
:host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}
|
|
802
1237
|
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
1238
|
+
/* pending: one plain line under the button */
|
|
1239
|
+
.note{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center;overflow-wrap:break-word}
|
|
1240
|
+
:host([theme=dark]) .note,:host-context(.dark) .note{color:#a1a1aa}
|
|
1241
|
+
/* "Other wallets" (0.5.6): one small link under the button */
|
|
1242
|
+
.ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1243
|
+
:host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
|
|
1244
|
+
.lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
|
|
1245
|
+
.lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
1246
|
+
/* error: one line plus Retry */
|
|
1247
|
+
.err{margin:0 0 8px;width:0;min-width:100%;font-size:13px;font-weight:600;line-height:1.4;color:#b91c1c;text-align:center;overflow-wrap:anywhere}
|
|
1248
|
+
:host([theme=dark]) .err,:host-context(.dark) .err{color:#fca5a5}
|
|
1249
|
+
/* signed in: the badge */
|
|
1250
|
+
button.chip{display:inline-flex;align-items:center;justify-content:flex-start;gap:10px;width:auto;max-width:100%;padding:5px 14px 5px 5px;border-radius:999px;
|
|
1251
|
+
background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.55);box-shadow:none;font-size:15px}
|
|
1252
|
+
button.chip:hover{background:#151517;border-color:#ffd24d}
|
|
1253
|
+
button.chip:active{background:#000;transform:none}
|
|
1254
|
+
button.chip:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}
|
|
1255
|
+
:host([theme=light]) button.chip{background:#fff;color:#09090b;border-color:#f59e0b}
|
|
1256
|
+
:host([theme=light]) button.chip:hover{background:#fffbeb}
|
|
1257
|
+
:host([full-width]) button.chip{width:100%}
|
|
1258
|
+
.av{position:relative;flex:none;display:inline-grid;place-items:center;width:30px;height:30px;border-radius:50%;overflow:hidden;
|
|
1259
|
+
background:radial-gradient(circle at 30% 30%,#ffe27a,#f5b800 55%,#b88600);color:#000;font-weight:800;font-size:14px}
|
|
1260
|
+
.av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
1261
|
+
.name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:min(52vw,260px)}
|
|
1262
|
+
.dot{flex:none;width:8px;height:8px;border-radius:50%;background:#ffd24d;box-shadow:0 0 0 3px rgba(255,210,77,.22)}
|
|
1263
|
+
:host([size=sm]) button.chip{font-size:13px;padding:3px 12px 3px 3px;gap:8px}
|
|
1264
|
+
:host([size=sm]) .av{width:26px;height:26px}
|
|
1265
|
+
:host([size=icon]) button.chip{width:48px;height:48px;padding:0;justify-content:center}
|
|
1266
|
+
:host([size=icon]) .name,:host([size=icon]) .dot{display:none}
|
|
1267
|
+
:host([size=icon]) .av{width:40px;height:40px}
|
|
1268
|
+
`;
|
|
1269
|
+
var MENU_CSS = `
|
|
1270
|
+
:host{all:initial}
|
|
1271
|
+
.menu{position:fixed;z-index:2147483600;box-sizing:border-box;min-width:200px;padding:6px;border-radius:14px;background:#0b0b0c;color:#ffd24d;
|
|
1272
|
+
border:1px solid rgba(255,210,77,.55);box-shadow:0 12px 32px rgba(0,0,0,.45);font:600 14px/1.3 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
1273
|
+
.who{padding:8px 10px 10px;font-size:12px;font-weight:500;color:#e7d9a8;overflow-wrap:anywhere;border-bottom:1px solid rgba(255,210,77,.3);margin-bottom:4px}
|
|
1274
|
+
.item{all:unset;box-sizing:border-box;display:block;width:100%;padding:11px 10px;border-radius:10px;cursor:pointer;color:#ffd24d}
|
|
1275
|
+
.item:hover,.item:focus-visible{background:#ffd24d;color:#000;outline:none}
|
|
1276
|
+
.tip{position:fixed;z-index:2147483600;box-sizing:border-box;padding:8px 12px;border-radius:10px;background:#0b0b0c;color:#e7d9a8;
|
|
1277
|
+
border:1px solid rgba(255,210,77,.4);box-shadow:0 8px 24px rgba(0,0,0,.35);font:500 12px/1.4 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
803
1278
|
`;
|
|
1279
|
+
var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
|
|
1280
|
+
var PENDING_LABEL = "Approve in bWalletX\u2026";
|
|
1281
|
+
var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
|
|
804
1282
|
function b64url2(bytes) {
|
|
805
1283
|
let s = "";
|
|
806
1284
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -833,14 +1311,15 @@ async function buildAuthorizeUrl(p) {
|
|
|
833
1311
|
async function postJson(url, body) {
|
|
834
1312
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
835
1313
|
const data = await res.json().catch(() => ({}));
|
|
836
|
-
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
1314
|
+
if (!res.ok) throw Object.assign(new Error(data.error ?? `HTTP ${res.status}`), { status: res.status });
|
|
837
1315
|
return data;
|
|
838
1316
|
}
|
|
839
1317
|
async function postAccess(url, body) {
|
|
840
1318
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
841
1319
|
return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
|
|
842
1320
|
}
|
|
843
|
-
var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url"];
|
|
1321
|
+
var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url", "state", "identity-key", "paymail", "handle", "avatar-url", "error", "other-wallets", "challenge-url"];
|
|
1322
|
+
var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
|
|
844
1323
|
function createClass() {
|
|
845
1324
|
return class BwalletxSigninElement extends HTMLElement {
|
|
846
1325
|
static get observedAttributes() {
|
|
@@ -849,12 +1328,23 @@ function createClass() {
|
|
|
849
1328
|
#root;
|
|
850
1329
|
#notFound = false;
|
|
851
1330
|
#busy = false;
|
|
1331
|
+
#phase = "signed-out";
|
|
1332
|
+
#account = null;
|
|
1333
|
+
#error = "";
|
|
1334
|
+
#restored = false;
|
|
1335
|
+
#pendingLabel = PENDING_LABEL;
|
|
1336
|
+
#menu = null;
|
|
852
1337
|
constructor() {
|
|
853
1338
|
super();
|
|
854
1339
|
this.#root = this.attachShadow({ mode: "open" });
|
|
855
1340
|
}
|
|
856
1341
|
connectedCallback() {
|
|
857
1342
|
this.#render();
|
|
1343
|
+
void this.#restore();
|
|
1344
|
+
}
|
|
1345
|
+
disconnectedCallback() {
|
|
1346
|
+
this.#menu?.close(false);
|
|
1347
|
+
this.#hideTip();
|
|
858
1348
|
}
|
|
859
1349
|
attributeChangedCallback() {
|
|
860
1350
|
if (this.isConnected) this.#render();
|
|
@@ -866,92 +1356,402 @@ function createClass() {
|
|
|
866
1356
|
get notFound() {
|
|
867
1357
|
return this.#notFound;
|
|
868
1358
|
}
|
|
1359
|
+
/** The state shown now (the `state` attribute when set, else the element's own). */
|
|
1360
|
+
get state() {
|
|
1361
|
+
const a = this.#attr("state");
|
|
1362
|
+
return a && STATES.includes(a) ? a : this.#phase;
|
|
1363
|
+
}
|
|
1364
|
+
/** The signed-in account, or null. */
|
|
1365
|
+
get account() {
|
|
1366
|
+
if (!this.#controlled) return this.#account;
|
|
1367
|
+
const identityKey = this.#attr("identity-key");
|
|
1368
|
+
return identityKey ? { identityKey, paymail: this.#attr("paymail"), handle: this.#attr("handle"), avatarUrl: this.#attr("avatar-url") } : null;
|
|
1369
|
+
}
|
|
1370
|
+
/** True while the menu is open (tests). */
|
|
1371
|
+
get menuOpen() {
|
|
1372
|
+
return !!this.#menu;
|
|
1373
|
+
}
|
|
869
1374
|
focus(opts) {
|
|
870
1375
|
this.button?.focus(opts);
|
|
871
1376
|
}
|
|
1377
|
+
get #controlled() {
|
|
1378
|
+
return this.hasAttribute("state");
|
|
1379
|
+
}
|
|
1380
|
+
get #stateful() {
|
|
1381
|
+
return !this.hasAttribute("stateless");
|
|
1382
|
+
}
|
|
872
1383
|
#attr(name) {
|
|
873
1384
|
return this.getAttribute(name);
|
|
874
1385
|
}
|
|
1386
|
+
/** Restore the badge from session-url once, with no wallet call. */
|
|
1387
|
+
async #restore() {
|
|
1388
|
+
const url = this.#attr("session-url");
|
|
1389
|
+
if (this.#restored || !url || this.#controlled || !this.#stateful) return;
|
|
1390
|
+
this.#restored = true;
|
|
1391
|
+
this.#set("restoring");
|
|
1392
|
+
try {
|
|
1393
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
1394
|
+
const j = await r.json().catch(() => ({}));
|
|
1395
|
+
if (this.#phase !== "restoring") return;
|
|
1396
|
+
if (j.identityKey) this.#signedIn({ identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null });
|
|
1397
|
+
else this.#set("signed-out");
|
|
1398
|
+
} catch {
|
|
1399
|
+
if (this.#phase === "restoring") this.#set("signed-out");
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
#set(p) {
|
|
1403
|
+
this.#phase = p;
|
|
1404
|
+
if (p !== "signed-in") this.#menu?.close(false);
|
|
1405
|
+
this.#render();
|
|
1406
|
+
}
|
|
1407
|
+
#signedIn(a) {
|
|
1408
|
+
this.#account = a;
|
|
1409
|
+
this.#error = "";
|
|
1410
|
+
this.#set("signed-in");
|
|
1411
|
+
const lookup = this.#attr("lookup-url");
|
|
1412
|
+
if (!a.paymail && lookup !== "off") {
|
|
1413
|
+
void lookupPaymail(a.identityKey, lookup ?? void 0).then((pm) => {
|
|
1414
|
+
if (pm && this.#account?.identityKey === a.identityKey && !this.#account.paymail) {
|
|
1415
|
+
this.#account = { ...this.#account, paymail: pm };
|
|
1416
|
+
this.#render();
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
875
1421
|
#render() {
|
|
876
1422
|
const size = this.#attr("size") ?? "lg";
|
|
877
1423
|
const brand = this.#attr("brand") === "bWallet" ? "bWallet" : "bWalletX";
|
|
878
|
-
const
|
|
1424
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
1425
|
+
const state = this.state;
|
|
1426
|
+
const acct = this.account;
|
|
1427
|
+
if (state === "signed-in" && acct) {
|
|
1428
|
+
this.#hideTip();
|
|
1429
|
+
const name = accountLabel(acct);
|
|
1430
|
+
const avatar = acct.avatarUrl ?? avatarUrlFor(acct.identityKey);
|
|
1431
|
+
const initial = (name.replace(/^\$/, "").charAt(0) || "b").toUpperCase();
|
|
1432
|
+
this.#root.innerHTML = `<style>${SIGNIN_CSS}</style><button type="button" class="chip" part="button chip" aria-haspopup="menu" aria-expanded="${this.#menu ? "true" : "false"}" aria-label="Signed in with bWalletX as ${esc(name)}. Account menu" title="Signed in as ${esc(name)}"><span class="av" part="avatar" aria-hidden="true">${esc(initial)}${avatar ? `<img alt="" src="${esc(avatar)}" referrerpolicy="no-referrer">` : ""}</span><span class="name" part="label">${esc(name)}</span><span class="dot" part="dot" aria-hidden="true"></span></button>`;
|
|
1433
|
+
const img = this.#root.querySelector("img");
|
|
1434
|
+
if (img) img.addEventListener("error", () => img.remove(), { once: true });
|
|
1435
|
+
const chip = this.button;
|
|
1436
|
+
chip.addEventListener("click", () => this.#menu ? this.#menu.close(true) : this.#openMenu());
|
|
1437
|
+
chip.addEventListener("keydown", (e) => {
|
|
1438
|
+
if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !this.#menu) {
|
|
1439
|
+
e.preventDefault();
|
|
1440
|
+
this.#openMenu();
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const pending = state === "pending";
|
|
1446
|
+
const restoring = state === "restoring";
|
|
1447
|
+
const error = state === "error";
|
|
1448
|
+
const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
|
|
1449
|
+
const text = pending ? pendingLabel : error ? "Retry" : signinText(this.#attr("label") ?? "signin", brand);
|
|
879
1450
|
const subAttr = this.#attr("subtitle");
|
|
880
1451
|
const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
|
|
881
|
-
const subtitle = subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
882
|
-
const loading = this.hasAttribute("loading") || this.#busy;
|
|
1452
|
+
const subtitle = pending || error ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
1453
|
+
const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
|
|
883
1454
|
const disabled = this.hasAttribute("disabled");
|
|
884
1455
|
const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
|
|
885
|
-
const aria = this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
1456
|
+
const aria = pending ? pendingLabel : this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
886
1457
|
const getUrl = this.#attr("get-url") ?? GET_URL;
|
|
887
1458
|
const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
|
|
888
|
-
const
|
|
1459
|
+
const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
|
|
889
1460
|
const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
|
|
890
1461
|
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
|
|
891
|
-
const
|
|
892
|
-
|
|
1462
|
+
const otherOff = this.#attr("other-wallets") === "off";
|
|
1463
|
+
const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
|
|
1464
|
+
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>.${otherOff ? "" : ` ${otherLink}`}</p>` : "";
|
|
1465
|
+
const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
|
|
1466
|
+
const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
|
|
1467
|
+
const compact = size === "sm" || size === "icon";
|
|
1468
|
+
const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
|
|
1469
|
+
const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
|
|
1470
|
+
this.#root.innerHTML = `<style>${SIGNIN_CSS}</style>${err}<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>${status}${note}${ow}${nf}`;
|
|
893
1471
|
this.button.addEventListener("click", () => void this.#click());
|
|
1472
|
+
for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
|
|
1473
|
+
bindPrewarm(this.button);
|
|
1474
|
+
if (pending && compact) this.#showTip(PENDING_NOTE);
|
|
1475
|
+
else this.#hideTip();
|
|
1476
|
+
}
|
|
1477
|
+
#tip = null;
|
|
1478
|
+
#showTip(text) {
|
|
1479
|
+
const b = this.button;
|
|
1480
|
+
if (!b || typeof document === "undefined") return;
|
|
1481
|
+
if (!this.#tip) {
|
|
1482
|
+
const host = document.createElement("div");
|
|
1483
|
+
host.setAttribute("data-bwalletx-tip", "");
|
|
1484
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1485
|
+
root.innerHTML = `<style>${MENU_CSS}</style><div class="tip" role="note"></div>`;
|
|
1486
|
+
document.body.appendChild(host);
|
|
1487
|
+
this.#tip = host;
|
|
1488
|
+
}
|
|
1489
|
+
const tip2 = this.#tip.shadowRoot.querySelector(".tip");
|
|
1490
|
+
tip2.textContent = text;
|
|
1491
|
+
const r = b.getBoundingClientRect();
|
|
1492
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1493
|
+
const width = Math.min(280, vw - 32);
|
|
1494
|
+
tip2.style.width = `${width}px`;
|
|
1495
|
+
tip2.style.left = `${Math.max(16, Math.min(r.right - width, vw - width - 16))}px`;
|
|
1496
|
+
tip2.style.top = `${r.bottom + 8}px`;
|
|
1497
|
+
}
|
|
1498
|
+
#hideTip() {
|
|
1499
|
+
this.#tip?.remove();
|
|
1500
|
+
this.#tip = null;
|
|
1501
|
+
}
|
|
1502
|
+
#openMenu() {
|
|
1503
|
+
const acct = this.account;
|
|
1504
|
+
const chip = this.button;
|
|
1505
|
+
if (!acct || !chip || typeof document === "undefined") return;
|
|
1506
|
+
const name = accountLabel(acct);
|
|
1507
|
+
const host = document.createElement("div");
|
|
1508
|
+
host.setAttribute("data-bwalletx-menu", "");
|
|
1509
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1510
|
+
const style = document.createElement("style");
|
|
1511
|
+
style.textContent = MENU_CSS;
|
|
1512
|
+
const menu = document.createElement("div");
|
|
1513
|
+
menu.className = "menu";
|
|
1514
|
+
menu.setAttribute("role", "menu");
|
|
1515
|
+
menu.setAttribute("aria-label", "bWalletX account");
|
|
1516
|
+
const who = document.createElement("div");
|
|
1517
|
+
who.className = "who";
|
|
1518
|
+
who.textContent = `Signed in as ${acct.paymail || name}`;
|
|
1519
|
+
const item = (text, run) => {
|
|
1520
|
+
const b = document.createElement("button");
|
|
1521
|
+
b.type = "button";
|
|
1522
|
+
b.className = "item";
|
|
1523
|
+
b.setAttribute("role", "menuitem");
|
|
1524
|
+
b.tabIndex = -1;
|
|
1525
|
+
b.textContent = text;
|
|
1526
|
+
b.addEventListener("click", () => run(b));
|
|
1527
|
+
return b;
|
|
1528
|
+
};
|
|
1529
|
+
const copyWhat = acct.paymail ? "paymail" : "key";
|
|
1530
|
+
const copy = item(`Copy ${copyWhat}`, (b) => {
|
|
1531
|
+
void copyText(acct.paymail || acct.identityKey).then((ok) => {
|
|
1532
|
+
b.textContent = ok ? "Copied" : `Copy ${copyWhat}`;
|
|
1533
|
+
setTimeout(() => close(true), ok ? 600 : 0);
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
const out = item("Disconnect", () => {
|
|
1537
|
+
close(false);
|
|
1538
|
+
void this.signOut();
|
|
1539
|
+
});
|
|
1540
|
+
menu.append(who, copy, out);
|
|
1541
|
+
root.append(style, menu);
|
|
1542
|
+
document.body.appendChild(host);
|
|
1543
|
+
const place = () => {
|
|
1544
|
+
const r = chip.getBoundingClientRect();
|
|
1545
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1546
|
+
const vh = window.innerHeight || document.documentElement.clientHeight || 640;
|
|
1547
|
+
const width = Math.min(260, vw - 32);
|
|
1548
|
+
menu.style.width = `${width}px`;
|
|
1549
|
+
const left = Math.max(16, Math.min(r.right - width, vw - width - 16));
|
|
1550
|
+
menu.style.left = `${left}px`;
|
|
1551
|
+
const h = menu.offsetHeight || 150;
|
|
1552
|
+
const below = r.bottom + 8;
|
|
1553
|
+
menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
|
|
1554
|
+
};
|
|
1555
|
+
place();
|
|
1556
|
+
const items = [copy, out];
|
|
1557
|
+
const focusAt = (i) => items[(i + items.length) % items.length].focus();
|
|
1558
|
+
const onKey = (e) => {
|
|
1559
|
+
const i = items.indexOf(root.activeElement);
|
|
1560
|
+
if (e.key === "Escape") {
|
|
1561
|
+
e.preventDefault();
|
|
1562
|
+
close(true);
|
|
1563
|
+
} else if (e.key === "ArrowDown") {
|
|
1564
|
+
e.preventDefault();
|
|
1565
|
+
focusAt(i + 1);
|
|
1566
|
+
} else if (e.key === "ArrowUp") {
|
|
1567
|
+
e.preventDefault();
|
|
1568
|
+
focusAt(i - 1);
|
|
1569
|
+
} else if (e.key === "Home") {
|
|
1570
|
+
e.preventDefault();
|
|
1571
|
+
focusAt(0);
|
|
1572
|
+
} else if (e.key === "End") {
|
|
1573
|
+
e.preventDefault();
|
|
1574
|
+
focusAt(items.length - 1);
|
|
1575
|
+
} else if (e.key === "Tab") close(false);
|
|
1576
|
+
};
|
|
1577
|
+
const onDown = (e) => {
|
|
1578
|
+
const path = e.composedPath?.() ?? [];
|
|
1579
|
+
if (!path.includes(host) && !path.includes(this)) close(false);
|
|
1580
|
+
};
|
|
1581
|
+
const onMove = () => close(false);
|
|
1582
|
+
menu.addEventListener("keydown", onKey);
|
|
1583
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
1584
|
+
window.addEventListener("resize", onMove);
|
|
1585
|
+
window.addEventListener("scroll", place, true);
|
|
1586
|
+
const close = (focusChip = false) => {
|
|
1587
|
+
if (this.#menu?.host !== host) return;
|
|
1588
|
+
this.#menu = null;
|
|
1589
|
+
menu.removeEventListener("keydown", onKey);
|
|
1590
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1591
|
+
window.removeEventListener("resize", onMove);
|
|
1592
|
+
window.removeEventListener("scroll", place, true);
|
|
1593
|
+
host.remove();
|
|
1594
|
+
this.button?.setAttribute("aria-expanded", "false");
|
|
1595
|
+
if (focusChip) this.button?.focus();
|
|
1596
|
+
};
|
|
1597
|
+
this.#menu = { host, close };
|
|
1598
|
+
chip.setAttribute("aria-expanded", "true");
|
|
1599
|
+
focusAt(0);
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Disconnect: emits bwalletx-disconnect (cancelable). Unless a listener cancels it (BwalletXProvider signs out
|
|
1603
|
+
* itself) or the element is driven by `state`, posts logout-url and shows signed out.
|
|
1604
|
+
*/
|
|
1605
|
+
async signOut() {
|
|
1606
|
+
this.#menu?.close(false);
|
|
1607
|
+
if (!this.#emit("bwalletx-disconnect", { element: this, account: this.account }, true)) return;
|
|
1608
|
+
if (this.#controlled) return;
|
|
1609
|
+
const url = this.#attr("logout-url");
|
|
1610
|
+
if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
1611
|
+
this.#account = null;
|
|
1612
|
+
this.#set("signed-out");
|
|
1613
|
+
this.#emit("bwalletx-signed-out");
|
|
1614
|
+
this.button?.focus();
|
|
894
1615
|
}
|
|
895
1616
|
#emit(type, detail, cancelable = false) {
|
|
896
1617
|
return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
|
|
897
1618
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
this.#
|
|
1619
|
+
/** "Other wallets" is offered under the button: other-wallets on, or auto at lg/md when this element signs in. */
|
|
1620
|
+
#otherWalletsShown(size) {
|
|
1621
|
+
const a = this.#attr("other-wallets");
|
|
1622
|
+
if (a === "off") return false;
|
|
1623
|
+
if (a === "on") return true;
|
|
1624
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
901
1625
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1626
|
+
/** Open the wallet chooser (the "Other wallets" link): phone QR, other browser wallets, desktop wallets. */
|
|
1627
|
+
otherWallets() {
|
|
1628
|
+
return this.#click("chooser");
|
|
1629
|
+
}
|
|
1630
|
+
#attempt(option, outcome, started) {
|
|
1631
|
+
this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Which wallet: bWalletX by its own marker goes straight to its sheet; otherwise the chooser ("Get bWalletX" first)
|
|
1635
|
+
* when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
|
|
1636
|
+
*/
|
|
1637
|
+
async #choose(mode, ms, pairing) {
|
|
1638
|
+
if (mode !== "chooser") {
|
|
1639
|
+
const f = await discoverBwalletX(ms);
|
|
1640
|
+
if (f) {
|
|
1641
|
+
const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
|
|
1642
|
+
return brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
|
|
1643
|
+
}
|
|
910
1644
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1645
|
+
const list = await discoverWallets({ ms: 50, pairing: () => pairing });
|
|
1646
|
+
if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
|
|
1647
|
+
return chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
|
|
1648
|
+
}
|
|
1649
|
+
async #click(mode = "signin") {
|
|
1650
|
+
if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
|
|
1651
|
+
if (!this.#emit("bwalletx-signin", { element: this, mode }, true)) return;
|
|
1652
|
+
if (mode === "signin") {
|
|
1653
|
+
const href = this.#attr("href");
|
|
1654
|
+
const authorizeUrl = this.#attr("authorize-url");
|
|
1655
|
+
if (authorizeUrl) {
|
|
1656
|
+
location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr("client-id"), redirectUri: this.#attr("redirect-uri"), scope: this.#attr("scope") }));
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
if (href) {
|
|
1660
|
+
location.assign(href);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
914
1663
|
}
|
|
915
1664
|
const challengeUrl = this.#attr("challenge-url");
|
|
916
1665
|
if (!challengeUrl) return;
|
|
1666
|
+
const own = this.#stateful && !this.#controlled;
|
|
1667
|
+
const prev = this.#phase === "signed-in" ? "signed-in" : "signed-out";
|
|
1668
|
+
const started = Date.now();
|
|
1669
|
+
const pairing = pairingModal();
|
|
1670
|
+
let option = null;
|
|
917
1671
|
this.#notFound = false;
|
|
918
|
-
this.#
|
|
1672
|
+
this.#busy = true;
|
|
1673
|
+
this.#pendingLabel = PENDING_LABEL;
|
|
1674
|
+
if (own) this.#set("pending");
|
|
1675
|
+
else this.#render();
|
|
919
1676
|
try {
|
|
920
|
-
const ms = Number(this.#attr("discover-ms")) ||
|
|
921
|
-
const
|
|
922
|
-
if (
|
|
1677
|
+
const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
|
|
1678
|
+
const picked = await this.#choose(mode, ms, pairing);
|
|
1679
|
+
if (picked === "not-found") {
|
|
923
1680
|
this.#notFound = true;
|
|
1681
|
+
if (own) this.#phase = "signed-out";
|
|
1682
|
+
this.#attempt(null, "not-found", started);
|
|
924
1683
|
this.#emit("bwalletx-not-found");
|
|
925
1684
|
if (this.#attr("require-token")) this.#emit("bwalletx-access-denied", { code: "not_installed", message: "No wallet found" });
|
|
926
1685
|
return;
|
|
927
1686
|
}
|
|
1687
|
+
if (!picked) {
|
|
1688
|
+
if (own) this.#phase = prev;
|
|
1689
|
+
this.#attempt(null, "cancelled", started);
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
option = picked;
|
|
1693
|
+
if (!option.isBwalletX) {
|
|
1694
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
1695
|
+
if (own) this.#render();
|
|
1696
|
+
}
|
|
1697
|
+
const cw = await option.connect({ identity: false });
|
|
1698
|
+
const wallet = cw.wallet;
|
|
1699
|
+
if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
|
|
1700
|
+
const sub = substrateOf(option);
|
|
1701
|
+
const method = sub === "extension" ? "extension" : sub === "in-app" || sub === "xdm" ? "in-app" : sub === "pairing" ? "pairing" : "wallet";
|
|
928
1702
|
const requireToken = this.#attr("require-token");
|
|
929
1703
|
if (requireToken) {
|
|
1704
|
+
if (own) this.#phase = "signed-out";
|
|
930
1705
|
const verifyUrl2 = this.#attr("verify-url");
|
|
931
1706
|
const min = this.#attr("min");
|
|
932
1707
|
const minBps = this.#attr("min-bps");
|
|
933
1708
|
const hint = { token: { id: requireToken, ...minBps ? { minBps: Number(minBps) } : { min: min ?? "1" } } };
|
|
934
1709
|
try {
|
|
935
1710
|
const a = await requestAccess({
|
|
936
|
-
wallet
|
|
1711
|
+
wallet,
|
|
937
1712
|
challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
|
|
938
1713
|
verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
|
|
939
1714
|
});
|
|
1715
|
+
this.#attempt(option, "signed-in", started);
|
|
940
1716
|
this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
|
|
941
1717
|
} catch (e) {
|
|
942
1718
|
const err = e instanceof AccessError ? e : new AccessError("bad_request", String(e));
|
|
1719
|
+
this.#attempt(option, outcomeOf(e), started);
|
|
943
1720
|
this.#emit("bwalletx-access-denied", { code: err.code, message: err.message, have: err.have, need: err.need });
|
|
944
1721
|
}
|
|
945
1722
|
return;
|
|
946
1723
|
}
|
|
947
|
-
const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson(challengeUrl, { identityKey }) });
|
|
948
1724
|
const verifyUrl = this.#attr("verify-url");
|
|
949
|
-
const
|
|
950
|
-
|
|
1725
|
+
const { result: r, verified } = await signInAndVerify({
|
|
1726
|
+
wallet,
|
|
1727
|
+
identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
|
|
1728
|
+
challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
|
|
1729
|
+
verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
|
|
1730
|
+
});
|
|
1731
|
+
if (verifyUrl) rememberIdentity(r.identityKey);
|
|
1732
|
+
const response = verifyUrl ? verified : null;
|
|
1733
|
+
if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
|
|
1734
|
+
this.#attempt(option, "signed-in", started);
|
|
1735
|
+
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
|
|
951
1736
|
} catch (error) {
|
|
952
|
-
|
|
1737
|
+
const outcome = outcomeOf(error);
|
|
1738
|
+
this.#attempt(option, outcome, started);
|
|
1739
|
+
const closedQr = outcome === "cancelled" && option?.kind === "pairing";
|
|
1740
|
+
if (own) {
|
|
1741
|
+
if (closedQr) this.#phase = prev;
|
|
1742
|
+
else {
|
|
1743
|
+
this.#error = signInErrorText(error);
|
|
1744
|
+
this.#phase = "error";
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (!closedQr) this.#emit("bwalletx-error", { error });
|
|
953
1748
|
} finally {
|
|
954
|
-
this.#
|
|
1749
|
+
this.#busy = false;
|
|
1750
|
+
try {
|
|
1751
|
+
pairing.close();
|
|
1752
|
+
} catch {
|
|
1753
|
+
}
|
|
1754
|
+
this.#render();
|
|
955
1755
|
}
|
|
956
1756
|
}
|
|
957
1757
|
};
|
|
@@ -983,7 +1783,21 @@ a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border
|
|
|
983
1783
|
.bwx-list{display:flex;flex-direction:column;gap:8px}
|
|
984
1784
|
.bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
|
|
985
1785
|
.bwx-opt img{width:24px;height:24px;border-radius:6px}
|
|
986
|
-
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f}
|
|
1786
|
+
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f;white-space:nowrap}
|
|
1787
|
+
.bwx-opt{min-height:48px;box-sizing:border-box}
|
|
1788
|
+
a.bwx-btn.bwx-opt{display:flex;text-align:left}
|
|
1789
|
+
.bwx-btn:focus{outline:none}
|
|
1790
|
+
.bwx-btn:focus-visible{outline:2px solid #e8c766;outline-offset:2px}
|
|
1791
|
+
.bwx-mk{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border-radius:50%;background:#000}
|
|
1792
|
+
.bwx-ico{display:inline-grid;place-items:center;flex:none;width:28px;height:28px;border-radius:8px;background:#2a2518;color:#e8c766;font-weight:700;font-size:13px}
|
|
1793
|
+
.bwx-opt img{width:28px;height:28px;border-radius:8px;flex:none}
|
|
1794
|
+
.bwx-txt{display:flex;flex-direction:column;min-width:0;gap:1px}
|
|
1795
|
+
.bwx-txt b{font-weight:600}
|
|
1796
|
+
.bwx-txt small{font-size:12px;color:#8d866f;font-weight:400}
|
|
1797
|
+
.bwx-primary .bwx-txt small{color:rgba(0,0,0,.68)}
|
|
1798
|
+
.bwx-primary .bwx-tag{color:rgba(0,0,0,.6)}
|
|
1799
|
+
.bwx-sep{font-size:12px;color:#8d866f;margin:6px 2px 0}
|
|
1800
|
+
.bwx-foot{font-size:12px;color:#8d866f;margin:12px 0 0}
|
|
987
1801
|
`;
|
|
988
1802
|
function mount() {
|
|
989
1803
|
const root = document.createElement("div");
|
|
@@ -1112,105 +1926,94 @@ function pickWallet(options, o = {}) {
|
|
|
1112
1926
|
ui.box.lastChild.append(cancel);
|
|
1113
1927
|
});
|
|
1114
1928
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
kind: "brc100",
|
|
1131
|
-
isBwalletX: info.isBwalletX ?? false,
|
|
1132
|
-
connect: () => brc100Connected(wallet, opt)
|
|
1133
|
-
};
|
|
1134
|
-
return opt;
|
|
1135
|
-
}
|
|
1136
|
-
function pairingOption(pairing) {
|
|
1137
|
-
const opt = {
|
|
1138
|
-
id: "bwalletx-pairing",
|
|
1139
|
-
name: "bWalletX on your phone",
|
|
1140
|
-
kind: "pairing",
|
|
1141
|
-
isBwalletX: true,
|
|
1142
|
-
async connect() {
|
|
1143
|
-
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
1144
|
-
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
1145
|
-
return brc100Connected(w, opt);
|
|
1146
|
-
}
|
|
1147
|
-
};
|
|
1148
|
-
return opt;
|
|
1149
|
-
}
|
|
1150
|
-
function handcashAdapter(o) {
|
|
1151
|
-
const opt = {
|
|
1152
|
-
id: "handcash",
|
|
1153
|
-
name: "HandCash",
|
|
1154
|
-
icon: o.icon,
|
|
1155
|
-
kind: "handcash",
|
|
1156
|
-
isBwalletX: false,
|
|
1157
|
-
async connect() {
|
|
1158
|
-
const p = await o.login();
|
|
1159
|
-
return {
|
|
1160
|
-
wallet: null,
|
|
1161
|
-
payer: o.pay ? { pay: o.pay } : null,
|
|
1162
|
-
identityKey: p.identityKey ?? null,
|
|
1163
|
-
handle: p.handle,
|
|
1164
|
-
paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
|
|
1165
|
-
optionId: opt.id,
|
|
1166
|
-
kind: "handcash",
|
|
1167
|
-
transitional: true
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
};
|
|
1171
|
-
return opt;
|
|
1172
|
-
}
|
|
1173
|
-
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
1174
|
-
async function probe(substrate, ms) {
|
|
1175
|
-
try {
|
|
1176
|
-
const w = new import_sdk5.WalletClient(substrate);
|
|
1177
|
-
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
1178
|
-
return ok ? w : null;
|
|
1179
|
-
} catch {
|
|
1180
|
-
return null;
|
|
1929
|
+
function optionRow(w, primary, sub, tag) {
|
|
1930
|
+
const b = el("button", `bwx-btn bwx-opt${primary ? " bwx-primary" : ""}`);
|
|
1931
|
+
b.type = "button";
|
|
1932
|
+
b.dataset.wallet = w.id;
|
|
1933
|
+
if (w.isBwalletX) {
|
|
1934
|
+
const mk = el("span", "bwx-mk");
|
|
1935
|
+
mk.innerHTML = markSvg(18);
|
|
1936
|
+
b.append(mk);
|
|
1937
|
+
} else if (w.icon) {
|
|
1938
|
+
const img = el("img");
|
|
1939
|
+
img.src = w.icon;
|
|
1940
|
+
img.alt = "";
|
|
1941
|
+
b.append(img);
|
|
1942
|
+
} else {
|
|
1943
|
+
b.append(el("span", "bwx-ico", (w.name.trim().charAt(0) || "W").toUpperCase()));
|
|
1181
1944
|
}
|
|
1945
|
+
const t = el("span", "bwx-txt");
|
|
1946
|
+
t.append(el("b", "", w.name));
|
|
1947
|
+
if (sub) t.append(el("small", "", sub));
|
|
1948
|
+
b.append(t);
|
|
1949
|
+
if (tag) b.append(el("span", "bwx-tag", tag));
|
|
1950
|
+
return b;
|
|
1182
1951
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
if (seen.has(rdns)) return;
|
|
1193
|
-
const isB = BWALLETX_RDNS.includes(rdns);
|
|
1194
|
-
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB }));
|
|
1952
|
+
function chooseWallet(options, o = {}) {
|
|
1953
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
1954
|
+
return new Promise((resolve) => {
|
|
1955
|
+
const ui = mount();
|
|
1956
|
+
ui.root.dataset.bwxChooser = "";
|
|
1957
|
+
const done = (w) => {
|
|
1958
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1959
|
+
ui.close();
|
|
1960
|
+
resolve(w);
|
|
1195
1961
|
};
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1962
|
+
const onKey = (e) => {
|
|
1963
|
+
if (e.key === "Escape") {
|
|
1964
|
+
e.preventDefault();
|
|
1965
|
+
done(null);
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
document.addEventListener("keydown", onKey, true);
|
|
1969
|
+
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
1970
|
+
const list = el("div", "bwx-list");
|
|
1971
|
+
const here = hasBwalletX(options);
|
|
1972
|
+
if (!here) {
|
|
1973
|
+
const get = el("a", "bwx-btn bwx-opt bwx-primary");
|
|
1974
|
+
get.href = o.getUrl ?? GET_URL;
|
|
1975
|
+
get.target = "_blank";
|
|
1976
|
+
get.rel = "noopener";
|
|
1977
|
+
get.dataset.wallet = "get-bwalletx";
|
|
1978
|
+
const mk = el("span", "bwx-mk");
|
|
1979
|
+
mk.innerHTML = markSvg(18);
|
|
1980
|
+
const t = el("span", "bwx-txt");
|
|
1981
|
+
t.append(el("b", "", "Get bWalletX"), el("small", "", "Free. Every feature on this site"));
|
|
1982
|
+
get.append(mk, t);
|
|
1983
|
+
list.append(get);
|
|
1205
1984
|
}
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1985
|
+
const ordered = chooserOrder(options);
|
|
1986
|
+
const touch = isTouchDevice();
|
|
1987
|
+
let sep = false;
|
|
1988
|
+
for (const w of ordered) {
|
|
1989
|
+
if (!here && !sep) {
|
|
1990
|
+
list.append(el("p", "bwx-sep", "Or use"));
|
|
1991
|
+
sep = true;
|
|
1992
|
+
}
|
|
1993
|
+
const sub = isPresentBwalletX(w) ? "Recommended" : w.kind === "pairing" ? touch ? "Open the bWalletX app" : "Scan a QR with your phone" : void 0;
|
|
1994
|
+
const tag = w.kind === "handcash" ? "until BRC-100" : !w.isBwalletX && w.kind === "brc100" ? "Fewer features" : void 0;
|
|
1995
|
+
const row2 = optionRow(w, isPresentBwalletX(w), sub, tag);
|
|
1996
|
+
row2.onclick = () => done(w);
|
|
1997
|
+
list.append(row2);
|
|
1998
|
+
}
|
|
1999
|
+
if (o.desktop !== false && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
|
|
2000
|
+
if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
|
|
2001
|
+
const d = desktopOption();
|
|
2002
|
+
const row2 = optionRow(d, false, "Metanet or BSV Desktop on this computer", "Fewer features");
|
|
2003
|
+
row2.onclick = () => done(d);
|
|
2004
|
+
list.append(row2);
|
|
2005
|
+
}
|
|
2006
|
+
ui.box.append(list);
|
|
2007
|
+
if (otherDetected(options).length || !here) ui.box.append(el("p", "bwx-foot", "Some features need bWalletX."));
|
|
2008
|
+
const row = el("div", "bwx-row");
|
|
2009
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
2010
|
+
cancel.type = "button";
|
|
2011
|
+
cancel.onclick = () => done(null);
|
|
2012
|
+
row.append(cancel);
|
|
2013
|
+
ui.box.append(row);
|
|
2014
|
+
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
2015
|
+
list.querySelector("a,button")?.focus();
|
|
2016
|
+
});
|
|
1214
2017
|
}
|
|
1215
2018
|
|
|
1216
2019
|
// src/pay.ts
|
|
@@ -1293,11 +2096,11 @@ async function resolvePaymailP2P(paymail, sats, f = fetch) {
|
|
|
1293
2096
|
return {
|
|
1294
2097
|
outputs: body.outputs.map((o) => ({ script: o.script, satoshis: o.satoshis })),
|
|
1295
2098
|
reference: body.reference,
|
|
1296
|
-
submit: receive ? async (
|
|
2099
|
+
submit: receive ? async (hex2) => {
|
|
1297
2100
|
await f(fill(receive), {
|
|
1298
2101
|
method: "POST",
|
|
1299
2102
|
headers: { "content-type": "application/json" },
|
|
1300
|
-
body: JSON.stringify({ hex, reference: body.reference, metadata: {} })
|
|
2103
|
+
body: JSON.stringify({ hex: hex2, reference: body.reference, metadata: {} })
|
|
1301
2104
|
});
|
|
1302
2105
|
} : void 0
|
|
1303
2106
|
};
|
|
@@ -1375,19 +2178,52 @@ async function buyToken(source, req) {
|
|
|
1375
2178
|
}
|
|
1376
2179
|
|
|
1377
2180
|
// src/react.ts
|
|
2181
|
+
function signinStateFor(v) {
|
|
2182
|
+
if (v.status === "restoring") return "restoring";
|
|
2183
|
+
if (v.status === "connecting") return "pending";
|
|
2184
|
+
if (v.status === "error") return "error";
|
|
2185
|
+
if (v.status === "connected" && v.identityKey) return "signed-in";
|
|
2186
|
+
return "signed-out";
|
|
2187
|
+
}
|
|
1378
2188
|
function BwalletxSignin(props) {
|
|
1379
2189
|
const ref = (0, import_react.useRef)(null);
|
|
1380
2190
|
const cb = (0, import_react.useRef)(props);
|
|
1381
2191
|
cb.current = props;
|
|
2192
|
+
const ctx = (0, import_react.useContext)(Ctx);
|
|
2193
|
+
const ctxRef = (0, import_react.useRef)(ctx);
|
|
2194
|
+
ctxRef.current = ctx;
|
|
2195
|
+
const driven = !!ctx && props.stateful !== false;
|
|
1382
2196
|
(0, import_react.useEffect)(() => {
|
|
1383
2197
|
defineBwalletxSignin();
|
|
1384
2198
|
const el2 = ref.current;
|
|
1385
2199
|
if (!el2) return;
|
|
1386
2200
|
const on = [
|
|
1387
|
-
[
|
|
2201
|
+
[
|
|
2202
|
+
"bwalletx-signin",
|
|
2203
|
+
(e) => {
|
|
2204
|
+
cb.current.onClick?.(e);
|
|
2205
|
+
const c = ctxRef.current;
|
|
2206
|
+
if (!e.defaultPrevented && c && cb.current.stateful !== false) {
|
|
2207
|
+
e.preventDefault();
|
|
2208
|
+
const mode = e.detail?.mode;
|
|
2209
|
+
c.connect(mode === "chooser" ? { chooser: true } : {}).catch(() => void 0);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
],
|
|
1388
2213
|
["bwalletx-signed-in", (e) => cb.current.onSignedIn?.(e.detail)],
|
|
1389
2214
|
["bwalletx-not-found", () => cb.current.onNotFound?.()],
|
|
1390
|
-
["bwalletx-error", (e) => cb.current.onError?.(e.detail.error)]
|
|
2215
|
+
["bwalletx-error", (e) => cb.current.onError?.(e.detail.error)],
|
|
2216
|
+
[
|
|
2217
|
+
"bwalletx-disconnect",
|
|
2218
|
+
(e) => {
|
|
2219
|
+
const c = ctxRef.current;
|
|
2220
|
+
if (c && cb.current.stateful !== false) {
|
|
2221
|
+
e.preventDefault();
|
|
2222
|
+
void c.disconnect().then(() => cb.current.onSignedOut?.());
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
],
|
|
2226
|
+
["bwalletx-signed-out", () => cb.current.onSignedOut?.()]
|
|
1391
2227
|
];
|
|
1392
2228
|
for (const [t, f] of on) el2.addEventListener(t, f);
|
|
1393
2229
|
return () => {
|
|
@@ -1396,6 +2232,7 @@ function BwalletxSignin(props) {
|
|
|
1396
2232
|
}, []);
|
|
1397
2233
|
const p = props;
|
|
1398
2234
|
const flag = (v) => v ? "" : void 0;
|
|
2235
|
+
const state = driven ? signinStateFor(ctx) : void 0;
|
|
1399
2236
|
return (0, import_react.createElement)("bwalletx-signin", {
|
|
1400
2237
|
ref,
|
|
1401
2238
|
id: p.id,
|
|
@@ -1411,6 +2248,16 @@ function BwalletxSignin(props) {
|
|
|
1411
2248
|
disabled: flag(p.disabled),
|
|
1412
2249
|
"challenge-url": p.challengeUrl,
|
|
1413
2250
|
"verify-url": p.verifyUrl,
|
|
2251
|
+
"session-url": driven ? void 0 : p.sessionUrl,
|
|
2252
|
+
"logout-url": driven ? void 0 : p.logoutUrl,
|
|
2253
|
+
"lookup-url": p.lookupUrl,
|
|
2254
|
+
stateless: p.stateful === false ? "" : void 0,
|
|
2255
|
+
state,
|
|
2256
|
+
"identity-key": driven ? ctx.identityKey ?? void 0 : void 0,
|
|
2257
|
+
paymail: driven ? ctx.paymail ?? void 0 : void 0,
|
|
2258
|
+
handle: driven ? ctx.handle ?? void 0 : void 0,
|
|
2259
|
+
"avatar-url": driven ? avatarUrlFor(ctx.identityKey) ?? void 0 : void 0,
|
|
2260
|
+
error: driven && ctx.status === "error" ? signInErrorText(ctx.error) : void 0,
|
|
1414
2261
|
"get-url": p.getUrl,
|
|
1415
2262
|
"web-url": p.webUrl,
|
|
1416
2263
|
href: p.href,
|
|
@@ -1419,6 +2266,7 @@ function BwalletxSignin(props) {
|
|
|
1419
2266
|
"redirect-uri": p.redirectUri,
|
|
1420
2267
|
scope: p.scope,
|
|
1421
2268
|
"discover-ms": p.discoverMs,
|
|
2269
|
+
"other-wallets": p.otherWallets,
|
|
1422
2270
|
"aria-label": p["aria-label"]
|
|
1423
2271
|
});
|
|
1424
2272
|
}
|
|
@@ -1430,6 +2278,7 @@ var empty = {
|
|
|
1430
2278
|
paymail: null,
|
|
1431
2279
|
wallet: null,
|
|
1432
2280
|
walletId: null,
|
|
2281
|
+
walletType: null,
|
|
1433
2282
|
transitional: false,
|
|
1434
2283
|
signedIn: false,
|
|
1435
2284
|
error: null
|
|
@@ -1491,45 +2340,42 @@ function BwalletXProvider(p) {
|
|
|
1491
2340
|
setState((s) => ({ ...s, status: "connecting", error: null }));
|
|
1492
2341
|
try {
|
|
1493
2342
|
const list = await listWallets();
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
2343
|
+
const fast = c.fastPath !== false;
|
|
2344
|
+
const r = await runConnect({
|
|
2345
|
+
list,
|
|
2346
|
+
walletId: opts.walletId,
|
|
2347
|
+
chooser: opts.chooser,
|
|
2348
|
+
picker: c.picker,
|
|
2349
|
+
fastPath: fast,
|
|
2350
|
+
pick: fast ? (l) => chooseWallet(l) : pickWallet,
|
|
2351
|
+
onAttempt: c.onAttempt,
|
|
2352
|
+
challengeUrl: c.challengeUrl,
|
|
2353
|
+
verifyUrl: c.verifyUrl,
|
|
2354
|
+
challengeNeedsIdentityKey: c.challengeNeedsIdentityKey,
|
|
2355
|
+
post: postJson2
|
|
2356
|
+
});
|
|
2357
|
+
if (!r) {
|
|
2358
|
+
setState((s) => ({ ...s, status: s.identityKey && s.signedIn ? "connected" : "idle" }));
|
|
1502
2359
|
return null;
|
|
1503
2360
|
}
|
|
1504
|
-
const cw =
|
|
1505
|
-
let { handle = null, paymail = null } = cw;
|
|
1506
|
-
let signedIn = false;
|
|
1507
|
-
if (cw.wallet && c.challengeUrl && c.verifyUrl) {
|
|
1508
|
-
const r = await signInWithBwalletX({
|
|
1509
|
-
wallet: cw.wallet,
|
|
1510
|
-
challenge: (identityKey) => postJson2(c.challengeUrl, { identityKey })
|
|
1511
|
-
});
|
|
1512
|
-
const v = await atStage("verify", () => postJson2(c.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }));
|
|
1513
|
-
handle = v.handle ?? handle;
|
|
1514
|
-
paymail = v.paymail ?? paymail;
|
|
1515
|
-
signedIn = true;
|
|
1516
|
-
}
|
|
2361
|
+
const cw = r.connected;
|
|
1517
2362
|
payer.current = cw.payer;
|
|
1518
2363
|
setState({
|
|
1519
2364
|
status: "connected",
|
|
1520
|
-
identityKey:
|
|
1521
|
-
handle,
|
|
1522
|
-
paymail,
|
|
2365
|
+
identityKey: r.identityKey,
|
|
2366
|
+
handle: r.handle,
|
|
2367
|
+
paymail: r.paymail,
|
|
1523
2368
|
wallet: cw.wallet,
|
|
1524
2369
|
walletId: cw.optionId,
|
|
2370
|
+
walletType: r.walletType,
|
|
1525
2371
|
transitional: !!cw.transitional,
|
|
1526
|
-
signedIn,
|
|
2372
|
+
signedIn: r.signedIn,
|
|
1527
2373
|
error: null
|
|
1528
2374
|
});
|
|
1529
|
-
return cw;
|
|
2375
|
+
return { ...cw, identityKey: r.identityKey, handle: r.handle ?? void 0, paymail: r.paymail ?? void 0 };
|
|
1530
2376
|
} catch (e) {
|
|
1531
2377
|
if (e?.message === "cancelled") {
|
|
1532
|
-
setState((s) => ({ ...s, status: s.identityKey ? "connected" : "idle" }));
|
|
2378
|
+
setState((s) => ({ ...s, status: s.identityKey && s.signedIn ? "connected" : "idle" }));
|
|
1533
2379
|
return null;
|
|
1534
2380
|
}
|
|
1535
2381
|
throw fail(e);
|
|
@@ -1545,6 +2391,17 @@ function BwalletXProvider(p) {
|
|
|
1545
2391
|
payer.current = null;
|
|
1546
2392
|
setState(empty);
|
|
1547
2393
|
}, [state.walletId]);
|
|
2394
|
+
(0, import_react.useEffect)(() => {
|
|
2395
|
+
const key = state.identityKey;
|
|
2396
|
+
if (!key || state.paymail || p.lookupUrl === "off") return;
|
|
2397
|
+
let live = true;
|
|
2398
|
+
void lookupPaymail(key, p.lookupUrl).then((pm) => {
|
|
2399
|
+
if (live && pm) setState((s) => s.identityKey === key && !s.paymail ? { ...s, paymail: pm } : s);
|
|
2400
|
+
});
|
|
2401
|
+
return () => {
|
|
2402
|
+
live = false;
|
|
2403
|
+
};
|
|
2404
|
+
}, [state.identityKey, state.paymail, p.lookupUrl]);
|
|
1548
2405
|
const need = () => {
|
|
1549
2406
|
if (!payer.current) throw new PaymentError("NOT_SUPPORTED", "Connect a wallet first (connect()).");
|
|
1550
2407
|
return payer.current;
|
|
@@ -1569,6 +2426,24 @@ function useBwalletX() {
|
|
|
1569
2426
|
if (!v) throw new Error("useBwalletX() must be used inside <BwalletXProvider>");
|
|
1570
2427
|
return v;
|
|
1571
2428
|
}
|
|
2429
|
+
function accountFor(v) {
|
|
2430
|
+
const status = signinStateFor(v);
|
|
2431
|
+
const signedIn = status === "signed-in";
|
|
2432
|
+
return {
|
|
2433
|
+
status,
|
|
2434
|
+
identityKey: signedIn ? v.identityKey : null,
|
|
2435
|
+
paymail: signedIn ? v.paymail : null,
|
|
2436
|
+
handle: signedIn ? v.handle : null,
|
|
2437
|
+
label: signedIn ? accountLabel(v) : null,
|
|
2438
|
+
avatarUrl: signedIn ? avatarUrlFor(v.identityKey) : null,
|
|
2439
|
+
error: status === "error" ? signInErrorText(v.error) : null,
|
|
2440
|
+
login: () => v.connect().then(() => void 0, () => void 0),
|
|
2441
|
+
logout: () => v.disconnect()
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
function useBwalletXAccount() {
|
|
2445
|
+
return accountFor(useBwalletX());
|
|
2446
|
+
}
|
|
1572
2447
|
var BwalletxProvider = BwalletXProvider;
|
|
1573
2448
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1574
2449
|
0 && (module.exports = {
|
|
@@ -1576,10 +2451,14 @@ var BwalletxProvider = BwalletXProvider;
|
|
|
1576
2451
|
BwalletxProvider,
|
|
1577
2452
|
BwalletxSignin,
|
|
1578
2453
|
PaymentError,
|
|
2454
|
+
accountFor,
|
|
2455
|
+
autoChoice,
|
|
1579
2456
|
brc100Option,
|
|
1580
2457
|
discoverWallets,
|
|
1581
2458
|
handcashAdapter,
|
|
1582
2459
|
pairingOption,
|
|
1583
|
-
|
|
2460
|
+
signinStateFor,
|
|
2461
|
+
useBwalletX,
|
|
2462
|
+
useBwalletXAccount
|
|
1584
2463
|
});
|
|
1585
2464
|
//# sourceMappingURL=react.cjs.map
|