@bwalletx/connect 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +97 -9
- package/dist/bwalletx-connect.global.js +97 -25
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/choice-B97AjqF4.d.cts +442 -0
- package/dist/choice-B97AjqF4.d.ts +442 -0
- package/dist/chunk-6EFFOIHK.js +2441 -0
- package/dist/chunk-6EFFOIHK.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-DIKJFKND.js +239 -0
- package/dist/chunk-DIKJFKND.js.map +1 -0
- package/dist/chunk-TNRI7ARM.js +206 -0
- package/dist/chunk-TNRI7ARM.js.map +1 -0
- package/dist/element-BXEoOdXY.d.cts +53 -0
- package/dist/element-BXEoOdXY.d.ts +53 -0
- package/dist/element.cjs +1576 -60
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +26 -26
- package/dist/element.d.ts +26 -26
- package/dist/element.js +35 -2
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +2046 -454
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +323 -16
- package/dist/index.d.ts +323 -16
- package/dist/index.js +186 -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 +1601 -210
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +115 -9
- package/dist/react.d.ts +115 -9
- package/dist/react.js +262 -46
- 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/package.json +10 -9
- package/dist/chunk-EXRQJTXF.js +0 -910
- package/dist/chunk-EXRQJTXF.js.map +0 -1
- package/dist/chunk-QRXFL66W.js.map +0 -1
- package/dist/chunk-RIOHG5JH.js +0 -459
- package/dist/chunk-RIOHG5JH.js.map +0 -1
- package/dist/wallets-CbzMtxrf.d.cts +0 -250
- package/dist/wallets-CbzMtxrf.d.ts +0 -250
package/dist/element.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,19 +17,42 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/define.ts
|
|
21
31
|
var define_exports = {};
|
|
22
32
|
__export(define_exports, {
|
|
33
|
+
BUTTON_TAG: () => BUTTON_TAG,
|
|
34
|
+
CONNECTED_KEY: () => CONNECTED_KEY,
|
|
35
|
+
CONTINUE_OTHER_LABEL: () => CONTINUE_OTHER_LABEL,
|
|
23
36
|
GET_URL: () => GET_URL,
|
|
37
|
+
MENU_CSS: () => MENU_CSS,
|
|
38
|
+
PENDING_LABEL: () => PENDING_LABEL,
|
|
39
|
+
PENDING_LABEL_OTHER: () => PENDING_LABEL_OTHER,
|
|
40
|
+
PENDING_NOTE: () => PENDING_NOTE,
|
|
24
41
|
SIGNIN_CSS: () => SIGNIN_CSS,
|
|
25
42
|
SIGNIN_TAG: () => SIGNIN_TAG,
|
|
43
|
+
SWITCH_NOTE: () => SWITCH_NOTE,
|
|
26
44
|
WEB_WALLET_URL: () => WEB_WALLET_URL,
|
|
45
|
+
_resetBwalletxButtons: () => _resetBwalletxButtons,
|
|
27
46
|
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
47
|
+
defineBwalletxButton: () => defineBwalletxButton,
|
|
28
48
|
defineBwalletxSignin: () => defineBwalletxSignin,
|
|
49
|
+
dropLegacyConnectedKey: () => dropLegacyConnectedKey,
|
|
50
|
+
getUrlFor: () => getUrlFor,
|
|
51
|
+
handleLabel: () => handleLabel,
|
|
29
52
|
markSvg: () => markSvg,
|
|
30
|
-
|
|
53
|
+
migrateConnectedKey: () => migrateConnectedKey,
|
|
54
|
+
signinText: () => signinText,
|
|
55
|
+
stateFor: () => stateFor
|
|
31
56
|
});
|
|
32
57
|
module.exports = __toCommonJS(define_exports);
|
|
33
58
|
|
|
@@ -56,21 +81,16 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
|
56
81
|
finish({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
|
|
57
82
|
}
|
|
58
83
|
};
|
|
59
|
-
const cwiNow = () => {
|
|
60
|
-
const cwi = window.CWI;
|
|
61
|
-
if (cwi) finish({ wallet: cwi, method: "in-app", rdns: null });
|
|
62
|
-
return !!cwi;
|
|
63
|
-
};
|
|
64
84
|
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
65
85
|
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
66
|
-
if (done
|
|
86
|
+
if (done) return;
|
|
67
87
|
if (isFramed()) {
|
|
68
88
|
void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
|
|
69
89
|
return;
|
|
70
90
|
}
|
|
71
91
|
timer = setTimeout(() => {
|
|
72
92
|
timer = void 0;
|
|
73
|
-
|
|
93
|
+
finish(null);
|
|
74
94
|
}, ms);
|
|
75
95
|
});
|
|
76
96
|
}
|
|
@@ -135,13 +155,16 @@ function prewarmBwalletX() {
|
|
|
135
155
|
});
|
|
136
156
|
});
|
|
137
157
|
}
|
|
138
|
-
function bindPrewarm(
|
|
158
|
+
function bindPrewarm(el2) {
|
|
139
159
|
const on = () => prewarmBwalletX();
|
|
140
160
|
const evs = ["pointerenter", "focus", "touchstart"];
|
|
141
|
-
for (const e of evs)
|
|
142
|
-
return () => evs.forEach((e) =>
|
|
161
|
+
for (const e of evs) el2.addEventListener(e, on, { passive: true });
|
|
162
|
+
return () => evs.forEach((e) => el2.removeEventListener(e, on));
|
|
143
163
|
}
|
|
144
164
|
|
|
165
|
+
// src/wallets.ts
|
|
166
|
+
var import_sdk4 = require("@bsv/sdk");
|
|
167
|
+
|
|
145
168
|
// src/pair.ts
|
|
146
169
|
var import_sdk3 = require("@bsv/sdk");
|
|
147
170
|
|
|
@@ -519,6 +542,11 @@ async function restorePairing(opts = {}) {
|
|
|
519
542
|
sealer.restore({ sent: p.sent, lastSeen: p.lastSeen });
|
|
520
543
|
return new Link(p, sealer, storage, key, opts.socket ?? defaultSocket(), opts.callTimeoutMs ?? 12e4).wallet();
|
|
521
544
|
}
|
|
545
|
+
async function clearPairing(opts = {}) {
|
|
546
|
+
const storage = opts.storage === void 0 ? defaultStorage() : opts.storage ?? memoryStorage();
|
|
547
|
+
await Promise.resolve(storage.removeItem(opts.storageKey ?? DEFAULT_KEY)).catch(() => {
|
|
548
|
+
});
|
|
549
|
+
}
|
|
522
550
|
|
|
523
551
|
// src/signin.ts
|
|
524
552
|
var LOGIN_PROTOCOL = [2, "bwallet sign in"];
|
|
@@ -552,27 +580,68 @@ async function connectBwalletX(opts = {}) {
|
|
|
552
580
|
status("pairing");
|
|
553
581
|
return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
|
|
554
582
|
}
|
|
583
|
+
async function waitIfLocked(wallet) {
|
|
584
|
+
let ok = false;
|
|
585
|
+
try {
|
|
586
|
+
ok = !!(await wallet.isAuthenticated({}))?.authenticated;
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
if (!ok) await Promise.resolve().then(() => wallet.waitForAuthentication({})).catch(() => {
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function challengeWantsIdentityKey(e) {
|
|
593
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
594
|
+
return /identityKey/i.test(msg);
|
|
595
|
+
}
|
|
555
596
|
async function signInWithBwalletX(opts) {
|
|
556
597
|
const { wallet, method } = await connectBwalletX(opts);
|
|
557
|
-
if (method !== "pairing") await wallet
|
|
598
|
+
if (method !== "pairing" && !opts.skipWaitForAuthentication) await waitIfLocked(wallet);
|
|
599
|
+
const status = () => {
|
|
600
|
+
try {
|
|
601
|
+
opts.onStatus?.("signing");
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const sign = (c2) => wallet.createSignature({
|
|
606
|
+
protocolID: c2.protocolID ?? LOGIN_PROTOCOL,
|
|
607
|
+
keyID: c2.keyID,
|
|
608
|
+
counterparty: "anyone",
|
|
609
|
+
data: Array.from(new TextEncoder().encode(c2.message))
|
|
558
610
|
});
|
|
559
|
-
const
|
|
611
|
+
const keyFirst = async () => {
|
|
612
|
+
const { publicKey: identityKey2 } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
|
|
613
|
+
status();
|
|
614
|
+
const c2 = await atStage("challenge", () => opts.challenge(identityKey2));
|
|
615
|
+
const { signature: signature2 } = await atStage("signature", () => sign(c2));
|
|
616
|
+
return { identityKey: identityKey2, signature: Array.from(signature2), nonce: c2.nonce, method, wallet };
|
|
617
|
+
};
|
|
618
|
+
if (opts.challengeNeedsIdentityKey && !opts.identityKey) return keyFirst();
|
|
619
|
+
let c;
|
|
560
620
|
try {
|
|
561
|
-
|
|
562
|
-
} catch {
|
|
621
|
+
c = await atStage("challenge", () => opts.challenge(opts.identityKey));
|
|
622
|
+
} catch (e) {
|
|
623
|
+
if (!opts.identityKey && challengeWantsIdentityKey(e)) return keyFirst();
|
|
624
|
+
throw e;
|
|
563
625
|
}
|
|
564
|
-
|
|
565
|
-
const { signature } = await
|
|
566
|
-
"
|
|
567
|
-
() =>
|
|
568
|
-
|
|
569
|
-
keyID: c.keyID,
|
|
570
|
-
counterparty: "anyone",
|
|
571
|
-
data: Array.from(new TextEncoder().encode(c.message))
|
|
572
|
-
})
|
|
573
|
-
);
|
|
626
|
+
status();
|
|
627
|
+
const [{ publicKey: identityKey }, { signature }] = await Promise.all([
|
|
628
|
+
opts.identityKey ? Promise.resolve({ publicKey: opts.identityKey }) : atStage("identity", () => wallet.getPublicKey({ identityKey: true })),
|
|
629
|
+
atStage("signature", () => sign(c))
|
|
630
|
+
]);
|
|
574
631
|
return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
|
|
575
632
|
}
|
|
633
|
+
async function signInAndVerify(opts) {
|
|
634
|
+
const hinted = !opts.identityKey && !!opts.identityKeyHint;
|
|
635
|
+
const r = await signInWithBwalletX({ ...opts, identityKey: opts.identityKey ?? opts.identityKeyHint });
|
|
636
|
+
try {
|
|
637
|
+
return { result: r, verified: await atStage("verify", () => opts.verify(r)) };
|
|
638
|
+
} catch (e) {
|
|
639
|
+
const status = e?.status;
|
|
640
|
+
if (!hinted || status !== 401) throw e;
|
|
641
|
+
const again = await signInWithBwalletX({ ...opts, wallet: r.wallet, identityKey: void 0, skipWaitForAuthentication: true });
|
|
642
|
+
return { result: again, verified: await atStage("verify", () => opts.verify(again)) };
|
|
643
|
+
}
|
|
644
|
+
}
|
|
576
645
|
async function atStage(stage, run) {
|
|
577
646
|
try {
|
|
578
647
|
return await run();
|
|
@@ -583,8 +652,647 @@ async function atStage(stage, run) {
|
|
|
583
652
|
}
|
|
584
653
|
}
|
|
585
654
|
|
|
655
|
+
// src/wallets.ts
|
|
656
|
+
async function brc100Connected(wallet, o, c = {}) {
|
|
657
|
+
if (c.identity === false) return { wallet, payer: wallet, identityKey: null, optionId: o.id, kind: o.kind };
|
|
658
|
+
await waitIfLocked(wallet);
|
|
659
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
660
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
|
|
661
|
+
}
|
|
662
|
+
function brc100Option(wallet, info) {
|
|
663
|
+
const opt = {
|
|
664
|
+
id: info.id,
|
|
665
|
+
name: info.name,
|
|
666
|
+
icon: info.icon,
|
|
667
|
+
rdns: info.rdns,
|
|
668
|
+
kind: "brc100",
|
|
669
|
+
isBwalletX: info.isBwalletX ?? false,
|
|
670
|
+
substrate: info.substrate,
|
|
671
|
+
wallet,
|
|
672
|
+
connect: (c) => brc100Connected(wallet, opt, c)
|
|
673
|
+
};
|
|
674
|
+
return opt;
|
|
675
|
+
}
|
|
676
|
+
function pairingOption(pairing) {
|
|
677
|
+
const opt = {
|
|
678
|
+
id: "bwalletx-pairing",
|
|
679
|
+
name: "bWalletX on your phone",
|
|
680
|
+
kind: "pairing",
|
|
681
|
+
isBwalletX: true,
|
|
682
|
+
substrate: "pairing",
|
|
683
|
+
async connect(c) {
|
|
684
|
+
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
685
|
+
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
686
|
+
return brc100Connected(w, opt, c);
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
return opt;
|
|
690
|
+
}
|
|
691
|
+
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
692
|
+
async function probe(substrate, ms) {
|
|
693
|
+
try {
|
|
694
|
+
const w = new import_sdk4.WalletClient(substrate);
|
|
695
|
+
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
696
|
+
return ok ? w : null;
|
|
697
|
+
} catch {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function cwiIsAnnounced(cwi, announced) {
|
|
702
|
+
return announced.some((o) => o.wallet === cwi || o.rdns === RDNS_MOBILE);
|
|
703
|
+
}
|
|
704
|
+
async function discoverWallets(o = {}) {
|
|
705
|
+
const out = [];
|
|
706
|
+
const ms = o.ms ?? 300;
|
|
707
|
+
if (typeof window !== "undefined") {
|
|
708
|
+
const seen = /* @__PURE__ */ new Map();
|
|
709
|
+
const onAnnounce = (e) => {
|
|
710
|
+
const d = e.detail;
|
|
711
|
+
if (!d?.wallet) return;
|
|
712
|
+
const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
|
|
713
|
+
if (seen.has(rdns)) return;
|
|
714
|
+
const isB = BWALLETX_RDNS.includes(rdns);
|
|
715
|
+
const substrate = rdns === RDNS_EXTENSION ? "extension" : rdns === RDNS_MOBILE ? "in-app" : "injected";
|
|
716
|
+
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB, substrate }));
|
|
717
|
+
};
|
|
718
|
+
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
719
|
+
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
720
|
+
const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
|
|
721
|
+
await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
|
|
722
|
+
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
723
|
+
out.push(...seen.values());
|
|
724
|
+
if (![...seen.values()].some((w) => w.isBwalletX) && isFramed()) {
|
|
725
|
+
const x = await probeXdm(Math.max(ms, 1200));
|
|
726
|
+
if (x) out.push(brc100Option(x, { id: "bwalletx-xdm", name: "bWalletX", isBwalletX: true, substrate: "xdm" }));
|
|
727
|
+
}
|
|
728
|
+
const cwi = window.CWI;
|
|
729
|
+
if (cwi && !cwiIsAnnounced(cwi, [...seen.values()])) {
|
|
730
|
+
out.push(brc100Option(cwi, { id: "window.CWI", name: seen.size ? "Another browser wallet" : "Browser wallet", isBwalletX: false, substrate: "window.CWI" }));
|
|
731
|
+
}
|
|
732
|
+
const [json, cicada] = await local;
|
|
733
|
+
if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)", substrate: "json-api" }));
|
|
734
|
+
if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)", substrate: "cicada" }));
|
|
735
|
+
}
|
|
736
|
+
if (o.pairing) out.push(pairingOption(o.pairing));
|
|
737
|
+
out.push(...o.extra ?? []);
|
|
738
|
+
const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
|
|
739
|
+
return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
|
|
740
|
+
}
|
|
741
|
+
var DESKTOP_OPTION_ID = "desktop";
|
|
742
|
+
function isLocalDesktop(w) {
|
|
743
|
+
return w.substrate === "json-api" || w.substrate === "cicada" || w.id === "localhost:3321" || w.id === "localhost:3301" || w.id === DESKTOP_OPTION_ID;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/account.ts
|
|
747
|
+
var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
748
|
+
var BAVATAR_URL = "https://chat.bwalletx.com/api/bavatar/";
|
|
749
|
+
function shortKey(key) {
|
|
750
|
+
return key.length > 12 ? `${key.slice(0, 6)}\u2026${key.slice(-4)}` : key;
|
|
751
|
+
}
|
|
752
|
+
function accountLabel(a) {
|
|
753
|
+
if (a.paymail && /@bwalletx\.com$/i.test(a.paymail)) return "$" + a.paymail.split("@")[0];
|
|
754
|
+
if (a.handle) return a.handle;
|
|
755
|
+
if (a.paymail) return a.paymail;
|
|
756
|
+
return a.identityKey ? shortKey(a.identityKey) : "";
|
|
757
|
+
}
|
|
758
|
+
function avatarUrlFor(identityKey) {
|
|
759
|
+
return identityKey && /^0[23][0-9a-fA-F]{64}$/.test(identityKey) ? `${BAVATAR_URL}${identityKey.toLowerCase()}.png` : null;
|
|
760
|
+
}
|
|
761
|
+
var cache = /* @__PURE__ */ new Map();
|
|
762
|
+
function lookupPaymail(identityKey, url = PAYMAIL_LOOKUP_URL) {
|
|
763
|
+
if (url === "off" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
764
|
+
const k = `${url}|${identityKey}`;
|
|
765
|
+
let p = cache.get(k);
|
|
766
|
+
if (!p) {
|
|
767
|
+
p = fetch(url + encodeURIComponent(identityKey)).then((r) => r.ok ? r.json() : null).then((j) => j && typeof j.paymail === "string" ? j.paymail : null).catch(() => null);
|
|
768
|
+
cache.set(k, p);
|
|
769
|
+
}
|
|
770
|
+
return p;
|
|
771
|
+
}
|
|
772
|
+
function isUserRejection(e) {
|
|
773
|
+
const code = e?.code;
|
|
774
|
+
if (code === "ERR_PERMISSION_DENIED" || code === 4001) return true;
|
|
775
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
776
|
+
return /denied|reject|cancel|declin|refused|not now|user closed/i.test(msg);
|
|
777
|
+
}
|
|
778
|
+
function signInErrorText(e) {
|
|
779
|
+
if (isUserRejection(e)) return "Sign-in cancelled";
|
|
780
|
+
if (e?.name === "MetanetUnreachableError") return e.message;
|
|
781
|
+
const code = e?.code;
|
|
782
|
+
if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
|
|
783
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
784
|
+
if (/lock/i.test(msg)) return "Your wallet is locked. Unlock it and try again";
|
|
785
|
+
if (/timeout|timed out|expired/i.test(msg)) return "The wallet did not answer in time";
|
|
786
|
+
if (/origin/i.test(msg)) return "This page is not on the site\u2019s configured origin";
|
|
787
|
+
return "Sign-in failed";
|
|
788
|
+
}
|
|
789
|
+
async function copyText(text) {
|
|
790
|
+
try {
|
|
791
|
+
await navigator.clipboard.writeText(text);
|
|
792
|
+
return true;
|
|
793
|
+
} catch {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
var IDENTITY_HINT_KEY = "bwx:identityKey";
|
|
798
|
+
function rememberedIdentity() {
|
|
799
|
+
try {
|
|
800
|
+
const k = globalThis.localStorage?.getItem(IDENTITY_HINT_KEY) ?? "";
|
|
801
|
+
return /^0[23][0-9a-f]{64}$/i.test(k) ? k : void 0;
|
|
802
|
+
} catch {
|
|
803
|
+
return void 0;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function rememberIdentity(key) {
|
|
807
|
+
try {
|
|
808
|
+
if (key) globalThis.localStorage?.setItem(IDENTITY_HINT_KEY, key);
|
|
809
|
+
else globalThis.localStorage?.removeItem(IDENTITY_HINT_KEY);
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/metanet.ts
|
|
815
|
+
var import_sdk5 = require("@bsv/sdk");
|
|
816
|
+
|
|
817
|
+
// src/device.ts
|
|
818
|
+
function isTouchDevice(env = globalThis) {
|
|
819
|
+
const nav = env.navigator;
|
|
820
|
+
if (!nav) return false;
|
|
821
|
+
const ua = nav.userAgent ?? "";
|
|
822
|
+
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
823
|
+
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
824
|
+
try {
|
|
825
|
+
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
826
|
+
} catch {
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/metanet.ts
|
|
832
|
+
var METANET_TYPE = "metanet";
|
|
833
|
+
var METANET_OPTION_ID = METANET_TYPE;
|
|
834
|
+
var METANET_LABEL = "Use Metanet Desktop or another BRC-100 wallet";
|
|
835
|
+
var METANET_NOTE = "Metanet Desktop must be running on this computer";
|
|
836
|
+
var METANET_REMEMBERED_NOTE = "Signs in with Metanet Desktop on this computer.";
|
|
837
|
+
var NOT_YOU_LABEL = "Not you? Use another wallet";
|
|
838
|
+
var METANET_UNREACHABLE = "We couldn\u2019t reach Metanet Desktop. Open it, then try again.";
|
|
839
|
+
var METANET_ANSWER_MS = 4e3;
|
|
840
|
+
var METANET_SILENT_MS = 2e3;
|
|
841
|
+
var MetanetUnreachableError = class extends Error {
|
|
842
|
+
code = "NOT_FOUND";
|
|
843
|
+
constructor() {
|
|
844
|
+
super(METANET_UNREACHABLE);
|
|
845
|
+
this.name = "MetanetUnreachableError";
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
function inAnyFrame(win = typeof window === "undefined" ? void 0 : window) {
|
|
849
|
+
if (!win) return false;
|
|
850
|
+
try {
|
|
851
|
+
return win.top !== win.self;
|
|
852
|
+
} catch {
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function metanetOffered(env = {}) {
|
|
857
|
+
const win = env.window ?? (typeof window === "undefined" ? void 0 : window);
|
|
858
|
+
if (!win) return false;
|
|
859
|
+
const nav = env.navigator ?? (typeof navigator === "undefined" ? void 0 : navigator);
|
|
860
|
+
const mm = env.matchMedia ?? (typeof matchMedia === "undefined" ? void 0 : (q) => matchMedia(q));
|
|
861
|
+
return !isTouchDevice({ navigator: nav, matchMedia: mm }) && !inAnyFrame(win);
|
|
862
|
+
}
|
|
863
|
+
function client(o) {
|
|
864
|
+
if (o.make) return o.make();
|
|
865
|
+
const originator = o.originator ?? (typeof location !== "undefined" && location.host ? location.host : void 0);
|
|
866
|
+
return new import_sdk5.WalletClient("json-api", originator);
|
|
867
|
+
}
|
|
868
|
+
async function answers(wallet) {
|
|
869
|
+
return Promise.resolve().then(() => wallet.getVersion({})).then((v) => typeof v?.version === "string", () => false);
|
|
870
|
+
}
|
|
871
|
+
async function connectMetanetDesktop(o = {}) {
|
|
872
|
+
let wallet;
|
|
873
|
+
try {
|
|
874
|
+
wallet = client(o);
|
|
875
|
+
} catch {
|
|
876
|
+
throw new MetanetUnreachableError();
|
|
877
|
+
}
|
|
878
|
+
let timer;
|
|
879
|
+
const ok = await Promise.race([answers(wallet), new Promise((r) => timer = setTimeout(() => r(false), o.timeoutMs ?? METANET_ANSWER_MS))]);
|
|
880
|
+
clearTimeout(timer);
|
|
881
|
+
if (!ok) throw new MetanetUnreachableError();
|
|
882
|
+
return wallet;
|
|
883
|
+
}
|
|
884
|
+
function metanetOption(o = {}) {
|
|
885
|
+
const opt = {
|
|
886
|
+
id: METANET_OPTION_ID,
|
|
887
|
+
name: METANET_LABEL,
|
|
888
|
+
kind: "brc100",
|
|
889
|
+
isBwalletX: false,
|
|
890
|
+
substrate: "json-api",
|
|
891
|
+
async connect() {
|
|
892
|
+
const wallet = await connectMetanetDesktop(o);
|
|
893
|
+
opt.wallet = wallet;
|
|
894
|
+
await waitIfLocked(wallet);
|
|
895
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
896
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: opt.id, kind: "brc100" };
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
return opt;
|
|
900
|
+
}
|
|
901
|
+
var KNOWN_KEYS_KEY = "bwx:known-keys";
|
|
902
|
+
var KEY_RE = /^0[23][0-9a-f]{64}$/;
|
|
903
|
+
var MAX_KNOWN = 8;
|
|
904
|
+
function knownIdentityKeys() {
|
|
905
|
+
try {
|
|
906
|
+
const v = JSON.parse(globalThis.localStorage?.getItem(KNOWN_KEYS_KEY) || "[]");
|
|
907
|
+
return Array.isArray(v) ? v.filter((k) => typeof k === "string" && KEY_RE.test(k)) : [];
|
|
908
|
+
} catch {
|
|
909
|
+
return [];
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function isKnownIdentityKey(key) {
|
|
913
|
+
if (!key) return false;
|
|
914
|
+
const k = key.toLowerCase();
|
|
915
|
+
if (!KEY_RE.test(k)) return false;
|
|
916
|
+
return knownIdentityKeys().includes(k) || rememberedIdentity()?.toLowerCase() === k;
|
|
917
|
+
}
|
|
918
|
+
function rememberKnownIdentityKey(key) {
|
|
919
|
+
const k = (key ?? "").toLowerCase();
|
|
920
|
+
if (!KEY_RE.test(k)) return;
|
|
921
|
+
try {
|
|
922
|
+
const list = [k, ...knownIdentityKeys().filter((x) => x !== k)].slice(0, MAX_KNOWN);
|
|
923
|
+
globalThis.localStorage?.setItem(KNOWN_KEYS_KEY, JSON.stringify(list));
|
|
924
|
+
} catch {
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
var triedThisLoad = false;
|
|
928
|
+
function silentAttemptUsed() {
|
|
929
|
+
return triedThisLoad;
|
|
930
|
+
}
|
|
931
|
+
function claimSilentAttempt() {
|
|
932
|
+
if (triedThisLoad) return false;
|
|
933
|
+
triedThisLoad = true;
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
async function trySilentMetanet(o = {}) {
|
|
937
|
+
if (!metanetOffered()) return { ok: false, stop: "not-here" };
|
|
938
|
+
if (!claimSilentAttempt()) return { ok: false, stop: "used" };
|
|
939
|
+
const run = async () => {
|
|
940
|
+
let wallet;
|
|
941
|
+
try {
|
|
942
|
+
wallet = client(o);
|
|
943
|
+
} catch {
|
|
944
|
+
return { ok: false, stop: "unreachable" };
|
|
945
|
+
}
|
|
946
|
+
if (!await answers(wallet)) return { ok: false, stop: "unreachable" };
|
|
947
|
+
const unlocked = await Promise.resolve().then(() => wallet.isAuthenticated({})).then((r2) => r2?.authenticated === true, () => false);
|
|
948
|
+
if (!unlocked) return { ok: false, stop: "locked" };
|
|
949
|
+
const identityKey = await Promise.resolve().then(() => wallet.getPublicKey({ identityKey: true })).then((r2) => r2.publicKey, () => null);
|
|
950
|
+
if (!identityKey) return { ok: false, stop: "unreachable" };
|
|
951
|
+
if (!isKnownIdentityKey(identityKey)) return { ok: false, stop: "unknown-key" };
|
|
952
|
+
return { ok: true, wallet, identityKey };
|
|
953
|
+
};
|
|
954
|
+
let timer;
|
|
955
|
+
const r = await Promise.race([
|
|
956
|
+
run().catch(() => ({ ok: false, stop: "unreachable" })),
|
|
957
|
+
new Promise((res) => timer = setTimeout(() => res({ ok: false, stop: "slow" }), o.windowMs ?? METANET_SILENT_MS))
|
|
958
|
+
]);
|
|
959
|
+
clearTimeout(timer);
|
|
960
|
+
return r;
|
|
961
|
+
}
|
|
962
|
+
var warnedLogout = false;
|
|
963
|
+
var isProduction = () => {
|
|
964
|
+
try {
|
|
965
|
+
return globalThis.process?.env?.NODE_ENV === "production";
|
|
966
|
+
} catch {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
function warnIfNoLogout(o) {
|
|
971
|
+
if (!o.verifyUrl || o.logoutUrl || warnedLogout || isProduction() || typeof console === "undefined") return false;
|
|
972
|
+
warnedLogout = true;
|
|
973
|
+
console.warn(
|
|
974
|
+
`@bwalletx/connect: ${o.where} has a verify URL but no logout URL. Set it: "Not you? Use another wallet" and Disconnect must be able to end a session the verify route created.`
|
|
975
|
+
);
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/choice.ts
|
|
980
|
+
var BWALLETX_TYPE = "bwalletx";
|
|
981
|
+
function substrateOf(o) {
|
|
982
|
+
if (o.substrate) return o.substrate;
|
|
983
|
+
if (o.kind === "pairing") return "pairing";
|
|
984
|
+
if (o.isBwalletX && o.kind === "brc100") return o.rdns === RDNS_MOBILE ? "in-app" : o.id === "bwalletx-xdm" ? "xdm" : "extension";
|
|
985
|
+
if (o.kind === "handcash" || o.kind === "custom") return "adapter";
|
|
986
|
+
if (o.rdns === RDNS_EXTENSION) return "extension";
|
|
987
|
+
if (o.rdns === RDNS_MOBILE) return "in-app";
|
|
988
|
+
if (o.id === "window.CWI") return "window.CWI";
|
|
989
|
+
if (o.id === "localhost:3321" || o.id === DESKTOP_OPTION_ID) return "json-api";
|
|
990
|
+
if (o.id === "localhost:3301") return "cicada";
|
|
991
|
+
return "injected";
|
|
992
|
+
}
|
|
993
|
+
function walletTypeOf(o) {
|
|
994
|
+
return o.isBwalletX ? BWALLETX_TYPE : o.rdns ?? o.id;
|
|
995
|
+
}
|
|
996
|
+
var OWN_MARKERS = ["extension", "in-app", "xdm"];
|
|
997
|
+
function isPresentBwalletX(o) {
|
|
998
|
+
return o.isBwalletX && o.kind === "brc100" && !!o.wallet && OWN_MARKERS.includes(substrateOf(o));
|
|
999
|
+
}
|
|
1000
|
+
function hasBwalletX(list) {
|
|
1001
|
+
return list.some(isPresentBwalletX);
|
|
1002
|
+
}
|
|
1003
|
+
function otherDetected(list) {
|
|
1004
|
+
return list.filter((o) => !o.isBwalletX && o.kind === "brc100" && !!o.wallet);
|
|
1005
|
+
}
|
|
1006
|
+
function chooserOrder(list) {
|
|
1007
|
+
const rank = (o) => isPresentBwalletX(o) ? 0 : o.isBwalletX ? 1 : o.kind === "brc100" ? 2 : o.kind === "custom" ? 3 : 4;
|
|
1008
|
+
return list.map((o, i) => ({ o, i })).sort((a, b) => rank(a.o) - rank(b.o) || a.i - b.i).map((x) => x.o);
|
|
1009
|
+
}
|
|
1010
|
+
function outcomeOf(e) {
|
|
1011
|
+
const err = e;
|
|
1012
|
+
if (err?.code === "NOT_FOUND") return "not-found";
|
|
1013
|
+
if (err?.name === "AbortError" || err?.message === "cancelled") return "cancelled";
|
|
1014
|
+
if (isUserRejection(e)) return "rejected";
|
|
1015
|
+
return "error";
|
|
1016
|
+
}
|
|
1017
|
+
function attemptFor(option, outcome, startedAt, now = Date.now()) {
|
|
1018
|
+
return {
|
|
1019
|
+
walletType: option ? walletTypeOf(option) : null,
|
|
1020
|
+
substrate: option ? substrateOf(option) : null,
|
|
1021
|
+
outcome,
|
|
1022
|
+
ms: Math.max(0, Math.round(now - startedAt))
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
function reportAttempt(hook, a) {
|
|
1026
|
+
if (!hook) return;
|
|
1027
|
+
try {
|
|
1028
|
+
hook(Object.freeze({ walletType: a.walletType, substrate: a.substrate, outcome: a.outcome, ms: a.ms }));
|
|
1029
|
+
} catch {
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
var WALLET_CHOICE_KEY = "bwx:wallet";
|
|
1033
|
+
var SUBSTRATES = ["extension", "in-app", "xdm", "injected", "window.CWI", "json-api", "cicada", "pairing", "adapter"];
|
|
1034
|
+
var TYPE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
1035
|
+
var KEY_LIKE = /^(0[23][0-9a-f]{64}|04[0-9a-f]{128}|[13][1-9A-HJ-NP-Za-km-z]{25,34})$/i;
|
|
1036
|
+
function isRememberable(o) {
|
|
1037
|
+
const t = walletTypeOf(o);
|
|
1038
|
+
return substrateOf(o) !== "window.CWI" && TYPE_RE.test(t) && !KEY_LIKE.test(t);
|
|
1039
|
+
}
|
|
1040
|
+
function isExplicitPick(o, reason) {
|
|
1041
|
+
return o.isBwalletX || reason === "chooser" || reason === "requested" || reason === "remembered";
|
|
1042
|
+
}
|
|
1043
|
+
function rememberWalletChoice(o) {
|
|
1044
|
+
try {
|
|
1045
|
+
if (!o) return void globalThis.localStorage?.removeItem(WALLET_CHOICE_KEY);
|
|
1046
|
+
if (!isRememberable(o)) return;
|
|
1047
|
+
const v = { type: walletTypeOf(o), substrate: substrateOf(o), v: 1 };
|
|
1048
|
+
globalThis.localStorage?.setItem(WALLET_CHOICE_KEY, JSON.stringify(v));
|
|
1049
|
+
} catch {
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function rememberedWalletChoice() {
|
|
1053
|
+
try {
|
|
1054
|
+
const raw = globalThis.localStorage?.getItem(WALLET_CHOICE_KEY);
|
|
1055
|
+
if (!raw) return null;
|
|
1056
|
+
const j = JSON.parse(raw);
|
|
1057
|
+
if (typeof j.type !== "string" || !TYPE_RE.test(j.type) || KEY_LIKE.test(j.type)) return null;
|
|
1058
|
+
if (!SUBSTRATES.includes(j.substrate) || j.substrate === "window.CWI") return null;
|
|
1059
|
+
return { type: j.type, substrate: j.substrate };
|
|
1060
|
+
} catch {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function forgetWalletChoice() {
|
|
1065
|
+
rememberWalletChoice(null);
|
|
1066
|
+
}
|
|
1067
|
+
function needsBwalletX(type) {
|
|
1068
|
+
return !!type && type !== BWALLETX_TYPE;
|
|
1069
|
+
}
|
|
1070
|
+
function tapGoesElsewhere(type) {
|
|
1071
|
+
return needsBwalletX(type) && type !== METANET_TYPE && type !== DESKTOP_OPTION_ID;
|
|
1072
|
+
}
|
|
1073
|
+
function isLocalDesktopType(r) {
|
|
1074
|
+
return r.type === METANET_TYPE || r.type === DESKTOP_OPTION_ID || r.substrate === "json-api" || r.substrate === "cicada";
|
|
1075
|
+
}
|
|
1076
|
+
function matchRemembered(list, r) {
|
|
1077
|
+
if (isLocalDesktopType(r)) return null;
|
|
1078
|
+
if (r.type === BWALLETX_TYPE) {
|
|
1079
|
+
return (r.substrate === "pairing" ? list.find((o) => o.kind === "pairing") : list.find(isPresentBwalletX)) ?? null;
|
|
1080
|
+
}
|
|
1081
|
+
return list.find((o) => !o.isBwalletX && !isLocalDesktop(o) && isRememberable(o) && walletTypeOf(o) === r.type) ?? null;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/modal.ts
|
|
1085
|
+
var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
|
|
1086
|
+
var CSS = `
|
|
1087
|
+
.bwx-ov{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;padding:16px;font:15px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
|
|
1088
|
+
.bwx-box{background:#0b0b0b;color:#f4f1e8;border:1px solid #3a3220;border-radius:16px;width:min(360px,100%);padding:20px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
|
|
1089
|
+
.bwx-box h2{margin:0 0 6px;font-size:18px;color:#e8c766}
|
|
1090
|
+
.bwx-box p{margin:0 0 14px;color:#bdb6a3;font-size:14px}
|
|
1091
|
+
.bwx-qr{background:#fff;border-radius:12px;padding:12px;display:flex;justify-content:center}
|
|
1092
|
+
.bwx-qr svg{width:100%;height:auto;max-width:260px}
|
|
1093
|
+
.bwx-code{margin:14px 0 0;text-align:center;font-size:28px;letter-spacing:.3em;color:#e8c766;font-variant-numeric:tabular-nums}
|
|
1094
|
+
.bwx-row{display:flex;gap:8px;margin-top:14px}
|
|
1095
|
+
.bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
|
|
1096
|
+
.bwx-btn:hover{border-color:#e8c766}
|
|
1097
|
+
a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
|
|
1098
|
+
.bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
|
|
1099
|
+
.bwx-stack{display:flex;flex-direction:column;gap:8px}
|
|
1100
|
+
.bwx-list{display:flex;flex-direction:column;gap:8px}
|
|
1101
|
+
.bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
|
|
1102
|
+
.bwx-opt img{width:24px;height:24px;border-radius:6px}
|
|
1103
|
+
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f;white-space:nowrap}
|
|
1104
|
+
.bwx-opt{min-height:48px;box-sizing:border-box}
|
|
1105
|
+
a.bwx-btn.bwx-opt{display:flex;text-align:left}
|
|
1106
|
+
.bwx-btn:focus{outline:none}
|
|
1107
|
+
.bwx-btn:focus-visible{outline:2px solid #e8c766;outline-offset:2px}
|
|
1108
|
+
.bwx-mk{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border-radius:50%;background:#000}
|
|
1109
|
+
.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}
|
|
1110
|
+
.bwx-opt img{width:28px;height:28px;border-radius:8px;flex:none}
|
|
1111
|
+
.bwx-txt{display:flex;flex-direction:column;min-width:0;gap:1px}
|
|
1112
|
+
.bwx-txt b{font-weight:600}
|
|
1113
|
+
.bwx-txt small{font-size:12px;color:#8d866f;font-weight:400}
|
|
1114
|
+
.bwx-primary .bwx-txt small{color:rgba(0,0,0,.68)}
|
|
1115
|
+
.bwx-primary .bwx-tag{color:rgba(0,0,0,.6)}
|
|
1116
|
+
.bwx-sep{font-size:12px;color:#8d866f;margin:6px 2px 0}
|
|
1117
|
+
.bwx-foot{font-size:12px;color:#8d866f;margin:12px 0 0}
|
|
1118
|
+
`;
|
|
1119
|
+
function mount() {
|
|
1120
|
+
const root = document.createElement("div");
|
|
1121
|
+
root.className = "bwx-ov";
|
|
1122
|
+
root.setAttribute("role", "dialog");
|
|
1123
|
+
root.setAttribute("aria-modal", "true");
|
|
1124
|
+
const style = document.createElement("style");
|
|
1125
|
+
style.textContent = CSS;
|
|
1126
|
+
const box = document.createElement("div");
|
|
1127
|
+
box.className = "bwx-box";
|
|
1128
|
+
root.append(style, box);
|
|
1129
|
+
document.body.appendChild(root);
|
|
1130
|
+
return { root, box, close: () => root.remove() };
|
|
1131
|
+
}
|
|
1132
|
+
function el(tag, cls, text) {
|
|
1133
|
+
const e = document.createElement(tag);
|
|
1134
|
+
if (cls) e.className = cls;
|
|
1135
|
+
if (text != null) e.textContent = text;
|
|
1136
|
+
return e;
|
|
1137
|
+
}
|
|
1138
|
+
function qrSvg(text) {
|
|
1139
|
+
const q = (0, import_qrcode_generator.default)(0, "M");
|
|
1140
|
+
q.addData(text);
|
|
1141
|
+
q.make();
|
|
1142
|
+
return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
|
1143
|
+
}
|
|
1144
|
+
function pairingModal(o = {}) {
|
|
1145
|
+
const ac = new AbortController();
|
|
1146
|
+
let ui = null;
|
|
1147
|
+
let codeEl = null;
|
|
1148
|
+
const close = () => {
|
|
1149
|
+
ui?.close();
|
|
1150
|
+
ui = null;
|
|
1151
|
+
};
|
|
1152
|
+
ac.signal.addEventListener("abort", close);
|
|
1153
|
+
return {
|
|
1154
|
+
signal: ac.signal,
|
|
1155
|
+
close,
|
|
1156
|
+
onLink(link) {
|
|
1157
|
+
if (typeof document === "undefined") return;
|
|
1158
|
+
close();
|
|
1159
|
+
ui = mount();
|
|
1160
|
+
const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
|
|
1161
|
+
const h = el("h2", "", o.title ?? "Connect bWalletX");
|
|
1162
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1163
|
+
cancel.onclick = () => {
|
|
1164
|
+
o.onCancel?.();
|
|
1165
|
+
ac.abort();
|
|
1166
|
+
};
|
|
1167
|
+
codeEl = el("div", "bwx-code", "");
|
|
1168
|
+
codeEl.setAttribute("aria-live", "polite");
|
|
1169
|
+
if (app) {
|
|
1170
|
+
const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
|
|
1171
|
+
const stack = el("div", "bwx-stack");
|
|
1172
|
+
const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
|
|
1173
|
+
open.href = link;
|
|
1174
|
+
open.rel = "noopener";
|
|
1175
|
+
const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
|
|
1176
|
+
web.onclick = () => {
|
|
1177
|
+
void navigator.clipboard?.writeText(link).catch(() => void 0);
|
|
1178
|
+
window.open(WEB_WALLET_URL, "_blank", "noopener");
|
|
1179
|
+
p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
|
|
1180
|
+
};
|
|
1181
|
+
stack.append(open, web, cancel);
|
|
1182
|
+
ui.box.append(h, p2, codeEl, stack);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
|
|
1186
|
+
const qr = el("div", "bwx-qr");
|
|
1187
|
+
qr.innerHTML = qrSvg(link);
|
|
1188
|
+
const row = el("div", "bwx-row");
|
|
1189
|
+
const copy = el("button", "bwx-btn", "Copy link");
|
|
1190
|
+
copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
|
|
1191
|
+
row.append(copy, cancel);
|
|
1192
|
+
ui.box.append(h, p, qr, codeEl, row);
|
|
1193
|
+
},
|
|
1194
|
+
onCode(code) {
|
|
1195
|
+
if (codeEl) {
|
|
1196
|
+
codeEl.textContent = code;
|
|
1197
|
+
const p = ui?.box.querySelector("p");
|
|
1198
|
+
if (p) p.textContent = "Check bWalletX shows the same code, then tap Connect.";
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
function optionRow(w, primary, sub, tag) {
|
|
1204
|
+
const b = el("button", `bwx-btn bwx-opt${primary ? " bwx-primary" : ""}`);
|
|
1205
|
+
b.type = "button";
|
|
1206
|
+
b.dataset.wallet = w.id;
|
|
1207
|
+
if (w.isBwalletX) {
|
|
1208
|
+
const mk = el("span", "bwx-mk");
|
|
1209
|
+
mk.innerHTML = markSvg(18);
|
|
1210
|
+
b.append(mk);
|
|
1211
|
+
} else if (w.icon) {
|
|
1212
|
+
const img = el("img");
|
|
1213
|
+
img.src = w.icon;
|
|
1214
|
+
img.alt = "";
|
|
1215
|
+
b.append(img);
|
|
1216
|
+
} else {
|
|
1217
|
+
b.append(el("span", "bwx-ico", (w.name.trim().charAt(0) || "W").toUpperCase()));
|
|
1218
|
+
}
|
|
1219
|
+
const t = el("span", "bwx-txt");
|
|
1220
|
+
t.append(el("b", "", w.name));
|
|
1221
|
+
if (sub) t.append(el("small", "", sub));
|
|
1222
|
+
b.append(t);
|
|
1223
|
+
if (tag) b.append(el("span", "bwx-tag", tag));
|
|
1224
|
+
return b;
|
|
1225
|
+
}
|
|
1226
|
+
function chooseWallet(options, o = {}) {
|
|
1227
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
1228
|
+
return new Promise((resolve) => {
|
|
1229
|
+
const ui = mount();
|
|
1230
|
+
ui.root.dataset.bwxChooser = "";
|
|
1231
|
+
const done = (w) => {
|
|
1232
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1233
|
+
ui.close();
|
|
1234
|
+
resolve(w);
|
|
1235
|
+
};
|
|
1236
|
+
const onKey = (e) => {
|
|
1237
|
+
if (e.key === "Escape") {
|
|
1238
|
+
e.preventDefault();
|
|
1239
|
+
done(null);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
document.addEventListener("keydown", onKey, true);
|
|
1243
|
+
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
1244
|
+
const list = el("div", "bwx-list");
|
|
1245
|
+
const here = hasBwalletX(options);
|
|
1246
|
+
if (!here) {
|
|
1247
|
+
const get = el("a", "bwx-btn bwx-opt bwx-primary");
|
|
1248
|
+
get.href = o.getUrl ?? GET_URL;
|
|
1249
|
+
get.target = "_blank";
|
|
1250
|
+
get.rel = "noopener";
|
|
1251
|
+
get.dataset.wallet = "get-bwalletx";
|
|
1252
|
+
const mk = el("span", "bwx-mk");
|
|
1253
|
+
mk.innerHTML = markSvg(18);
|
|
1254
|
+
const t = el("span", "bwx-txt");
|
|
1255
|
+
t.append(el("b", "", "Get bWalletX"), el("small", "", "Free. Every feature on this site"));
|
|
1256
|
+
get.append(mk, t);
|
|
1257
|
+
list.append(get);
|
|
1258
|
+
}
|
|
1259
|
+
const ordered = chooserOrder(options);
|
|
1260
|
+
const touch = isTouchDevice();
|
|
1261
|
+
let sep = false;
|
|
1262
|
+
for (const w of ordered) {
|
|
1263
|
+
if (!here && !sep) {
|
|
1264
|
+
list.append(el("p", "bwx-sep", "Or use"));
|
|
1265
|
+
sep = true;
|
|
1266
|
+
}
|
|
1267
|
+
const sub = isPresentBwalletX(w) ? "Recommended" : w.kind === "pairing" ? touch ? "Open the bWalletX app" : "Scan a QR with your phone" : void 0;
|
|
1268
|
+
const tag = w.kind === "handcash" ? "until BRC-100" : !w.isBwalletX && w.kind === "brc100" ? "Fewer features" : void 0;
|
|
1269
|
+
const row2 = optionRow(w, isPresentBwalletX(w), sub, tag);
|
|
1270
|
+
row2.onclick = () => done(w);
|
|
1271
|
+
list.append(row2);
|
|
1272
|
+
}
|
|
1273
|
+
if (o.desktop !== false && metanetOffered() && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
|
|
1274
|
+
if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
|
|
1275
|
+
const d = metanetOption();
|
|
1276
|
+
const row2 = optionRow(d, false, METANET_NOTE);
|
|
1277
|
+
row2.title = "Connects to Metanet Desktop on this computer, never to a browser extension.";
|
|
1278
|
+
row2.onclick = () => done(d);
|
|
1279
|
+
list.append(row2);
|
|
1280
|
+
}
|
|
1281
|
+
ui.box.append(list);
|
|
1282
|
+
if (otherDetected(options).length || !here) ui.box.append(el("p", "bwx-foot", "Some features need bWalletX."));
|
|
1283
|
+
const row = el("div", "bwx-row");
|
|
1284
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1285
|
+
cancel.type = "button";
|
|
1286
|
+
cancel.onclick = () => done(null);
|
|
1287
|
+
row.append(cancel);
|
|
1288
|
+
ui.box.append(row);
|
|
1289
|
+
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
1290
|
+
list.querySelector("a,button")?.focus();
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
|
|
586
1294
|
// src/access-client.ts
|
|
587
|
-
var
|
|
1295
|
+
var import_sdk6 = require("@bsv/sdk");
|
|
588
1296
|
|
|
589
1297
|
// src/access.ts
|
|
590
1298
|
var ACCESS_PROTOCOL = [2, "bwalletx access"];
|
|
@@ -722,7 +1430,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
|
|
|
722
1430
|
const counterparty = ci.counterparty ?? "self";
|
|
723
1431
|
for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
|
|
724
1432
|
const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
|
|
725
|
-
const h =
|
|
1433
|
+
const h = import_sdk6.Utils.toHex(import_sdk6.Hash.hash160(import_sdk6.PublicKey.fromString(publicKey).encode(true)));
|
|
726
1434
|
if (want && h !== want) continue;
|
|
727
1435
|
const existing = byKey.get(publicKey);
|
|
728
1436
|
if (existing) {
|
|
@@ -768,11 +1476,53 @@ async function requestAccess(o) {
|
|
|
768
1476
|
}
|
|
769
1477
|
}
|
|
770
1478
|
|
|
1479
|
+
// src/connect-flow.ts
|
|
1480
|
+
async function runSilentMetanet(o = {}) {
|
|
1481
|
+
const s = await trySilentMetanet(o);
|
|
1482
|
+
if (!s.ok || o.isAbandoned?.()) return null;
|
|
1483
|
+
const started = Date.now();
|
|
1484
|
+
const opt = metanetOption(o);
|
|
1485
|
+
opt.wallet = s.wallet;
|
|
1486
|
+
const cw = { wallet: s.wallet, payer: s.wallet, identityKey: s.identityKey, optionId: METANET_OPTION_ID, kind: "brc100" };
|
|
1487
|
+
const done = (signedIn, handle = null, paymail = null) => {
|
|
1488
|
+
rememberWalletChoice(opt);
|
|
1489
|
+
if (signedIn) rememberKnownIdentityKey(s.identityKey);
|
|
1490
|
+
reportAttempt(o.onAttempt, attemptFor(opt, signedIn ? "signed-in" : "connected", started));
|
|
1491
|
+
return { connected: cw, identityKey: s.identityKey, handle, paymail, signedIn, walletType: walletTypeOf(opt), substrate: "json-api", reason: "remembered" };
|
|
1492
|
+
};
|
|
1493
|
+
if (o.sessionKey !== void 0 && o.sessionKey !== null) {
|
|
1494
|
+
return o.sessionKey.toLowerCase() === s.identityKey.toLowerCase() ? done(true) : null;
|
|
1495
|
+
}
|
|
1496
|
+
if (!o.challengeUrl || !o.verifyUrl || !o.post) return done(false);
|
|
1497
|
+
if (!isKnownIdentityKey(s.identityKey)) return null;
|
|
1498
|
+
try {
|
|
1499
|
+
o.onSignIn?.();
|
|
1500
|
+
const post = o.post;
|
|
1501
|
+
const { result, verified } = await signInAndVerify({
|
|
1502
|
+
wallet: s.wallet,
|
|
1503
|
+
identityKey: s.identityKey,
|
|
1504
|
+
skipWaitForAuthentication: true,
|
|
1505
|
+
// trySilentMetanet just saw it unlocked
|
|
1506
|
+
challenge: (key) => post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
1507
|
+
verify: (r) => post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
1508
|
+
});
|
|
1509
|
+
if (o.isAbandoned?.()) {
|
|
1510
|
+
await Promise.resolve(o.logout?.()).catch(() => void 0);
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1513
|
+
rememberIdentity(result.identityKey);
|
|
1514
|
+
return done(true, verified.handle ?? null, verified.paymail ?? null);
|
|
1515
|
+
} catch (e) {
|
|
1516
|
+
reportAttempt(o.onAttempt, attemptFor(opt, outcomeOf(e), started));
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
771
1521
|
// src/element.ts
|
|
772
1522
|
var SIGNIN_TAG = "bwalletx-signin";
|
|
773
1523
|
var GET_URL = "https://bwalletx.com/get";
|
|
774
1524
|
var WEB_WALLET_URL = "https://web.bwalletx.com";
|
|
775
|
-
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect" };
|
|
1525
|
+
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect", get: "Get", play: "Play with" };
|
|
776
1526
|
function signinText(label = "signin", brand = "bWalletX") {
|
|
777
1527
|
return `${VERBS[label] ?? VERBS.signin} ${brand}`;
|
|
778
1528
|
}
|
|
@@ -827,7 +1577,60 @@ button[aria-busy=true]{cursor:wait}
|
|
|
827
1577
|
.nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
828
1578
|
:host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}
|
|
829
1579
|
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
1580
|
+
/* pending: one plain line under the button */
|
|
1581
|
+
.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}
|
|
1582
|
+
:host([theme=dark]) .note,:host-context(.dark) .note{color:#a1a1aa}
|
|
1583
|
+
/* "Other wallets" (0.5.6): one small link under the button */
|
|
1584
|
+
.ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1585
|
+
:host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
|
|
1586
|
+
.sw{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#a16207;text-align:center}
|
|
1587
|
+
:host([theme=dark]) .sw,:host-context(.dark) .sw{color:#fcd34d}
|
|
1588
|
+
.mn{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1589
|
+
.mn small{display:block;font-size:11px;opacity:.85}
|
|
1590
|
+
:host([theme=dark]) .mn,:host-context(.dark) .mn{color:#a1a1aa}
|
|
1591
|
+
.lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
|
|
1592
|
+
.lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
1593
|
+
/* error: one line plus Retry */
|
|
1594
|
+
.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}
|
|
1595
|
+
:host([theme=dark]) .err,:host-context(.dark) .err{color:#fca5a5}
|
|
1596
|
+
/* signed in: the badge */
|
|
1597
|
+
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;
|
|
1598
|
+
background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.55);box-shadow:none;font-size:15px}
|
|
1599
|
+
button.chip:hover{background:#151517;border-color:#ffd24d}
|
|
1600
|
+
button.chip:active{background:#000;transform:none}
|
|
1601
|
+
button.chip:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}
|
|
1602
|
+
:host([theme=light]) button.chip{background:#fff;color:#09090b;border-color:#f59e0b}
|
|
1603
|
+
:host([theme=light]) button.chip:hover{background:#fffbeb}
|
|
1604
|
+
:host([full-width]) button.chip{width:100%}
|
|
1605
|
+
.av{position:relative;flex:none;display:inline-grid;place-items:center;width:30px;height:30px;border-radius:50%;overflow:hidden;
|
|
1606
|
+
background:radial-gradient(circle at 30% 30%,#ffe27a,#f5b800 55%,#b88600);color:#000;font-weight:800;font-size:14px}
|
|
1607
|
+
.av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
1608
|
+
.name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:min(52vw,260px)}
|
|
1609
|
+
.dot{flex:none;width:8px;height:8px;border-radius:50%;background:#ffd24d;box-shadow:0 0 0 3px rgba(255,210,77,.22)}
|
|
1610
|
+
:host([size=sm]) button.chip{font-size:13px;padding:3px 12px 3px 3px;gap:8px}
|
|
1611
|
+
:host([size=sm]) .av{width:26px;height:26px}
|
|
1612
|
+
:host([size=icon]) button.chip{width:48px;height:48px;padding:0;justify-content:center}
|
|
1613
|
+
:host([size=icon]) .name,:host([size=icon]) .dot{display:none}
|
|
1614
|
+
:host([size=icon]) .av{width:40px;height:40px}
|
|
1615
|
+
`;
|
|
1616
|
+
var MENU_CSS = `
|
|
1617
|
+
:host{all:initial}
|
|
1618
|
+
.menu{position:fixed;z-index:2147483600;box-sizing:border-box;min-width:200px;padding:6px;border-radius:14px;background:#0b0b0c;color:#ffd24d;
|
|
1619
|
+
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}
|
|
1620
|
+
.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}
|
|
1621
|
+
.item{all:unset;box-sizing:border-box;display:block;width:100%;padding:11px 10px;border-radius:10px;cursor:pointer;color:#ffd24d}
|
|
1622
|
+
.item:hover,.item:focus-visible{background:#ffd24d;color:#000;outline:none}
|
|
1623
|
+
.tip{position:fixed;z-index:2147483600;box-sizing:border-box;padding:8px 12px;border-radius:10px;background:#0b0b0c;color:#e7d9a8;
|
|
1624
|
+
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}
|
|
830
1625
|
`;
|
|
1626
|
+
var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
|
|
1627
|
+
var PENDING_LABEL = "Approve in bWalletX\u2026";
|
|
1628
|
+
var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
|
|
1629
|
+
var SWITCH_NOTE = "Some features need bWalletX";
|
|
1630
|
+
var CONTINUE_OTHER_LABEL = "Continue with your wallet";
|
|
1631
|
+
function walletGlyph(px) {
|
|
1632
|
+
return `<svg viewBox="0 0 24 24" width="${px}" height="${px}" aria-hidden="true" focusable="false" fill="none" stroke="#FFD24D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 7V5a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2"/><path d="M3 6v12a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-3"/></svg>`;
|
|
1633
|
+
}
|
|
831
1634
|
function b64url2(bytes) {
|
|
832
1635
|
let s = "";
|
|
833
1636
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -860,14 +1663,15 @@ async function buildAuthorizeUrl(p) {
|
|
|
860
1663
|
async function postJson(url, body) {
|
|
861
1664
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
862
1665
|
const data = await res.json().catch(() => ({}));
|
|
863
|
-
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
1666
|
+
if (!res.ok) throw Object.assign(new Error(data.error ?? `HTTP ${res.status}`), { status: res.status });
|
|
864
1667
|
return data;
|
|
865
1668
|
}
|
|
866
1669
|
async function postAccess(url, body) {
|
|
867
1670
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
868
1671
|
return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
|
|
869
1672
|
}
|
|
870
|
-
var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url"];
|
|
1673
|
+
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", "wallet-type", "switch-note", "remember", "metanet"];
|
|
1674
|
+
var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
|
|
871
1675
|
function createClass() {
|
|
872
1676
|
return class BwalletxSigninElement extends HTMLElement {
|
|
873
1677
|
static get observedAttributes() {
|
|
@@ -876,12 +1680,30 @@ function createClass() {
|
|
|
876
1680
|
#root;
|
|
877
1681
|
#notFound = false;
|
|
878
1682
|
#busy = false;
|
|
1683
|
+
#phase = "signed-out";
|
|
1684
|
+
#account = null;
|
|
1685
|
+
#error = "";
|
|
1686
|
+
#restored = false;
|
|
1687
|
+
#pendingLabel = PENDING_LABEL;
|
|
1688
|
+
/** The wallet type signed in with here (0.6.0), for the Switch note. */
|
|
1689
|
+
#signedType = null;
|
|
1690
|
+
#menu = null;
|
|
1691
|
+
/** (0.7.0) A Metanet sign-in (tapped or silent) is in flight. */
|
|
1692
|
+
#viaMetanet = false;
|
|
1693
|
+
/** (0.7.0) Bumped by "Not you?": a Metanet sign-in started before it lands nobody. */
|
|
1694
|
+
#run = 0;
|
|
879
1695
|
constructor() {
|
|
880
1696
|
super();
|
|
881
1697
|
this.#root = this.attachShadow({ mode: "open" });
|
|
882
1698
|
}
|
|
883
1699
|
connectedCallback() {
|
|
1700
|
+
if (!this.#controlled && this.#stateful) warnIfNoLogout({ verifyUrl: this.#attr("verify-url"), logoutUrl: this.#attr("logout-url"), where: "<bwalletx-signin>" });
|
|
884
1701
|
this.#render();
|
|
1702
|
+
void this.#restore().then(() => this.#silentMetanet());
|
|
1703
|
+
}
|
|
1704
|
+
disconnectedCallback() {
|
|
1705
|
+
this.#menu?.close(false);
|
|
1706
|
+
this.#hideTip();
|
|
885
1707
|
}
|
|
886
1708
|
attributeChangedCallback() {
|
|
887
1709
|
if (this.isConnected) this.#render();
|
|
@@ -893,93 +1715,557 @@ function createClass() {
|
|
|
893
1715
|
get notFound() {
|
|
894
1716
|
return this.#notFound;
|
|
895
1717
|
}
|
|
1718
|
+
/** The state shown now (the `state` attribute when set, else the element's own). */
|
|
1719
|
+
get state() {
|
|
1720
|
+
const a = this.#attr("state");
|
|
1721
|
+
return a && STATES.includes(a) ? a : this.#phase;
|
|
1722
|
+
}
|
|
1723
|
+
/** The signed-in account, or null. */
|
|
1724
|
+
get account() {
|
|
1725
|
+
if (!this.#controlled) return this.#account;
|
|
1726
|
+
const identityKey = this.#attr("identity-key");
|
|
1727
|
+
return identityKey ? { identityKey, paymail: this.#attr("paymail"), handle: this.#attr("handle"), avatarUrl: this.#attr("avatar-url") } : null;
|
|
1728
|
+
}
|
|
1729
|
+
/** True while the menu is open (tests). */
|
|
1730
|
+
get menuOpen() {
|
|
1731
|
+
return !!this.#menu;
|
|
1732
|
+
}
|
|
896
1733
|
focus(opts) {
|
|
897
1734
|
this.button?.focus(opts);
|
|
898
1735
|
}
|
|
1736
|
+
get #controlled() {
|
|
1737
|
+
return this.hasAttribute("state");
|
|
1738
|
+
}
|
|
1739
|
+
get #stateful() {
|
|
1740
|
+
return !this.hasAttribute("stateless");
|
|
1741
|
+
}
|
|
899
1742
|
#attr(name) {
|
|
900
1743
|
return this.getAttribute(name);
|
|
901
1744
|
}
|
|
1745
|
+
/** Restore the badge from session-url once, with no wallet call. */
|
|
1746
|
+
async #restore() {
|
|
1747
|
+
const url = this.#attr("session-url");
|
|
1748
|
+
if (this.#restored || !url || this.#controlled || !this.#stateful) return;
|
|
1749
|
+
this.#restored = true;
|
|
1750
|
+
this.#set("restoring");
|
|
1751
|
+
try {
|
|
1752
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
1753
|
+
const j = await r.json().catch(() => ({}));
|
|
1754
|
+
if (this.#phase !== "restoring") return;
|
|
1755
|
+
if (j.identityKey) this.#signedIn({ identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null });
|
|
1756
|
+
else this.#set("signed-out");
|
|
1757
|
+
} catch {
|
|
1758
|
+
if (this.#phase === "restoring") this.#set("signed-out");
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
#set(p) {
|
|
1762
|
+
this.#phase = p;
|
|
1763
|
+
if (p !== "signed-in") this.#menu?.close(false);
|
|
1764
|
+
this.#render();
|
|
1765
|
+
}
|
|
1766
|
+
#signedIn(a) {
|
|
1767
|
+
this.#account = a;
|
|
1768
|
+
this.#error = "";
|
|
1769
|
+
this.#set("signed-in");
|
|
1770
|
+
const lookup = this.#attr("lookup-url");
|
|
1771
|
+
if (!a.paymail && lookup !== "off") {
|
|
1772
|
+
void lookupPaymail(a.identityKey, lookup ?? void 0).then((pm) => {
|
|
1773
|
+
if (pm && this.#account?.identityKey === a.identityKey && !this.#account.paymail) {
|
|
1774
|
+
this.#account = { ...this.#account, paymail: pm };
|
|
1775
|
+
this.#render();
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
902
1780
|
#render() {
|
|
903
1781
|
const size = this.#attr("size") ?? "lg";
|
|
904
1782
|
const brand = this.#attr("brand") === "bWallet" ? "bWallet" : "bWalletX";
|
|
905
|
-
const
|
|
1783
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
1784
|
+
const state = this.state;
|
|
1785
|
+
const acct = this.account;
|
|
1786
|
+
if (state === "signed-in" && acct) {
|
|
1787
|
+
this.#hideTip();
|
|
1788
|
+
const name = accountLabel(acct);
|
|
1789
|
+
const avatar = acct.avatarUrl ?? avatarUrlFor(acct.identityKey);
|
|
1790
|
+
const initial = (name.replace(/^\$/, "").charAt(0) || "b").toUpperCase();
|
|
1791
|
+
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>`;
|
|
1792
|
+
const img = this.#root.querySelector("img");
|
|
1793
|
+
if (img) img.addEventListener("error", () => img.remove(), { once: true });
|
|
1794
|
+
const chip = this.button;
|
|
1795
|
+
chip.addEventListener("click", () => this.#menu ? this.#menu.close(true) : this.#openMenu());
|
|
1796
|
+
chip.addEventListener("keydown", (e) => {
|
|
1797
|
+
if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !this.#menu) {
|
|
1798
|
+
e.preventDefault();
|
|
1799
|
+
this.#openMenu();
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
const pending = state === "pending";
|
|
1805
|
+
const restoring = state === "restoring";
|
|
1806
|
+
const error = state === "error";
|
|
1807
|
+
const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
|
|
1808
|
+
const elsewhere = state === "signed-out" && tapGoesElsewhere(this.#walletType());
|
|
1809
|
+
const text = pending ? pendingLabel : error ? "Retry" : elsewhere ? CONTINUE_OTHER_LABEL : signinText(this.#attr("label") ?? "signin", brand);
|
|
906
1810
|
const subAttr = this.#attr("subtitle");
|
|
907
1811
|
const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
|
|
908
|
-
const subtitle = subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
909
|
-
const loading = this.hasAttribute("loading") || this.#busy;
|
|
1812
|
+
const subtitle = pending || error || elsewhere ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
1813
|
+
const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
|
|
910
1814
|
const disabled = this.hasAttribute("disabled");
|
|
911
1815
|
const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
|
|
912
|
-
const aria = this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
1816
|
+
const aria = pending ? pendingLabel : this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
913
1817
|
const getUrl = this.#attr("get-url") ?? GET_URL;
|
|
914
1818
|
const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
|
|
915
|
-
const
|
|
1819
|
+
const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
|
|
916
1820
|
const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
|
|
917
|
-
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
|
|
918
|
-
const
|
|
919
|
-
|
|
1821
|
+
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : elsewhere ? walletGlyph(markPx - 4) : markSvg(markPx);
|
|
1822
|
+
const otherOff = this.#attr("other-wallets") === "off";
|
|
1823
|
+
const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
|
|
1824
|
+
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>` : "";
|
|
1825
|
+
const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
|
|
1826
|
+
const sw = !pending && !restoring && this.#switchNoteShown(size) ? `<p class="sw" part="switch-note">${esc(SWITCH_NOTE)} \xB7 <button type="button" class="lnk" data-act="switch" part="switch-link">Switch</button></p>` : "";
|
|
1827
|
+
const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
|
|
1828
|
+
const mnHere = this.#metanetShown(size);
|
|
1829
|
+
const mnRemembered = mnHere && (pending ? this.#metanetPending() : !restoring && this.#walletType() === METANET_TYPE);
|
|
1830
|
+
const notYou = `<button type="button" class="lnk" data-act="not-you" part="not-you-link">${esc(NOT_YOU_LABEL)}</button>`;
|
|
1831
|
+
const mn = !mnHere ? "" : pending ? mnRemembered ? `<p class="mn" part="metanet">${esc(METANET_REMEMBERED_NOTE)} ${notYou}</p>` : "" : restoring ? "" : `<p class="mn" part="metanet"><button type="button" class="lnk" data-act="metanet" part="metanet-link" title="Connects to Metanet Desktop on this computer, never to a browser extension.">${esc(METANET_LABEL)}</button><small>${esc(METANET_NOTE)}</small>${mnRemembered ? `<small>${esc(METANET_REMEMBERED_NOTE)} ${notYou}</small>` : ""}</p>`;
|
|
1832
|
+
const compact = size === "sm" || size === "icon";
|
|
1833
|
+
const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
|
|
1834
|
+
const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
|
|
1835
|
+
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}${sw}${mn}${ow}${nf}`;
|
|
920
1836
|
this.button.addEventListener("click", () => void this.#click());
|
|
1837
|
+
for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
|
|
1838
|
+
this.#root.querySelector("[data-act=switch]")?.addEventListener("click", () => void this.switchToBwalletX());
|
|
1839
|
+
this.#root.querySelector("[data-act=metanet]")?.addEventListener("click", () => void this.useMetanet());
|
|
1840
|
+
this.#root.querySelector("[data-act=not-you]")?.addEventListener("click", () => this.notYou());
|
|
921
1841
|
bindPrewarm(this.button);
|
|
1842
|
+
if (pending && compact) this.#showTip(PENDING_NOTE);
|
|
1843
|
+
else this.#hideTip();
|
|
1844
|
+
}
|
|
1845
|
+
#tip = null;
|
|
1846
|
+
#showTip(text) {
|
|
1847
|
+
const b = this.button;
|
|
1848
|
+
if (!b || typeof document === "undefined") return;
|
|
1849
|
+
if (!this.#tip) {
|
|
1850
|
+
const host = document.createElement("div");
|
|
1851
|
+
host.setAttribute("data-bwalletx-tip", "");
|
|
1852
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1853
|
+
root.innerHTML = `<style>${MENU_CSS}</style><div class="tip" role="note"></div>`;
|
|
1854
|
+
document.body.appendChild(host);
|
|
1855
|
+
this.#tip = host;
|
|
1856
|
+
}
|
|
1857
|
+
const tip = this.#tip.shadowRoot.querySelector(".tip");
|
|
1858
|
+
tip.textContent = text;
|
|
1859
|
+
const r = b.getBoundingClientRect();
|
|
1860
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1861
|
+
const width = Math.min(280, vw - 32);
|
|
1862
|
+
tip.style.width = `${width}px`;
|
|
1863
|
+
tip.style.left = `${Math.max(16, Math.min(r.right - width, vw - width - 16))}px`;
|
|
1864
|
+
tip.style.top = `${r.bottom + 8}px`;
|
|
1865
|
+
}
|
|
1866
|
+
#hideTip() {
|
|
1867
|
+
this.#tip?.remove();
|
|
1868
|
+
this.#tip = null;
|
|
1869
|
+
}
|
|
1870
|
+
#openMenu() {
|
|
1871
|
+
const acct = this.account;
|
|
1872
|
+
const chip = this.button;
|
|
1873
|
+
if (!acct || !chip || typeof document === "undefined") return;
|
|
1874
|
+
const name = accountLabel(acct);
|
|
1875
|
+
const host = document.createElement("div");
|
|
1876
|
+
host.setAttribute("data-bwalletx-menu", "");
|
|
1877
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1878
|
+
const style = document.createElement("style");
|
|
1879
|
+
style.textContent = MENU_CSS;
|
|
1880
|
+
const menu = document.createElement("div");
|
|
1881
|
+
menu.className = "menu";
|
|
1882
|
+
menu.setAttribute("role", "menu");
|
|
1883
|
+
menu.setAttribute("aria-label", "bWalletX account");
|
|
1884
|
+
const who = document.createElement("div");
|
|
1885
|
+
who.className = "who";
|
|
1886
|
+
who.textContent = `Signed in as ${acct.paymail || name}`;
|
|
1887
|
+
const item = (text, run) => {
|
|
1888
|
+
const b = document.createElement("button");
|
|
1889
|
+
b.type = "button";
|
|
1890
|
+
b.className = "item";
|
|
1891
|
+
b.setAttribute("role", "menuitem");
|
|
1892
|
+
b.tabIndex = -1;
|
|
1893
|
+
b.textContent = text;
|
|
1894
|
+
b.addEventListener("click", () => run(b));
|
|
1895
|
+
return b;
|
|
1896
|
+
};
|
|
1897
|
+
const copyWhat = acct.paymail ? "paymail" : "key";
|
|
1898
|
+
const copy = item(`Copy ${copyWhat}`, (b) => {
|
|
1899
|
+
void copyText(acct.paymail || acct.identityKey).then((ok) => {
|
|
1900
|
+
b.textContent = ok ? "Copied" : `Copy ${copyWhat}`;
|
|
1901
|
+
setTimeout(() => close(true), ok ? 600 : 0);
|
|
1902
|
+
});
|
|
1903
|
+
});
|
|
1904
|
+
const out = item("Disconnect", () => {
|
|
1905
|
+
close(false);
|
|
1906
|
+
void this.signOut();
|
|
1907
|
+
});
|
|
1908
|
+
const items = [copy, out];
|
|
1909
|
+
menu.append(who);
|
|
1910
|
+
if (needsBwalletX(this.#walletType())) {
|
|
1911
|
+
const sw = item("Switch to bWalletX", () => {
|
|
1912
|
+
close(false);
|
|
1913
|
+
void this.switchToBwalletX();
|
|
1914
|
+
});
|
|
1915
|
+
const line = document.createElement("div");
|
|
1916
|
+
line.className = "who";
|
|
1917
|
+
line.textContent = SWITCH_NOTE;
|
|
1918
|
+
menu.append(line, sw);
|
|
1919
|
+
items.unshift(sw);
|
|
1920
|
+
}
|
|
1921
|
+
menu.append(copy, out);
|
|
1922
|
+
root.append(style, menu);
|
|
1923
|
+
document.body.appendChild(host);
|
|
1924
|
+
const place = () => {
|
|
1925
|
+
const r = chip.getBoundingClientRect();
|
|
1926
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1927
|
+
const vh = window.innerHeight || document.documentElement.clientHeight || 640;
|
|
1928
|
+
const width = Math.min(260, vw - 32);
|
|
1929
|
+
menu.style.width = `${width}px`;
|
|
1930
|
+
const left = Math.max(16, Math.min(r.right - width, vw - width - 16));
|
|
1931
|
+
menu.style.left = `${left}px`;
|
|
1932
|
+
const h = menu.offsetHeight || 150;
|
|
1933
|
+
const below = r.bottom + 8;
|
|
1934
|
+
menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
|
|
1935
|
+
};
|
|
1936
|
+
place();
|
|
1937
|
+
const focusAt = (i) => items[(i + items.length) % items.length].focus();
|
|
1938
|
+
const onKey = (e) => {
|
|
1939
|
+
const i = items.indexOf(root.activeElement);
|
|
1940
|
+
if (e.key === "Escape") {
|
|
1941
|
+
e.preventDefault();
|
|
1942
|
+
close(true);
|
|
1943
|
+
} else if (e.key === "ArrowDown") {
|
|
1944
|
+
e.preventDefault();
|
|
1945
|
+
focusAt(i + 1);
|
|
1946
|
+
} else if (e.key === "ArrowUp") {
|
|
1947
|
+
e.preventDefault();
|
|
1948
|
+
focusAt(i - 1);
|
|
1949
|
+
} else if (e.key === "Home") {
|
|
1950
|
+
e.preventDefault();
|
|
1951
|
+
focusAt(0);
|
|
1952
|
+
} else if (e.key === "End") {
|
|
1953
|
+
e.preventDefault();
|
|
1954
|
+
focusAt(items.length - 1);
|
|
1955
|
+
} else if (e.key === "Tab") close(false);
|
|
1956
|
+
};
|
|
1957
|
+
const onDown = (e) => {
|
|
1958
|
+
const path = e.composedPath?.() ?? [];
|
|
1959
|
+
if (!path.includes(host) && !path.includes(this)) close(false);
|
|
1960
|
+
};
|
|
1961
|
+
const onMove = () => close(false);
|
|
1962
|
+
menu.addEventListener("keydown", onKey);
|
|
1963
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
1964
|
+
window.addEventListener("resize", onMove);
|
|
1965
|
+
window.addEventListener("scroll", place, true);
|
|
1966
|
+
const close = (focusChip = false) => {
|
|
1967
|
+
if (this.#menu?.host !== host) return;
|
|
1968
|
+
this.#menu = null;
|
|
1969
|
+
menu.removeEventListener("keydown", onKey);
|
|
1970
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1971
|
+
window.removeEventListener("resize", onMove);
|
|
1972
|
+
window.removeEventListener("scroll", place, true);
|
|
1973
|
+
host.remove();
|
|
1974
|
+
this.button?.setAttribute("aria-expanded", "false");
|
|
1975
|
+
if (focusChip) this.button?.focus();
|
|
1976
|
+
};
|
|
1977
|
+
this.#menu = { host, close };
|
|
1978
|
+
chip.setAttribute("aria-expanded", "true");
|
|
1979
|
+
focusAt(0);
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Disconnect: emits bwalletx-disconnect (cancelable). Unless a listener cancels it (BwalletXProvider signs out
|
|
1983
|
+
* itself) or the element is driven by `state`, posts logout-url and shows signed out.
|
|
1984
|
+
*/
|
|
1985
|
+
async signOut() {
|
|
1986
|
+
this.#menu?.close(false);
|
|
1987
|
+
if (!this.#emit("bwalletx-disconnect", { element: this, account: this.account }, true)) return;
|
|
1988
|
+
if (this.#controlled) return;
|
|
1989
|
+
const url = this.#attr("logout-url");
|
|
1990
|
+
if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
1991
|
+
if (this.#remember) forgetWalletChoice();
|
|
1992
|
+
this.#signedType = null;
|
|
1993
|
+
this.#account = null;
|
|
1994
|
+
this.#set("signed-out");
|
|
1995
|
+
this.#emit("bwalletx-signed-out");
|
|
1996
|
+
this.button?.focus();
|
|
922
1997
|
}
|
|
923
1998
|
#emit(type, detail, cancelable = false) {
|
|
924
1999
|
return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
|
|
925
2000
|
}
|
|
926
|
-
|
|
927
|
-
|
|
2001
|
+
/** "Other wallets" is offered under the button: other-wallets on, or auto at lg/md when this element signs in. */
|
|
2002
|
+
#otherWalletsShown(size) {
|
|
2003
|
+
const a = this.#attr("other-wallets");
|
|
2004
|
+
if (a === "off") return false;
|
|
2005
|
+
if (a === "on") return true;
|
|
2006
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
2007
|
+
}
|
|
2008
|
+
/** Open the wallet chooser (the "Other wallets" link): phone QR, other browser wallets, desktop wallets. */
|
|
2009
|
+
otherWallets() {
|
|
2010
|
+
return this.#click("chooser");
|
|
2011
|
+
}
|
|
2012
|
+
/** (0.6.0) "Switch": sign in with bWalletX (its sheet when here, else the chooser with Get bWalletX first). */
|
|
2013
|
+
switchToBwalletX() {
|
|
2014
|
+
return this.#click("bwalletx");
|
|
2015
|
+
}
|
|
2016
|
+
get #remember() {
|
|
2017
|
+
return this.#attr("remember") !== "off";
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* (0.7.0) "Use Metanet Desktop or another BRC-100 wallet" is offered: metanet on, or auto at lg/md when this
|
|
2021
|
+
* element signs in or follows a provider. Never on a phone or inside a frame.
|
|
2022
|
+
*/
|
|
2023
|
+
#metanetShown(size) {
|
|
2024
|
+
const a = this.#attr("metanet");
|
|
2025
|
+
if (a === "off" || !metanetOffered()) return false;
|
|
2026
|
+
if (a === "on") return true;
|
|
2027
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
2028
|
+
}
|
|
2029
|
+
/** A Metanet sign-in is on screen: our own, or (driven) the provider's with Metanet remembered. */
|
|
2030
|
+
#metanetPending() {
|
|
2031
|
+
return this.#controlled ? this.#attr("wallet-type") === METANET_TYPE : this.#viaMetanet;
|
|
2032
|
+
}
|
|
2033
|
+
/** (0.7.0) The Metanet link: connect to Metanet Desktop on localhost:3321 (after this tap, never before). */
|
|
2034
|
+
useMetanet() {
|
|
2035
|
+
return this.#click("metanet");
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* (0.7.0) "Not you? Use another wallet": forget the remembered wallet here, and drop a Metanet sign-in in flight
|
|
2039
|
+
* (if its verify already set a session, logout-url ends it). Emits bwalletx-not-you (cancelable).
|
|
2040
|
+
*/
|
|
2041
|
+
notYou() {
|
|
2042
|
+
if (!this.#emit("bwalletx-not-you", { element: this }, true)) return;
|
|
2043
|
+
this.#run++;
|
|
2044
|
+
if (this.#remember) forgetWalletChoice();
|
|
2045
|
+
this.#signedType = null;
|
|
2046
|
+
if (this.#viaMetanet && !this.#controlled) {
|
|
2047
|
+
this.#viaMetanet = false;
|
|
2048
|
+
this.#busy = false;
|
|
2049
|
+
if (this.#phase === "pending") this.#phase = "signed-out";
|
|
2050
|
+
}
|
|
928
2051
|
this.#render();
|
|
929
2052
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (
|
|
936
|
-
|
|
2053
|
+
/**
|
|
2054
|
+
* (0.7.0) Rule 4: back with Metanet remembered and no session, ONE silent attempt per page load (2 s, a known
|
|
2055
|
+
* identity key only), then the signature alone. Any stop or failure is silent: the normal button stays.
|
|
2056
|
+
*/
|
|
2057
|
+
async #silentMetanet() {
|
|
2058
|
+
if (this.#controlled || !this.#stateful || !this.#remember || this.#attr("require-token")) return;
|
|
2059
|
+
if (!this.#metanetShown(this.#attr("size") ?? "lg")) return;
|
|
2060
|
+
const challengeUrl = this.#attr("challenge-url");
|
|
2061
|
+
const verifyUrl = this.#attr("verify-url");
|
|
2062
|
+
if (!challengeUrl || !verifyUrl || this.#phase !== "signed-out" || this.#busy || silentAttemptUsed()) return;
|
|
2063
|
+
if (rememberedWalletChoice()?.type !== METANET_TYPE || !metanetOffered()) return;
|
|
2064
|
+
const run = ++this.#run;
|
|
2065
|
+
const live2 = () => run === this.#run;
|
|
2066
|
+
const started = Date.now();
|
|
2067
|
+
const r = await runSilentMetanet({
|
|
2068
|
+
challengeUrl,
|
|
2069
|
+
verifyUrl,
|
|
2070
|
+
post: (url, body) => postJson(url, body),
|
|
2071
|
+
isAbandoned: () => !live2(),
|
|
2072
|
+
logout: async () => {
|
|
2073
|
+
const out = this.#attr("logout-url");
|
|
2074
|
+
if (out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2075
|
+
},
|
|
2076
|
+
onSignIn: () => {
|
|
2077
|
+
if (!live2() || this.#phase !== "signed-out" || this.#busy) return;
|
|
2078
|
+
this.#viaMetanet = true;
|
|
2079
|
+
this.#busy = true;
|
|
2080
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
2081
|
+
this.#set("pending");
|
|
2082
|
+
},
|
|
2083
|
+
onAttempt: (a) => this.#emit("bwalletx-attempt", a)
|
|
2084
|
+
});
|
|
2085
|
+
if (!live2()) return;
|
|
2086
|
+
this.#viaMetanet = false;
|
|
2087
|
+
this.#busy = false;
|
|
2088
|
+
if (!r) {
|
|
2089
|
+
if (this.#phase === "pending") this.#set("signed-out");
|
|
937
2090
|
return;
|
|
938
2091
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
2092
|
+
this.#signedType = r.walletType;
|
|
2093
|
+
this.#signedIn({ identityKey: r.identityKey, handle: r.handle, paymail: r.paymail });
|
|
2094
|
+
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method: "wallet", nonce: null, signature: null, response: { handle: r.handle, paymail: r.paymail }, silent: true, ms: Date.now() - started });
|
|
2095
|
+
}
|
|
2096
|
+
/** The wallet type the Switch note judges: the provider's, else what signed in here, else the remembered one. */
|
|
2097
|
+
#walletType() {
|
|
2098
|
+
if (this.#controlled) return this.#attr("wallet-type");
|
|
2099
|
+
return this.#signedType ?? (this.#remember ? rememberedWalletChoice()?.type ?? null : null);
|
|
2100
|
+
}
|
|
2101
|
+
#switchNoteShown(size) {
|
|
2102
|
+
const a = this.#attr("switch-note");
|
|
2103
|
+
if (a === "off" || !tapGoesElsewhere(this.#walletType())) return false;
|
|
2104
|
+
return a === "on" || size === "lg" || size === "md";
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Signed in: note the wallet type, and remember it for this origin when the user picked it (0.6.0), plus the
|
|
2108
|
+
* identity key as known here (0.7.0, public keys only: Metanet's silent attempt goes on only for these). Pass a key
|
|
2109
|
+
* only after the site's verify route accepted it.
|
|
2110
|
+
*/
|
|
2111
|
+
#picked(option, reason, identityKey) {
|
|
2112
|
+
this.#signedType = walletTypeOf(option);
|
|
2113
|
+
if (this.#remember && isExplicitPick(option, reason)) rememberWalletChoice(option);
|
|
2114
|
+
if (this.#remember) rememberKnownIdentityKey(identityKey);
|
|
2115
|
+
}
|
|
2116
|
+
#attempt(option, outcome, started) {
|
|
2117
|
+
this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Which wallet: bWalletX by its own marker goes straight to its sheet; otherwise the chooser ("Get bWalletX" first)
|
|
2121
|
+
* when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
|
|
2122
|
+
*/
|
|
2123
|
+
async #choose(mode, ms, pairing) {
|
|
2124
|
+
if (mode === "metanet") return { option: metanetOption(), reason: "requested" };
|
|
2125
|
+
const r = mode === "signin" && this.#remember ? rememberedWalletChoice() : null;
|
|
2126
|
+
if (r && r.type !== METANET_TYPE && !(r.type === BWALLETX_TYPE && r.substrate !== "pairing")) {
|
|
2127
|
+
const m = matchRemembered(await discoverWallets({ ms, pairing: () => pairing }), r);
|
|
2128
|
+
if (m) return { option: m, reason: "remembered" };
|
|
2129
|
+
}
|
|
2130
|
+
if (mode !== "chooser") {
|
|
2131
|
+
const f = await discoverBwalletX(ms);
|
|
2132
|
+
if (f) {
|
|
2133
|
+
const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
|
|
2134
|
+
const option = brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
|
|
2135
|
+
return { option, reason: "bwalletx" };
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
const list = await discoverWallets({ ms: 50, pairing: () => pairing });
|
|
2139
|
+
if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
|
|
2140
|
+
const picked = await chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
|
|
2141
|
+
return picked ? { option: picked, reason: "chooser" } : null;
|
|
2142
|
+
}
|
|
2143
|
+
async #click(mode = "signin") {
|
|
2144
|
+
if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
|
|
2145
|
+
if (!this.#emit("bwalletx-signin", { element: this, mode }, true)) return;
|
|
2146
|
+
if (mode === "signin") {
|
|
2147
|
+
const href = this.#attr("href");
|
|
2148
|
+
const authorizeUrl = this.#attr("authorize-url");
|
|
2149
|
+
if (authorizeUrl) {
|
|
2150
|
+
location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr("client-id"), redirectUri: this.#attr("redirect-uri"), scope: this.#attr("scope") }));
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (href) {
|
|
2154
|
+
location.assign(href);
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
942
2157
|
}
|
|
943
2158
|
const challengeUrl = this.#attr("challenge-url");
|
|
944
2159
|
if (!challengeUrl) return;
|
|
2160
|
+
const own = this.#stateful && !this.#controlled;
|
|
2161
|
+
const prev = this.#phase === "signed-in" ? "signed-in" : "signed-out";
|
|
2162
|
+
const started = Date.now();
|
|
2163
|
+
const pairing = pairingModal();
|
|
2164
|
+
let option = null;
|
|
2165
|
+
const run = ++this.#run;
|
|
2166
|
+
const live2 = () => run === this.#run;
|
|
945
2167
|
this.#notFound = false;
|
|
946
|
-
this.#
|
|
2168
|
+
this.#busy = true;
|
|
2169
|
+
this.#viaMetanet = mode === "metanet";
|
|
2170
|
+
this.#pendingLabel = mode === "metanet" ? PENDING_LABEL_OTHER : PENDING_LABEL;
|
|
2171
|
+
if (own) this.#set("pending");
|
|
2172
|
+
else this.#render();
|
|
947
2173
|
try {
|
|
948
2174
|
const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
2175
|
+
const picked = await this.#choose(mode, ms, pairing);
|
|
2176
|
+
if (picked === "not-found") {
|
|
951
2177
|
this.#notFound = true;
|
|
2178
|
+
if (own) this.#phase = "signed-out";
|
|
2179
|
+
this.#attempt(null, "not-found", started);
|
|
952
2180
|
this.#emit("bwalletx-not-found");
|
|
953
2181
|
if (this.#attr("require-token")) this.#emit("bwalletx-access-denied", { code: "not_installed", message: "No wallet found" });
|
|
954
2182
|
return;
|
|
955
2183
|
}
|
|
2184
|
+
if (!picked) {
|
|
2185
|
+
if (own) this.#phase = prev;
|
|
2186
|
+
this.#attempt(null, "cancelled", started);
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
option = picked.option;
|
|
2190
|
+
const reason = picked.reason;
|
|
2191
|
+
if (!option.isBwalletX) {
|
|
2192
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
2193
|
+
if (own) this.#render();
|
|
2194
|
+
}
|
|
2195
|
+
const cw = await option.connect({ identity: false });
|
|
2196
|
+
if (!live2()) return;
|
|
2197
|
+
const wallet = cw.wallet;
|
|
2198
|
+
if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
|
|
2199
|
+
const sub = substrateOf(option);
|
|
2200
|
+
const method = sub === "extension" ? "extension" : sub === "in-app" || sub === "xdm" ? "in-app" : sub === "pairing" ? "pairing" : "wallet";
|
|
956
2201
|
const requireToken = this.#attr("require-token");
|
|
957
2202
|
if (requireToken) {
|
|
2203
|
+
if (own) this.#phase = "signed-out";
|
|
958
2204
|
const verifyUrl2 = this.#attr("verify-url");
|
|
959
2205
|
const min = this.#attr("min");
|
|
960
2206
|
const minBps = this.#attr("min-bps");
|
|
961
2207
|
const hint = { token: { id: requireToken, ...minBps ? { minBps: Number(minBps) } : { min: min ?? "1" } } };
|
|
962
2208
|
try {
|
|
963
2209
|
const a = await requestAccess({
|
|
964
|
-
wallet
|
|
2210
|
+
wallet,
|
|
965
2211
|
challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
|
|
966
2212
|
verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
|
|
967
2213
|
});
|
|
2214
|
+
this.#picked(option, reason, verifyUrl2 ? a.response.identityKey : null);
|
|
2215
|
+
this.#attempt(option, "signed-in", started);
|
|
968
2216
|
this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
|
|
969
2217
|
} catch (e) {
|
|
970
2218
|
const err = e instanceof AccessError ? e : new AccessError("bad_request", String(e));
|
|
2219
|
+
this.#attempt(option, outcomeOf(e), started);
|
|
971
2220
|
this.#emit("bwalletx-access-denied", { code: err.code, message: err.message, have: err.have, need: err.need });
|
|
972
2221
|
}
|
|
973
2222
|
return;
|
|
974
2223
|
}
|
|
975
|
-
const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson(challengeUrl, { identityKey }) });
|
|
976
2224
|
const verifyUrl = this.#attr("verify-url");
|
|
977
|
-
const
|
|
978
|
-
|
|
2225
|
+
const { result: r, verified } = await signInAndVerify({
|
|
2226
|
+
wallet,
|
|
2227
|
+
// Metanet hands over its key on connect (one request at a time), so its sign-in is the signature alone.
|
|
2228
|
+
identityKey: cw.identityKey ?? void 0,
|
|
2229
|
+
skipWaitForAuthentication: option.id === METANET_OPTION_ID,
|
|
2230
|
+
// its connect() just checked the lock
|
|
2231
|
+
identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
|
|
2232
|
+
challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
|
|
2233
|
+
verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
|
|
2234
|
+
});
|
|
2235
|
+
if (!live2()) {
|
|
2236
|
+
const out = this.#attr("logout-url");
|
|
2237
|
+
if (verifyUrl && out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
if (verifyUrl) rememberIdentity(r.identityKey);
|
|
2241
|
+
const response = verifyUrl ? verified : null;
|
|
2242
|
+
this.#picked(option, reason, verifyUrl ? r.identityKey : null);
|
|
2243
|
+
if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
|
|
2244
|
+
this.#attempt(option, "signed-in", started);
|
|
2245
|
+
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
|
|
979
2246
|
} catch (error) {
|
|
980
|
-
|
|
2247
|
+
const outcome = outcomeOf(error);
|
|
2248
|
+
this.#attempt(option, outcome, started);
|
|
2249
|
+
if (!live2()) return;
|
|
2250
|
+
const closedQr = outcome === "cancelled" && option?.kind === "pairing";
|
|
2251
|
+
if (own) {
|
|
2252
|
+
if (closedQr) this.#phase = prev;
|
|
2253
|
+
else {
|
|
2254
|
+
this.#error = signInErrorText(error);
|
|
2255
|
+
this.#phase = "error";
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
if (!closedQr) this.#emit("bwalletx-error", { error });
|
|
981
2259
|
} finally {
|
|
982
|
-
|
|
2260
|
+
try {
|
|
2261
|
+
pairing.close();
|
|
2262
|
+
} catch {
|
|
2263
|
+
}
|
|
2264
|
+
if (live2()) {
|
|
2265
|
+
this.#busy = false;
|
|
2266
|
+
this.#viaMetanet = false;
|
|
2267
|
+
this.#render();
|
|
2268
|
+
}
|
|
983
2269
|
}
|
|
984
2270
|
}
|
|
985
2271
|
};
|
|
@@ -993,17 +2279,247 @@ function defineBwalletxSignin(tag = SIGNIN_TAG) {
|
|
|
993
2279
|
return C;
|
|
994
2280
|
}
|
|
995
2281
|
|
|
2282
|
+
// src/wallet-button.ts
|
|
2283
|
+
var BUTTON_TAG = "bwalletx-button";
|
|
2284
|
+
var CONNECTED_KEY = "bwx:connected";
|
|
2285
|
+
function migrateConnectedKey() {
|
|
2286
|
+
const had = dropLegacyConnectedKey();
|
|
2287
|
+
if (had && !rememberedWalletChoice()) rememberBwalletX("extension");
|
|
2288
|
+
return had;
|
|
2289
|
+
}
|
|
2290
|
+
function rememberBwalletX(substrate) {
|
|
2291
|
+
rememberWalletChoice({ id: "bwalletx", name: "bWalletX", kind: substrate === "pairing" ? "pairing" : "brc100", isBwalletX: true, substrate, connect: async () => {
|
|
2292
|
+
throw new Error("unused");
|
|
2293
|
+
} });
|
|
2294
|
+
}
|
|
2295
|
+
function dropLegacyConnectedKey() {
|
|
2296
|
+
try {
|
|
2297
|
+
const had = globalThis.localStorage?.getItem(CONNECTED_KEY) != null;
|
|
2298
|
+
if (had) globalThis.localStorage?.removeItem(CONNECTED_KEY);
|
|
2299
|
+
return had;
|
|
2300
|
+
} catch {
|
|
2301
|
+
return false;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
var LOOKUP = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
2305
|
+
var LEGACY = '[data-bwx-connect], .nav-connect, .nav-acct, a[href^="https://bwalletx.com/get"], a[href^="https://www.bwalletx.com/get"]';
|
|
2306
|
+
function getUrlFor(ua, touchMac = false) {
|
|
2307
|
+
if (/iPhone|iPad|iPod/i.test(ua) || touchMac && /Macintosh/.test(ua)) return "https://bwalletx.com/iphone";
|
|
2308
|
+
if (/Android/i.test(ua)) return "https://bwalletx.com/android";
|
|
2309
|
+
if (/Chrome\//.test(ua) && !/Edg\/|OPR\//.test(ua)) return "https://bwalletx.com/extension";
|
|
2310
|
+
return "https://bwalletx.com/get";
|
|
2311
|
+
}
|
|
2312
|
+
function stateFor(o) {
|
|
2313
|
+
if (o.connected) return "connected";
|
|
2314
|
+
if (o.found === null) return o.phone ? "available" : "checking";
|
|
2315
|
+
return o.found || o.phone ? "available" : "none";
|
|
2316
|
+
}
|
|
2317
|
+
function handleLabel(key, paymail) {
|
|
2318
|
+
if (paymail && /@bwalletx\.com$/i.test(paymail)) return "$" + paymail.split("@")[0];
|
|
2319
|
+
if (paymail) return paymail;
|
|
2320
|
+
return key.slice(0, 6) + "\u2026" + key.slice(-4);
|
|
2321
|
+
}
|
|
2322
|
+
var CHIP_CSS = `
|
|
2323
|
+
a.b,button.b{all:unset;box-sizing:border-box}
|
|
2324
|
+
.chip{background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.45);box-shadow:none}
|
|
2325
|
+
.chip:hover{background:#151517;border-color:#ffd24d}
|
|
2326
|
+
.chip .dot{width:8px;height:8px;border-radius:50%;background:#34d399;flex:none}
|
|
2327
|
+
.txt b{overflow:hidden;text-overflow:ellipsis;max-width:min(40vw,220px)}
|
|
2328
|
+
:host(:not([size])) button,:host(:not([size])) a.btn,:host([size=sm]) a.btn{padding:6px 12px 6px 6px;border-radius:8px;font-size:13px;gap:8px;min-height:36px}
|
|
2329
|
+
:host(:not([size])) .mark,:host([size=sm]) a.btn .mark{width:22px;height:22px}
|
|
2330
|
+
a.btn{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;cursor:pointer;
|
|
2331
|
+
padding:16px 20px;border-radius:12px;font-size:16px;font-weight:700;line-height:1.2;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;
|
|
2332
|
+
box-shadow:0 10px 15px -3px rgba(245,158,11,.2),0 4px 6px -4px rgba(245,158,11,.2)}
|
|
2333
|
+
a.btn:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}
|
|
2334
|
+
a.btn:focus-visible{outline:2px solid #b45309;outline-offset:3px}
|
|
2335
|
+
:host([size=md]) a.btn{padding:10px 16px;border-radius:10px;font-size:15px;gap:10px}
|
|
2336
|
+
`;
|
|
2337
|
+
var live = /* @__PURE__ */ new Set();
|
|
2338
|
+
function createClass2() {
|
|
2339
|
+
return class BwalletxButtonElement extends HTMLElement {
|
|
2340
|
+
#root;
|
|
2341
|
+
#state = "checking";
|
|
2342
|
+
#key = null;
|
|
2343
|
+
#paymail = null;
|
|
2344
|
+
#duplicate = false;
|
|
2345
|
+
constructor() {
|
|
2346
|
+
super();
|
|
2347
|
+
this.#root = this.attachShadow({ mode: "open" });
|
|
2348
|
+
}
|
|
2349
|
+
get state() {
|
|
2350
|
+
return this.#state;
|
|
2351
|
+
}
|
|
2352
|
+
get identityKey() {
|
|
2353
|
+
return this.#key;
|
|
2354
|
+
}
|
|
2355
|
+
connectedCallback() {
|
|
2356
|
+
if (live.size && ![...live].includes(this)) {
|
|
2357
|
+
this.#duplicate = true;
|
|
2358
|
+
this.hidden = true;
|
|
2359
|
+
console.warn("[bwalletx] A second <bwalletx-button> is on this page; it is hidden. Use one per page.");
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
live.add(this);
|
|
2363
|
+
this.#hideLegacy();
|
|
2364
|
+
migrateConnectedKey();
|
|
2365
|
+
const phone = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
2366
|
+
this.#set(stateFor({ connected: false, found: null, phone }));
|
|
2367
|
+
const ms = Number(this.getAttribute("discover-ms")) || 800;
|
|
2368
|
+
void discoverBwalletX(ms).then((f) => {
|
|
2369
|
+
if (!this.isConnected) return;
|
|
2370
|
+
if (this.#state !== "connected" && this.#state !== "connecting") this.#set(stateFor({ connected: false, found: !!f, phone }));
|
|
2371
|
+
}).catch(() => {
|
|
2372
|
+
if (this.#state === "checking") this.#set("none");
|
|
2373
|
+
});
|
|
2374
|
+
void this.#restoreSession();
|
|
2375
|
+
}
|
|
2376
|
+
/** A live session your server reports (session-url): the chip, with no wallet call. */
|
|
2377
|
+
async #restoreSession() {
|
|
2378
|
+
const url = this.getAttribute("session-url");
|
|
2379
|
+
if (!url) return;
|
|
2380
|
+
try {
|
|
2381
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
2382
|
+
const j = await r.json().catch(() => ({}));
|
|
2383
|
+
if (j.identityKey && this.isConnected && this.#state !== "connected" && this.#state !== "connecting") {
|
|
2384
|
+
this.#connected(j.identityKey);
|
|
2385
|
+
if (j.paymail) {
|
|
2386
|
+
this.#paymail = j.paymail;
|
|
2387
|
+
this.#render();
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
} catch {
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
disconnectedCallback() {
|
|
2394
|
+
if (!this.#duplicate) live.delete(this);
|
|
2395
|
+
}
|
|
2396
|
+
#hideLegacy() {
|
|
2397
|
+
const scope = this.closest("header, nav") ?? document;
|
|
2398
|
+
for (const el2 of Array.from(scope.querySelectorAll(LEGACY))) {
|
|
2399
|
+
if (el2 === this || el2.contains(this)) continue;
|
|
2400
|
+
el2.hidden = true;
|
|
2401
|
+
el2.style.setProperty("display", "none", "important");
|
|
2402
|
+
console.warn("[bwalletx] Hid a second wallet control next to <bwalletx-button>:", el2, ". Use only <bwalletx-button>; don't add your own Get or Connect links.");
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
#emit(type, detail) {
|
|
2406
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
2407
|
+
}
|
|
2408
|
+
#set(s) {
|
|
2409
|
+
this.#state = s;
|
|
2410
|
+
this.setAttribute("state", s);
|
|
2411
|
+
this.#render();
|
|
2412
|
+
}
|
|
2413
|
+
#connected(key) {
|
|
2414
|
+
this.#key = key;
|
|
2415
|
+
this.#set("connected");
|
|
2416
|
+
this.#emit("bwalletx-connected", { identityKey: key });
|
|
2417
|
+
const lookup = this.getAttribute("lookup-url");
|
|
2418
|
+
if (lookup === "off") return;
|
|
2419
|
+
void fetch((lookup ?? LOOKUP) + encodeURIComponent(key)).then((r) => r.ok ? r.json() : null).then((j) => {
|
|
2420
|
+
if (j && typeof j.paymail === "string" && this.#key === key) {
|
|
2421
|
+
this.#paymail = j.paymail;
|
|
2422
|
+
this.#render();
|
|
2423
|
+
}
|
|
2424
|
+
}).catch(() => void 0);
|
|
2425
|
+
}
|
|
2426
|
+
async connect() {
|
|
2427
|
+
if (this.#state === "connecting" || this.#state === "connected") return;
|
|
2428
|
+
const modal = pairingModal({ title: "Connect bWalletX" });
|
|
2429
|
+
this.#set("connecting");
|
|
2430
|
+
try {
|
|
2431
|
+
const { wallet, method } = await connectBwalletX({ pairing: modal });
|
|
2432
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
2433
|
+
rememberBwalletX(method === "pairing" ? "pairing" : method === "in-app" ? "in-app" : "extension");
|
|
2434
|
+
this.#connected(publicKey);
|
|
2435
|
+
} catch (e) {
|
|
2436
|
+
if (e?.code === "NOT_FOUND") this.#set("none");
|
|
2437
|
+
else {
|
|
2438
|
+
this.#set("available");
|
|
2439
|
+
if (e?.name !== "AbortError") console.warn("[bwalletx] connect:", e);
|
|
2440
|
+
}
|
|
2441
|
+
} finally {
|
|
2442
|
+
try {
|
|
2443
|
+
modal.close();
|
|
2444
|
+
} catch {
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
disconnect() {
|
|
2449
|
+
try {
|
|
2450
|
+
clearPairing();
|
|
2451
|
+
} catch {
|
|
2452
|
+
}
|
|
2453
|
+
try {
|
|
2454
|
+
window.postMessage({ type: "bwallet:disconnect" }, location.origin);
|
|
2455
|
+
} catch {
|
|
2456
|
+
}
|
|
2457
|
+
this.#key = null;
|
|
2458
|
+
this.#paymail = null;
|
|
2459
|
+
dropLegacyConnectedKey();
|
|
2460
|
+
if (rememberedWalletChoice()?.type === BWALLETX_TYPE) forgetWalletChoice();
|
|
2461
|
+
this.#set("available");
|
|
2462
|
+
this.#emit("bwalletx-disconnected");
|
|
2463
|
+
}
|
|
2464
|
+
#render() {
|
|
2465
|
+
const esc = (s2) => s2.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
2466
|
+
const size = this.getAttribute("size") ?? "sm";
|
|
2467
|
+
const px = size === "lg" ? 20 : size === "md" ? 17 : 15;
|
|
2468
|
+
const mark = `<span class="mark" part="mark">${markSvg(px)}</span>`;
|
|
2469
|
+
const css = `<style>${SIGNIN_CSS}${CHIP_CSS}</style>`;
|
|
2470
|
+
const s = this.#state;
|
|
2471
|
+
if (s === "none") {
|
|
2472
|
+
const href = this.getAttribute("get-url") ?? getUrlFor(navigator.userAgent, (navigator.maxTouchPoints ?? 0) > 1);
|
|
2473
|
+
this.#root.innerHTML = `${css}<a class="btn" part="button" href="${esc(href)}" target="_blank" rel="noopener">${mark}<span class="txt" part="text"><b part="label">Get bWalletX</b></span></a>`;
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (s === "connected" && this.#key) {
|
|
2477
|
+
const name = handleLabel(this.#key, this.#paymail);
|
|
2478
|
+
this.#root.innerHTML = `${css}<button type="button" class="chip" part="button chip" title="Connected to bWalletX. Click to disconnect." aria-label="Connected as ${esc(name)}. Disconnect">${mark}<span class="txt" part="text"><b part="label">${esc(name)}</b></span><span class="dot" aria-hidden="true"></span></button>`;
|
|
2479
|
+
this.#root.querySelector("button").onclick = () => this.disconnect();
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
const busy = s === "connecting" || s === "checking";
|
|
2483
|
+
this.#root.innerHTML = `${css}<button type="button" part="button"${busy ? ' aria-busy="true"' : ""}>${busy ? '<span class="mark" part="mark"><span class="spin" aria-hidden="true"></span></span>' : mark}<span class="txt" part="text"><b part="label">${s === "connecting" ? "Connecting\u2026" : "Connect bWalletX"}</b></span></button>`;
|
|
2484
|
+
this.#root.querySelector("button").onclick = () => void this.connect();
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
function defineBwalletxButton(tag = BUTTON_TAG) {
|
|
2489
|
+
if (typeof customElements === "undefined" || customElements.get(tag)) return;
|
|
2490
|
+
customElements.define(tag, createClass2());
|
|
2491
|
+
}
|
|
2492
|
+
function _resetBwalletxButtons() {
|
|
2493
|
+
live.clear();
|
|
2494
|
+
}
|
|
2495
|
+
|
|
996
2496
|
// src/define.ts
|
|
997
2497
|
defineBwalletxSignin();
|
|
2498
|
+
defineBwalletxButton();
|
|
998
2499
|
// Annotate the CommonJS export names for ESM import in node:
|
|
999
2500
|
0 && (module.exports = {
|
|
2501
|
+
BUTTON_TAG,
|
|
2502
|
+
CONNECTED_KEY,
|
|
2503
|
+
CONTINUE_OTHER_LABEL,
|
|
1000
2504
|
GET_URL,
|
|
2505
|
+
MENU_CSS,
|
|
2506
|
+
PENDING_LABEL,
|
|
2507
|
+
PENDING_LABEL_OTHER,
|
|
2508
|
+
PENDING_NOTE,
|
|
1001
2509
|
SIGNIN_CSS,
|
|
1002
2510
|
SIGNIN_TAG,
|
|
2511
|
+
SWITCH_NOTE,
|
|
1003
2512
|
WEB_WALLET_URL,
|
|
2513
|
+
_resetBwalletxButtons,
|
|
1004
2514
|
buildAuthorizeUrl,
|
|
2515
|
+
defineBwalletxButton,
|
|
1005
2516
|
defineBwalletxSignin,
|
|
2517
|
+
dropLegacyConnectedKey,
|
|
2518
|
+
getUrlFor,
|
|
2519
|
+
handleLabel,
|
|
1006
2520
|
markSvg,
|
|
1007
|
-
|
|
2521
|
+
migrateConnectedKey,
|
|
2522
|
+
signinText,
|
|
2523
|
+
stateFor
|
|
1008
2524
|
});
|
|
1009
2525
|
//# sourceMappingURL=element.cjs.map
|