@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/react.cjs
CHANGED
|
@@ -34,12 +34,16 @@ __export(react_exports, {
|
|
|
34
34
|
BwalletxProvider: () => BwalletxProvider,
|
|
35
35
|
BwalletxSignin: () => BwalletxSignin,
|
|
36
36
|
PaymentError: () => PaymentError,
|
|
37
|
+
accountFor: () => accountFor,
|
|
38
|
+
autoChoice: () => autoChoice,
|
|
37
39
|
brc100Option: () => brc100Option,
|
|
38
40
|
default: () => react_default,
|
|
39
41
|
discoverWallets: () => discoverWallets,
|
|
40
42
|
handcashAdapter: () => handcashAdapter,
|
|
41
43
|
pairingOption: () => pairingOption,
|
|
42
|
-
|
|
44
|
+
signinStateFor: () => signinStateFor,
|
|
45
|
+
useBwalletX: () => useBwalletX,
|
|
46
|
+
useBwalletXAccount: () => useBwalletXAccount
|
|
43
47
|
});
|
|
44
48
|
module.exports = __toCommonJS(react_exports);
|
|
45
49
|
var import_react = require("react");
|
|
@@ -446,6 +450,74 @@ function declareWalletTheme(theme) {
|
|
|
446
450
|
if (meta.content !== id) meta.content = id;
|
|
447
451
|
}
|
|
448
452
|
|
|
453
|
+
// src/account.ts
|
|
454
|
+
var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
455
|
+
var BAVATAR_URL = "https://chat.bwalletx.com/api/bavatar/";
|
|
456
|
+
function shortKey(key) {
|
|
457
|
+
return key.length > 12 ? `${key.slice(0, 6)}\u2026${key.slice(-4)}` : key;
|
|
458
|
+
}
|
|
459
|
+
function accountLabel(a) {
|
|
460
|
+
if (a.paymail && /@bwalletx\.com$/i.test(a.paymail)) return "$" + a.paymail.split("@")[0];
|
|
461
|
+
if (a.handle) return a.handle;
|
|
462
|
+
if (a.paymail) return a.paymail;
|
|
463
|
+
return a.identityKey ? shortKey(a.identityKey) : "";
|
|
464
|
+
}
|
|
465
|
+
function avatarUrlFor(identityKey) {
|
|
466
|
+
return identityKey && /^0[23][0-9a-fA-F]{64}$/.test(identityKey) ? `${BAVATAR_URL}${identityKey.toLowerCase()}.png` : null;
|
|
467
|
+
}
|
|
468
|
+
var cache = /* @__PURE__ */ new Map();
|
|
469
|
+
function lookupPaymail(identityKey, url = PAYMAIL_LOOKUP_URL) {
|
|
470
|
+
if (url === "off" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
471
|
+
const k = `${url}|${identityKey}`;
|
|
472
|
+
let p = cache.get(k);
|
|
473
|
+
if (!p) {
|
|
474
|
+
p = fetch(url + encodeURIComponent(identityKey)).then((r) => r.ok ? r.json() : null).then((j) => j && typeof j.paymail === "string" ? j.paymail : null).catch(() => null);
|
|
475
|
+
cache.set(k, p);
|
|
476
|
+
}
|
|
477
|
+
return p;
|
|
478
|
+
}
|
|
479
|
+
function isUserRejection(e) {
|
|
480
|
+
const code = e?.code;
|
|
481
|
+
if (code === "ERR_PERMISSION_DENIED" || code === 4001) return true;
|
|
482
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
483
|
+
return /denied|reject|cancel|declin|refused|not now|user closed/i.test(msg);
|
|
484
|
+
}
|
|
485
|
+
function signInErrorText(e) {
|
|
486
|
+
if (isUserRejection(e)) return "Sign-in cancelled";
|
|
487
|
+
if (e?.name === "MetanetUnreachableError") return e.message;
|
|
488
|
+
const code = e?.code;
|
|
489
|
+
if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
|
|
490
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
491
|
+
if (/lock/i.test(msg)) return "Your wallet is locked. Unlock it and try again";
|
|
492
|
+
if (/timeout|timed out|expired/i.test(msg)) return "The wallet did not answer in time";
|
|
493
|
+
if (/origin/i.test(msg)) return "This page is not on the site\u2019s configured origin";
|
|
494
|
+
return "Sign-in failed";
|
|
495
|
+
}
|
|
496
|
+
async function copyText(text) {
|
|
497
|
+
try {
|
|
498
|
+
await navigator.clipboard.writeText(text);
|
|
499
|
+
return true;
|
|
500
|
+
} catch {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
var IDENTITY_HINT_KEY = "bwx:identityKey";
|
|
505
|
+
function rememberedIdentity() {
|
|
506
|
+
try {
|
|
507
|
+
const k = globalThis.localStorage?.getItem(IDENTITY_HINT_KEY) ?? "";
|
|
508
|
+
return /^0[23][0-9a-f]{64}$/i.test(k) ? k : void 0;
|
|
509
|
+
} catch {
|
|
510
|
+
return void 0;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function rememberIdentity(key) {
|
|
514
|
+
try {
|
|
515
|
+
if (key) globalThis.localStorage?.setItem(IDENTITY_HINT_KEY, key);
|
|
516
|
+
else globalThis.localStorage?.removeItem(IDENTITY_HINT_KEY);
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
449
521
|
// src/discover.ts
|
|
450
522
|
var import_sdk3 = require("@bsv/sdk");
|
|
451
523
|
var RDNS_EXTENSION = "com.bwalletx.extension";
|
|
@@ -457,7 +529,7 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
|
457
529
|
return new Promise((resolve) => {
|
|
458
530
|
let done = false;
|
|
459
531
|
let timer;
|
|
460
|
-
const
|
|
532
|
+
const finish2 = (r) => {
|
|
461
533
|
if (done) return;
|
|
462
534
|
done = true;
|
|
463
535
|
if (timer !== void 0) clearTimeout(timer);
|
|
@@ -468,24 +540,19 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
|
468
540
|
const d = e.detail;
|
|
469
541
|
const rdns = d?.info?.rdns ?? "";
|
|
470
542
|
if (d?.wallet && BWALLETX_RDNS.includes(rdns)) {
|
|
471
|
-
|
|
543
|
+
finish2({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
|
|
472
544
|
}
|
|
473
545
|
};
|
|
474
|
-
const cwiNow = () => {
|
|
475
|
-
const cwi = window.CWI;
|
|
476
|
-
if (cwi) finish({ wallet: cwi, method: "in-app", rdns: null });
|
|
477
|
-
return !!cwi;
|
|
478
|
-
};
|
|
479
546
|
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
480
547
|
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
481
|
-
if (done
|
|
548
|
+
if (done) return;
|
|
482
549
|
if (isFramed()) {
|
|
483
|
-
void probeXdm(Math.max(ms, 1200)).then((w) =>
|
|
550
|
+
void probeXdm(Math.max(ms, 1200)).then((w) => finish2(w ? { wallet: w, method: "in-app", rdns: null } : null));
|
|
484
551
|
return;
|
|
485
552
|
}
|
|
486
553
|
timer = setTimeout(() => {
|
|
487
554
|
timer = void 0;
|
|
488
|
-
|
|
555
|
+
finish2(null);
|
|
489
556
|
}, ms);
|
|
490
557
|
});
|
|
491
558
|
}
|
|
@@ -589,27 +656,68 @@ async function connectBwalletX(opts = {}) {
|
|
|
589
656
|
status("pairing");
|
|
590
657
|
return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
|
|
591
658
|
}
|
|
659
|
+
async function waitIfLocked(wallet) {
|
|
660
|
+
let ok = false;
|
|
661
|
+
try {
|
|
662
|
+
ok = !!(await wallet.isAuthenticated({}))?.authenticated;
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
if (!ok) await Promise.resolve().then(() => wallet.waitForAuthentication({})).catch(() => {
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
function challengeWantsIdentityKey(e) {
|
|
669
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
670
|
+
return /identityKey/i.test(msg);
|
|
671
|
+
}
|
|
592
672
|
async function signInWithBwalletX(opts) {
|
|
593
673
|
const { wallet, method } = await connectBwalletX(opts);
|
|
594
|
-
if (method !== "pairing") await wallet
|
|
674
|
+
if (method !== "pairing" && !opts.skipWaitForAuthentication) await waitIfLocked(wallet);
|
|
675
|
+
const status = () => {
|
|
676
|
+
try {
|
|
677
|
+
opts.onStatus?.("signing");
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
const sign = (c2) => wallet.createSignature({
|
|
682
|
+
protocolID: c2.protocolID ?? LOGIN_PROTOCOL,
|
|
683
|
+
keyID: c2.keyID,
|
|
684
|
+
counterparty: "anyone",
|
|
685
|
+
data: Array.from(new TextEncoder().encode(c2.message))
|
|
595
686
|
});
|
|
596
|
-
const
|
|
687
|
+
const keyFirst = async () => {
|
|
688
|
+
const { publicKey: identityKey2 } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
|
|
689
|
+
status();
|
|
690
|
+
const c2 = await atStage("challenge", () => opts.challenge(identityKey2));
|
|
691
|
+
const { signature: signature2 } = await atStage("signature", () => sign(c2));
|
|
692
|
+
return { identityKey: identityKey2, signature: Array.from(signature2), nonce: c2.nonce, method, wallet };
|
|
693
|
+
};
|
|
694
|
+
if (opts.challengeNeedsIdentityKey && !opts.identityKey) return keyFirst();
|
|
695
|
+
let c;
|
|
597
696
|
try {
|
|
598
|
-
|
|
599
|
-
} catch {
|
|
697
|
+
c = await atStage("challenge", () => opts.challenge(opts.identityKey));
|
|
698
|
+
} catch (e) {
|
|
699
|
+
if (!opts.identityKey && challengeWantsIdentityKey(e)) return keyFirst();
|
|
700
|
+
throw e;
|
|
600
701
|
}
|
|
601
|
-
|
|
602
|
-
const { signature } = await
|
|
603
|
-
"
|
|
604
|
-
() =>
|
|
605
|
-
|
|
606
|
-
keyID: c.keyID,
|
|
607
|
-
counterparty: "anyone",
|
|
608
|
-
data: Array.from(new TextEncoder().encode(c.message))
|
|
609
|
-
})
|
|
610
|
-
);
|
|
702
|
+
status();
|
|
703
|
+
const [{ publicKey: identityKey }, { signature }] = await Promise.all([
|
|
704
|
+
opts.identityKey ? Promise.resolve({ publicKey: opts.identityKey }) : atStage("identity", () => wallet.getPublicKey({ identityKey: true })),
|
|
705
|
+
atStage("signature", () => sign(c))
|
|
706
|
+
]);
|
|
611
707
|
return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
|
|
612
708
|
}
|
|
709
|
+
async function signInAndVerify(opts) {
|
|
710
|
+
const hinted = !opts.identityKey && !!opts.identityKeyHint;
|
|
711
|
+
const r = await signInWithBwalletX({ ...opts, identityKey: opts.identityKey ?? opts.identityKeyHint });
|
|
712
|
+
try {
|
|
713
|
+
return { result: r, verified: await atStage("verify", () => opts.verify(r)) };
|
|
714
|
+
} catch (e) {
|
|
715
|
+
const status = e?.status;
|
|
716
|
+
if (!hinted || status !== 401) throw e;
|
|
717
|
+
const again = await signInWithBwalletX({ ...opts, wallet: r.wallet, identityKey: void 0, skipWaitForAuthentication: true });
|
|
718
|
+
return { result: again, verified: await atStage("verify", () => opts.verify(again)) };
|
|
719
|
+
}
|
|
720
|
+
}
|
|
613
721
|
async function atStage(stage, run) {
|
|
614
722
|
try {
|
|
615
723
|
return await run();
|
|
@@ -620,11 +728,556 @@ async function atStage(stage, run) {
|
|
|
620
728
|
}
|
|
621
729
|
}
|
|
622
730
|
|
|
731
|
+
// src/metanet.ts
|
|
732
|
+
var import_sdk4 = require("@bsv/sdk");
|
|
733
|
+
|
|
734
|
+
// src/device.ts
|
|
735
|
+
function isTouchDevice(env = globalThis) {
|
|
736
|
+
const nav = env.navigator;
|
|
737
|
+
if (!nav) return false;
|
|
738
|
+
const ua = nav.userAgent ?? "";
|
|
739
|
+
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
740
|
+
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
741
|
+
try {
|
|
742
|
+
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
743
|
+
} catch {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/metanet.ts
|
|
749
|
+
var METANET_TYPE = "metanet";
|
|
750
|
+
var METANET_OPTION_ID = METANET_TYPE;
|
|
751
|
+
var METANET_LABEL = "Use Metanet Desktop or another BRC-100 wallet";
|
|
752
|
+
var METANET_NOTE = "Metanet Desktop must be running on this computer";
|
|
753
|
+
var METANET_REMEMBERED_NOTE = "Signs in with Metanet Desktop on this computer.";
|
|
754
|
+
var NOT_YOU_LABEL = "Not you? Use another wallet";
|
|
755
|
+
var METANET_UNREACHABLE = "We couldn\u2019t reach Metanet Desktop. Open it, then try again.";
|
|
756
|
+
var METANET_ANSWER_MS = 4e3;
|
|
757
|
+
var METANET_SILENT_MS = 2e3;
|
|
758
|
+
var MetanetUnreachableError = class extends Error {
|
|
759
|
+
code = "NOT_FOUND";
|
|
760
|
+
constructor() {
|
|
761
|
+
super(METANET_UNREACHABLE);
|
|
762
|
+
this.name = "MetanetUnreachableError";
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
function inAnyFrame(win = typeof window === "undefined" ? void 0 : window) {
|
|
766
|
+
if (!win) return false;
|
|
767
|
+
try {
|
|
768
|
+
return win.top !== win.self;
|
|
769
|
+
} catch {
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function metanetOffered(env = {}) {
|
|
774
|
+
const win = env.window ?? (typeof window === "undefined" ? void 0 : window);
|
|
775
|
+
if (!win) return false;
|
|
776
|
+
const nav = env.navigator ?? (typeof navigator === "undefined" ? void 0 : navigator);
|
|
777
|
+
const mm = env.matchMedia ?? (typeof matchMedia === "undefined" ? void 0 : (q) => matchMedia(q));
|
|
778
|
+
return !isTouchDevice({ navigator: nav, matchMedia: mm }) && !inAnyFrame(win);
|
|
779
|
+
}
|
|
780
|
+
function client(o) {
|
|
781
|
+
if (o.make) return o.make();
|
|
782
|
+
const originator = o.originator ?? (typeof location !== "undefined" && location.host ? location.host : void 0);
|
|
783
|
+
return new import_sdk4.WalletClient("json-api", originator);
|
|
784
|
+
}
|
|
785
|
+
async function answers(wallet) {
|
|
786
|
+
return Promise.resolve().then(() => wallet.getVersion({})).then((v) => typeof v?.version === "string", () => false);
|
|
787
|
+
}
|
|
788
|
+
async function connectMetanetDesktop(o = {}) {
|
|
789
|
+
let wallet;
|
|
790
|
+
try {
|
|
791
|
+
wallet = client(o);
|
|
792
|
+
} catch {
|
|
793
|
+
throw new MetanetUnreachableError();
|
|
794
|
+
}
|
|
795
|
+
let timer;
|
|
796
|
+
const ok = await Promise.race([answers(wallet), new Promise((r) => timer = setTimeout(() => r(false), o.timeoutMs ?? METANET_ANSWER_MS))]);
|
|
797
|
+
clearTimeout(timer);
|
|
798
|
+
if (!ok) throw new MetanetUnreachableError();
|
|
799
|
+
return wallet;
|
|
800
|
+
}
|
|
801
|
+
function metanetOption(o = {}) {
|
|
802
|
+
const opt = {
|
|
803
|
+
id: METANET_OPTION_ID,
|
|
804
|
+
name: METANET_LABEL,
|
|
805
|
+
kind: "brc100",
|
|
806
|
+
isBwalletX: false,
|
|
807
|
+
substrate: "json-api",
|
|
808
|
+
async connect() {
|
|
809
|
+
const wallet = await connectMetanetDesktop(o);
|
|
810
|
+
opt.wallet = wallet;
|
|
811
|
+
await waitIfLocked(wallet);
|
|
812
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
813
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: opt.id, kind: "brc100" };
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
return opt;
|
|
817
|
+
}
|
|
818
|
+
var KNOWN_KEYS_KEY = "bwx:known-keys";
|
|
819
|
+
var KEY_RE = /^0[23][0-9a-f]{64}$/;
|
|
820
|
+
var MAX_KNOWN = 8;
|
|
821
|
+
function knownIdentityKeys() {
|
|
822
|
+
try {
|
|
823
|
+
const v = JSON.parse(globalThis.localStorage?.getItem(KNOWN_KEYS_KEY) || "[]");
|
|
824
|
+
return Array.isArray(v) ? v.filter((k) => typeof k === "string" && KEY_RE.test(k)) : [];
|
|
825
|
+
} catch {
|
|
826
|
+
return [];
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function isKnownIdentityKey(key) {
|
|
830
|
+
if (!key) return false;
|
|
831
|
+
const k = key.toLowerCase();
|
|
832
|
+
if (!KEY_RE.test(k)) return false;
|
|
833
|
+
return knownIdentityKeys().includes(k) || rememberedIdentity()?.toLowerCase() === k;
|
|
834
|
+
}
|
|
835
|
+
function rememberKnownIdentityKey(key) {
|
|
836
|
+
const k = (key ?? "").toLowerCase();
|
|
837
|
+
if (!KEY_RE.test(k)) return;
|
|
838
|
+
try {
|
|
839
|
+
const list = [k, ...knownIdentityKeys().filter((x) => x !== k)].slice(0, MAX_KNOWN);
|
|
840
|
+
globalThis.localStorage?.setItem(KNOWN_KEYS_KEY, JSON.stringify(list));
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
var triedThisLoad = false;
|
|
845
|
+
function silentAttemptUsed() {
|
|
846
|
+
return triedThisLoad;
|
|
847
|
+
}
|
|
848
|
+
function claimSilentAttempt() {
|
|
849
|
+
if (triedThisLoad) return false;
|
|
850
|
+
triedThisLoad = true;
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
async function trySilentMetanet(o = {}) {
|
|
854
|
+
if (!metanetOffered()) return { ok: false, stop: "not-here" };
|
|
855
|
+
if (!claimSilentAttempt()) return { ok: false, stop: "used" };
|
|
856
|
+
const run = async () => {
|
|
857
|
+
let wallet;
|
|
858
|
+
try {
|
|
859
|
+
wallet = client(o);
|
|
860
|
+
} catch {
|
|
861
|
+
return { ok: false, stop: "unreachable" };
|
|
862
|
+
}
|
|
863
|
+
if (!await answers(wallet)) return { ok: false, stop: "unreachable" };
|
|
864
|
+
const unlocked = await Promise.resolve().then(() => wallet.isAuthenticated({})).then((r2) => r2?.authenticated === true, () => false);
|
|
865
|
+
if (!unlocked) return { ok: false, stop: "locked" };
|
|
866
|
+
const identityKey = await Promise.resolve().then(() => wallet.getPublicKey({ identityKey: true })).then((r2) => r2.publicKey, () => null);
|
|
867
|
+
if (!identityKey) return { ok: false, stop: "unreachable" };
|
|
868
|
+
if (!isKnownIdentityKey(identityKey)) return { ok: false, stop: "unknown-key" };
|
|
869
|
+
return { ok: true, wallet, identityKey };
|
|
870
|
+
};
|
|
871
|
+
let timer;
|
|
872
|
+
const r = await Promise.race([
|
|
873
|
+
run().catch(() => ({ ok: false, stop: "unreachable" })),
|
|
874
|
+
new Promise((res) => timer = setTimeout(() => res({ ok: false, stop: "slow" }), o.windowMs ?? METANET_SILENT_MS))
|
|
875
|
+
]);
|
|
876
|
+
clearTimeout(timer);
|
|
877
|
+
return r;
|
|
878
|
+
}
|
|
879
|
+
var warnedLogout = false;
|
|
880
|
+
var isProduction = () => {
|
|
881
|
+
try {
|
|
882
|
+
return globalThis.process?.env?.NODE_ENV === "production";
|
|
883
|
+
} catch {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
function warnIfNoLogout(o) {
|
|
888
|
+
if (!o.verifyUrl || o.logoutUrl || warnedLogout || isProduction() || typeof console === "undefined") return false;
|
|
889
|
+
warnedLogout = true;
|
|
890
|
+
console.warn(
|
|
891
|
+
`@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.`
|
|
892
|
+
);
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/wallets.ts
|
|
897
|
+
var import_sdk5 = require("@bsv/sdk");
|
|
898
|
+
async function brc100Connected(wallet, o, c = {}) {
|
|
899
|
+
if (c.identity === false) return { wallet, payer: wallet, identityKey: null, optionId: o.id, kind: o.kind };
|
|
900
|
+
await waitIfLocked(wallet);
|
|
901
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
902
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
|
|
903
|
+
}
|
|
904
|
+
function brc100Option(wallet, info) {
|
|
905
|
+
const opt = {
|
|
906
|
+
id: info.id,
|
|
907
|
+
name: info.name,
|
|
908
|
+
icon: info.icon,
|
|
909
|
+
rdns: info.rdns,
|
|
910
|
+
kind: "brc100",
|
|
911
|
+
isBwalletX: info.isBwalletX ?? false,
|
|
912
|
+
substrate: info.substrate,
|
|
913
|
+
wallet,
|
|
914
|
+
connect: (c) => brc100Connected(wallet, opt, c)
|
|
915
|
+
};
|
|
916
|
+
return opt;
|
|
917
|
+
}
|
|
918
|
+
function pairingOption(pairing) {
|
|
919
|
+
const opt = {
|
|
920
|
+
id: "bwalletx-pairing",
|
|
921
|
+
name: "bWalletX on your phone",
|
|
922
|
+
kind: "pairing",
|
|
923
|
+
isBwalletX: true,
|
|
924
|
+
substrate: "pairing",
|
|
925
|
+
async connect(c) {
|
|
926
|
+
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
927
|
+
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
928
|
+
return brc100Connected(w, opt, c);
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
return opt;
|
|
932
|
+
}
|
|
933
|
+
function handcashAdapter(o) {
|
|
934
|
+
const opt = {
|
|
935
|
+
id: "handcash",
|
|
936
|
+
name: "HandCash",
|
|
937
|
+
icon: o.icon,
|
|
938
|
+
kind: "handcash",
|
|
939
|
+
isBwalletX: false,
|
|
940
|
+
substrate: "adapter",
|
|
941
|
+
async connect() {
|
|
942
|
+
const p = await o.login();
|
|
943
|
+
return {
|
|
944
|
+
wallet: null,
|
|
945
|
+
payer: o.pay ? { pay: o.pay } : null,
|
|
946
|
+
identityKey: p.identityKey ?? null,
|
|
947
|
+
handle: p.handle,
|
|
948
|
+
paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
|
|
949
|
+
optionId: opt.id,
|
|
950
|
+
kind: "handcash",
|
|
951
|
+
transitional: true
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
return opt;
|
|
956
|
+
}
|
|
957
|
+
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
958
|
+
async function probe(substrate, ms) {
|
|
959
|
+
try {
|
|
960
|
+
const w = new import_sdk5.WalletClient(substrate);
|
|
961
|
+
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
962
|
+
return ok ? w : null;
|
|
963
|
+
} catch {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function cwiIsAnnounced(cwi, announced) {
|
|
968
|
+
return announced.some((o) => o.wallet === cwi || o.rdns === RDNS_MOBILE);
|
|
969
|
+
}
|
|
970
|
+
async function discoverWallets(o = {}) {
|
|
971
|
+
const out = [];
|
|
972
|
+
const ms = o.ms ?? 300;
|
|
973
|
+
if (typeof window !== "undefined") {
|
|
974
|
+
const seen = /* @__PURE__ */ new Map();
|
|
975
|
+
const onAnnounce = (e) => {
|
|
976
|
+
const d = e.detail;
|
|
977
|
+
if (!d?.wallet) return;
|
|
978
|
+
const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
|
|
979
|
+
if (seen.has(rdns)) return;
|
|
980
|
+
const isB = BWALLETX_RDNS.includes(rdns);
|
|
981
|
+
const substrate = rdns === RDNS_EXTENSION ? "extension" : rdns === RDNS_MOBILE ? "in-app" : "injected";
|
|
982
|
+
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB, substrate }));
|
|
983
|
+
};
|
|
984
|
+
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
985
|
+
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
986
|
+
const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
|
|
987
|
+
await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
|
|
988
|
+
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
989
|
+
out.push(...seen.values());
|
|
990
|
+
if (![...seen.values()].some((w) => w.isBwalletX) && isFramed()) {
|
|
991
|
+
const x = await probeXdm(Math.max(ms, 1200));
|
|
992
|
+
if (x) out.push(brc100Option(x, { id: "bwalletx-xdm", name: "bWalletX", isBwalletX: true, substrate: "xdm" }));
|
|
993
|
+
}
|
|
994
|
+
const cwi = window.CWI;
|
|
995
|
+
if (cwi && !cwiIsAnnounced(cwi, [...seen.values()])) {
|
|
996
|
+
out.push(brc100Option(cwi, { id: "window.CWI", name: seen.size ? "Another browser wallet" : "Browser wallet", isBwalletX: false, substrate: "window.CWI" }));
|
|
997
|
+
}
|
|
998
|
+
const [json, cicada] = await local;
|
|
999
|
+
if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)", substrate: "json-api" }));
|
|
1000
|
+
if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)", substrate: "cicada" }));
|
|
1001
|
+
}
|
|
1002
|
+
if (o.pairing) out.push(pairingOption(o.pairing));
|
|
1003
|
+
out.push(...o.extra ?? []);
|
|
1004
|
+
const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
|
|
1005
|
+
return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
|
|
1006
|
+
}
|
|
1007
|
+
var DESKTOP_OPTION_ID = "desktop";
|
|
1008
|
+
function autoChoice(list, force = false) {
|
|
1009
|
+
const auto = list.filter((w) => !isLocalDesktop(w));
|
|
1010
|
+
const detected = auto.filter((w) => w.kind === "brc100");
|
|
1011
|
+
const others = auto.filter((w) => w.kind === "handcash" || w.kind === "custom");
|
|
1012
|
+
const hasLocal = auto.length !== list.length;
|
|
1013
|
+
if (force) return detected[0] ?? auto.find((w) => w.kind !== "pairing") ?? auto[0] ?? null;
|
|
1014
|
+
if (!list.length) return null;
|
|
1015
|
+
if (list.length === 1) return hasLocal ? void 0 : list[0];
|
|
1016
|
+
if (detected.length === 1 && others.length === 0 && !hasLocal) return detected[0];
|
|
1017
|
+
if (detected.length === 0 && others.length === 0) return auto[0] ?? void 0;
|
|
1018
|
+
return void 0;
|
|
1019
|
+
}
|
|
1020
|
+
function isLocalDesktop(w) {
|
|
1021
|
+
return w.substrate === "json-api" || w.substrate === "cicada" || w.id === "localhost:3321" || w.id === "localhost:3301" || w.id === DESKTOP_OPTION_ID;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/choice.ts
|
|
1025
|
+
var BWALLETX_TYPE = "bwalletx";
|
|
1026
|
+
function substrateOf(o) {
|
|
1027
|
+
if (o.substrate) return o.substrate;
|
|
1028
|
+
if (o.kind === "pairing") return "pairing";
|
|
1029
|
+
if (o.isBwalletX && o.kind === "brc100") return o.rdns === RDNS_MOBILE ? "in-app" : o.id === "bwalletx-xdm" ? "xdm" : "extension";
|
|
1030
|
+
if (o.kind === "handcash" || o.kind === "custom") return "adapter";
|
|
1031
|
+
if (o.rdns === RDNS_EXTENSION) return "extension";
|
|
1032
|
+
if (o.rdns === RDNS_MOBILE) return "in-app";
|
|
1033
|
+
if (o.id === "window.CWI") return "window.CWI";
|
|
1034
|
+
if (o.id === "localhost:3321" || o.id === DESKTOP_OPTION_ID) return "json-api";
|
|
1035
|
+
if (o.id === "localhost:3301") return "cicada";
|
|
1036
|
+
return "injected";
|
|
1037
|
+
}
|
|
1038
|
+
function walletTypeOf(o) {
|
|
1039
|
+
return o.isBwalletX ? BWALLETX_TYPE : o.rdns ?? o.id;
|
|
1040
|
+
}
|
|
1041
|
+
var OWN_MARKERS = ["extension", "in-app", "xdm"];
|
|
1042
|
+
function isPresentBwalletX(o) {
|
|
1043
|
+
return o.isBwalletX && o.kind === "brc100" && !!o.wallet && OWN_MARKERS.includes(substrateOf(o));
|
|
1044
|
+
}
|
|
1045
|
+
function hasBwalletX(list) {
|
|
1046
|
+
return list.some(isPresentBwalletX);
|
|
1047
|
+
}
|
|
1048
|
+
function otherDetected(list) {
|
|
1049
|
+
return list.filter((o) => !o.isBwalletX && o.kind === "brc100" && !!o.wallet);
|
|
1050
|
+
}
|
|
1051
|
+
function chooserOrder(list) {
|
|
1052
|
+
const rank = (o) => isPresentBwalletX(o) ? 0 : o.isBwalletX ? 1 : o.kind === "brc100" ? 2 : o.kind === "custom" ? 3 : 4;
|
|
1053
|
+
return list.map((o, i) => ({ o, i })).sort((a, b) => rank(a.o) - rank(b.o) || a.i - b.i).map((x) => x.o);
|
|
1054
|
+
}
|
|
1055
|
+
function decideWallet(list, o = {}) {
|
|
1056
|
+
if (o.walletId) {
|
|
1057
|
+
const w = list.find((x) => x.id === o.walletId);
|
|
1058
|
+
if (w) return { option: w, reason: "requested" };
|
|
1059
|
+
}
|
|
1060
|
+
if (o.chooser) return { option: null, reason: list.length || o.picker !== false ? "chooser" : "none" };
|
|
1061
|
+
if (o.fastPath === false) {
|
|
1062
|
+
const a = autoChoice([...list], o.picker === false);
|
|
1063
|
+
return a === void 0 ? { option: null, reason: "chooser" } : a === null ? { option: null, reason: "none" } : { option: a, reason: "legacy" };
|
|
1064
|
+
}
|
|
1065
|
+
if (o.remembered && !o.preferBwalletX) {
|
|
1066
|
+
const m = matchRemembered(list, o.remembered);
|
|
1067
|
+
if (m) return { option: m, reason: "remembered" };
|
|
1068
|
+
}
|
|
1069
|
+
const b = list.find(isPresentBwalletX);
|
|
1070
|
+
if (b) return { option: b, reason: "bwalletx" };
|
|
1071
|
+
if (o.picker === false) {
|
|
1072
|
+
const phone = list.find((x) => x.isBwalletX);
|
|
1073
|
+
return phone ? { option: phone, reason: "bwalletx-phone" } : { option: null, reason: "none" };
|
|
1074
|
+
}
|
|
1075
|
+
return list.length ? { option: null, reason: "chooser" } : { option: null, reason: "none" };
|
|
1076
|
+
}
|
|
1077
|
+
function outcomeOf(e) {
|
|
1078
|
+
const err = e;
|
|
1079
|
+
if (err?.code === "NOT_FOUND") return "not-found";
|
|
1080
|
+
if (err?.name === "AbortError" || err?.message === "cancelled") return "cancelled";
|
|
1081
|
+
if (isUserRejection(e)) return "rejected";
|
|
1082
|
+
return "error";
|
|
1083
|
+
}
|
|
1084
|
+
function attemptFor(option, outcome, startedAt, now = Date.now()) {
|
|
1085
|
+
return {
|
|
1086
|
+
walletType: option ? walletTypeOf(option) : null,
|
|
1087
|
+
substrate: option ? substrateOf(option) : null,
|
|
1088
|
+
outcome,
|
|
1089
|
+
ms: Math.max(0, Math.round(now - startedAt))
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
function reportAttempt(hook, a) {
|
|
1093
|
+
if (!hook) return;
|
|
1094
|
+
try {
|
|
1095
|
+
hook(Object.freeze({ walletType: a.walletType, substrate: a.substrate, outcome: a.outcome, ms: a.ms }));
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
var WALLET_CHOICE_KEY = "bwx:wallet";
|
|
1100
|
+
var SUBSTRATES = ["extension", "in-app", "xdm", "injected", "window.CWI", "json-api", "cicada", "pairing", "adapter"];
|
|
1101
|
+
var TYPE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
1102
|
+
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;
|
|
1103
|
+
function isRememberable(o) {
|
|
1104
|
+
const t = walletTypeOf(o);
|
|
1105
|
+
return substrateOf(o) !== "window.CWI" && TYPE_RE.test(t) && !KEY_LIKE.test(t);
|
|
1106
|
+
}
|
|
1107
|
+
function isExplicitPick(o, reason) {
|
|
1108
|
+
return o.isBwalletX || reason === "chooser" || reason === "requested" || reason === "remembered";
|
|
1109
|
+
}
|
|
1110
|
+
function rememberWalletChoice(o) {
|
|
1111
|
+
try {
|
|
1112
|
+
if (!o) return void globalThis.localStorage?.removeItem(WALLET_CHOICE_KEY);
|
|
1113
|
+
if (!isRememberable(o)) return;
|
|
1114
|
+
const v = { type: walletTypeOf(o), substrate: substrateOf(o), v: 1 };
|
|
1115
|
+
globalThis.localStorage?.setItem(WALLET_CHOICE_KEY, JSON.stringify(v));
|
|
1116
|
+
} catch {
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function rememberedWalletChoice() {
|
|
1120
|
+
try {
|
|
1121
|
+
const raw = globalThis.localStorage?.getItem(WALLET_CHOICE_KEY);
|
|
1122
|
+
if (!raw) return null;
|
|
1123
|
+
const j = JSON.parse(raw);
|
|
1124
|
+
if (typeof j.type !== "string" || !TYPE_RE.test(j.type) || KEY_LIKE.test(j.type)) return null;
|
|
1125
|
+
if (!SUBSTRATES.includes(j.substrate) || j.substrate === "window.CWI") return null;
|
|
1126
|
+
return { type: j.type, substrate: j.substrate };
|
|
1127
|
+
} catch {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
function forgetWalletChoice() {
|
|
1132
|
+
rememberWalletChoice(null);
|
|
1133
|
+
}
|
|
1134
|
+
function needsBwalletX(type) {
|
|
1135
|
+
return !!type && type !== BWALLETX_TYPE;
|
|
1136
|
+
}
|
|
1137
|
+
function tapGoesElsewhere(type) {
|
|
1138
|
+
return needsBwalletX(type) && type !== METANET_TYPE && type !== DESKTOP_OPTION_ID;
|
|
1139
|
+
}
|
|
1140
|
+
function isLocalDesktopType(r) {
|
|
1141
|
+
return r.type === METANET_TYPE || r.type === DESKTOP_OPTION_ID || r.substrate === "json-api" || r.substrate === "cicada";
|
|
1142
|
+
}
|
|
1143
|
+
function matchRemembered(list, r) {
|
|
1144
|
+
if (isLocalDesktopType(r)) return null;
|
|
1145
|
+
if (r.type === BWALLETX_TYPE) {
|
|
1146
|
+
return (r.substrate === "pairing" ? list.find((o) => o.kind === "pairing") : list.find(isPresentBwalletX)) ?? null;
|
|
1147
|
+
}
|
|
1148
|
+
return list.find((o) => !o.isBwalletX && !isLocalDesktop(o) && isRememberable(o) && walletTypeOf(o) === r.type) ?? null;
|
|
1149
|
+
}
|
|
1150
|
+
var ATTACHABLE = ["extension", "in-app", "xdm", "injected"];
|
|
1151
|
+
function planAutoConnect(o) {
|
|
1152
|
+
const r = o.remembered;
|
|
1153
|
+
if (!r || o.enabled === false) return { action: "none", remembered: r, needsBwalletX: false };
|
|
1154
|
+
if (r.type === METANET_TYPE) {
|
|
1155
|
+
return { action: o.metanetHere ?? metanetOffered() ? "silent-metanet" : "none", remembered: r, needsBwalletX: false };
|
|
1156
|
+
}
|
|
1157
|
+
if (isLocalDesktopType(r)) return { action: "none", remembered: r, needsBwalletX: false };
|
|
1158
|
+
const action = o.signedIn && ATTACHABLE.includes(r.substrate) ? "attach" : "wait-for-tap";
|
|
1159
|
+
return { action, remembered: r, needsBwalletX: needsBwalletX(r.type) };
|
|
1160
|
+
}
|
|
1161
|
+
async function attachRemembered(r, o = {}) {
|
|
1162
|
+
if (!ATTACHABLE.includes(r.substrate)) return null;
|
|
1163
|
+
const list = await discoverWallets({ ms: o.ms });
|
|
1164
|
+
const m = matchRemembered(list, r);
|
|
1165
|
+
return m && m.kind === "brc100" && m.wallet && ATTACHABLE.includes(substrateOf(m)) ? m : null;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/connect-flow.ts
|
|
1169
|
+
async function runConnect(o) {
|
|
1170
|
+
const started = Date.now();
|
|
1171
|
+
const { list } = o;
|
|
1172
|
+
let choice = null;
|
|
1173
|
+
let reason = "none";
|
|
1174
|
+
try {
|
|
1175
|
+
if (o.metanet) {
|
|
1176
|
+
if (!metanetOffered()) throw Object.assign(new Error("Metanet Desktop runs on a computer, outside any frame."), { code: "NOT_SUPPORTED" });
|
|
1177
|
+
choice = metanetOption(typeof o.metanet === "object" ? o.metanet : {});
|
|
1178
|
+
reason = "requested";
|
|
1179
|
+
return await finish(o, choice, reason, started);
|
|
1180
|
+
}
|
|
1181
|
+
const d = decideWallet(list, {
|
|
1182
|
+
walletId: o.walletId,
|
|
1183
|
+
chooser: o.chooser,
|
|
1184
|
+
picker: o.picker,
|
|
1185
|
+
fastPath: o.fastPath,
|
|
1186
|
+
remembered: o.remembered,
|
|
1187
|
+
preferBwalletX: o.preferBwalletX
|
|
1188
|
+
});
|
|
1189
|
+
choice = d.option;
|
|
1190
|
+
reason = d.reason;
|
|
1191
|
+
if (!choice && d.reason === "chooser") choice = await o.pick(list);
|
|
1192
|
+
if (!choice) {
|
|
1193
|
+
if (d.reason === "none" || !list.length) throw Object.assign(new Error("No wallet found. Get bWalletX at bwalletx.com."), { code: "NOT_FOUND" });
|
|
1194
|
+
reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
return await finish(o, choice, reason, started);
|
|
1198
|
+
} catch (e) {
|
|
1199
|
+
reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
|
|
1200
|
+
throw e;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
async function finish(o, choice, reason, started) {
|
|
1204
|
+
const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
|
|
1205
|
+
const cw = await choice.connect(signIn ? { identity: false } : void 0);
|
|
1206
|
+
let handle = cw.handle ?? null;
|
|
1207
|
+
let paymail = cw.paymail ?? null;
|
|
1208
|
+
let identityKey = cw.identityKey;
|
|
1209
|
+
let signedIn = false;
|
|
1210
|
+
if (cw.wallet && signIn) {
|
|
1211
|
+
const { result, verified } = await signInAndVerify({
|
|
1212
|
+
wallet: cw.wallet,
|
|
1213
|
+
identityKey: cw.identityKey ?? void 0,
|
|
1214
|
+
// Metanet's connect() just checked the lock: don't ask again (rule 7, the minimum).
|
|
1215
|
+
skipWaitForAuthentication: choice.id === METANET_OPTION_ID,
|
|
1216
|
+
// The key that signed in here last time: a repeat sign-in is then the signature alone.
|
|
1217
|
+
identityKeyHint: rememberedIdentity(),
|
|
1218
|
+
challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
|
|
1219
|
+
challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
1220
|
+
verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
1221
|
+
});
|
|
1222
|
+
rememberIdentity(result.identityKey);
|
|
1223
|
+
identityKey = result.identityKey;
|
|
1224
|
+
handle = verified.handle ?? handle;
|
|
1225
|
+
paymail = verified.paymail ?? paymail;
|
|
1226
|
+
signedIn = true;
|
|
1227
|
+
} else if (cw.wallet && !identityKey) {
|
|
1228
|
+
identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
|
|
1229
|
+
}
|
|
1230
|
+
if (o.remember && isExplicitPick(choice, reason)) rememberWalletChoice(choice);
|
|
1231
|
+
if (o.remember && signedIn) rememberKnownIdentityKey(identityKey);
|
|
1232
|
+
reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
|
|
1233
|
+
return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
|
|
1234
|
+
}
|
|
1235
|
+
async function runSilentMetanet(o = {}) {
|
|
1236
|
+
const s = await trySilentMetanet(o);
|
|
1237
|
+
if (!s.ok || o.isAbandoned?.()) return null;
|
|
1238
|
+
const started = Date.now();
|
|
1239
|
+
const opt = metanetOption(o);
|
|
1240
|
+
opt.wallet = s.wallet;
|
|
1241
|
+
const cw = { wallet: s.wallet, payer: s.wallet, identityKey: s.identityKey, optionId: METANET_OPTION_ID, kind: "brc100" };
|
|
1242
|
+
const done = (signedIn, handle = null, paymail = null) => {
|
|
1243
|
+
rememberWalletChoice(opt);
|
|
1244
|
+
if (signedIn) rememberKnownIdentityKey(s.identityKey);
|
|
1245
|
+
reportAttempt(o.onAttempt, attemptFor(opt, signedIn ? "signed-in" : "connected", started));
|
|
1246
|
+
return { connected: cw, identityKey: s.identityKey, handle, paymail, signedIn, walletType: walletTypeOf(opt), substrate: "json-api", reason: "remembered" };
|
|
1247
|
+
};
|
|
1248
|
+
if (o.sessionKey !== void 0 && o.sessionKey !== null) {
|
|
1249
|
+
return o.sessionKey.toLowerCase() === s.identityKey.toLowerCase() ? done(true) : null;
|
|
1250
|
+
}
|
|
1251
|
+
if (!o.challengeUrl || !o.verifyUrl || !o.post) return done(false);
|
|
1252
|
+
if (!isKnownIdentityKey(s.identityKey)) return null;
|
|
1253
|
+
try {
|
|
1254
|
+
o.onSignIn?.();
|
|
1255
|
+
const post = o.post;
|
|
1256
|
+
const { result, verified } = await signInAndVerify({
|
|
1257
|
+
wallet: s.wallet,
|
|
1258
|
+
identityKey: s.identityKey,
|
|
1259
|
+
skipWaitForAuthentication: true,
|
|
1260
|
+
// trySilentMetanet just saw it unlocked
|
|
1261
|
+
challenge: (key) => post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
1262
|
+
verify: (r) => post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
1263
|
+
});
|
|
1264
|
+
if (o.isAbandoned?.()) {
|
|
1265
|
+
await Promise.resolve(o.logout?.()).catch(() => void 0);
|
|
1266
|
+
return null;
|
|
1267
|
+
}
|
|
1268
|
+
rememberIdentity(result.identityKey);
|
|
1269
|
+
return done(true, verified.handle ?? null, verified.paymail ?? null);
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
reportAttempt(o.onAttempt, attemptFor(opt, outcomeOf(e), started));
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
623
1276
|
// src/modal.ts
|
|
624
1277
|
var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
|
|
625
1278
|
|
|
626
1279
|
// src/access-client.ts
|
|
627
|
-
var
|
|
1280
|
+
var import_sdk6 = require("@bsv/sdk");
|
|
628
1281
|
|
|
629
1282
|
// src/access.ts
|
|
630
1283
|
var ACCESS_PROTOCOL = [2, "bwalletx access"];
|
|
@@ -762,7 +1415,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
|
|
|
762
1415
|
const counterparty = ci.counterparty ?? "self";
|
|
763
1416
|
for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
|
|
764
1417
|
const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
|
|
765
|
-
const h =
|
|
1418
|
+
const h = import_sdk6.Utils.toHex(import_sdk6.Hash.hash160(import_sdk6.PublicKey.fromString(publicKey).encode(true)));
|
|
766
1419
|
if (want && h !== want) continue;
|
|
767
1420
|
const existing = byKey.get(publicKey);
|
|
768
1421
|
if (existing) {
|
|
@@ -812,7 +1465,7 @@ async function requestAccess(o) {
|
|
|
812
1465
|
var SIGNIN_TAG = "bwalletx-signin";
|
|
813
1466
|
var GET_URL = "https://bwalletx.com/get";
|
|
814
1467
|
var WEB_WALLET_URL = "https://web.bwalletx.com";
|
|
815
|
-
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect" };
|
|
1468
|
+
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect", get: "Get", play: "Play with" };
|
|
816
1469
|
function signinText(label = "signin", brand = "bWalletX") {
|
|
817
1470
|
return `${VERBS[label] ?? VERBS.signin} ${brand}`;
|
|
818
1471
|
}
|
|
@@ -867,7 +1520,60 @@ button[aria-busy=true]{cursor:wait}
|
|
|
867
1520
|
.nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
868
1521
|
:host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}
|
|
869
1522
|
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
1523
|
+
/* pending: one plain line under the button */
|
|
1524
|
+
.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}
|
|
1525
|
+
:host([theme=dark]) .note,:host-context(.dark) .note{color:#a1a1aa}
|
|
1526
|
+
/* "Other wallets" (0.5.6): one small link under the button */
|
|
1527
|
+
.ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1528
|
+
:host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
|
|
1529
|
+
.sw{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#a16207;text-align:center}
|
|
1530
|
+
:host([theme=dark]) .sw,:host-context(.dark) .sw{color:#fcd34d}
|
|
1531
|
+
.mn{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1532
|
+
.mn small{display:block;font-size:11px;opacity:.85}
|
|
1533
|
+
:host([theme=dark]) .mn,:host-context(.dark) .mn{color:#a1a1aa}
|
|
1534
|
+
.lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
|
|
1535
|
+
.lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
1536
|
+
/* error: one line plus Retry */
|
|
1537
|
+
.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}
|
|
1538
|
+
:host([theme=dark]) .err,:host-context(.dark) .err{color:#fca5a5}
|
|
1539
|
+
/* signed in: the badge */
|
|
1540
|
+
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;
|
|
1541
|
+
background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.55);box-shadow:none;font-size:15px}
|
|
1542
|
+
button.chip:hover{background:#151517;border-color:#ffd24d}
|
|
1543
|
+
button.chip:active{background:#000;transform:none}
|
|
1544
|
+
button.chip:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}
|
|
1545
|
+
:host([theme=light]) button.chip{background:#fff;color:#09090b;border-color:#f59e0b}
|
|
1546
|
+
:host([theme=light]) button.chip:hover{background:#fffbeb}
|
|
1547
|
+
:host([full-width]) button.chip{width:100%}
|
|
1548
|
+
.av{position:relative;flex:none;display:inline-grid;place-items:center;width:30px;height:30px;border-radius:50%;overflow:hidden;
|
|
1549
|
+
background:radial-gradient(circle at 30% 30%,#ffe27a,#f5b800 55%,#b88600);color:#000;font-weight:800;font-size:14px}
|
|
1550
|
+
.av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
1551
|
+
.name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:min(52vw,260px)}
|
|
1552
|
+
.dot{flex:none;width:8px;height:8px;border-radius:50%;background:#ffd24d;box-shadow:0 0 0 3px rgba(255,210,77,.22)}
|
|
1553
|
+
:host([size=sm]) button.chip{font-size:13px;padding:3px 12px 3px 3px;gap:8px}
|
|
1554
|
+
:host([size=sm]) .av{width:26px;height:26px}
|
|
1555
|
+
:host([size=icon]) button.chip{width:48px;height:48px;padding:0;justify-content:center}
|
|
1556
|
+
:host([size=icon]) .name,:host([size=icon]) .dot{display:none}
|
|
1557
|
+
:host([size=icon]) .av{width:40px;height:40px}
|
|
1558
|
+
`;
|
|
1559
|
+
var MENU_CSS = `
|
|
1560
|
+
:host{all:initial}
|
|
1561
|
+
.menu{position:fixed;z-index:2147483600;box-sizing:border-box;min-width:200px;padding:6px;border-radius:14px;background:#0b0b0c;color:#ffd24d;
|
|
1562
|
+
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}
|
|
1563
|
+
.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}
|
|
1564
|
+
.item{all:unset;box-sizing:border-box;display:block;width:100%;padding:11px 10px;border-radius:10px;cursor:pointer;color:#ffd24d}
|
|
1565
|
+
.item:hover,.item:focus-visible{background:#ffd24d;color:#000;outline:none}
|
|
1566
|
+
.tip{position:fixed;z-index:2147483600;box-sizing:border-box;padding:8px 12px;border-radius:10px;background:#0b0b0c;color:#e7d9a8;
|
|
1567
|
+
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}
|
|
870
1568
|
`;
|
|
1569
|
+
var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
|
|
1570
|
+
var PENDING_LABEL = "Approve in bWalletX\u2026";
|
|
1571
|
+
var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
|
|
1572
|
+
var SWITCH_NOTE = "Some features need bWalletX";
|
|
1573
|
+
var CONTINUE_OTHER_LABEL = "Continue with your wallet";
|
|
1574
|
+
function walletGlyph(px) {
|
|
1575
|
+
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>`;
|
|
1576
|
+
}
|
|
871
1577
|
function b64url2(bytes) {
|
|
872
1578
|
let s = "";
|
|
873
1579
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -900,14 +1606,15 @@ async function buildAuthorizeUrl(p) {
|
|
|
900
1606
|
async function postJson(url, body) {
|
|
901
1607
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
902
1608
|
const data = await res.json().catch(() => ({}));
|
|
903
|
-
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
1609
|
+
if (!res.ok) throw Object.assign(new Error(data.error ?? `HTTP ${res.status}`), { status: res.status });
|
|
904
1610
|
return data;
|
|
905
1611
|
}
|
|
906
1612
|
async function postAccess(url, body) {
|
|
907
1613
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
908
1614
|
return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
|
|
909
1615
|
}
|
|
910
|
-
var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url"];
|
|
1616
|
+
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"];
|
|
1617
|
+
var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
|
|
911
1618
|
function createClass() {
|
|
912
1619
|
return class BwalletxSigninElement extends HTMLElement {
|
|
913
1620
|
static get observedAttributes() {
|
|
@@ -916,12 +1623,30 @@ function createClass() {
|
|
|
916
1623
|
#root;
|
|
917
1624
|
#notFound = false;
|
|
918
1625
|
#busy = false;
|
|
1626
|
+
#phase = "signed-out";
|
|
1627
|
+
#account = null;
|
|
1628
|
+
#error = "";
|
|
1629
|
+
#restored = false;
|
|
1630
|
+
#pendingLabel = PENDING_LABEL;
|
|
1631
|
+
/** The wallet type signed in with here (0.6.0), for the Switch note. */
|
|
1632
|
+
#signedType = null;
|
|
1633
|
+
#menu = null;
|
|
1634
|
+
/** (0.7.0) A Metanet sign-in (tapped or silent) is in flight. */
|
|
1635
|
+
#viaMetanet = false;
|
|
1636
|
+
/** (0.7.0) Bumped by "Not you?": a Metanet sign-in started before it lands nobody. */
|
|
1637
|
+
#run = 0;
|
|
919
1638
|
constructor() {
|
|
920
1639
|
super();
|
|
921
1640
|
this.#root = this.attachShadow({ mode: "open" });
|
|
922
1641
|
}
|
|
923
1642
|
connectedCallback() {
|
|
1643
|
+
if (!this.#controlled && this.#stateful) warnIfNoLogout({ verifyUrl: this.#attr("verify-url"), logoutUrl: this.#attr("logout-url"), where: "<bwalletx-signin>" });
|
|
924
1644
|
this.#render();
|
|
1645
|
+
void this.#restore().then(() => this.#silentMetanet());
|
|
1646
|
+
}
|
|
1647
|
+
disconnectedCallback() {
|
|
1648
|
+
this.#menu?.close(false);
|
|
1649
|
+
this.#hideTip();
|
|
925
1650
|
}
|
|
926
1651
|
attributeChangedCallback() {
|
|
927
1652
|
if (this.isConnected) this.#render();
|
|
@@ -933,93 +1658,557 @@ function createClass() {
|
|
|
933
1658
|
get notFound() {
|
|
934
1659
|
return this.#notFound;
|
|
935
1660
|
}
|
|
1661
|
+
/** The state shown now (the `state` attribute when set, else the element's own). */
|
|
1662
|
+
get state() {
|
|
1663
|
+
const a = this.#attr("state");
|
|
1664
|
+
return a && STATES.includes(a) ? a : this.#phase;
|
|
1665
|
+
}
|
|
1666
|
+
/** The signed-in account, or null. */
|
|
1667
|
+
get account() {
|
|
1668
|
+
if (!this.#controlled) return this.#account;
|
|
1669
|
+
const identityKey = this.#attr("identity-key");
|
|
1670
|
+
return identityKey ? { identityKey, paymail: this.#attr("paymail"), handle: this.#attr("handle"), avatarUrl: this.#attr("avatar-url") } : null;
|
|
1671
|
+
}
|
|
1672
|
+
/** True while the menu is open (tests). */
|
|
1673
|
+
get menuOpen() {
|
|
1674
|
+
return !!this.#menu;
|
|
1675
|
+
}
|
|
936
1676
|
focus(opts) {
|
|
937
1677
|
this.button?.focus(opts);
|
|
938
1678
|
}
|
|
1679
|
+
get #controlled() {
|
|
1680
|
+
return this.hasAttribute("state");
|
|
1681
|
+
}
|
|
1682
|
+
get #stateful() {
|
|
1683
|
+
return !this.hasAttribute("stateless");
|
|
1684
|
+
}
|
|
939
1685
|
#attr(name) {
|
|
940
1686
|
return this.getAttribute(name);
|
|
941
1687
|
}
|
|
1688
|
+
/** Restore the badge from session-url once, with no wallet call. */
|
|
1689
|
+
async #restore() {
|
|
1690
|
+
const url = this.#attr("session-url");
|
|
1691
|
+
if (this.#restored || !url || this.#controlled || !this.#stateful) return;
|
|
1692
|
+
this.#restored = true;
|
|
1693
|
+
this.#set("restoring");
|
|
1694
|
+
try {
|
|
1695
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
1696
|
+
const j = await r.json().catch(() => ({}));
|
|
1697
|
+
if (this.#phase !== "restoring") return;
|
|
1698
|
+
if (j.identityKey) this.#signedIn({ identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null });
|
|
1699
|
+
else this.#set("signed-out");
|
|
1700
|
+
} catch {
|
|
1701
|
+
if (this.#phase === "restoring") this.#set("signed-out");
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
#set(p) {
|
|
1705
|
+
this.#phase = p;
|
|
1706
|
+
if (p !== "signed-in") this.#menu?.close(false);
|
|
1707
|
+
this.#render();
|
|
1708
|
+
}
|
|
1709
|
+
#signedIn(a) {
|
|
1710
|
+
this.#account = a;
|
|
1711
|
+
this.#error = "";
|
|
1712
|
+
this.#set("signed-in");
|
|
1713
|
+
const lookup = this.#attr("lookup-url");
|
|
1714
|
+
if (!a.paymail && lookup !== "off") {
|
|
1715
|
+
void lookupPaymail(a.identityKey, lookup ?? void 0).then((pm) => {
|
|
1716
|
+
if (pm && this.#account?.identityKey === a.identityKey && !this.#account.paymail) {
|
|
1717
|
+
this.#account = { ...this.#account, paymail: pm };
|
|
1718
|
+
this.#render();
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
942
1723
|
#render() {
|
|
943
1724
|
const size = this.#attr("size") ?? "lg";
|
|
944
1725
|
const brand = this.#attr("brand") === "bWallet" ? "bWallet" : "bWalletX";
|
|
945
|
-
const
|
|
1726
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
1727
|
+
const state = this.state;
|
|
1728
|
+
const acct = this.account;
|
|
1729
|
+
if (state === "signed-in" && acct) {
|
|
1730
|
+
this.#hideTip();
|
|
1731
|
+
const name = accountLabel(acct);
|
|
1732
|
+
const avatar = acct.avatarUrl ?? avatarUrlFor(acct.identityKey);
|
|
1733
|
+
const initial = (name.replace(/^\$/, "").charAt(0) || "b").toUpperCase();
|
|
1734
|
+
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>`;
|
|
1735
|
+
const img = this.#root.querySelector("img");
|
|
1736
|
+
if (img) img.addEventListener("error", () => img.remove(), { once: true });
|
|
1737
|
+
const chip = this.button;
|
|
1738
|
+
chip.addEventListener("click", () => this.#menu ? this.#menu.close(true) : this.#openMenu());
|
|
1739
|
+
chip.addEventListener("keydown", (e) => {
|
|
1740
|
+
if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !this.#menu) {
|
|
1741
|
+
e.preventDefault();
|
|
1742
|
+
this.#openMenu();
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
const pending = state === "pending";
|
|
1748
|
+
const restoring = state === "restoring";
|
|
1749
|
+
const error = state === "error";
|
|
1750
|
+
const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
|
|
1751
|
+
const elsewhere = state === "signed-out" && tapGoesElsewhere(this.#walletType());
|
|
1752
|
+
const text = pending ? pendingLabel : error ? "Retry" : elsewhere ? CONTINUE_OTHER_LABEL : signinText(this.#attr("label") ?? "signin", brand);
|
|
946
1753
|
const subAttr = this.#attr("subtitle");
|
|
947
1754
|
const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
|
|
948
|
-
const subtitle = subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
949
|
-
const loading = this.hasAttribute("loading") || this.#busy;
|
|
1755
|
+
const subtitle = pending || error || elsewhere ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
1756
|
+
const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
|
|
950
1757
|
const disabled = this.hasAttribute("disabled");
|
|
951
1758
|
const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
|
|
952
|
-
const aria = this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
1759
|
+
const aria = pending ? pendingLabel : this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
953
1760
|
const getUrl = this.#attr("get-url") ?? GET_URL;
|
|
954
1761
|
const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
|
|
955
|
-
const
|
|
1762
|
+
const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
|
|
956
1763
|
const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
|
|
957
|
-
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
|
|
958
|
-
const
|
|
959
|
-
|
|
1764
|
+
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : elsewhere ? walletGlyph(markPx - 4) : markSvg(markPx);
|
|
1765
|
+
const otherOff = this.#attr("other-wallets") === "off";
|
|
1766
|
+
const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
|
|
1767
|
+
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>` : "";
|
|
1768
|
+
const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
|
|
1769
|
+
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>` : "";
|
|
1770
|
+
const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
|
|
1771
|
+
const mnHere = this.#metanetShown(size);
|
|
1772
|
+
const mnRemembered = mnHere && (pending ? this.#metanetPending() : !restoring && this.#walletType() === METANET_TYPE);
|
|
1773
|
+
const notYou = `<button type="button" class="lnk" data-act="not-you" part="not-you-link">${esc(NOT_YOU_LABEL)}</button>`;
|
|
1774
|
+
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>`;
|
|
1775
|
+
const compact = size === "sm" || size === "icon";
|
|
1776
|
+
const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
|
|
1777
|
+
const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
|
|
1778
|
+
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}`;
|
|
960
1779
|
this.button.addEventListener("click", () => void this.#click());
|
|
1780
|
+
for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
|
|
1781
|
+
this.#root.querySelector("[data-act=switch]")?.addEventListener("click", () => void this.switchToBwalletX());
|
|
1782
|
+
this.#root.querySelector("[data-act=metanet]")?.addEventListener("click", () => void this.useMetanet());
|
|
1783
|
+
this.#root.querySelector("[data-act=not-you]")?.addEventListener("click", () => this.notYou());
|
|
961
1784
|
bindPrewarm(this.button);
|
|
1785
|
+
if (pending && compact) this.#showTip(PENDING_NOTE);
|
|
1786
|
+
else this.#hideTip();
|
|
1787
|
+
}
|
|
1788
|
+
#tip = null;
|
|
1789
|
+
#showTip(text) {
|
|
1790
|
+
const b = this.button;
|
|
1791
|
+
if (!b || typeof document === "undefined") return;
|
|
1792
|
+
if (!this.#tip) {
|
|
1793
|
+
const host = document.createElement("div");
|
|
1794
|
+
host.setAttribute("data-bwalletx-tip", "");
|
|
1795
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1796
|
+
root.innerHTML = `<style>${MENU_CSS}</style><div class="tip" role="note"></div>`;
|
|
1797
|
+
document.body.appendChild(host);
|
|
1798
|
+
this.#tip = host;
|
|
1799
|
+
}
|
|
1800
|
+
const tip2 = this.#tip.shadowRoot.querySelector(".tip");
|
|
1801
|
+
tip2.textContent = text;
|
|
1802
|
+
const r = b.getBoundingClientRect();
|
|
1803
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1804
|
+
const width = Math.min(280, vw - 32);
|
|
1805
|
+
tip2.style.width = `${width}px`;
|
|
1806
|
+
tip2.style.left = `${Math.max(16, Math.min(r.right - width, vw - width - 16))}px`;
|
|
1807
|
+
tip2.style.top = `${r.bottom + 8}px`;
|
|
1808
|
+
}
|
|
1809
|
+
#hideTip() {
|
|
1810
|
+
this.#tip?.remove();
|
|
1811
|
+
this.#tip = null;
|
|
1812
|
+
}
|
|
1813
|
+
#openMenu() {
|
|
1814
|
+
const acct = this.account;
|
|
1815
|
+
const chip = this.button;
|
|
1816
|
+
if (!acct || !chip || typeof document === "undefined") return;
|
|
1817
|
+
const name = accountLabel(acct);
|
|
1818
|
+
const host = document.createElement("div");
|
|
1819
|
+
host.setAttribute("data-bwalletx-menu", "");
|
|
1820
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1821
|
+
const style = document.createElement("style");
|
|
1822
|
+
style.textContent = MENU_CSS;
|
|
1823
|
+
const menu = document.createElement("div");
|
|
1824
|
+
menu.className = "menu";
|
|
1825
|
+
menu.setAttribute("role", "menu");
|
|
1826
|
+
menu.setAttribute("aria-label", "bWalletX account");
|
|
1827
|
+
const who = document.createElement("div");
|
|
1828
|
+
who.className = "who";
|
|
1829
|
+
who.textContent = `Signed in as ${acct.paymail || name}`;
|
|
1830
|
+
const item = (text, run) => {
|
|
1831
|
+
const b = document.createElement("button");
|
|
1832
|
+
b.type = "button";
|
|
1833
|
+
b.className = "item";
|
|
1834
|
+
b.setAttribute("role", "menuitem");
|
|
1835
|
+
b.tabIndex = -1;
|
|
1836
|
+
b.textContent = text;
|
|
1837
|
+
b.addEventListener("click", () => run(b));
|
|
1838
|
+
return b;
|
|
1839
|
+
};
|
|
1840
|
+
const copyWhat = acct.paymail ? "paymail" : "key";
|
|
1841
|
+
const copy = item(`Copy ${copyWhat}`, (b) => {
|
|
1842
|
+
void copyText(acct.paymail || acct.identityKey).then((ok) => {
|
|
1843
|
+
b.textContent = ok ? "Copied" : `Copy ${copyWhat}`;
|
|
1844
|
+
setTimeout(() => close(true), ok ? 600 : 0);
|
|
1845
|
+
});
|
|
1846
|
+
});
|
|
1847
|
+
const out = item("Disconnect", () => {
|
|
1848
|
+
close(false);
|
|
1849
|
+
void this.signOut();
|
|
1850
|
+
});
|
|
1851
|
+
const items = [copy, out];
|
|
1852
|
+
menu.append(who);
|
|
1853
|
+
if (needsBwalletX(this.#walletType())) {
|
|
1854
|
+
const sw = item("Switch to bWalletX", () => {
|
|
1855
|
+
close(false);
|
|
1856
|
+
void this.switchToBwalletX();
|
|
1857
|
+
});
|
|
1858
|
+
const line = document.createElement("div");
|
|
1859
|
+
line.className = "who";
|
|
1860
|
+
line.textContent = SWITCH_NOTE;
|
|
1861
|
+
menu.append(line, sw);
|
|
1862
|
+
items.unshift(sw);
|
|
1863
|
+
}
|
|
1864
|
+
menu.append(copy, out);
|
|
1865
|
+
root.append(style, menu);
|
|
1866
|
+
document.body.appendChild(host);
|
|
1867
|
+
const place = () => {
|
|
1868
|
+
const r = chip.getBoundingClientRect();
|
|
1869
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1870
|
+
const vh = window.innerHeight || document.documentElement.clientHeight || 640;
|
|
1871
|
+
const width = Math.min(260, vw - 32);
|
|
1872
|
+
menu.style.width = `${width}px`;
|
|
1873
|
+
const left = Math.max(16, Math.min(r.right - width, vw - width - 16));
|
|
1874
|
+
menu.style.left = `${left}px`;
|
|
1875
|
+
const h = menu.offsetHeight || 150;
|
|
1876
|
+
const below = r.bottom + 8;
|
|
1877
|
+
menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
|
|
1878
|
+
};
|
|
1879
|
+
place();
|
|
1880
|
+
const focusAt = (i) => items[(i + items.length) % items.length].focus();
|
|
1881
|
+
const onKey = (e) => {
|
|
1882
|
+
const i = items.indexOf(root.activeElement);
|
|
1883
|
+
if (e.key === "Escape") {
|
|
1884
|
+
e.preventDefault();
|
|
1885
|
+
close(true);
|
|
1886
|
+
} else if (e.key === "ArrowDown") {
|
|
1887
|
+
e.preventDefault();
|
|
1888
|
+
focusAt(i + 1);
|
|
1889
|
+
} else if (e.key === "ArrowUp") {
|
|
1890
|
+
e.preventDefault();
|
|
1891
|
+
focusAt(i - 1);
|
|
1892
|
+
} else if (e.key === "Home") {
|
|
1893
|
+
e.preventDefault();
|
|
1894
|
+
focusAt(0);
|
|
1895
|
+
} else if (e.key === "End") {
|
|
1896
|
+
e.preventDefault();
|
|
1897
|
+
focusAt(items.length - 1);
|
|
1898
|
+
} else if (e.key === "Tab") close(false);
|
|
1899
|
+
};
|
|
1900
|
+
const onDown = (e) => {
|
|
1901
|
+
const path = e.composedPath?.() ?? [];
|
|
1902
|
+
if (!path.includes(host) && !path.includes(this)) close(false);
|
|
1903
|
+
};
|
|
1904
|
+
const onMove = () => close(false);
|
|
1905
|
+
menu.addEventListener("keydown", onKey);
|
|
1906
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
1907
|
+
window.addEventListener("resize", onMove);
|
|
1908
|
+
window.addEventListener("scroll", place, true);
|
|
1909
|
+
const close = (focusChip = false) => {
|
|
1910
|
+
if (this.#menu?.host !== host) return;
|
|
1911
|
+
this.#menu = null;
|
|
1912
|
+
menu.removeEventListener("keydown", onKey);
|
|
1913
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1914
|
+
window.removeEventListener("resize", onMove);
|
|
1915
|
+
window.removeEventListener("scroll", place, true);
|
|
1916
|
+
host.remove();
|
|
1917
|
+
this.button?.setAttribute("aria-expanded", "false");
|
|
1918
|
+
if (focusChip) this.button?.focus();
|
|
1919
|
+
};
|
|
1920
|
+
this.#menu = { host, close };
|
|
1921
|
+
chip.setAttribute("aria-expanded", "true");
|
|
1922
|
+
focusAt(0);
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Disconnect: emits bwalletx-disconnect (cancelable). Unless a listener cancels it (BwalletXProvider signs out
|
|
1926
|
+
* itself) or the element is driven by `state`, posts logout-url and shows signed out.
|
|
1927
|
+
*/
|
|
1928
|
+
async signOut() {
|
|
1929
|
+
this.#menu?.close(false);
|
|
1930
|
+
if (!this.#emit("bwalletx-disconnect", { element: this, account: this.account }, true)) return;
|
|
1931
|
+
if (this.#controlled) return;
|
|
1932
|
+
const url = this.#attr("logout-url");
|
|
1933
|
+
if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
1934
|
+
if (this.#remember) forgetWalletChoice();
|
|
1935
|
+
this.#signedType = null;
|
|
1936
|
+
this.#account = null;
|
|
1937
|
+
this.#set("signed-out");
|
|
1938
|
+
this.#emit("bwalletx-signed-out");
|
|
1939
|
+
this.button?.focus();
|
|
962
1940
|
}
|
|
963
1941
|
#emit(type, detail, cancelable = false) {
|
|
964
1942
|
return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
|
|
965
1943
|
}
|
|
966
|
-
|
|
967
|
-
|
|
1944
|
+
/** "Other wallets" is offered under the button: other-wallets on, or auto at lg/md when this element signs in. */
|
|
1945
|
+
#otherWalletsShown(size) {
|
|
1946
|
+
const a = this.#attr("other-wallets");
|
|
1947
|
+
if (a === "off") return false;
|
|
1948
|
+
if (a === "on") return true;
|
|
1949
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
1950
|
+
}
|
|
1951
|
+
/** Open the wallet chooser (the "Other wallets" link): phone QR, other browser wallets, desktop wallets. */
|
|
1952
|
+
otherWallets() {
|
|
1953
|
+
return this.#click("chooser");
|
|
1954
|
+
}
|
|
1955
|
+
/** (0.6.0) "Switch": sign in with bWalletX (its sheet when here, else the chooser with Get bWalletX first). */
|
|
1956
|
+
switchToBwalletX() {
|
|
1957
|
+
return this.#click("bwalletx");
|
|
1958
|
+
}
|
|
1959
|
+
get #remember() {
|
|
1960
|
+
return this.#attr("remember") !== "off";
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* (0.7.0) "Use Metanet Desktop or another BRC-100 wallet" is offered: metanet on, or auto at lg/md when this
|
|
1964
|
+
* element signs in or follows a provider. Never on a phone or inside a frame.
|
|
1965
|
+
*/
|
|
1966
|
+
#metanetShown(size) {
|
|
1967
|
+
const a = this.#attr("metanet");
|
|
1968
|
+
if (a === "off" || !metanetOffered()) return false;
|
|
1969
|
+
if (a === "on") return true;
|
|
1970
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
1971
|
+
}
|
|
1972
|
+
/** A Metanet sign-in is on screen: our own, or (driven) the provider's with Metanet remembered. */
|
|
1973
|
+
#metanetPending() {
|
|
1974
|
+
return this.#controlled ? this.#attr("wallet-type") === METANET_TYPE : this.#viaMetanet;
|
|
1975
|
+
}
|
|
1976
|
+
/** (0.7.0) The Metanet link: connect to Metanet Desktop on localhost:3321 (after this tap, never before). */
|
|
1977
|
+
useMetanet() {
|
|
1978
|
+
return this.#click("metanet");
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* (0.7.0) "Not you? Use another wallet": forget the remembered wallet here, and drop a Metanet sign-in in flight
|
|
1982
|
+
* (if its verify already set a session, logout-url ends it). Emits bwalletx-not-you (cancelable).
|
|
1983
|
+
*/
|
|
1984
|
+
notYou() {
|
|
1985
|
+
if (!this.#emit("bwalletx-not-you", { element: this }, true)) return;
|
|
1986
|
+
this.#run++;
|
|
1987
|
+
if (this.#remember) forgetWalletChoice();
|
|
1988
|
+
this.#signedType = null;
|
|
1989
|
+
if (this.#viaMetanet && !this.#controlled) {
|
|
1990
|
+
this.#viaMetanet = false;
|
|
1991
|
+
this.#busy = false;
|
|
1992
|
+
if (this.#phase === "pending") this.#phase = "signed-out";
|
|
1993
|
+
}
|
|
968
1994
|
this.#render();
|
|
969
1995
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
if (
|
|
976
|
-
|
|
1996
|
+
/**
|
|
1997
|
+
* (0.7.0) Rule 4: back with Metanet remembered and no session, ONE silent attempt per page load (2 s, a known
|
|
1998
|
+
* identity key only), then the signature alone. Any stop or failure is silent: the normal button stays.
|
|
1999
|
+
*/
|
|
2000
|
+
async #silentMetanet() {
|
|
2001
|
+
if (this.#controlled || !this.#stateful || !this.#remember || this.#attr("require-token")) return;
|
|
2002
|
+
if (!this.#metanetShown(this.#attr("size") ?? "lg")) return;
|
|
2003
|
+
const challengeUrl = this.#attr("challenge-url");
|
|
2004
|
+
const verifyUrl = this.#attr("verify-url");
|
|
2005
|
+
if (!challengeUrl || !verifyUrl || this.#phase !== "signed-out" || this.#busy || silentAttemptUsed()) return;
|
|
2006
|
+
if (rememberedWalletChoice()?.type !== METANET_TYPE || !metanetOffered()) return;
|
|
2007
|
+
const run = ++this.#run;
|
|
2008
|
+
const live = () => run === this.#run;
|
|
2009
|
+
const started = Date.now();
|
|
2010
|
+
const r = await runSilentMetanet({
|
|
2011
|
+
challengeUrl,
|
|
2012
|
+
verifyUrl,
|
|
2013
|
+
post: (url, body) => postJson(url, body),
|
|
2014
|
+
isAbandoned: () => !live(),
|
|
2015
|
+
logout: async () => {
|
|
2016
|
+
const out = this.#attr("logout-url");
|
|
2017
|
+
if (out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2018
|
+
},
|
|
2019
|
+
onSignIn: () => {
|
|
2020
|
+
if (!live() || this.#phase !== "signed-out" || this.#busy) return;
|
|
2021
|
+
this.#viaMetanet = true;
|
|
2022
|
+
this.#busy = true;
|
|
2023
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
2024
|
+
this.#set("pending");
|
|
2025
|
+
},
|
|
2026
|
+
onAttempt: (a) => this.#emit("bwalletx-attempt", a)
|
|
2027
|
+
});
|
|
2028
|
+
if (!live()) return;
|
|
2029
|
+
this.#viaMetanet = false;
|
|
2030
|
+
this.#busy = false;
|
|
2031
|
+
if (!r) {
|
|
2032
|
+
if (this.#phase === "pending") this.#set("signed-out");
|
|
977
2033
|
return;
|
|
978
2034
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
2035
|
+
this.#signedType = r.walletType;
|
|
2036
|
+
this.#signedIn({ identityKey: r.identityKey, handle: r.handle, paymail: r.paymail });
|
|
2037
|
+
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 });
|
|
2038
|
+
}
|
|
2039
|
+
/** The wallet type the Switch note judges: the provider's, else what signed in here, else the remembered one. */
|
|
2040
|
+
#walletType() {
|
|
2041
|
+
if (this.#controlled) return this.#attr("wallet-type");
|
|
2042
|
+
return this.#signedType ?? (this.#remember ? rememberedWalletChoice()?.type ?? null : null);
|
|
2043
|
+
}
|
|
2044
|
+
#switchNoteShown(size) {
|
|
2045
|
+
const a = this.#attr("switch-note");
|
|
2046
|
+
if (a === "off" || !tapGoesElsewhere(this.#walletType())) return false;
|
|
2047
|
+
return a === "on" || size === "lg" || size === "md";
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Signed in: note the wallet type, and remember it for this origin when the user picked it (0.6.0), plus the
|
|
2051
|
+
* identity key as known here (0.7.0, public keys only: Metanet's silent attempt goes on only for these). Pass a key
|
|
2052
|
+
* only after the site's verify route accepted it.
|
|
2053
|
+
*/
|
|
2054
|
+
#picked(option, reason, identityKey) {
|
|
2055
|
+
this.#signedType = walletTypeOf(option);
|
|
2056
|
+
if (this.#remember && isExplicitPick(option, reason)) rememberWalletChoice(option);
|
|
2057
|
+
if (this.#remember) rememberKnownIdentityKey(identityKey);
|
|
2058
|
+
}
|
|
2059
|
+
#attempt(option, outcome, started) {
|
|
2060
|
+
this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Which wallet: bWalletX by its own marker goes straight to its sheet; otherwise the chooser ("Get bWalletX" first)
|
|
2064
|
+
* when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
|
|
2065
|
+
*/
|
|
2066
|
+
async #choose(mode, ms, pairing) {
|
|
2067
|
+
if (mode === "metanet") return { option: metanetOption(), reason: "requested" };
|
|
2068
|
+
const r = mode === "signin" && this.#remember ? rememberedWalletChoice() : null;
|
|
2069
|
+
if (r && r.type !== METANET_TYPE && !(r.type === BWALLETX_TYPE && r.substrate !== "pairing")) {
|
|
2070
|
+
const m = matchRemembered(await discoverWallets({ ms, pairing: () => pairing }), r);
|
|
2071
|
+
if (m) return { option: m, reason: "remembered" };
|
|
2072
|
+
}
|
|
2073
|
+
if (mode !== "chooser") {
|
|
2074
|
+
const f = await discoverBwalletX(ms);
|
|
2075
|
+
if (f) {
|
|
2076
|
+
const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
|
|
2077
|
+
const option = brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
|
|
2078
|
+
return { option, reason: "bwalletx" };
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
const list = await discoverWallets({ ms: 50, pairing: () => pairing });
|
|
2082
|
+
if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
|
|
2083
|
+
const picked = await chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
|
|
2084
|
+
return picked ? { option: picked, reason: "chooser" } : null;
|
|
2085
|
+
}
|
|
2086
|
+
async #click(mode = "signin") {
|
|
2087
|
+
if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
|
|
2088
|
+
if (!this.#emit("bwalletx-signin", { element: this, mode }, true)) return;
|
|
2089
|
+
if (mode === "signin") {
|
|
2090
|
+
const href = this.#attr("href");
|
|
2091
|
+
const authorizeUrl = this.#attr("authorize-url");
|
|
2092
|
+
if (authorizeUrl) {
|
|
2093
|
+
location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr("client-id"), redirectUri: this.#attr("redirect-uri"), scope: this.#attr("scope") }));
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (href) {
|
|
2097
|
+
location.assign(href);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
982
2100
|
}
|
|
983
2101
|
const challengeUrl = this.#attr("challenge-url");
|
|
984
2102
|
if (!challengeUrl) return;
|
|
2103
|
+
const own = this.#stateful && !this.#controlled;
|
|
2104
|
+
const prev = this.#phase === "signed-in" ? "signed-in" : "signed-out";
|
|
2105
|
+
const started = Date.now();
|
|
2106
|
+
const pairing = pairingModal();
|
|
2107
|
+
let option = null;
|
|
2108
|
+
const run = ++this.#run;
|
|
2109
|
+
const live = () => run === this.#run;
|
|
985
2110
|
this.#notFound = false;
|
|
986
|
-
this.#
|
|
2111
|
+
this.#busy = true;
|
|
2112
|
+
this.#viaMetanet = mode === "metanet";
|
|
2113
|
+
this.#pendingLabel = mode === "metanet" ? PENDING_LABEL_OTHER : PENDING_LABEL;
|
|
2114
|
+
if (own) this.#set("pending");
|
|
2115
|
+
else this.#render();
|
|
987
2116
|
try {
|
|
988
2117
|
const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
|
|
989
|
-
const
|
|
990
|
-
if (
|
|
2118
|
+
const picked = await this.#choose(mode, ms, pairing);
|
|
2119
|
+
if (picked === "not-found") {
|
|
991
2120
|
this.#notFound = true;
|
|
2121
|
+
if (own) this.#phase = "signed-out";
|
|
2122
|
+
this.#attempt(null, "not-found", started);
|
|
992
2123
|
this.#emit("bwalletx-not-found");
|
|
993
2124
|
if (this.#attr("require-token")) this.#emit("bwalletx-access-denied", { code: "not_installed", message: "No wallet found" });
|
|
994
2125
|
return;
|
|
995
2126
|
}
|
|
2127
|
+
if (!picked) {
|
|
2128
|
+
if (own) this.#phase = prev;
|
|
2129
|
+
this.#attempt(null, "cancelled", started);
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
option = picked.option;
|
|
2133
|
+
const reason = picked.reason;
|
|
2134
|
+
if (!option.isBwalletX) {
|
|
2135
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
2136
|
+
if (own) this.#render();
|
|
2137
|
+
}
|
|
2138
|
+
const cw = await option.connect({ identity: false });
|
|
2139
|
+
if (!live()) return;
|
|
2140
|
+
const wallet = cw.wallet;
|
|
2141
|
+
if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
|
|
2142
|
+
const sub = substrateOf(option);
|
|
2143
|
+
const method = sub === "extension" ? "extension" : sub === "in-app" || sub === "xdm" ? "in-app" : sub === "pairing" ? "pairing" : "wallet";
|
|
996
2144
|
const requireToken = this.#attr("require-token");
|
|
997
2145
|
if (requireToken) {
|
|
2146
|
+
if (own) this.#phase = "signed-out";
|
|
998
2147
|
const verifyUrl2 = this.#attr("verify-url");
|
|
999
2148
|
const min = this.#attr("min");
|
|
1000
2149
|
const minBps = this.#attr("min-bps");
|
|
1001
2150
|
const hint = { token: { id: requireToken, ...minBps ? { minBps: Number(minBps) } : { min: min ?? "1" } } };
|
|
1002
2151
|
try {
|
|
1003
2152
|
const a = await requestAccess({
|
|
1004
|
-
wallet
|
|
2153
|
+
wallet,
|
|
1005
2154
|
challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
|
|
1006
2155
|
verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
|
|
1007
2156
|
});
|
|
2157
|
+
this.#picked(option, reason, verifyUrl2 ? a.response.identityKey : null);
|
|
2158
|
+
this.#attempt(option, "signed-in", started);
|
|
1008
2159
|
this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
|
|
1009
2160
|
} catch (e) {
|
|
1010
2161
|
const err = e instanceof AccessError ? e : new AccessError("bad_request", String(e));
|
|
2162
|
+
this.#attempt(option, outcomeOf(e), started);
|
|
1011
2163
|
this.#emit("bwalletx-access-denied", { code: err.code, message: err.message, have: err.have, need: err.need });
|
|
1012
2164
|
}
|
|
1013
2165
|
return;
|
|
1014
2166
|
}
|
|
1015
|
-
const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson(challengeUrl, { identityKey }) });
|
|
1016
2167
|
const verifyUrl = this.#attr("verify-url");
|
|
1017
|
-
const
|
|
1018
|
-
|
|
2168
|
+
const { result: r, verified } = await signInAndVerify({
|
|
2169
|
+
wallet,
|
|
2170
|
+
// Metanet hands over its key on connect (one request at a time), so its sign-in is the signature alone.
|
|
2171
|
+
identityKey: cw.identityKey ?? void 0,
|
|
2172
|
+
skipWaitForAuthentication: option.id === METANET_OPTION_ID,
|
|
2173
|
+
// its connect() just checked the lock
|
|
2174
|
+
identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
|
|
2175
|
+
challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
|
|
2176
|
+
verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
|
|
2177
|
+
});
|
|
2178
|
+
if (!live()) {
|
|
2179
|
+
const out = this.#attr("logout-url");
|
|
2180
|
+
if (verifyUrl && out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
if (verifyUrl) rememberIdentity(r.identityKey);
|
|
2184
|
+
const response = verifyUrl ? verified : null;
|
|
2185
|
+
this.#picked(option, reason, verifyUrl ? r.identityKey : null);
|
|
2186
|
+
if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
|
|
2187
|
+
this.#attempt(option, "signed-in", started);
|
|
2188
|
+
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
|
|
1019
2189
|
} catch (error) {
|
|
1020
|
-
|
|
2190
|
+
const outcome = outcomeOf(error);
|
|
2191
|
+
this.#attempt(option, outcome, started);
|
|
2192
|
+
if (!live()) return;
|
|
2193
|
+
const closedQr = outcome === "cancelled" && option?.kind === "pairing";
|
|
2194
|
+
if (own) {
|
|
2195
|
+
if (closedQr) this.#phase = prev;
|
|
2196
|
+
else {
|
|
2197
|
+
this.#error = signInErrorText(error);
|
|
2198
|
+
this.#phase = "error";
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (!closedQr) this.#emit("bwalletx-error", { error });
|
|
1021
2202
|
} finally {
|
|
1022
|
-
|
|
2203
|
+
try {
|
|
2204
|
+
pairing.close();
|
|
2205
|
+
} catch {
|
|
2206
|
+
}
|
|
2207
|
+
if (live()) {
|
|
2208
|
+
this.#busy = false;
|
|
2209
|
+
this.#viaMetanet = false;
|
|
2210
|
+
this.#render();
|
|
2211
|
+
}
|
|
1023
2212
|
}
|
|
1024
2213
|
}
|
|
1025
2214
|
};
|
|
@@ -1051,7 +2240,21 @@ a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border
|
|
|
1051
2240
|
.bwx-list{display:flex;flex-direction:column;gap:8px}
|
|
1052
2241
|
.bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
|
|
1053
2242
|
.bwx-opt img{width:24px;height:24px;border-radius:6px}
|
|
1054
|
-
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f}
|
|
2243
|
+
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f;white-space:nowrap}
|
|
2244
|
+
.bwx-opt{min-height:48px;box-sizing:border-box}
|
|
2245
|
+
a.bwx-btn.bwx-opt{display:flex;text-align:left}
|
|
2246
|
+
.bwx-btn:focus{outline:none}
|
|
2247
|
+
.bwx-btn:focus-visible{outline:2px solid #e8c766;outline-offset:2px}
|
|
2248
|
+
.bwx-mk{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border-radius:50%;background:#000}
|
|
2249
|
+
.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}
|
|
2250
|
+
.bwx-opt img{width:28px;height:28px;border-radius:8px;flex:none}
|
|
2251
|
+
.bwx-txt{display:flex;flex-direction:column;min-width:0;gap:1px}
|
|
2252
|
+
.bwx-txt b{font-weight:600}
|
|
2253
|
+
.bwx-txt small{font-size:12px;color:#8d866f;font-weight:400}
|
|
2254
|
+
.bwx-primary .bwx-txt small{color:rgba(0,0,0,.68)}
|
|
2255
|
+
.bwx-primary .bwx-tag{color:rgba(0,0,0,.6)}
|
|
2256
|
+
.bwx-sep{font-size:12px;color:#8d866f;margin:6px 2px 0}
|
|
2257
|
+
.bwx-foot{font-size:12px;color:#8d866f;margin:12px 0 0}
|
|
1055
2258
|
`;
|
|
1056
2259
|
function mount() {
|
|
1057
2260
|
const root = document.createElement("div");
|
|
@@ -1078,18 +2281,6 @@ function qrSvg(text) {
|
|
|
1078
2281
|
q.make();
|
|
1079
2282
|
return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
|
1080
2283
|
}
|
|
1081
|
-
function isTouchDevice(env = globalThis) {
|
|
1082
|
-
const nav = env.navigator;
|
|
1083
|
-
if (!nav) return false;
|
|
1084
|
-
const ua = nav.userAgent ?? "";
|
|
1085
|
-
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
1086
|
-
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
1087
|
-
try {
|
|
1088
|
-
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
1089
|
-
} catch {
|
|
1090
|
-
return false;
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
2284
|
function pairingModal(o = {}) {
|
|
1094
2285
|
const ac = new AbortController();
|
|
1095
2286
|
let ui = null;
|
|
@@ -1180,110 +2371,109 @@ function pickWallet(options, o = {}) {
|
|
|
1180
2371
|
ui.box.lastChild.append(cancel);
|
|
1181
2372
|
});
|
|
1182
2373
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
kind: "brc100",
|
|
1199
|
-
isBwalletX: info.isBwalletX ?? false,
|
|
1200
|
-
connect: () => brc100Connected(wallet, opt)
|
|
1201
|
-
};
|
|
1202
|
-
return opt;
|
|
1203
|
-
}
|
|
1204
|
-
function pairingOption(pairing) {
|
|
1205
|
-
const opt = {
|
|
1206
|
-
id: "bwalletx-pairing",
|
|
1207
|
-
name: "bWalletX on your phone",
|
|
1208
|
-
kind: "pairing",
|
|
1209
|
-
isBwalletX: true,
|
|
1210
|
-
async connect() {
|
|
1211
|
-
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
1212
|
-
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
1213
|
-
return brc100Connected(w, opt);
|
|
1214
|
-
}
|
|
1215
|
-
};
|
|
1216
|
-
return opt;
|
|
1217
|
-
}
|
|
1218
|
-
function handcashAdapter(o) {
|
|
1219
|
-
const opt = {
|
|
1220
|
-
id: "handcash",
|
|
1221
|
-
name: "HandCash",
|
|
1222
|
-
icon: o.icon,
|
|
1223
|
-
kind: "handcash",
|
|
1224
|
-
isBwalletX: false,
|
|
1225
|
-
async connect() {
|
|
1226
|
-
const p = await o.login();
|
|
1227
|
-
return {
|
|
1228
|
-
wallet: null,
|
|
1229
|
-
payer: o.pay ? { pay: o.pay } : null,
|
|
1230
|
-
identityKey: p.identityKey ?? null,
|
|
1231
|
-
handle: p.handle,
|
|
1232
|
-
paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
|
|
1233
|
-
optionId: opt.id,
|
|
1234
|
-
kind: "handcash",
|
|
1235
|
-
transitional: true
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
};
|
|
1239
|
-
return opt;
|
|
1240
|
-
}
|
|
1241
|
-
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
1242
|
-
async function probe(substrate, ms) {
|
|
1243
|
-
try {
|
|
1244
|
-
const w = new import_sdk5.WalletClient(substrate);
|
|
1245
|
-
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
1246
|
-
return ok ? w : null;
|
|
1247
|
-
} catch {
|
|
1248
|
-
return null;
|
|
2374
|
+
function optionRow(w, primary, sub, tag) {
|
|
2375
|
+
const b = el("button", `bwx-btn bwx-opt${primary ? " bwx-primary" : ""}`);
|
|
2376
|
+
b.type = "button";
|
|
2377
|
+
b.dataset.wallet = w.id;
|
|
2378
|
+
if (w.isBwalletX) {
|
|
2379
|
+
const mk = el("span", "bwx-mk");
|
|
2380
|
+
mk.innerHTML = markSvg(18);
|
|
2381
|
+
b.append(mk);
|
|
2382
|
+
} else if (w.icon) {
|
|
2383
|
+
const img = el("img");
|
|
2384
|
+
img.src = w.icon;
|
|
2385
|
+
img.alt = "";
|
|
2386
|
+
b.append(img);
|
|
2387
|
+
} else {
|
|
2388
|
+
b.append(el("span", "bwx-ico", (w.name.trim().charAt(0) || "W").toUpperCase()));
|
|
1249
2389
|
}
|
|
2390
|
+
const t = el("span", "bwx-txt");
|
|
2391
|
+
t.append(el("b", "", w.name));
|
|
2392
|
+
if (sub) t.append(el("small", "", sub));
|
|
2393
|
+
b.append(t);
|
|
2394
|
+
if (tag) b.append(el("span", "bwx-tag", tag));
|
|
2395
|
+
return b;
|
|
1250
2396
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
if (seen.has(rdns)) return;
|
|
1261
|
-
const isB = BWALLETX_RDNS.includes(rdns);
|
|
1262
|
-
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB }));
|
|
2397
|
+
function chooseWallet(options, o = {}) {
|
|
2398
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
2399
|
+
return new Promise((resolve) => {
|
|
2400
|
+
const ui = mount();
|
|
2401
|
+
ui.root.dataset.bwxChooser = "";
|
|
2402
|
+
const done = (w) => {
|
|
2403
|
+
document.removeEventListener("keydown", onKey, true);
|
|
2404
|
+
ui.close();
|
|
2405
|
+
resolve(w);
|
|
1263
2406
|
};
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
2407
|
+
const onKey = (e) => {
|
|
2408
|
+
if (e.key === "Escape") {
|
|
2409
|
+
e.preventDefault();
|
|
2410
|
+
done(null);
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
document.addEventListener("keydown", onKey, true);
|
|
2414
|
+
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
2415
|
+
const list = el("div", "bwx-list");
|
|
2416
|
+
const here = hasBwalletX(options);
|
|
2417
|
+
if (!here) {
|
|
2418
|
+
const get = el("a", "bwx-btn bwx-opt bwx-primary");
|
|
2419
|
+
get.href = o.getUrl ?? GET_URL;
|
|
2420
|
+
get.target = "_blank";
|
|
2421
|
+
get.rel = "noopener";
|
|
2422
|
+
get.dataset.wallet = "get-bwalletx";
|
|
2423
|
+
const mk = el("span", "bwx-mk");
|
|
2424
|
+
mk.innerHTML = markSvg(18);
|
|
2425
|
+
const t = el("span", "bwx-txt");
|
|
2426
|
+
t.append(el("b", "", "Get bWalletX"), el("small", "", "Free. Every feature on this site"));
|
|
2427
|
+
get.append(mk, t);
|
|
2428
|
+
list.append(get);
|
|
1273
2429
|
}
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
2430
|
+
const ordered = chooserOrder(options);
|
|
2431
|
+
const touch = isTouchDevice();
|
|
2432
|
+
let sep = false;
|
|
2433
|
+
for (const w of ordered) {
|
|
2434
|
+
if (!here && !sep) {
|
|
2435
|
+
list.append(el("p", "bwx-sep", "Or use"));
|
|
2436
|
+
sep = true;
|
|
2437
|
+
}
|
|
2438
|
+
const sub = isPresentBwalletX(w) ? "Recommended" : w.kind === "pairing" ? touch ? "Open the bWalletX app" : "Scan a QR with your phone" : void 0;
|
|
2439
|
+
const tag = w.kind === "handcash" ? "until BRC-100" : !w.isBwalletX && w.kind === "brc100" ? "Fewer features" : void 0;
|
|
2440
|
+
const row2 = optionRow(w, isPresentBwalletX(w), sub, tag);
|
|
2441
|
+
row2.onclick = () => done(w);
|
|
2442
|
+
list.append(row2);
|
|
2443
|
+
}
|
|
2444
|
+
if (o.desktop !== false && metanetOffered() && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
|
|
2445
|
+
if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
|
|
2446
|
+
const d = metanetOption();
|
|
2447
|
+
const row2 = optionRow(d, false, METANET_NOTE);
|
|
2448
|
+
row2.title = "Connects to Metanet Desktop on this computer, never to a browser extension.";
|
|
2449
|
+
row2.onclick = () => done(d);
|
|
2450
|
+
list.append(row2);
|
|
2451
|
+
}
|
|
2452
|
+
ui.box.append(list);
|
|
2453
|
+
if (otherDetected(options).length || !here) ui.box.append(el("p", "bwx-foot", "Some features need bWalletX."));
|
|
2454
|
+
const row = el("div", "bwx-row");
|
|
2455
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
2456
|
+
cancel.type = "button";
|
|
2457
|
+
cancel.onclick = () => done(null);
|
|
2458
|
+
row.append(cancel);
|
|
2459
|
+
ui.box.append(row);
|
|
2460
|
+
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
2461
|
+
list.querySelector("a,button")?.focus();
|
|
2462
|
+
});
|
|
1282
2463
|
}
|
|
1283
2464
|
|
|
1284
2465
|
// src/pay.ts
|
|
1285
|
-
var
|
|
2466
|
+
var import_sdk7 = require("@bsv/sdk");
|
|
1286
2467
|
var BRC29_PROTOCOL = [2, "3241645161d8"];
|
|
2468
|
+
var NO_COINS_TEXT = "Your wallet doesn't have enough to cover this payment and its small network fee. Add a little and try again.";
|
|
2469
|
+
function isInsufficientFunds(e) {
|
|
2470
|
+
const err = e;
|
|
2471
|
+
if (!err) return false;
|
|
2472
|
+
if (err.name === "WERR_INSUFFICIENT_FUNDS" || err.code === "WERR_INSUFFICIENT_FUNDS" || err.code === "ERR_INSUFFICIENT_FUNDS") return true;
|
|
2473
|
+
if (err.isError === true && err.code === 7) return true;
|
|
2474
|
+
const msg = typeof err.message === "string" ? err.message : String(e);
|
|
2475
|
+
return /insufficient (funds|balance)|not enough (funds|coins|satoshis|sats)|more satoshis are needed/i.test(msg);
|
|
2476
|
+
}
|
|
1287
2477
|
var PaymentError = class extends Error {
|
|
1288
2478
|
constructor(code, message, maybeSent = false, cause) {
|
|
1289
2479
|
super(message);
|
|
@@ -1302,13 +2492,13 @@ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
|
|
|
1302
2492
|
function randomB64(n) {
|
|
1303
2493
|
const b = new Uint8Array(n);
|
|
1304
2494
|
globalThis.crypto.getRandomValues(b);
|
|
1305
|
-
return
|
|
2495
|
+
return import_sdk7.Utils.toBase64(Array.from(b));
|
|
1306
2496
|
}
|
|
1307
2497
|
function opReturn(text) {
|
|
1308
|
-
const d =
|
|
2498
|
+
const d = import_sdk7.Utils.toArray(text, "utf8");
|
|
1309
2499
|
const len = d.length;
|
|
1310
2500
|
const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
|
|
1311
|
-
return
|
|
2501
|
+
return import_sdk7.Utils.toHex([0, 106, ...push, ...d]);
|
|
1312
2502
|
}
|
|
1313
2503
|
var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
1314
2504
|
var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
|
|
@@ -1379,6 +2569,7 @@ async function runAction(wallet, description, outputs, labels) {
|
|
|
1379
2569
|
options: { randomizeOutputs: false }
|
|
1380
2570
|
});
|
|
1381
2571
|
} catch (e) {
|
|
2572
|
+
if (isInsufficientFunds(e)) throw new PaymentError("INSUFFICIENT_FUNDS", NO_COINS_TEXT, false, e);
|
|
1382
2573
|
const msg = e?.message ?? String(e);
|
|
1383
2574
|
const rejected = /denied|reject|cancel|abort|user/i.test(msg) || e?.code === 4001;
|
|
1384
2575
|
throw new PaymentError(rejected ? "WALLET_REJECTED" : "WALLET_ERROR", rejected ? "The payment was declined in the wallet." : `The wallet could not make the payment: ${msg}`, !rejected, e);
|
|
@@ -1404,7 +2595,7 @@ async function pay(source, req, o = {}) {
|
|
|
1404
2595
|
} catch (e) {
|
|
1405
2596
|
throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
|
|
1406
2597
|
}
|
|
1407
|
-
outputs = [{ script: new
|
|
2598
|
+
outputs = [{ script: new import_sdk7.P2PKH().lock(import_sdk7.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
|
|
1408
2599
|
remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
|
|
1409
2600
|
} else if (isPaymail(to)) {
|
|
1410
2601
|
const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
|
|
@@ -1412,7 +2603,7 @@ async function pay(source, req, o = {}) {
|
|
|
1412
2603
|
submit = r2.submit;
|
|
1413
2604
|
} else {
|
|
1414
2605
|
try {
|
|
1415
|
-
outputs = [{ script: new
|
|
2606
|
+
outputs = [{ script: new import_sdk7.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
|
|
1416
2607
|
} catch {
|
|
1417
2608
|
throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
|
|
1418
2609
|
}
|
|
@@ -1422,7 +2613,7 @@ async function pay(source, req, o = {}) {
|
|
|
1422
2613
|
if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
|
|
1423
2614
|
const tx = r.tx ? Array.from(r.tx) : void 0;
|
|
1424
2615
|
if (submit && tx) {
|
|
1425
|
-
await submit(
|
|
2616
|
+
await submit(import_sdk7.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
|
|
1426
2617
|
});
|
|
1427
2618
|
}
|
|
1428
2619
|
return { txid: r.txid, tx, sats, outputs, remittance };
|
|
@@ -1443,19 +2634,62 @@ async function buyToken(source, req) {
|
|
|
1443
2634
|
}
|
|
1444
2635
|
|
|
1445
2636
|
// src/react.ts
|
|
2637
|
+
function signinStateFor(v) {
|
|
2638
|
+
if (v.status === "restoring") return "restoring";
|
|
2639
|
+
if (v.status === "connecting") return "pending";
|
|
2640
|
+
if (v.status === "error") return "error";
|
|
2641
|
+
if (v.status === "connected" && v.identityKey) return "signed-in";
|
|
2642
|
+
return "signed-out";
|
|
2643
|
+
}
|
|
1446
2644
|
function BwalletxSignin(props) {
|
|
1447
2645
|
const ref = (0, import_react.useRef)(null);
|
|
1448
2646
|
const cb = (0, import_react.useRef)(props);
|
|
1449
2647
|
cb.current = props;
|
|
2648
|
+
const ctx = (0, import_react.useContext)(Ctx);
|
|
2649
|
+
const ctxRef = (0, import_react.useRef)(ctx);
|
|
2650
|
+
ctxRef.current = ctx;
|
|
2651
|
+
const driven = !!ctx && props.stateful !== false;
|
|
1450
2652
|
(0, import_react.useEffect)(() => {
|
|
1451
2653
|
defineBwalletxSignin();
|
|
1452
2654
|
const el2 = ref.current;
|
|
1453
2655
|
if (!el2) return;
|
|
1454
2656
|
const on = [
|
|
1455
|
-
[
|
|
2657
|
+
[
|
|
2658
|
+
"bwalletx-signin",
|
|
2659
|
+
(e) => {
|
|
2660
|
+
cb.current.onClick?.(e);
|
|
2661
|
+
const c = ctxRef.current;
|
|
2662
|
+
if (!e.defaultPrevented && c && cb.current.stateful !== false) {
|
|
2663
|
+
e.preventDefault();
|
|
2664
|
+
const mode = e.detail?.mode;
|
|
2665
|
+
c.connect(mode === "chooser" ? { chooser: true } : mode === "bwalletx" ? { bwalletx: true } : mode === "metanet" ? { metanet: true } : {}).catch(() => void 0);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
],
|
|
1456
2669
|
["bwalletx-signed-in", (e) => cb.current.onSignedIn?.(e.detail)],
|
|
1457
2670
|
["bwalletx-not-found", () => cb.current.onNotFound?.()],
|
|
1458
|
-
["bwalletx-error", (e) => cb.current.onError?.(e.detail.error)]
|
|
2671
|
+
["bwalletx-error", (e) => cb.current.onError?.(e.detail.error)],
|
|
2672
|
+
[
|
|
2673
|
+
"bwalletx-disconnect",
|
|
2674
|
+
(e) => {
|
|
2675
|
+
const c = ctxRef.current;
|
|
2676
|
+
if (c && cb.current.stateful !== false) {
|
|
2677
|
+
e.preventDefault();
|
|
2678
|
+
void c.disconnect().then(() => cb.current.onSignedOut?.());
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
],
|
|
2682
|
+
["bwalletx-signed-out", () => cb.current.onSignedOut?.()],
|
|
2683
|
+
[
|
|
2684
|
+
"bwalletx-not-you",
|
|
2685
|
+
(e) => {
|
|
2686
|
+
const c = ctxRef.current;
|
|
2687
|
+
if (c && cb.current.stateful !== false) {
|
|
2688
|
+
e.preventDefault();
|
|
2689
|
+
c.forgetWallet();
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
]
|
|
1459
2693
|
];
|
|
1460
2694
|
for (const [t, f] of on) el2.addEventListener(t, f);
|
|
1461
2695
|
return () => {
|
|
@@ -1464,6 +2698,7 @@ function BwalletxSignin(props) {
|
|
|
1464
2698
|
}, []);
|
|
1465
2699
|
const p = props;
|
|
1466
2700
|
const flag = (v) => v ? "" : void 0;
|
|
2701
|
+
const state = driven ? signinStateFor(ctx) : void 0;
|
|
1467
2702
|
return (0, import_react.createElement)("bwalletx-signin", {
|
|
1468
2703
|
ref,
|
|
1469
2704
|
id: p.id,
|
|
@@ -1479,6 +2714,16 @@ function BwalletxSignin(props) {
|
|
|
1479
2714
|
disabled: flag(p.disabled),
|
|
1480
2715
|
"challenge-url": p.challengeUrl,
|
|
1481
2716
|
"verify-url": p.verifyUrl,
|
|
2717
|
+
"session-url": driven ? void 0 : p.sessionUrl,
|
|
2718
|
+
"logout-url": driven ? void 0 : p.logoutUrl,
|
|
2719
|
+
"lookup-url": p.lookupUrl,
|
|
2720
|
+
stateless: p.stateful === false ? "" : void 0,
|
|
2721
|
+
state,
|
|
2722
|
+
"identity-key": driven ? ctx.identityKey ?? void 0 : void 0,
|
|
2723
|
+
paymail: driven ? ctx.paymail ?? void 0 : void 0,
|
|
2724
|
+
handle: driven ? ctx.handle ?? void 0 : void 0,
|
|
2725
|
+
"avatar-url": driven ? avatarUrlFor(ctx.identityKey) ?? void 0 : void 0,
|
|
2726
|
+
error: driven && ctx.status === "error" ? signInErrorText(ctx.error) : void 0,
|
|
1482
2727
|
"get-url": p.getUrl,
|
|
1483
2728
|
"web-url": p.webUrl,
|
|
1484
2729
|
href: p.href,
|
|
@@ -1487,6 +2732,9 @@ function BwalletxSignin(props) {
|
|
|
1487
2732
|
"redirect-uri": p.redirectUri,
|
|
1488
2733
|
scope: p.scope,
|
|
1489
2734
|
"discover-ms": p.discoverMs,
|
|
2735
|
+
"other-wallets": p.otherWallets,
|
|
2736
|
+
metanet: p.metanet,
|
|
2737
|
+
"wallet-type": driven ? ctx.walletType ?? ctx.rememberedWallet?.type ?? void 0 : void 0,
|
|
1490
2738
|
"aria-label": p["aria-label"]
|
|
1491
2739
|
});
|
|
1492
2740
|
}
|
|
@@ -1498,9 +2746,13 @@ var empty = {
|
|
|
1498
2746
|
paymail: null,
|
|
1499
2747
|
wallet: null,
|
|
1500
2748
|
walletId: null,
|
|
2749
|
+
walletType: null,
|
|
1501
2750
|
transitional: false,
|
|
1502
2751
|
signedIn: false,
|
|
1503
|
-
error: null
|
|
2752
|
+
error: null,
|
|
2753
|
+
rememberedWallet: null,
|
|
2754
|
+
needsBwalletX: false,
|
|
2755
|
+
metanetOffered: false
|
|
1504
2756
|
};
|
|
1505
2757
|
var Ctx = (0, import_react.createContext)(null);
|
|
1506
2758
|
async function postJson2(url, body) {
|
|
@@ -1511,6 +2763,10 @@ async function postJson2(url, body) {
|
|
|
1511
2763
|
}
|
|
1512
2764
|
function BwalletXProvider(p) {
|
|
1513
2765
|
const [state, setState] = (0, import_react.useState)(empty);
|
|
2766
|
+
const stateRef = (0, import_react.useRef)(state);
|
|
2767
|
+
stateRef.current = state;
|
|
2768
|
+
const silentRun = (0, import_react.useRef)(0);
|
|
2769
|
+
const sessionChecked = (0, import_react.useRef)(!p.sessionUrl);
|
|
1514
2770
|
const [wallets, setWallets] = (0, import_react.useState)([]);
|
|
1515
2771
|
const props = (0, import_react.useRef)(p);
|
|
1516
2772
|
props.current = p;
|
|
@@ -1521,6 +2777,9 @@ function BwalletXProvider(p) {
|
|
|
1521
2777
|
props.current.onError?.(err);
|
|
1522
2778
|
return err;
|
|
1523
2779
|
}, []);
|
|
2780
|
+
(0, import_react.useEffect)(() => {
|
|
2781
|
+
warnIfNoLogout({ verifyUrl: p.verifyUrl, logoutUrl: p.logoutUrl, where: "<BwalletXProvider>" });
|
|
2782
|
+
}, [p.verifyUrl, p.logoutUrl]);
|
|
1524
2783
|
const themeOn = !!p.theme && (state.status === "connecting" || state.status === "connected");
|
|
1525
2784
|
(0, import_react.useEffect)(() => {
|
|
1526
2785
|
if (!themeOn) return;
|
|
@@ -1534,10 +2793,15 @@ function BwalletXProvider(p) {
|
|
|
1534
2793
|
setState((s) => ({ ...s, status: "restoring" }));
|
|
1535
2794
|
fetch(url, { credentials: "same-origin" }).then((r) => r.json()).then((j) => {
|
|
1536
2795
|
if (!live) return;
|
|
2796
|
+
sessionChecked.current = true;
|
|
1537
2797
|
setState(
|
|
1538
2798
|
(s) => j.identityKey ? { ...s, status: "connected", identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null, signedIn: true } : { ...s, status: s.status === "restoring" ? "idle" : s.status }
|
|
1539
2799
|
);
|
|
1540
|
-
}).catch(() =>
|
|
2800
|
+
}).catch(() => {
|
|
2801
|
+
if (!live) return;
|
|
2802
|
+
sessionChecked.current = true;
|
|
2803
|
+
setState((s) => ({ ...s, status: s.status === "restoring" ? "idle" : s.status }));
|
|
2804
|
+
});
|
|
1541
2805
|
return () => {
|
|
1542
2806
|
live = false;
|
|
1543
2807
|
};
|
|
@@ -1558,46 +2822,50 @@ function BwalletXProvider(p) {
|
|
|
1558
2822
|
const c = props.current;
|
|
1559
2823
|
setState((s) => ({ ...s, status: "connecting", error: null }));
|
|
1560
2824
|
try {
|
|
1561
|
-
const list = await listWallets();
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
2825
|
+
const list = opts.metanet ? [] : await listWallets();
|
|
2826
|
+
const fast = c.fastPath !== false;
|
|
2827
|
+
const r = await runConnect({
|
|
2828
|
+
list,
|
|
2829
|
+
walletId: opts.walletId,
|
|
2830
|
+
chooser: opts.chooser,
|
|
2831
|
+
picker: c.picker,
|
|
2832
|
+
fastPath: fast,
|
|
2833
|
+
pick: fast ? (l) => chooseWallet(l) : pickWallet,
|
|
2834
|
+
onAttempt: c.onAttempt,
|
|
2835
|
+
remembered: fast && c.rememberChoice !== false ? rememberedWalletChoice() : null,
|
|
2836
|
+
remember: fast && c.rememberChoice !== false,
|
|
2837
|
+
preferBwalletX: opts.bwalletx,
|
|
2838
|
+
metanet: opts.metanet,
|
|
2839
|
+
challengeUrl: c.challengeUrl,
|
|
2840
|
+
verifyUrl: c.verifyUrl,
|
|
2841
|
+
challengeNeedsIdentityKey: c.challengeNeedsIdentityKey,
|
|
2842
|
+
post: postJson2
|
|
2843
|
+
});
|
|
2844
|
+
if (!r) {
|
|
2845
|
+
setState((s) => ({ ...s, status: s.identityKey && s.signedIn ? "connected" : "idle" }));
|
|
1570
2846
|
return null;
|
|
1571
2847
|
}
|
|
1572
|
-
const cw =
|
|
1573
|
-
let { handle = null, paymail = null } = cw;
|
|
1574
|
-
let signedIn = false;
|
|
1575
|
-
if (cw.wallet && c.challengeUrl && c.verifyUrl) {
|
|
1576
|
-
const r = await signInWithBwalletX({
|
|
1577
|
-
wallet: cw.wallet,
|
|
1578
|
-
challenge: (identityKey) => postJson2(c.challengeUrl, { identityKey })
|
|
1579
|
-
});
|
|
1580
|
-
const v = await atStage("verify", () => postJson2(c.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }));
|
|
1581
|
-
handle = v.handle ?? handle;
|
|
1582
|
-
paymail = v.paymail ?? paymail;
|
|
1583
|
-
signedIn = true;
|
|
1584
|
-
}
|
|
2848
|
+
const cw = r.connected;
|
|
1585
2849
|
payer.current = cw.payer;
|
|
1586
2850
|
setState({
|
|
1587
2851
|
status: "connected",
|
|
1588
|
-
identityKey:
|
|
1589
|
-
handle,
|
|
1590
|
-
paymail,
|
|
2852
|
+
identityKey: r.identityKey,
|
|
2853
|
+
handle: r.handle,
|
|
2854
|
+
paymail: r.paymail,
|
|
1591
2855
|
wallet: cw.wallet,
|
|
1592
2856
|
walletId: cw.optionId,
|
|
2857
|
+
walletType: r.walletType,
|
|
1593
2858
|
transitional: !!cw.transitional,
|
|
1594
|
-
signedIn,
|
|
1595
|
-
error: null
|
|
2859
|
+
signedIn: r.signedIn,
|
|
2860
|
+
error: null,
|
|
2861
|
+
rememberedWallet: c.rememberChoice !== false ? rememberedWalletChoice() : null,
|
|
2862
|
+
needsBwalletX: needsBwalletX(r.walletType),
|
|
2863
|
+
metanetOffered: metanetOffered()
|
|
1596
2864
|
});
|
|
1597
|
-
return cw;
|
|
2865
|
+
return { ...cw, identityKey: r.identityKey, handle: r.handle ?? void 0, paymail: r.paymail ?? void 0 };
|
|
1598
2866
|
} catch (e) {
|
|
1599
2867
|
if (e?.message === "cancelled") {
|
|
1600
|
-
setState((s) => ({ ...s, status: s.identityKey ? "connected" : "idle" }));
|
|
2868
|
+
setState((s) => ({ ...s, status: s.identityKey && s.signedIn ? "connected" : "idle" }));
|
|
1601
2869
|
return null;
|
|
1602
2870
|
}
|
|
1603
2871
|
throw fail(e);
|
|
@@ -1610,9 +2878,107 @@ function BwalletXProvider(p) {
|
|
|
1610
2878
|
if (c.logoutUrl) await postJson2(c.logoutUrl, {}).catch(() => {
|
|
1611
2879
|
});
|
|
1612
2880
|
if (state.walletId === "bwalletx-pairing") await clearPairing();
|
|
2881
|
+
if (c.rememberChoice !== false) forgetWalletChoice();
|
|
2882
|
+
silentRun.current++;
|
|
1613
2883
|
payer.current = null;
|
|
1614
|
-
setState(empty);
|
|
2884
|
+
setState({ ...empty, metanetOffered: metanetOffered() });
|
|
1615
2885
|
}, [state.walletId]);
|
|
2886
|
+
const switchToBwalletX = (0, import_react.useCallback)(() => connect({ bwalletx: true }), [connect]);
|
|
2887
|
+
const connectMetanet = (0, import_react.useCallback)(() => connect({ metanet: true }), [connect]);
|
|
2888
|
+
const forgetWallet = (0, import_react.useCallback)(() => {
|
|
2889
|
+
if (props.current.rememberChoice !== false) forgetWalletChoice();
|
|
2890
|
+
silentRun.current++;
|
|
2891
|
+
setState((s) => ({
|
|
2892
|
+
...s,
|
|
2893
|
+
rememberedWallet: null,
|
|
2894
|
+
needsBwalletX: s.walletType ? needsBwalletX(s.walletType) : false,
|
|
2895
|
+
// A silent Metanet sign-in on screen goes back to the normal choice.
|
|
2896
|
+
status: s.status === "connecting" && !s.wallet ? s.signedIn && s.identityKey ? "connected" : "idle" : s.status
|
|
2897
|
+
}));
|
|
2898
|
+
}, []);
|
|
2899
|
+
(0, import_react.useEffect)(() => {
|
|
2900
|
+
setState((s) => s.metanetOffered === metanetOffered() ? s : { ...s, metanetOffered: metanetOffered() });
|
|
2901
|
+
if (p.rememberChoice === false) return;
|
|
2902
|
+
const r = rememberedWalletChoice();
|
|
2903
|
+
if (r) setState((s) => s.rememberedWallet ? s : { ...s, rememberedWallet: r, needsBwalletX: s.walletType ? s.needsBwalletX : needsBwalletX(r.type) && r.type !== METANET_TYPE });
|
|
2904
|
+
}, [p.rememberChoice]);
|
|
2905
|
+
const sessionOnly = state.status === "connected" && state.signedIn && !state.wallet;
|
|
2906
|
+
(0, import_react.useEffect)(() => {
|
|
2907
|
+
const c = props.current;
|
|
2908
|
+
if (!sessionOnly || c.autoConnect === false || c.rememberChoice === false || c.fastPath === false) return;
|
|
2909
|
+
const plan = planAutoConnect({ remembered: rememberedWalletChoice(), signedIn: true });
|
|
2910
|
+
if (plan.action !== "attach" || !plan.remembered) return;
|
|
2911
|
+
let live = true;
|
|
2912
|
+
void attachRemembered(plan.remembered, { ms: c.discoverMs }).then((opt) => {
|
|
2913
|
+
if (!live || !opt?.wallet) return;
|
|
2914
|
+
payer.current = opt.wallet;
|
|
2915
|
+
setState(
|
|
2916
|
+
(s) => s.status === "connected" && !s.wallet ? { ...s, wallet: opt.wallet, walletId: opt.id, walletType: walletTypeOf(opt), needsBwalletX: needsBwalletX(walletTypeOf(opt)) } : s
|
|
2917
|
+
);
|
|
2918
|
+
});
|
|
2919
|
+
return () => {
|
|
2920
|
+
live = false;
|
|
2921
|
+
};
|
|
2922
|
+
}, [sessionOnly]);
|
|
2923
|
+
const silentReady = state.status === "idle" || sessionOnly;
|
|
2924
|
+
(0, import_react.useEffect)(() => {
|
|
2925
|
+
const c = props.current;
|
|
2926
|
+
if (!silentReady || !sessionChecked.current || c.autoConnect === false || c.rememberChoice === false || c.fastPath === false || silentAttemptUsed()) return;
|
|
2927
|
+
const plan = planAutoConnect({ remembered: rememberedWalletChoice(), signedIn: sessionOnly });
|
|
2928
|
+
if (plan.action !== "silent-metanet") return;
|
|
2929
|
+
const run = ++silentRun.current;
|
|
2930
|
+
const live = () => run === silentRun.current;
|
|
2931
|
+
let shown = false;
|
|
2932
|
+
const session = stateRef.current;
|
|
2933
|
+
void runSilentMetanet({
|
|
2934
|
+
sessionKey: sessionOnly ? session.identityKey : null,
|
|
2935
|
+
challengeUrl: c.challengeUrl,
|
|
2936
|
+
verifyUrl: c.verifyUrl,
|
|
2937
|
+
post: postJson2,
|
|
2938
|
+
onAttempt: c.onAttempt,
|
|
2939
|
+
isAbandoned: () => !live(),
|
|
2940
|
+
logout: () => props.current.logoutUrl ? postJson2(props.current.logoutUrl, {}) : Promise.resolve(),
|
|
2941
|
+
onSignIn: () => {
|
|
2942
|
+
if (!live()) return;
|
|
2943
|
+
shown = true;
|
|
2944
|
+
setState((s) => s.status === "idle" ? { ...s, status: "connecting", error: null } : s);
|
|
2945
|
+
}
|
|
2946
|
+
}).then((r) => {
|
|
2947
|
+
if (!live()) return;
|
|
2948
|
+
if (!r) {
|
|
2949
|
+
if (shown) setState((s) => s.status === "connecting" && !s.wallet ? { ...s, status: s.signedIn && s.identityKey ? "connected" : "idle" } : s);
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
const cw = r.connected;
|
|
2953
|
+
payer.current = cw.payer;
|
|
2954
|
+
setState((s) => ({
|
|
2955
|
+
...s,
|
|
2956
|
+
status: "connected",
|
|
2957
|
+
identityKey: r.identityKey,
|
|
2958
|
+
handle: r.handle ?? s.handle,
|
|
2959
|
+
paymail: r.paymail ?? s.paymail,
|
|
2960
|
+
wallet: cw.wallet,
|
|
2961
|
+
walletId: cw.optionId,
|
|
2962
|
+
walletType: r.walletType,
|
|
2963
|
+
transitional: false,
|
|
2964
|
+
signedIn: r.signedIn || s.signedIn,
|
|
2965
|
+
error: null,
|
|
2966
|
+
rememberedWallet: rememberedWalletChoice(),
|
|
2967
|
+
needsBwalletX: true
|
|
2968
|
+
}));
|
|
2969
|
+
});
|
|
2970
|
+
}, [silentReady, sessionOnly]);
|
|
2971
|
+
(0, import_react.useEffect)(() => {
|
|
2972
|
+
const key = state.identityKey;
|
|
2973
|
+
if (!key || state.paymail || p.lookupUrl === "off") return;
|
|
2974
|
+
let live = true;
|
|
2975
|
+
void lookupPaymail(key, p.lookupUrl).then((pm) => {
|
|
2976
|
+
if (live && pm) setState((s) => s.identityKey === key && !s.paymail ? { ...s, paymail: pm } : s);
|
|
2977
|
+
});
|
|
2978
|
+
return () => {
|
|
2979
|
+
live = false;
|
|
2980
|
+
};
|
|
2981
|
+
}, [state.identityKey, state.paymail, p.lookupUrl]);
|
|
1616
2982
|
const need = () => {
|
|
1617
2983
|
if (!payer.current) throw new PaymentError("NOT_SUPPORTED", "Connect a wallet first (connect()).");
|
|
1618
2984
|
return payer.current;
|
|
@@ -1623,12 +2989,15 @@ function BwalletXProvider(p) {
|
|
|
1623
2989
|
wallets,
|
|
1624
2990
|
connect,
|
|
1625
2991
|
disconnect,
|
|
2992
|
+
switchToBwalletX,
|
|
2993
|
+
connectMetanet,
|
|
2994
|
+
forgetWallet,
|
|
1626
2995
|
listWallets,
|
|
1627
2996
|
pay: (req) => pay(need(), req, props.current.payOptions),
|
|
1628
2997
|
tip: (req) => tip(need(), req, props.current.payOptions),
|
|
1629
2998
|
buyToken: (req) => buyToken(need(), req)
|
|
1630
2999
|
}),
|
|
1631
|
-
[state, wallets, connect, disconnect, listWallets]
|
|
3000
|
+
[state, wallets, connect, disconnect, switchToBwalletX, connectMetanet, forgetWallet, listWallets]
|
|
1632
3001
|
);
|
|
1633
3002
|
return (0, import_react.createElement)(Ctx.Provider, { value }, p.children);
|
|
1634
3003
|
}
|
|
@@ -1637,6 +3006,24 @@ function useBwalletX() {
|
|
|
1637
3006
|
if (!v) throw new Error("useBwalletX() must be used inside <BwalletXProvider>");
|
|
1638
3007
|
return v;
|
|
1639
3008
|
}
|
|
3009
|
+
function accountFor(v) {
|
|
3010
|
+
const status = signinStateFor(v);
|
|
3011
|
+
const signedIn = status === "signed-in";
|
|
3012
|
+
return {
|
|
3013
|
+
status,
|
|
3014
|
+
identityKey: signedIn ? v.identityKey : null,
|
|
3015
|
+
paymail: signedIn ? v.paymail : null,
|
|
3016
|
+
handle: signedIn ? v.handle : null,
|
|
3017
|
+
label: signedIn ? accountLabel(v) : null,
|
|
3018
|
+
avatarUrl: signedIn ? avatarUrlFor(v.identityKey) : null,
|
|
3019
|
+
error: status === "error" ? signInErrorText(v.error) : null,
|
|
3020
|
+
login: () => v.connect().then(() => void 0, () => void 0),
|
|
3021
|
+
logout: () => v.disconnect()
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
function useBwalletXAccount() {
|
|
3025
|
+
return accountFor(useBwalletX());
|
|
3026
|
+
}
|
|
1640
3027
|
var BwalletxProvider = BwalletXProvider;
|
|
1641
3028
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1642
3029
|
0 && (module.exports = {
|
|
@@ -1644,10 +3031,14 @@ var BwalletxProvider = BwalletXProvider;
|
|
|
1644
3031
|
BwalletxProvider,
|
|
1645
3032
|
BwalletxSignin,
|
|
1646
3033
|
PaymentError,
|
|
3034
|
+
accountFor,
|
|
3035
|
+
autoChoice,
|
|
1647
3036
|
brc100Option,
|
|
1648
3037
|
discoverWallets,
|
|
1649
3038
|
handcashAdapter,
|
|
1650
3039
|
pairingOption,
|
|
1651
|
-
|
|
3040
|
+
signinStateFor,
|
|
3041
|
+
useBwalletX,
|
|
3042
|
+
useBwalletXAccount
|
|
1652
3043
|
});
|
|
1653
3044
|
//# sourceMappingURL=react.cjs.map
|