@bwalletx/connect 0.5.3 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +80 -8
- package/dist/bwalletx-connect.global.js +92 -25
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{wallets-VOKIKBbO.d.ts → choice-DCh-tgpT.d.cts} +113 -5
- package/dist/{wallets-VOKIKBbO.d.cts → choice-DCh-tgpT.d.ts} +113 -5
- package/dist/chunk-34DKBQNN.js +222 -0
- package/dist/chunk-34DKBQNN.js.map +1 -0
- package/dist/{chunk-QRXFL66W.js → chunk-A2WLGS25.js} +6 -4
- package/dist/chunk-A2WLGS25.js.map +1 -0
- package/dist/chunk-IFGOAROM.js +255 -0
- package/dist/chunk-IFGOAROM.js.map +1 -0
- package/dist/chunk-LS6AYEBO.js +1886 -0
- package/dist/chunk-LS6AYEBO.js.map +1 -0
- package/dist/element-D9Zl-x12.d.cts +46 -0
- package/dist/element-D9Zl-x12.d.ts +46 -0
- package/dist/element.cjs +1221 -67
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +23 -28
- package/dist/element.d.ts +23 -28
- package/dist/element.js +29 -2
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +1539 -449
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -12
- package/dist/index.d.ts +185 -12
- package/dist/index.js +106 -12
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +5 -3
- package/dist/next.cjs.map +1 -1
- package/dist/next.js +1 -1
- package/dist/react.cjs +1073 -194
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +75 -8
- package/dist/react.d.ts +75 -8
- package/dist/react.js +121 -40
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +5 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +8 -3
- package/dist/server.d.ts +8 -3
- package/dist/server.js +1 -1
- package/dist/shell-react.cjs.map +1 -1
- package/dist/shell-react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-IUDD7YTQ.js +0 -839
- package/dist/chunk-IUDD7YTQ.js.map +0 -1
- package/dist/chunk-PI42H3XY.js +0 -459
- package/dist/chunk-PI42H3XY.js.map +0 -1
- package/dist/chunk-QRXFL66W.js.map +0 -1
package/dist/element.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,19 +17,39 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/define.ts
|
|
21
31
|
var define_exports = {};
|
|
22
32
|
__export(define_exports, {
|
|
33
|
+
BUTTON_TAG: () => BUTTON_TAG,
|
|
34
|
+
CONNECTED_KEY: () => CONNECTED_KEY,
|
|
23
35
|
GET_URL: () => GET_URL,
|
|
36
|
+
MENU_CSS: () => MENU_CSS,
|
|
37
|
+
PENDING_LABEL: () => PENDING_LABEL,
|
|
38
|
+
PENDING_LABEL_OTHER: () => PENDING_LABEL_OTHER,
|
|
39
|
+
PENDING_NOTE: () => PENDING_NOTE,
|
|
24
40
|
SIGNIN_CSS: () => SIGNIN_CSS,
|
|
25
41
|
SIGNIN_TAG: () => SIGNIN_TAG,
|
|
26
42
|
WEB_WALLET_URL: () => WEB_WALLET_URL,
|
|
43
|
+
_resetBwalletxButtons: () => _resetBwalletxButtons,
|
|
27
44
|
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
45
|
+
defineBwalletxButton: () => defineBwalletxButton,
|
|
28
46
|
defineBwalletxSignin: () => defineBwalletxSignin,
|
|
47
|
+
dropLegacyConnectedKey: () => dropLegacyConnectedKey,
|
|
48
|
+
getUrlFor: () => getUrlFor,
|
|
49
|
+
handleLabel: () => handleLabel,
|
|
29
50
|
markSvg: () => markSvg,
|
|
30
|
-
signinText: () => signinText
|
|
51
|
+
signinText: () => signinText,
|
|
52
|
+
stateFor: () => stateFor
|
|
31
53
|
});
|
|
32
54
|
module.exports = __toCommonJS(define_exports);
|
|
33
55
|
|
|
@@ -36,13 +58,16 @@ var import_sdk = require("@bsv/sdk");
|
|
|
36
58
|
var RDNS_EXTENSION = "com.bwalletx.extension";
|
|
37
59
|
var RDNS_MOBILE = "space.bwallet.mobile";
|
|
38
60
|
var BWALLETX_RDNS = [RDNS_EXTENSION, RDNS_MOBILE];
|
|
39
|
-
|
|
61
|
+
var DEFAULT_DISCOVER_MS = 300;
|
|
62
|
+
function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
40
63
|
if (typeof window === "undefined") return Promise.resolve(null);
|
|
41
64
|
return new Promise((resolve) => {
|
|
42
65
|
let done = false;
|
|
66
|
+
let timer;
|
|
43
67
|
const finish = (r) => {
|
|
44
68
|
if (done) return;
|
|
45
69
|
done = true;
|
|
70
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
46
71
|
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
47
72
|
resolve(r);
|
|
48
73
|
};
|
|
@@ -55,11 +80,14 @@ function discoverBwalletX(ms = 1200) {
|
|
|
55
80
|
};
|
|
56
81
|
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
57
82
|
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
if (done) return;
|
|
84
|
+
if (isFramed()) {
|
|
85
|
+
void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
timer = setTimeout(() => {
|
|
89
|
+
timer = void 0;
|
|
90
|
+
finish(null);
|
|
63
91
|
}, ms);
|
|
64
92
|
});
|
|
65
93
|
}
|
|
@@ -115,6 +143,24 @@ async function probeXdm(ms = 1200, allowed = BWALLETX_PARENT_ORIGINS) {
|
|
|
115
143
|
return null;
|
|
116
144
|
}
|
|
117
145
|
}
|
|
146
|
+
var warmedAt = 0;
|
|
147
|
+
function prewarmBwalletX() {
|
|
148
|
+
if (typeof window === "undefined" || Date.now() - warmedAt < 2e4) return;
|
|
149
|
+
warmedAt = Date.now();
|
|
150
|
+
void discoverBwalletX(0).then((f) => {
|
|
151
|
+
if (f && f.method === "extension") void f.wallet.isAuthenticated({}).catch(() => {
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function bindPrewarm(el2) {
|
|
156
|
+
const on = () => prewarmBwalletX();
|
|
157
|
+
const evs = ["pointerenter", "focus", "touchstart"];
|
|
158
|
+
for (const e of evs) el2.addEventListener(e, on, { passive: true });
|
|
159
|
+
return () => evs.forEach((e) => el2.removeEventListener(e, on));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/wallets.ts
|
|
163
|
+
var import_sdk4 = require("@bsv/sdk");
|
|
118
164
|
|
|
119
165
|
// src/pair.ts
|
|
120
166
|
var import_sdk3 = require("@bsv/sdk");
|
|
@@ -155,10 +201,22 @@ function pairUrl(link) {
|
|
|
155
201
|
const q = new URLSearchParams({ v: link.v, r: link.r, c: link.c, k: link.k, o: link.o, e: String(link.e) });
|
|
156
202
|
return `https://${PAIR_HOST}/pair?${q}`;
|
|
157
203
|
}
|
|
158
|
-
var
|
|
204
|
+
var newClaim = () => b64url(crypto.getRandomValues(new Uint8Array(16)));
|
|
205
|
+
var WALLET_CLAIM_INFO = "bwallet-pair-wallet-claim-v1";
|
|
206
|
+
var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
207
|
+
async function walletClaimFor(channel, sitePub) {
|
|
208
|
+
const d = await crypto.subtle.digest("SHA-256", enc.encode(`${WALLET_CLAIM_INFO}|${channel}|${sitePub}`));
|
|
209
|
+
return b64url(new Uint8Array(d));
|
|
210
|
+
}
|
|
211
|
+
async function claimDigest(claim) {
|
|
212
|
+
return hex(new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(claim))));
|
|
213
|
+
}
|
|
214
|
+
var relaySocketUrl = (relay, channel, role, expiry, claim, walletClaimHash) => {
|
|
159
215
|
const local = /^(localhost|127\.0\.0\.1)(:\d+)?$/.test(relay);
|
|
160
216
|
const q = new URLSearchParams({ role });
|
|
161
217
|
if (expiry) q.set("e", String(expiry));
|
|
218
|
+
if (claim) q.set("claim", claim);
|
|
219
|
+
if (walletClaimHash && role === "site") q.set("wc", walletClaimHash);
|
|
162
220
|
return `${local ? "ws" : "wss"}://${relay}/v1/c/${channel}?${q}`;
|
|
163
221
|
};
|
|
164
222
|
async function deriveSession(mine, theirs, channel) {
|
|
@@ -256,6 +314,7 @@ function parseStored(raw) {
|
|
|
256
314
|
if (typeof p.s !== "string" || !/^[0-9a-f]{64}$/i.test(p.s)) return null;
|
|
257
315
|
if (typeof p.k !== "string" || !/^0[23][0-9a-f]{64}$/i.test(p.k)) return null;
|
|
258
316
|
if (!n(p.sent) || !n(p.lastSeen) || !n(p.pairedAt)) return null;
|
|
317
|
+
if (p.sc !== void 0 && (typeof p.sc !== "string" || !/^[A-Za-z0-9_-]{22,64}$/.test(p.sc))) return null;
|
|
259
318
|
return p;
|
|
260
319
|
} catch {
|
|
261
320
|
return null;
|
|
@@ -313,7 +372,12 @@ var Link = class {
|
|
|
313
372
|
await this.opening;
|
|
314
373
|
}
|
|
315
374
|
reopen() {
|
|
316
|
-
|
|
375
|
+
if (!this.p.sc) {
|
|
376
|
+
this.p = { ...this.p, sc: newClaim() };
|
|
377
|
+
this.persist();
|
|
378
|
+
}
|
|
379
|
+
const e = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S - 10;
|
|
380
|
+
const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, "site", e, this.p.sc));
|
|
317
381
|
return new Promise((resolve, reject) => {
|
|
318
382
|
ws.onopen = () => {
|
|
319
383
|
this.ws = ws;
|
|
@@ -407,11 +471,13 @@ function pairBwalletX(opts) {
|
|
|
407
471
|
const siteKey = import_sdk3.PrivateKey.fromRandom();
|
|
408
472
|
const channel = newChannel();
|
|
409
473
|
const expiry = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S;
|
|
410
|
-
|
|
474
|
+
const siteClaim = newClaim();
|
|
475
|
+
const sitePub = siteKey.toPublicKey().toString();
|
|
476
|
+
return walletClaimFor(channel, sitePub).then(claimDigest).then((wc) => new Promise((resolve, reject) => {
|
|
411
477
|
if (opts.signal?.aborted) return reject(new Error("cancelled"));
|
|
412
478
|
let ws;
|
|
413
479
|
try {
|
|
414
|
-
ws = socket(relaySocketUrl(relay, channel, "site", expiry));
|
|
480
|
+
ws = socket(relaySocketUrl(relay, channel, "site", expiry, siteClaim, wc));
|
|
415
481
|
} catch (e) {
|
|
416
482
|
return reject(e);
|
|
417
483
|
}
|
|
@@ -450,7 +516,7 @@ function pairBwalletX(opts) {
|
|
|
450
516
|
if (msg.t === "ready") {
|
|
451
517
|
settled = true;
|
|
452
518
|
clearTimeout(timer);
|
|
453
|
-
const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now() };
|
|
519
|
+
const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now(), sc: siteClaim };
|
|
454
520
|
await Promise.resolve(storage.setItem(key, JSON.stringify(p))).catch(() => {
|
|
455
521
|
});
|
|
456
522
|
const link = new Link(p, sealer, storage, key, socket, opts.callTimeoutMs ?? 12e4);
|
|
@@ -460,8 +526,8 @@ function pairBwalletX(opts) {
|
|
|
460
526
|
fail(new Error("Pairing was declined in bWalletX"));
|
|
461
527
|
}
|
|
462
528
|
});
|
|
463
|
-
opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k:
|
|
464
|
-
});
|
|
529
|
+
opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: sitePub, o: origin, e: expiry }));
|
|
530
|
+
}));
|
|
465
531
|
}
|
|
466
532
|
async function restorePairing(opts = {}) {
|
|
467
533
|
const storage = opts.storage === void 0 ? defaultStorage() : opts.storage ?? memoryStorage();
|
|
@@ -473,39 +539,106 @@ async function restorePairing(opts = {}) {
|
|
|
473
539
|
sealer.restore({ sent: p.sent, lastSeen: p.lastSeen });
|
|
474
540
|
return new Link(p, sealer, storage, key, opts.socket ?? defaultSocket(), opts.callTimeoutMs ?? 12e4).wallet();
|
|
475
541
|
}
|
|
542
|
+
async function clearPairing(opts = {}) {
|
|
543
|
+
const storage = opts.storage === void 0 ? defaultStorage() : opts.storage ?? memoryStorage();
|
|
544
|
+
await Promise.resolve(storage.removeItem(opts.storageKey ?? DEFAULT_KEY)).catch(() => {
|
|
545
|
+
});
|
|
546
|
+
}
|
|
476
547
|
|
|
477
548
|
// src/signin.ts
|
|
478
549
|
var LOGIN_PROTOCOL = [2, "bwallet sign in"];
|
|
479
550
|
async function connectBwalletX(opts = {}) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
551
|
+
const status = (s) => {
|
|
552
|
+
try {
|
|
553
|
+
opts.onStatus?.(s);
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
if (opts.wallet) {
|
|
558
|
+
status("opening");
|
|
559
|
+
return { wallet: opts.wallet, method: "wallet" };
|
|
560
|
+
}
|
|
561
|
+
status("finding");
|
|
562
|
+
const found = await discoverBwalletX(opts.discoverMs ?? DEFAULT_DISCOVER_MS);
|
|
563
|
+
if (found) {
|
|
564
|
+
status("opening");
|
|
565
|
+
return { wallet: found.wallet, method: found.method };
|
|
566
|
+
}
|
|
483
567
|
if (opts.reusePairing !== false) {
|
|
484
568
|
const restored = await restorePairing(opts.pairing ?? {});
|
|
485
|
-
if (restored)
|
|
569
|
+
if (restored) {
|
|
570
|
+
status("opening");
|
|
571
|
+
return { wallet: restored, method: "pairing" };
|
|
572
|
+
}
|
|
486
573
|
}
|
|
487
574
|
if (!opts.pairing) {
|
|
488
575
|
throw Object.assign(new Error("bWalletX was not found in this browser"), { code: "NOT_FOUND" });
|
|
489
576
|
}
|
|
577
|
+
status("pairing");
|
|
490
578
|
return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
|
|
491
579
|
}
|
|
580
|
+
async function waitIfLocked(wallet) {
|
|
581
|
+
let ok = false;
|
|
582
|
+
try {
|
|
583
|
+
ok = !!(await wallet.isAuthenticated({}))?.authenticated;
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
if (!ok) await Promise.resolve().then(() => wallet.waitForAuthentication({})).catch(() => {
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function challengeWantsIdentityKey(e) {
|
|
590
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
591
|
+
return /identityKey/i.test(msg);
|
|
592
|
+
}
|
|
492
593
|
async function signInWithBwalletX(opts) {
|
|
493
594
|
const { wallet, method } = await connectBwalletX(opts);
|
|
494
|
-
if (method !== "pairing") await wallet
|
|
595
|
+
if (method !== "pairing" && !opts.skipWaitForAuthentication) await waitIfLocked(wallet);
|
|
596
|
+
const status = () => {
|
|
597
|
+
try {
|
|
598
|
+
opts.onStatus?.("signing");
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
const sign = (c2) => wallet.createSignature({
|
|
603
|
+
protocolID: c2.protocolID ?? LOGIN_PROTOCOL,
|
|
604
|
+
keyID: c2.keyID,
|
|
605
|
+
counterparty: "anyone",
|
|
606
|
+
data: Array.from(new TextEncoder().encode(c2.message))
|
|
495
607
|
});
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
"
|
|
500
|
-
() =>
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
608
|
+
const keyFirst = async () => {
|
|
609
|
+
const { publicKey: identityKey2 } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
|
|
610
|
+
status();
|
|
611
|
+
const c2 = await atStage("challenge", () => opts.challenge(identityKey2));
|
|
612
|
+
const { signature: signature2 } = await atStage("signature", () => sign(c2));
|
|
613
|
+
return { identityKey: identityKey2, signature: Array.from(signature2), nonce: c2.nonce, method, wallet };
|
|
614
|
+
};
|
|
615
|
+
if (opts.challengeNeedsIdentityKey && !opts.identityKey) return keyFirst();
|
|
616
|
+
let c;
|
|
617
|
+
try {
|
|
618
|
+
c = await atStage("challenge", () => opts.challenge(opts.identityKey));
|
|
619
|
+
} catch (e) {
|
|
620
|
+
if (!opts.identityKey && challengeWantsIdentityKey(e)) return keyFirst();
|
|
621
|
+
throw e;
|
|
622
|
+
}
|
|
623
|
+
status();
|
|
624
|
+
const [{ publicKey: identityKey }, { signature }] = await Promise.all([
|
|
625
|
+
opts.identityKey ? Promise.resolve({ publicKey: opts.identityKey }) : atStage("identity", () => wallet.getPublicKey({ identityKey: true })),
|
|
626
|
+
atStage("signature", () => sign(c))
|
|
627
|
+
]);
|
|
507
628
|
return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
|
|
508
629
|
}
|
|
630
|
+
async function signInAndVerify(opts) {
|
|
631
|
+
const hinted = !opts.identityKey && !!opts.identityKeyHint;
|
|
632
|
+
const r = await signInWithBwalletX({ ...opts, identityKey: opts.identityKey ?? opts.identityKeyHint });
|
|
633
|
+
try {
|
|
634
|
+
return { result: r, verified: await atStage("verify", () => opts.verify(r)) };
|
|
635
|
+
} catch (e) {
|
|
636
|
+
const status = e?.status;
|
|
637
|
+
if (!hinted || status !== 401) throw e;
|
|
638
|
+
const again = await signInWithBwalletX({ ...opts, wallet: r.wallet, identityKey: void 0, skipWaitForAuthentication: true });
|
|
639
|
+
return { result: again, verified: await atStage("verify", () => opts.verify(again)) };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
509
642
|
async function atStage(stage, run) {
|
|
510
643
|
try {
|
|
511
644
|
return await run();
|
|
@@ -516,8 +649,449 @@ async function atStage(stage, run) {
|
|
|
516
649
|
}
|
|
517
650
|
}
|
|
518
651
|
|
|
652
|
+
// src/wallets.ts
|
|
653
|
+
async function brc100Connected(wallet, o, c = {}) {
|
|
654
|
+
if (c.identity === false) return { wallet, payer: wallet, identityKey: null, optionId: o.id, kind: o.kind };
|
|
655
|
+
await waitIfLocked(wallet);
|
|
656
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
657
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
|
|
658
|
+
}
|
|
659
|
+
function brc100Option(wallet, info) {
|
|
660
|
+
const opt = {
|
|
661
|
+
id: info.id,
|
|
662
|
+
name: info.name,
|
|
663
|
+
icon: info.icon,
|
|
664
|
+
rdns: info.rdns,
|
|
665
|
+
kind: "brc100",
|
|
666
|
+
isBwalletX: info.isBwalletX ?? false,
|
|
667
|
+
substrate: info.substrate,
|
|
668
|
+
wallet,
|
|
669
|
+
connect: (c) => brc100Connected(wallet, opt, c)
|
|
670
|
+
};
|
|
671
|
+
return opt;
|
|
672
|
+
}
|
|
673
|
+
function pairingOption(pairing) {
|
|
674
|
+
const opt = {
|
|
675
|
+
id: "bwalletx-pairing",
|
|
676
|
+
name: "bWalletX on your phone",
|
|
677
|
+
kind: "pairing",
|
|
678
|
+
isBwalletX: true,
|
|
679
|
+
substrate: "pairing",
|
|
680
|
+
async connect(c) {
|
|
681
|
+
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
682
|
+
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
683
|
+
return brc100Connected(w, opt, c);
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
return opt;
|
|
687
|
+
}
|
|
688
|
+
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
689
|
+
async function probe(substrate, ms) {
|
|
690
|
+
try {
|
|
691
|
+
const w = new import_sdk4.WalletClient(substrate);
|
|
692
|
+
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
693
|
+
return ok ? w : null;
|
|
694
|
+
} catch {
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function cwiIsAnnounced(cwi, announced) {
|
|
699
|
+
return announced.some((o) => o.wallet === cwi || o.rdns === RDNS_MOBILE);
|
|
700
|
+
}
|
|
701
|
+
async function discoverWallets(o = {}) {
|
|
702
|
+
const out = [];
|
|
703
|
+
const ms = o.ms ?? 300;
|
|
704
|
+
if (typeof window !== "undefined") {
|
|
705
|
+
const seen = /* @__PURE__ */ new Map();
|
|
706
|
+
const onAnnounce = (e) => {
|
|
707
|
+
const d = e.detail;
|
|
708
|
+
if (!d?.wallet) return;
|
|
709
|
+
const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
|
|
710
|
+
if (seen.has(rdns)) return;
|
|
711
|
+
const isB = BWALLETX_RDNS.includes(rdns);
|
|
712
|
+
const substrate = rdns === RDNS_EXTENSION ? "extension" : rdns === RDNS_MOBILE ? "in-app" : "injected";
|
|
713
|
+
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB, substrate }));
|
|
714
|
+
};
|
|
715
|
+
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
716
|
+
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
717
|
+
const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
|
|
718
|
+
await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
|
|
719
|
+
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
720
|
+
out.push(...seen.values());
|
|
721
|
+
if (![...seen.values()].some((w) => w.isBwalletX) && isFramed()) {
|
|
722
|
+
const x = await probeXdm(Math.max(ms, 1200));
|
|
723
|
+
if (x) out.push(brc100Option(x, { id: "bwalletx-xdm", name: "bWalletX", isBwalletX: true, substrate: "xdm" }));
|
|
724
|
+
}
|
|
725
|
+
const cwi = window.CWI;
|
|
726
|
+
if (cwi && !cwiIsAnnounced(cwi, [...seen.values()])) {
|
|
727
|
+
out.push(brc100Option(cwi, { id: "window.CWI", name: seen.size ? "Another browser wallet" : "Browser wallet", isBwalletX: false, substrate: "window.CWI" }));
|
|
728
|
+
}
|
|
729
|
+
const [json, cicada] = await local;
|
|
730
|
+
if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)", substrate: "json-api" }));
|
|
731
|
+
if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)", substrate: "cicada" }));
|
|
732
|
+
}
|
|
733
|
+
if (o.pairing) out.push(pairingOption(o.pairing));
|
|
734
|
+
out.push(...o.extra ?? []);
|
|
735
|
+
const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
|
|
736
|
+
return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
|
|
737
|
+
}
|
|
738
|
+
var DESKTOP_OPTION_ID = "desktop";
|
|
739
|
+
function desktopOption(ms = 1500) {
|
|
740
|
+
const opt = {
|
|
741
|
+
id: DESKTOP_OPTION_ID,
|
|
742
|
+
name: "Desktop wallet",
|
|
743
|
+
kind: "brc100",
|
|
744
|
+
isBwalletX: false,
|
|
745
|
+
substrate: "json-api",
|
|
746
|
+
async connect(c) {
|
|
747
|
+
const [json, cicada] = await Promise.all([probe("json-api", ms), probe("Cicada", ms)]);
|
|
748
|
+
const w = json ?? cicada;
|
|
749
|
+
if (!w) throw Object.assign(new Error("No desktop wallet answered on this computer"), { code: "NOT_FOUND" });
|
|
750
|
+
opt.wallet = w;
|
|
751
|
+
opt.substrate = json ? "json-api" : "cicada";
|
|
752
|
+
return brc100Connected(w, opt, c);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
return opt;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/account.ts
|
|
759
|
+
var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
760
|
+
var BAVATAR_URL = "https://chat.bwalletx.com/api/bavatar/";
|
|
761
|
+
function shortKey(key) {
|
|
762
|
+
return key.length > 12 ? `${key.slice(0, 6)}\u2026${key.slice(-4)}` : key;
|
|
763
|
+
}
|
|
764
|
+
function accountLabel(a) {
|
|
765
|
+
if (a.paymail && /@bwalletx\.com$/i.test(a.paymail)) return "$" + a.paymail.split("@")[0];
|
|
766
|
+
if (a.handle) return a.handle;
|
|
767
|
+
if (a.paymail) return a.paymail;
|
|
768
|
+
return a.identityKey ? shortKey(a.identityKey) : "";
|
|
769
|
+
}
|
|
770
|
+
function avatarUrlFor(identityKey) {
|
|
771
|
+
return identityKey && /^0[23][0-9a-fA-F]{64}$/.test(identityKey) ? `${BAVATAR_URL}${identityKey.toLowerCase()}.png` : null;
|
|
772
|
+
}
|
|
773
|
+
var cache = /* @__PURE__ */ new Map();
|
|
774
|
+
function lookupPaymail(identityKey, url = PAYMAIL_LOOKUP_URL) {
|
|
775
|
+
if (url === "off" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
776
|
+
const k = `${url}|${identityKey}`;
|
|
777
|
+
let p = cache.get(k);
|
|
778
|
+
if (!p) {
|
|
779
|
+
p = fetch(url + encodeURIComponent(identityKey)).then((r) => r.ok ? r.json() : null).then((j) => j && typeof j.paymail === "string" ? j.paymail : null).catch(() => null);
|
|
780
|
+
cache.set(k, p);
|
|
781
|
+
}
|
|
782
|
+
return p;
|
|
783
|
+
}
|
|
784
|
+
function isUserRejection(e) {
|
|
785
|
+
const code = e?.code;
|
|
786
|
+
if (code === "ERR_PERMISSION_DENIED" || code === 4001) return true;
|
|
787
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
788
|
+
return /denied|reject|cancel|declin|refused|not now|user closed/i.test(msg);
|
|
789
|
+
}
|
|
790
|
+
function signInErrorText(e) {
|
|
791
|
+
if (isUserRejection(e)) return "Sign-in cancelled";
|
|
792
|
+
const code = e?.code;
|
|
793
|
+
if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
|
|
794
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
795
|
+
if (/lock/i.test(msg)) return "Your wallet is locked. Unlock it and try again";
|
|
796
|
+
if (/timeout|timed out|expired/i.test(msg)) return "The wallet did not answer in time";
|
|
797
|
+
if (/origin/i.test(msg)) return "This page is not on the site\u2019s configured origin";
|
|
798
|
+
return "Sign-in failed";
|
|
799
|
+
}
|
|
800
|
+
async function copyText(text) {
|
|
801
|
+
try {
|
|
802
|
+
await navigator.clipboard.writeText(text);
|
|
803
|
+
return true;
|
|
804
|
+
} catch {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
var IDENTITY_HINT_KEY = "bwx:identityKey";
|
|
809
|
+
function rememberedIdentity() {
|
|
810
|
+
try {
|
|
811
|
+
const k = globalThis.localStorage?.getItem(IDENTITY_HINT_KEY) ?? "";
|
|
812
|
+
return /^0[23][0-9a-f]{64}$/i.test(k) ? k : void 0;
|
|
813
|
+
} catch {
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function rememberIdentity(key) {
|
|
818
|
+
try {
|
|
819
|
+
if (key) globalThis.localStorage?.setItem(IDENTITY_HINT_KEY, key);
|
|
820
|
+
else globalThis.localStorage?.removeItem(IDENTITY_HINT_KEY);
|
|
821
|
+
} catch {
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/choice.ts
|
|
826
|
+
var BWALLETX_TYPE = "bwalletx";
|
|
827
|
+
function substrateOf(o) {
|
|
828
|
+
if (o.substrate) return o.substrate;
|
|
829
|
+
if (o.kind === "pairing") return "pairing";
|
|
830
|
+
if (o.isBwalletX && o.kind === "brc100") return o.rdns === RDNS_MOBILE ? "in-app" : o.id === "bwalletx-xdm" ? "xdm" : "extension";
|
|
831
|
+
if (o.kind === "handcash" || o.kind === "custom") return "adapter";
|
|
832
|
+
if (o.rdns === RDNS_EXTENSION) return "extension";
|
|
833
|
+
if (o.rdns === RDNS_MOBILE) return "in-app";
|
|
834
|
+
if (o.id === "window.CWI") return "window.CWI";
|
|
835
|
+
if (o.id === "localhost:3321" || o.id === DESKTOP_OPTION_ID) return "json-api";
|
|
836
|
+
if (o.id === "localhost:3301") return "cicada";
|
|
837
|
+
return "injected";
|
|
838
|
+
}
|
|
839
|
+
function walletTypeOf(o) {
|
|
840
|
+
return o.isBwalletX ? BWALLETX_TYPE : o.rdns ?? o.id;
|
|
841
|
+
}
|
|
842
|
+
var OWN_MARKERS = ["extension", "in-app", "xdm"];
|
|
843
|
+
function isPresentBwalletX(o) {
|
|
844
|
+
return o.isBwalletX && o.kind === "brc100" && !!o.wallet && OWN_MARKERS.includes(substrateOf(o));
|
|
845
|
+
}
|
|
846
|
+
function hasBwalletX(list) {
|
|
847
|
+
return list.some(isPresentBwalletX);
|
|
848
|
+
}
|
|
849
|
+
function otherDetected(list) {
|
|
850
|
+
return list.filter((o) => !o.isBwalletX && o.kind === "brc100" && !!o.wallet);
|
|
851
|
+
}
|
|
852
|
+
function chooserOrder(list) {
|
|
853
|
+
const rank = (o) => isPresentBwalletX(o) ? 0 : o.isBwalletX ? 1 : o.kind === "brc100" ? 2 : o.kind === "custom" ? 3 : 4;
|
|
854
|
+
return list.map((o, i) => ({ o, i })).sort((a, b) => rank(a.o) - rank(b.o) || a.i - b.i).map((x) => x.o);
|
|
855
|
+
}
|
|
856
|
+
function outcomeOf(e) {
|
|
857
|
+
const err = e;
|
|
858
|
+
if (err?.code === "NOT_FOUND") return "not-found";
|
|
859
|
+
if (err?.name === "AbortError" || err?.message === "cancelled") return "cancelled";
|
|
860
|
+
if (isUserRejection(e)) return "rejected";
|
|
861
|
+
return "error";
|
|
862
|
+
}
|
|
863
|
+
function attemptFor(option, outcome, startedAt, now = Date.now()) {
|
|
864
|
+
return {
|
|
865
|
+
walletType: option ? walletTypeOf(option) : null,
|
|
866
|
+
substrate: option ? substrateOf(option) : null,
|
|
867
|
+
outcome,
|
|
868
|
+
ms: Math.max(0, Math.round(now - startedAt))
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/modal.ts
|
|
873
|
+
var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
|
|
874
|
+
var CSS = `
|
|
875
|
+
.bwx-ov{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;padding:16px;font:15px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
|
|
876
|
+
.bwx-box{background:#0b0b0b;color:#f4f1e8;border:1px solid #3a3220;border-radius:16px;width:min(360px,100%);padding:20px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
|
|
877
|
+
.bwx-box h2{margin:0 0 6px;font-size:18px;color:#e8c766}
|
|
878
|
+
.bwx-box p{margin:0 0 14px;color:#bdb6a3;font-size:14px}
|
|
879
|
+
.bwx-qr{background:#fff;border-radius:12px;padding:12px;display:flex;justify-content:center}
|
|
880
|
+
.bwx-qr svg{width:100%;height:auto;max-width:260px}
|
|
881
|
+
.bwx-code{margin:14px 0 0;text-align:center;font-size:28px;letter-spacing:.3em;color:#e8c766;font-variant-numeric:tabular-nums}
|
|
882
|
+
.bwx-row{display:flex;gap:8px;margin-top:14px}
|
|
883
|
+
.bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
|
|
884
|
+
.bwx-btn:hover{border-color:#e8c766}
|
|
885
|
+
a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
|
|
886
|
+
.bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
|
|
887
|
+
.bwx-stack{display:flex;flex-direction:column;gap:8px}
|
|
888
|
+
.bwx-list{display:flex;flex-direction:column;gap:8px}
|
|
889
|
+
.bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
|
|
890
|
+
.bwx-opt img{width:24px;height:24px;border-radius:6px}
|
|
891
|
+
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f;white-space:nowrap}
|
|
892
|
+
.bwx-opt{min-height:48px;box-sizing:border-box}
|
|
893
|
+
a.bwx-btn.bwx-opt{display:flex;text-align:left}
|
|
894
|
+
.bwx-btn:focus{outline:none}
|
|
895
|
+
.bwx-btn:focus-visible{outline:2px solid #e8c766;outline-offset:2px}
|
|
896
|
+
.bwx-mk{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border-radius:50%;background:#000}
|
|
897
|
+
.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}
|
|
898
|
+
.bwx-opt img{width:28px;height:28px;border-radius:8px;flex:none}
|
|
899
|
+
.bwx-txt{display:flex;flex-direction:column;min-width:0;gap:1px}
|
|
900
|
+
.bwx-txt b{font-weight:600}
|
|
901
|
+
.bwx-txt small{font-size:12px;color:#8d866f;font-weight:400}
|
|
902
|
+
.bwx-primary .bwx-txt small{color:rgba(0,0,0,.68)}
|
|
903
|
+
.bwx-primary .bwx-tag{color:rgba(0,0,0,.6)}
|
|
904
|
+
.bwx-sep{font-size:12px;color:#8d866f;margin:6px 2px 0}
|
|
905
|
+
.bwx-foot{font-size:12px;color:#8d866f;margin:12px 0 0}
|
|
906
|
+
`;
|
|
907
|
+
function mount() {
|
|
908
|
+
const root = document.createElement("div");
|
|
909
|
+
root.className = "bwx-ov";
|
|
910
|
+
root.setAttribute("role", "dialog");
|
|
911
|
+
root.setAttribute("aria-modal", "true");
|
|
912
|
+
const style = document.createElement("style");
|
|
913
|
+
style.textContent = CSS;
|
|
914
|
+
const box = document.createElement("div");
|
|
915
|
+
box.className = "bwx-box";
|
|
916
|
+
root.append(style, box);
|
|
917
|
+
document.body.appendChild(root);
|
|
918
|
+
return { root, box, close: () => root.remove() };
|
|
919
|
+
}
|
|
920
|
+
function el(tag, cls, text) {
|
|
921
|
+
const e = document.createElement(tag);
|
|
922
|
+
if (cls) e.className = cls;
|
|
923
|
+
if (text != null) e.textContent = text;
|
|
924
|
+
return e;
|
|
925
|
+
}
|
|
926
|
+
function qrSvg(text) {
|
|
927
|
+
const q = (0, import_qrcode_generator.default)(0, "M");
|
|
928
|
+
q.addData(text);
|
|
929
|
+
q.make();
|
|
930
|
+
return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
|
931
|
+
}
|
|
932
|
+
function isTouchDevice(env = globalThis) {
|
|
933
|
+
const nav = env.navigator;
|
|
934
|
+
if (!nav) return false;
|
|
935
|
+
const ua = nav.userAgent ?? "";
|
|
936
|
+
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
937
|
+
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
938
|
+
try {
|
|
939
|
+
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
940
|
+
} catch {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function pairingModal(o = {}) {
|
|
945
|
+
const ac = new AbortController();
|
|
946
|
+
let ui = null;
|
|
947
|
+
let codeEl = null;
|
|
948
|
+
const close = () => {
|
|
949
|
+
ui?.close();
|
|
950
|
+
ui = null;
|
|
951
|
+
};
|
|
952
|
+
ac.signal.addEventListener("abort", close);
|
|
953
|
+
return {
|
|
954
|
+
signal: ac.signal,
|
|
955
|
+
close,
|
|
956
|
+
onLink(link) {
|
|
957
|
+
if (typeof document === "undefined") return;
|
|
958
|
+
close();
|
|
959
|
+
ui = mount();
|
|
960
|
+
const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
|
|
961
|
+
const h = el("h2", "", o.title ?? "Connect bWalletX");
|
|
962
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
963
|
+
cancel.onclick = () => {
|
|
964
|
+
o.onCancel?.();
|
|
965
|
+
ac.abort();
|
|
966
|
+
};
|
|
967
|
+
codeEl = el("div", "bwx-code", "");
|
|
968
|
+
codeEl.setAttribute("aria-live", "polite");
|
|
969
|
+
if (app) {
|
|
970
|
+
const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
|
|
971
|
+
const stack = el("div", "bwx-stack");
|
|
972
|
+
const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
|
|
973
|
+
open.href = link;
|
|
974
|
+
open.rel = "noopener";
|
|
975
|
+
const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
|
|
976
|
+
web.onclick = () => {
|
|
977
|
+
void navigator.clipboard?.writeText(link).catch(() => void 0);
|
|
978
|
+
window.open(WEB_WALLET_URL, "_blank", "noopener");
|
|
979
|
+
p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
|
|
980
|
+
};
|
|
981
|
+
stack.append(open, web, cancel);
|
|
982
|
+
ui.box.append(h, p2, codeEl, stack);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
|
|
986
|
+
const qr = el("div", "bwx-qr");
|
|
987
|
+
qr.innerHTML = qrSvg(link);
|
|
988
|
+
const row = el("div", "bwx-row");
|
|
989
|
+
const copy = el("button", "bwx-btn", "Copy link");
|
|
990
|
+
copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
|
|
991
|
+
row.append(copy, cancel);
|
|
992
|
+
ui.box.append(h, p, qr, codeEl, row);
|
|
993
|
+
},
|
|
994
|
+
onCode(code) {
|
|
995
|
+
if (codeEl) {
|
|
996
|
+
codeEl.textContent = code;
|
|
997
|
+
const p = ui?.box.querySelector("p");
|
|
998
|
+
if (p) p.textContent = "Check bWalletX shows the same code, then tap Connect.";
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function optionRow(w, primary, sub, tag) {
|
|
1004
|
+
const b = el("button", `bwx-btn bwx-opt${primary ? " bwx-primary" : ""}`);
|
|
1005
|
+
b.type = "button";
|
|
1006
|
+
b.dataset.wallet = w.id;
|
|
1007
|
+
if (w.isBwalletX) {
|
|
1008
|
+
const mk = el("span", "bwx-mk");
|
|
1009
|
+
mk.innerHTML = markSvg(18);
|
|
1010
|
+
b.append(mk);
|
|
1011
|
+
} else if (w.icon) {
|
|
1012
|
+
const img = el("img");
|
|
1013
|
+
img.src = w.icon;
|
|
1014
|
+
img.alt = "";
|
|
1015
|
+
b.append(img);
|
|
1016
|
+
} else {
|
|
1017
|
+
b.append(el("span", "bwx-ico", (w.name.trim().charAt(0) || "W").toUpperCase()));
|
|
1018
|
+
}
|
|
1019
|
+
const t = el("span", "bwx-txt");
|
|
1020
|
+
t.append(el("b", "", w.name));
|
|
1021
|
+
if (sub) t.append(el("small", "", sub));
|
|
1022
|
+
b.append(t);
|
|
1023
|
+
if (tag) b.append(el("span", "bwx-tag", tag));
|
|
1024
|
+
return b;
|
|
1025
|
+
}
|
|
1026
|
+
function chooseWallet(options, o = {}) {
|
|
1027
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
1028
|
+
return new Promise((resolve) => {
|
|
1029
|
+
const ui = mount();
|
|
1030
|
+
ui.root.dataset.bwxChooser = "";
|
|
1031
|
+
const done = (w) => {
|
|
1032
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1033
|
+
ui.close();
|
|
1034
|
+
resolve(w);
|
|
1035
|
+
};
|
|
1036
|
+
const onKey = (e) => {
|
|
1037
|
+
if (e.key === "Escape") {
|
|
1038
|
+
e.preventDefault();
|
|
1039
|
+
done(null);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
document.addEventListener("keydown", onKey, true);
|
|
1043
|
+
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
1044
|
+
const list = el("div", "bwx-list");
|
|
1045
|
+
const here = hasBwalletX(options);
|
|
1046
|
+
if (!here) {
|
|
1047
|
+
const get = el("a", "bwx-btn bwx-opt bwx-primary");
|
|
1048
|
+
get.href = o.getUrl ?? GET_URL;
|
|
1049
|
+
get.target = "_blank";
|
|
1050
|
+
get.rel = "noopener";
|
|
1051
|
+
get.dataset.wallet = "get-bwalletx";
|
|
1052
|
+
const mk = el("span", "bwx-mk");
|
|
1053
|
+
mk.innerHTML = markSvg(18);
|
|
1054
|
+
const t = el("span", "bwx-txt");
|
|
1055
|
+
t.append(el("b", "", "Get bWalletX"), el("small", "", "Free. Every feature on this site"));
|
|
1056
|
+
get.append(mk, t);
|
|
1057
|
+
list.append(get);
|
|
1058
|
+
}
|
|
1059
|
+
const ordered = chooserOrder(options);
|
|
1060
|
+
const touch = isTouchDevice();
|
|
1061
|
+
let sep = false;
|
|
1062
|
+
for (const w of ordered) {
|
|
1063
|
+
if (!here && !sep) {
|
|
1064
|
+
list.append(el("p", "bwx-sep", "Or use"));
|
|
1065
|
+
sep = true;
|
|
1066
|
+
}
|
|
1067
|
+
const sub = isPresentBwalletX(w) ? "Recommended" : w.kind === "pairing" ? touch ? "Open the bWalletX app" : "Scan a QR with your phone" : void 0;
|
|
1068
|
+
const tag = w.kind === "handcash" ? "until BRC-100" : !w.isBwalletX && w.kind === "brc100" ? "Fewer features" : void 0;
|
|
1069
|
+
const row2 = optionRow(w, isPresentBwalletX(w), sub, tag);
|
|
1070
|
+
row2.onclick = () => done(w);
|
|
1071
|
+
list.append(row2);
|
|
1072
|
+
}
|
|
1073
|
+
if (o.desktop !== false && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
|
|
1074
|
+
if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
|
|
1075
|
+
const d = desktopOption();
|
|
1076
|
+
const row2 = optionRow(d, false, "Metanet or BSV Desktop on this computer", "Fewer features");
|
|
1077
|
+
row2.onclick = () => done(d);
|
|
1078
|
+
list.append(row2);
|
|
1079
|
+
}
|
|
1080
|
+
ui.box.append(list);
|
|
1081
|
+
if (otherDetected(options).length || !here) ui.box.append(el("p", "bwx-foot", "Some features need bWalletX."));
|
|
1082
|
+
const row = el("div", "bwx-row");
|
|
1083
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1084
|
+
cancel.type = "button";
|
|
1085
|
+
cancel.onclick = () => done(null);
|
|
1086
|
+
row.append(cancel);
|
|
1087
|
+
ui.box.append(row);
|
|
1088
|
+
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
1089
|
+
list.querySelector("a,button")?.focus();
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
|
|
519
1093
|
// src/access-client.ts
|
|
520
|
-
var
|
|
1094
|
+
var import_sdk5 = require("@bsv/sdk");
|
|
521
1095
|
|
|
522
1096
|
// src/access.ts
|
|
523
1097
|
var ACCESS_PROTOCOL = [2, "bwalletx access"];
|
|
@@ -655,7 +1229,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
|
|
|
655
1229
|
const counterparty = ci.counterparty ?? "self";
|
|
656
1230
|
for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
|
|
657
1231
|
const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
|
|
658
|
-
const h =
|
|
1232
|
+
const h = import_sdk5.Utils.toHex(import_sdk5.Hash.hash160(import_sdk5.PublicKey.fromString(publicKey).encode(true)));
|
|
659
1233
|
if (want && h !== want) continue;
|
|
660
1234
|
const existing = byKey.get(publicKey);
|
|
661
1235
|
if (existing) {
|
|
@@ -705,7 +1279,7 @@ async function requestAccess(o) {
|
|
|
705
1279
|
var SIGNIN_TAG = "bwalletx-signin";
|
|
706
1280
|
var GET_URL = "https://bwalletx.com/get";
|
|
707
1281
|
var WEB_WALLET_URL = "https://web.bwalletx.com";
|
|
708
|
-
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect" };
|
|
1282
|
+
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect", get: "Get", play: "Play with" };
|
|
709
1283
|
function signinText(label = "signin", brand = "bWalletX") {
|
|
710
1284
|
return `${VERBS[label] ?? VERBS.signin} ${brand}`;
|
|
711
1285
|
}
|
|
@@ -760,7 +1334,50 @@ button[aria-busy=true]{cursor:wait}
|
|
|
760
1334
|
.nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
761
1335
|
:host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}
|
|
762
1336
|
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
1337
|
+
/* pending: one plain line under the button */
|
|
1338
|
+
.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}
|
|
1339
|
+
:host([theme=dark]) .note,:host-context(.dark) .note{color:#a1a1aa}
|
|
1340
|
+
/* "Other wallets" (0.5.6): one small link under the button */
|
|
1341
|
+
.ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1342
|
+
:host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
|
|
1343
|
+
.lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
|
|
1344
|
+
.lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
1345
|
+
/* error: one line plus Retry */
|
|
1346
|
+
.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}
|
|
1347
|
+
:host([theme=dark]) .err,:host-context(.dark) .err{color:#fca5a5}
|
|
1348
|
+
/* signed in: the badge */
|
|
1349
|
+
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;
|
|
1350
|
+
background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.55);box-shadow:none;font-size:15px}
|
|
1351
|
+
button.chip:hover{background:#151517;border-color:#ffd24d}
|
|
1352
|
+
button.chip:active{background:#000;transform:none}
|
|
1353
|
+
button.chip:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}
|
|
1354
|
+
:host([theme=light]) button.chip{background:#fff;color:#09090b;border-color:#f59e0b}
|
|
1355
|
+
:host([theme=light]) button.chip:hover{background:#fffbeb}
|
|
1356
|
+
:host([full-width]) button.chip{width:100%}
|
|
1357
|
+
.av{position:relative;flex:none;display:inline-grid;place-items:center;width:30px;height:30px;border-radius:50%;overflow:hidden;
|
|
1358
|
+
background:radial-gradient(circle at 30% 30%,#ffe27a,#f5b800 55%,#b88600);color:#000;font-weight:800;font-size:14px}
|
|
1359
|
+
.av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
1360
|
+
.name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:min(52vw,260px)}
|
|
1361
|
+
.dot{flex:none;width:8px;height:8px;border-radius:50%;background:#ffd24d;box-shadow:0 0 0 3px rgba(255,210,77,.22)}
|
|
1362
|
+
:host([size=sm]) button.chip{font-size:13px;padding:3px 12px 3px 3px;gap:8px}
|
|
1363
|
+
:host([size=sm]) .av{width:26px;height:26px}
|
|
1364
|
+
:host([size=icon]) button.chip{width:48px;height:48px;padding:0;justify-content:center}
|
|
1365
|
+
:host([size=icon]) .name,:host([size=icon]) .dot{display:none}
|
|
1366
|
+
:host([size=icon]) .av{width:40px;height:40px}
|
|
1367
|
+
`;
|
|
1368
|
+
var MENU_CSS = `
|
|
1369
|
+
:host{all:initial}
|
|
1370
|
+
.menu{position:fixed;z-index:2147483600;box-sizing:border-box;min-width:200px;padding:6px;border-radius:14px;background:#0b0b0c;color:#ffd24d;
|
|
1371
|
+
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}
|
|
1372
|
+
.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}
|
|
1373
|
+
.item{all:unset;box-sizing:border-box;display:block;width:100%;padding:11px 10px;border-radius:10px;cursor:pointer;color:#ffd24d}
|
|
1374
|
+
.item:hover,.item:focus-visible{background:#ffd24d;color:#000;outline:none}
|
|
1375
|
+
.tip{position:fixed;z-index:2147483600;box-sizing:border-box;padding:8px 12px;border-radius:10px;background:#0b0b0c;color:#e7d9a8;
|
|
1376
|
+
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}
|
|
763
1377
|
`;
|
|
1378
|
+
var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
|
|
1379
|
+
var PENDING_LABEL = "Approve in bWalletX\u2026";
|
|
1380
|
+
var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
|
|
764
1381
|
function b64url2(bytes) {
|
|
765
1382
|
let s = "";
|
|
766
1383
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -793,14 +1410,15 @@ async function buildAuthorizeUrl(p) {
|
|
|
793
1410
|
async function postJson(url, body) {
|
|
794
1411
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
795
1412
|
const data = await res.json().catch(() => ({}));
|
|
796
|
-
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
1413
|
+
if (!res.ok) throw Object.assign(new Error(data.error ?? `HTTP ${res.status}`), { status: res.status });
|
|
797
1414
|
return data;
|
|
798
1415
|
}
|
|
799
1416
|
async function postAccess(url, body) {
|
|
800
1417
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
801
1418
|
return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
|
|
802
1419
|
}
|
|
803
|
-
var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url"];
|
|
1420
|
+
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"];
|
|
1421
|
+
var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
|
|
804
1422
|
function createClass() {
|
|
805
1423
|
return class BwalletxSigninElement extends HTMLElement {
|
|
806
1424
|
static get observedAttributes() {
|
|
@@ -809,12 +1427,23 @@ function createClass() {
|
|
|
809
1427
|
#root;
|
|
810
1428
|
#notFound = false;
|
|
811
1429
|
#busy = false;
|
|
1430
|
+
#phase = "signed-out";
|
|
1431
|
+
#account = null;
|
|
1432
|
+
#error = "";
|
|
1433
|
+
#restored = false;
|
|
1434
|
+
#pendingLabel = PENDING_LABEL;
|
|
1435
|
+
#menu = null;
|
|
812
1436
|
constructor() {
|
|
813
1437
|
super();
|
|
814
1438
|
this.#root = this.attachShadow({ mode: "open" });
|
|
815
1439
|
}
|
|
816
1440
|
connectedCallback() {
|
|
817
1441
|
this.#render();
|
|
1442
|
+
void this.#restore();
|
|
1443
|
+
}
|
|
1444
|
+
disconnectedCallback() {
|
|
1445
|
+
this.#menu?.close(false);
|
|
1446
|
+
this.#hideTip();
|
|
818
1447
|
}
|
|
819
1448
|
attributeChangedCallback() {
|
|
820
1449
|
if (this.isConnected) this.#render();
|
|
@@ -826,92 +1455,402 @@ function createClass() {
|
|
|
826
1455
|
get notFound() {
|
|
827
1456
|
return this.#notFound;
|
|
828
1457
|
}
|
|
1458
|
+
/** The state shown now (the `state` attribute when set, else the element's own). */
|
|
1459
|
+
get state() {
|
|
1460
|
+
const a = this.#attr("state");
|
|
1461
|
+
return a && STATES.includes(a) ? a : this.#phase;
|
|
1462
|
+
}
|
|
1463
|
+
/** The signed-in account, or null. */
|
|
1464
|
+
get account() {
|
|
1465
|
+
if (!this.#controlled) return this.#account;
|
|
1466
|
+
const identityKey = this.#attr("identity-key");
|
|
1467
|
+
return identityKey ? { identityKey, paymail: this.#attr("paymail"), handle: this.#attr("handle"), avatarUrl: this.#attr("avatar-url") } : null;
|
|
1468
|
+
}
|
|
1469
|
+
/** True while the menu is open (tests). */
|
|
1470
|
+
get menuOpen() {
|
|
1471
|
+
return !!this.#menu;
|
|
1472
|
+
}
|
|
829
1473
|
focus(opts) {
|
|
830
1474
|
this.button?.focus(opts);
|
|
831
1475
|
}
|
|
1476
|
+
get #controlled() {
|
|
1477
|
+
return this.hasAttribute("state");
|
|
1478
|
+
}
|
|
1479
|
+
get #stateful() {
|
|
1480
|
+
return !this.hasAttribute("stateless");
|
|
1481
|
+
}
|
|
832
1482
|
#attr(name) {
|
|
833
1483
|
return this.getAttribute(name);
|
|
834
1484
|
}
|
|
1485
|
+
/** Restore the badge from session-url once, with no wallet call. */
|
|
1486
|
+
async #restore() {
|
|
1487
|
+
const url = this.#attr("session-url");
|
|
1488
|
+
if (this.#restored || !url || this.#controlled || !this.#stateful) return;
|
|
1489
|
+
this.#restored = true;
|
|
1490
|
+
this.#set("restoring");
|
|
1491
|
+
try {
|
|
1492
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
1493
|
+
const j = await r.json().catch(() => ({}));
|
|
1494
|
+
if (this.#phase !== "restoring") return;
|
|
1495
|
+
if (j.identityKey) this.#signedIn({ identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null });
|
|
1496
|
+
else this.#set("signed-out");
|
|
1497
|
+
} catch {
|
|
1498
|
+
if (this.#phase === "restoring") this.#set("signed-out");
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
#set(p) {
|
|
1502
|
+
this.#phase = p;
|
|
1503
|
+
if (p !== "signed-in") this.#menu?.close(false);
|
|
1504
|
+
this.#render();
|
|
1505
|
+
}
|
|
1506
|
+
#signedIn(a) {
|
|
1507
|
+
this.#account = a;
|
|
1508
|
+
this.#error = "";
|
|
1509
|
+
this.#set("signed-in");
|
|
1510
|
+
const lookup = this.#attr("lookup-url");
|
|
1511
|
+
if (!a.paymail && lookup !== "off") {
|
|
1512
|
+
void lookupPaymail(a.identityKey, lookup ?? void 0).then((pm) => {
|
|
1513
|
+
if (pm && this.#account?.identityKey === a.identityKey && !this.#account.paymail) {
|
|
1514
|
+
this.#account = { ...this.#account, paymail: pm };
|
|
1515
|
+
this.#render();
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
835
1520
|
#render() {
|
|
836
1521
|
const size = this.#attr("size") ?? "lg";
|
|
837
1522
|
const brand = this.#attr("brand") === "bWallet" ? "bWallet" : "bWalletX";
|
|
838
|
-
const
|
|
1523
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
1524
|
+
const state = this.state;
|
|
1525
|
+
const acct = this.account;
|
|
1526
|
+
if (state === "signed-in" && acct) {
|
|
1527
|
+
this.#hideTip();
|
|
1528
|
+
const name = accountLabel(acct);
|
|
1529
|
+
const avatar = acct.avatarUrl ?? avatarUrlFor(acct.identityKey);
|
|
1530
|
+
const initial = (name.replace(/^\$/, "").charAt(0) || "b").toUpperCase();
|
|
1531
|
+
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>`;
|
|
1532
|
+
const img = this.#root.querySelector("img");
|
|
1533
|
+
if (img) img.addEventListener("error", () => img.remove(), { once: true });
|
|
1534
|
+
const chip = this.button;
|
|
1535
|
+
chip.addEventListener("click", () => this.#menu ? this.#menu.close(true) : this.#openMenu());
|
|
1536
|
+
chip.addEventListener("keydown", (e) => {
|
|
1537
|
+
if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !this.#menu) {
|
|
1538
|
+
e.preventDefault();
|
|
1539
|
+
this.#openMenu();
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
const pending = state === "pending";
|
|
1545
|
+
const restoring = state === "restoring";
|
|
1546
|
+
const error = state === "error";
|
|
1547
|
+
const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
|
|
1548
|
+
const text = pending ? pendingLabel : error ? "Retry" : signinText(this.#attr("label") ?? "signin", brand);
|
|
839
1549
|
const subAttr = this.#attr("subtitle");
|
|
840
1550
|
const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
|
|
841
|
-
const subtitle = subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
842
|
-
const loading = this.hasAttribute("loading") || this.#busy;
|
|
1551
|
+
const subtitle = pending || error ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
1552
|
+
const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
|
|
843
1553
|
const disabled = this.hasAttribute("disabled");
|
|
844
1554
|
const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
|
|
845
|
-
const aria = this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
1555
|
+
const aria = pending ? pendingLabel : this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
846
1556
|
const getUrl = this.#attr("get-url") ?? GET_URL;
|
|
847
1557
|
const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
|
|
848
|
-
const
|
|
1558
|
+
const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
|
|
849
1559
|
const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
|
|
850
1560
|
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
|
|
851
|
-
const
|
|
852
|
-
|
|
1561
|
+
const otherOff = this.#attr("other-wallets") === "off";
|
|
1562
|
+
const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
|
|
1563
|
+
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>` : "";
|
|
1564
|
+
const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
|
|
1565
|
+
const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
|
|
1566
|
+
const compact = size === "sm" || size === "icon";
|
|
1567
|
+
const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
|
|
1568
|
+
const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
|
|
1569
|
+
this.#root.innerHTML = `<style>${SIGNIN_CSS}</style>${err}<button type="button" part="button" aria-label="${esc(aria)}" title="${esc(subtitle ? `${text} (${subtitle})` : text)}"${disabled ? " disabled" : ""}${loading ? ' aria-busy="true"' : ""}><span class="mark" part="mark">${mark}</span>${label}</button>${status}${note}${ow}${nf}`;
|
|
853
1570
|
this.button.addEventListener("click", () => void this.#click());
|
|
1571
|
+
for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
|
|
1572
|
+
bindPrewarm(this.button);
|
|
1573
|
+
if (pending && compact) this.#showTip(PENDING_NOTE);
|
|
1574
|
+
else this.#hideTip();
|
|
1575
|
+
}
|
|
1576
|
+
#tip = null;
|
|
1577
|
+
#showTip(text) {
|
|
1578
|
+
const b = this.button;
|
|
1579
|
+
if (!b || typeof document === "undefined") return;
|
|
1580
|
+
if (!this.#tip) {
|
|
1581
|
+
const host = document.createElement("div");
|
|
1582
|
+
host.setAttribute("data-bwalletx-tip", "");
|
|
1583
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1584
|
+
root.innerHTML = `<style>${MENU_CSS}</style><div class="tip" role="note"></div>`;
|
|
1585
|
+
document.body.appendChild(host);
|
|
1586
|
+
this.#tip = host;
|
|
1587
|
+
}
|
|
1588
|
+
const tip = this.#tip.shadowRoot.querySelector(".tip");
|
|
1589
|
+
tip.textContent = text;
|
|
1590
|
+
const r = b.getBoundingClientRect();
|
|
1591
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1592
|
+
const width = Math.min(280, vw - 32);
|
|
1593
|
+
tip.style.width = `${width}px`;
|
|
1594
|
+
tip.style.left = `${Math.max(16, Math.min(r.right - width, vw - width - 16))}px`;
|
|
1595
|
+
tip.style.top = `${r.bottom + 8}px`;
|
|
1596
|
+
}
|
|
1597
|
+
#hideTip() {
|
|
1598
|
+
this.#tip?.remove();
|
|
1599
|
+
this.#tip = null;
|
|
1600
|
+
}
|
|
1601
|
+
#openMenu() {
|
|
1602
|
+
const acct = this.account;
|
|
1603
|
+
const chip = this.button;
|
|
1604
|
+
if (!acct || !chip || typeof document === "undefined") return;
|
|
1605
|
+
const name = accountLabel(acct);
|
|
1606
|
+
const host = document.createElement("div");
|
|
1607
|
+
host.setAttribute("data-bwalletx-menu", "");
|
|
1608
|
+
const root = host.attachShadow({ mode: "open" });
|
|
1609
|
+
const style = document.createElement("style");
|
|
1610
|
+
style.textContent = MENU_CSS;
|
|
1611
|
+
const menu = document.createElement("div");
|
|
1612
|
+
menu.className = "menu";
|
|
1613
|
+
menu.setAttribute("role", "menu");
|
|
1614
|
+
menu.setAttribute("aria-label", "bWalletX account");
|
|
1615
|
+
const who = document.createElement("div");
|
|
1616
|
+
who.className = "who";
|
|
1617
|
+
who.textContent = `Signed in as ${acct.paymail || name}`;
|
|
1618
|
+
const item = (text, run) => {
|
|
1619
|
+
const b = document.createElement("button");
|
|
1620
|
+
b.type = "button";
|
|
1621
|
+
b.className = "item";
|
|
1622
|
+
b.setAttribute("role", "menuitem");
|
|
1623
|
+
b.tabIndex = -1;
|
|
1624
|
+
b.textContent = text;
|
|
1625
|
+
b.addEventListener("click", () => run(b));
|
|
1626
|
+
return b;
|
|
1627
|
+
};
|
|
1628
|
+
const copyWhat = acct.paymail ? "paymail" : "key";
|
|
1629
|
+
const copy = item(`Copy ${copyWhat}`, (b) => {
|
|
1630
|
+
void copyText(acct.paymail || acct.identityKey).then((ok) => {
|
|
1631
|
+
b.textContent = ok ? "Copied" : `Copy ${copyWhat}`;
|
|
1632
|
+
setTimeout(() => close(true), ok ? 600 : 0);
|
|
1633
|
+
});
|
|
1634
|
+
});
|
|
1635
|
+
const out = item("Disconnect", () => {
|
|
1636
|
+
close(false);
|
|
1637
|
+
void this.signOut();
|
|
1638
|
+
});
|
|
1639
|
+
menu.append(who, copy, out);
|
|
1640
|
+
root.append(style, menu);
|
|
1641
|
+
document.body.appendChild(host);
|
|
1642
|
+
const place = () => {
|
|
1643
|
+
const r = chip.getBoundingClientRect();
|
|
1644
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
1645
|
+
const vh = window.innerHeight || document.documentElement.clientHeight || 640;
|
|
1646
|
+
const width = Math.min(260, vw - 32);
|
|
1647
|
+
menu.style.width = `${width}px`;
|
|
1648
|
+
const left = Math.max(16, Math.min(r.right - width, vw - width - 16));
|
|
1649
|
+
menu.style.left = `${left}px`;
|
|
1650
|
+
const h = menu.offsetHeight || 150;
|
|
1651
|
+
const below = r.bottom + 8;
|
|
1652
|
+
menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
|
|
1653
|
+
};
|
|
1654
|
+
place();
|
|
1655
|
+
const items = [copy, out];
|
|
1656
|
+
const focusAt = (i) => items[(i + items.length) % items.length].focus();
|
|
1657
|
+
const onKey = (e) => {
|
|
1658
|
+
const i = items.indexOf(root.activeElement);
|
|
1659
|
+
if (e.key === "Escape") {
|
|
1660
|
+
e.preventDefault();
|
|
1661
|
+
close(true);
|
|
1662
|
+
} else if (e.key === "ArrowDown") {
|
|
1663
|
+
e.preventDefault();
|
|
1664
|
+
focusAt(i + 1);
|
|
1665
|
+
} else if (e.key === "ArrowUp") {
|
|
1666
|
+
e.preventDefault();
|
|
1667
|
+
focusAt(i - 1);
|
|
1668
|
+
} else if (e.key === "Home") {
|
|
1669
|
+
e.preventDefault();
|
|
1670
|
+
focusAt(0);
|
|
1671
|
+
} else if (e.key === "End") {
|
|
1672
|
+
e.preventDefault();
|
|
1673
|
+
focusAt(items.length - 1);
|
|
1674
|
+
} else if (e.key === "Tab") close(false);
|
|
1675
|
+
};
|
|
1676
|
+
const onDown = (e) => {
|
|
1677
|
+
const path = e.composedPath?.() ?? [];
|
|
1678
|
+
if (!path.includes(host) && !path.includes(this)) close(false);
|
|
1679
|
+
};
|
|
1680
|
+
const onMove = () => close(false);
|
|
1681
|
+
menu.addEventListener("keydown", onKey);
|
|
1682
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
1683
|
+
window.addEventListener("resize", onMove);
|
|
1684
|
+
window.addEventListener("scroll", place, true);
|
|
1685
|
+
const close = (focusChip = false) => {
|
|
1686
|
+
if (this.#menu?.host !== host) return;
|
|
1687
|
+
this.#menu = null;
|
|
1688
|
+
menu.removeEventListener("keydown", onKey);
|
|
1689
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1690
|
+
window.removeEventListener("resize", onMove);
|
|
1691
|
+
window.removeEventListener("scroll", place, true);
|
|
1692
|
+
host.remove();
|
|
1693
|
+
this.button?.setAttribute("aria-expanded", "false");
|
|
1694
|
+
if (focusChip) this.button?.focus();
|
|
1695
|
+
};
|
|
1696
|
+
this.#menu = { host, close };
|
|
1697
|
+
chip.setAttribute("aria-expanded", "true");
|
|
1698
|
+
focusAt(0);
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Disconnect: emits bwalletx-disconnect (cancelable). Unless a listener cancels it (BwalletXProvider signs out
|
|
1702
|
+
* itself) or the element is driven by `state`, posts logout-url and shows signed out.
|
|
1703
|
+
*/
|
|
1704
|
+
async signOut() {
|
|
1705
|
+
this.#menu?.close(false);
|
|
1706
|
+
if (!this.#emit("bwalletx-disconnect", { element: this, account: this.account }, true)) return;
|
|
1707
|
+
if (this.#controlled) return;
|
|
1708
|
+
const url = this.#attr("logout-url");
|
|
1709
|
+
if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
1710
|
+
this.#account = null;
|
|
1711
|
+
this.#set("signed-out");
|
|
1712
|
+
this.#emit("bwalletx-signed-out");
|
|
1713
|
+
this.button?.focus();
|
|
854
1714
|
}
|
|
855
1715
|
#emit(type, detail, cancelable = false) {
|
|
856
1716
|
return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
|
|
857
1717
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
this.#
|
|
1718
|
+
/** "Other wallets" is offered under the button: other-wallets on, or auto at lg/md when this element signs in. */
|
|
1719
|
+
#otherWalletsShown(size) {
|
|
1720
|
+
const a = this.#attr("other-wallets");
|
|
1721
|
+
if (a === "off") return false;
|
|
1722
|
+
if (a === "on") return true;
|
|
1723
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
861
1724
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1725
|
+
/** Open the wallet chooser (the "Other wallets" link): phone QR, other browser wallets, desktop wallets. */
|
|
1726
|
+
otherWallets() {
|
|
1727
|
+
return this.#click("chooser");
|
|
1728
|
+
}
|
|
1729
|
+
#attempt(option, outcome, started) {
|
|
1730
|
+
this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Which wallet: bWalletX by its own marker goes straight to its sheet; otherwise the chooser ("Get bWalletX" first)
|
|
1734
|
+
* when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
|
|
1735
|
+
*/
|
|
1736
|
+
async #choose(mode, ms, pairing) {
|
|
1737
|
+
if (mode !== "chooser") {
|
|
1738
|
+
const f = await discoverBwalletX(ms);
|
|
1739
|
+
if (f) {
|
|
1740
|
+
const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
|
|
1741
|
+
return brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
|
|
1742
|
+
}
|
|
870
1743
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1744
|
+
const list = await discoverWallets({ ms: 50, pairing: () => pairing });
|
|
1745
|
+
if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
|
|
1746
|
+
return chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
|
|
1747
|
+
}
|
|
1748
|
+
async #click(mode = "signin") {
|
|
1749
|
+
if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
|
|
1750
|
+
if (!this.#emit("bwalletx-signin", { element: this, mode }, true)) return;
|
|
1751
|
+
if (mode === "signin") {
|
|
1752
|
+
const href = this.#attr("href");
|
|
1753
|
+
const authorizeUrl = this.#attr("authorize-url");
|
|
1754
|
+
if (authorizeUrl) {
|
|
1755
|
+
location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr("client-id"), redirectUri: this.#attr("redirect-uri"), scope: this.#attr("scope") }));
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
if (href) {
|
|
1759
|
+
location.assign(href);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
874
1762
|
}
|
|
875
1763
|
const challengeUrl = this.#attr("challenge-url");
|
|
876
1764
|
if (!challengeUrl) return;
|
|
1765
|
+
const own = this.#stateful && !this.#controlled;
|
|
1766
|
+
const prev = this.#phase === "signed-in" ? "signed-in" : "signed-out";
|
|
1767
|
+
const started = Date.now();
|
|
1768
|
+
const pairing = pairingModal();
|
|
1769
|
+
let option = null;
|
|
877
1770
|
this.#notFound = false;
|
|
878
|
-
this.#
|
|
1771
|
+
this.#busy = true;
|
|
1772
|
+
this.#pendingLabel = PENDING_LABEL;
|
|
1773
|
+
if (own) this.#set("pending");
|
|
1774
|
+
else this.#render();
|
|
879
1775
|
try {
|
|
880
|
-
const ms = Number(this.#attr("discover-ms")) ||
|
|
881
|
-
const
|
|
882
|
-
if (
|
|
1776
|
+
const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
|
|
1777
|
+
const picked = await this.#choose(mode, ms, pairing);
|
|
1778
|
+
if (picked === "not-found") {
|
|
883
1779
|
this.#notFound = true;
|
|
1780
|
+
if (own) this.#phase = "signed-out";
|
|
1781
|
+
this.#attempt(null, "not-found", started);
|
|
884
1782
|
this.#emit("bwalletx-not-found");
|
|
885
1783
|
if (this.#attr("require-token")) this.#emit("bwalletx-access-denied", { code: "not_installed", message: "No wallet found" });
|
|
886
1784
|
return;
|
|
887
1785
|
}
|
|
1786
|
+
if (!picked) {
|
|
1787
|
+
if (own) this.#phase = prev;
|
|
1788
|
+
this.#attempt(null, "cancelled", started);
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
option = picked;
|
|
1792
|
+
if (!option.isBwalletX) {
|
|
1793
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
1794
|
+
if (own) this.#render();
|
|
1795
|
+
}
|
|
1796
|
+
const cw = await option.connect({ identity: false });
|
|
1797
|
+
const wallet = cw.wallet;
|
|
1798
|
+
if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
|
|
1799
|
+
const sub = substrateOf(option);
|
|
1800
|
+
const method = sub === "extension" ? "extension" : sub === "in-app" || sub === "xdm" ? "in-app" : sub === "pairing" ? "pairing" : "wallet";
|
|
888
1801
|
const requireToken = this.#attr("require-token");
|
|
889
1802
|
if (requireToken) {
|
|
1803
|
+
if (own) this.#phase = "signed-out";
|
|
890
1804
|
const verifyUrl2 = this.#attr("verify-url");
|
|
891
1805
|
const min = this.#attr("min");
|
|
892
1806
|
const minBps = this.#attr("min-bps");
|
|
893
1807
|
const hint = { token: { id: requireToken, ...minBps ? { minBps: Number(minBps) } : { min: min ?? "1" } } };
|
|
894
1808
|
try {
|
|
895
1809
|
const a = await requestAccess({
|
|
896
|
-
wallet
|
|
1810
|
+
wallet,
|
|
897
1811
|
challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
|
|
898
1812
|
verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
|
|
899
1813
|
});
|
|
1814
|
+
this.#attempt(option, "signed-in", started);
|
|
900
1815
|
this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
|
|
901
1816
|
} catch (e) {
|
|
902
1817
|
const err = e instanceof AccessError ? e : new AccessError("bad_request", String(e));
|
|
1818
|
+
this.#attempt(option, outcomeOf(e), started);
|
|
903
1819
|
this.#emit("bwalletx-access-denied", { code: err.code, message: err.message, have: err.have, need: err.need });
|
|
904
1820
|
}
|
|
905
1821
|
return;
|
|
906
1822
|
}
|
|
907
|
-
const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson(challengeUrl, { identityKey }) });
|
|
908
1823
|
const verifyUrl = this.#attr("verify-url");
|
|
909
|
-
const
|
|
910
|
-
|
|
1824
|
+
const { result: r, verified } = await signInAndVerify({
|
|
1825
|
+
wallet,
|
|
1826
|
+
identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
|
|
1827
|
+
challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
|
|
1828
|
+
verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
|
|
1829
|
+
});
|
|
1830
|
+
if (verifyUrl) rememberIdentity(r.identityKey);
|
|
1831
|
+
const response = verifyUrl ? verified : null;
|
|
1832
|
+
if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
|
|
1833
|
+
this.#attempt(option, "signed-in", started);
|
|
1834
|
+
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
|
|
911
1835
|
} catch (error) {
|
|
912
|
-
|
|
1836
|
+
const outcome = outcomeOf(error);
|
|
1837
|
+
this.#attempt(option, outcome, started);
|
|
1838
|
+
const closedQr = outcome === "cancelled" && option?.kind === "pairing";
|
|
1839
|
+
if (own) {
|
|
1840
|
+
if (closedQr) this.#phase = prev;
|
|
1841
|
+
else {
|
|
1842
|
+
this.#error = signInErrorText(error);
|
|
1843
|
+
this.#phase = "error";
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
if (!closedQr) this.#emit("bwalletx-error", { error });
|
|
913
1847
|
} finally {
|
|
914
|
-
this.#
|
|
1848
|
+
this.#busy = false;
|
|
1849
|
+
try {
|
|
1850
|
+
pairing.close();
|
|
1851
|
+
} catch {
|
|
1852
|
+
}
|
|
1853
|
+
this.#render();
|
|
915
1854
|
}
|
|
916
1855
|
}
|
|
917
1856
|
};
|
|
@@ -925,17 +1864,232 @@ function defineBwalletxSignin(tag = SIGNIN_TAG) {
|
|
|
925
1864
|
return C;
|
|
926
1865
|
}
|
|
927
1866
|
|
|
1867
|
+
// src/wallet-button.ts
|
|
1868
|
+
var BUTTON_TAG = "bwalletx-button";
|
|
1869
|
+
var CONNECTED_KEY = "bwx:connected";
|
|
1870
|
+
function dropLegacyConnectedKey() {
|
|
1871
|
+
try {
|
|
1872
|
+
const had = globalThis.localStorage?.getItem(CONNECTED_KEY) != null;
|
|
1873
|
+
if (had) globalThis.localStorage?.removeItem(CONNECTED_KEY);
|
|
1874
|
+
return had;
|
|
1875
|
+
} catch {
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
var LOOKUP = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
1880
|
+
var LEGACY = '[data-bwx-connect], .nav-connect, .nav-acct, a[href^="https://bwalletx.com/get"], a[href^="https://www.bwalletx.com/get"]';
|
|
1881
|
+
function getUrlFor(ua, touchMac = false) {
|
|
1882
|
+
if (/iPhone|iPad|iPod/i.test(ua) || touchMac && /Macintosh/.test(ua)) return "https://bwalletx.com/iphone";
|
|
1883
|
+
if (/Android/i.test(ua)) return "https://bwalletx.com/android";
|
|
1884
|
+
if (/Chrome\//.test(ua) && !/Edg\/|OPR\//.test(ua)) return "https://bwalletx.com/extension";
|
|
1885
|
+
return "https://bwalletx.com/get";
|
|
1886
|
+
}
|
|
1887
|
+
function stateFor(o) {
|
|
1888
|
+
if (o.connected) return "connected";
|
|
1889
|
+
if (o.found === null) return o.phone ? "available" : "checking";
|
|
1890
|
+
return o.found || o.phone ? "available" : "none";
|
|
1891
|
+
}
|
|
1892
|
+
function handleLabel(key, paymail) {
|
|
1893
|
+
if (paymail && /@bwalletx\.com$/i.test(paymail)) return "$" + paymail.split("@")[0];
|
|
1894
|
+
if (paymail) return paymail;
|
|
1895
|
+
return key.slice(0, 6) + "\u2026" + key.slice(-4);
|
|
1896
|
+
}
|
|
1897
|
+
var CHIP_CSS = `
|
|
1898
|
+
a.b,button.b{all:unset;box-sizing:border-box}
|
|
1899
|
+
.chip{background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.45);box-shadow:none}
|
|
1900
|
+
.chip:hover{background:#151517;border-color:#ffd24d}
|
|
1901
|
+
.chip .dot{width:8px;height:8px;border-radius:50%;background:#34d399;flex:none}
|
|
1902
|
+
.txt b{overflow:hidden;text-overflow:ellipsis;max-width:min(40vw,220px)}
|
|
1903
|
+
:host(:not([size])) button,:host(:not([size])) a.btn,:host([size=sm]) a.btn{padding:6px 12px 6px 6px;border-radius:8px;font-size:13px;gap:8px;min-height:36px}
|
|
1904
|
+
:host(:not([size])) .mark,:host([size=sm]) a.btn .mark{width:22px;height:22px}
|
|
1905
|
+
a.btn{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;cursor:pointer;
|
|
1906
|
+
padding:16px 20px;border-radius:12px;font-size:16px;font-weight:700;line-height:1.2;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;
|
|
1907
|
+
box-shadow:0 10px 15px -3px rgba(245,158,11,.2),0 4px 6px -4px rgba(245,158,11,.2)}
|
|
1908
|
+
a.btn:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}
|
|
1909
|
+
a.btn:focus-visible{outline:2px solid #b45309;outline-offset:3px}
|
|
1910
|
+
:host([size=md]) a.btn{padding:10px 16px;border-radius:10px;font-size:15px;gap:10px}
|
|
1911
|
+
`;
|
|
1912
|
+
var live = /* @__PURE__ */ new Set();
|
|
1913
|
+
function createClass2() {
|
|
1914
|
+
return class BwalletxButtonElement extends HTMLElement {
|
|
1915
|
+
#root;
|
|
1916
|
+
#state = "checking";
|
|
1917
|
+
#key = null;
|
|
1918
|
+
#paymail = null;
|
|
1919
|
+
#duplicate = false;
|
|
1920
|
+
constructor() {
|
|
1921
|
+
super();
|
|
1922
|
+
this.#root = this.attachShadow({ mode: "open" });
|
|
1923
|
+
}
|
|
1924
|
+
get state() {
|
|
1925
|
+
return this.#state;
|
|
1926
|
+
}
|
|
1927
|
+
get identityKey() {
|
|
1928
|
+
return this.#key;
|
|
1929
|
+
}
|
|
1930
|
+
connectedCallback() {
|
|
1931
|
+
if (live.size && ![...live].includes(this)) {
|
|
1932
|
+
this.#duplicate = true;
|
|
1933
|
+
this.hidden = true;
|
|
1934
|
+
console.warn("[bwalletx] A second <bwalletx-button> is on this page; it is hidden. Use one per page.");
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
live.add(this);
|
|
1938
|
+
this.#hideLegacy();
|
|
1939
|
+
dropLegacyConnectedKey();
|
|
1940
|
+
const phone = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
1941
|
+
this.#set(stateFor({ connected: false, found: null, phone }));
|
|
1942
|
+
const ms = Number(this.getAttribute("discover-ms")) || 800;
|
|
1943
|
+
void discoverBwalletX(ms).then((f) => {
|
|
1944
|
+
if (!this.isConnected) return;
|
|
1945
|
+
if (this.#state !== "connected" && this.#state !== "connecting") this.#set(stateFor({ connected: false, found: !!f, phone }));
|
|
1946
|
+
}).catch(() => {
|
|
1947
|
+
if (this.#state === "checking") this.#set("none");
|
|
1948
|
+
});
|
|
1949
|
+
void this.#restoreSession();
|
|
1950
|
+
}
|
|
1951
|
+
/** A live session your server reports (session-url): the chip, with no wallet call. */
|
|
1952
|
+
async #restoreSession() {
|
|
1953
|
+
const url = this.getAttribute("session-url");
|
|
1954
|
+
if (!url) return;
|
|
1955
|
+
try {
|
|
1956
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
1957
|
+
const j = await r.json().catch(() => ({}));
|
|
1958
|
+
if (j.identityKey && this.isConnected && this.#state !== "connected" && this.#state !== "connecting") {
|
|
1959
|
+
this.#connected(j.identityKey);
|
|
1960
|
+
if (j.paymail) {
|
|
1961
|
+
this.#paymail = j.paymail;
|
|
1962
|
+
this.#render();
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
} catch {
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
disconnectedCallback() {
|
|
1969
|
+
if (!this.#duplicate) live.delete(this);
|
|
1970
|
+
}
|
|
1971
|
+
#hideLegacy() {
|
|
1972
|
+
const scope = this.closest("header, nav") ?? document;
|
|
1973
|
+
for (const el2 of Array.from(scope.querySelectorAll(LEGACY))) {
|
|
1974
|
+
if (el2 === this || el2.contains(this)) continue;
|
|
1975
|
+
el2.hidden = true;
|
|
1976
|
+
el2.style.setProperty("display", "none", "important");
|
|
1977
|
+
console.warn("[bwalletx] Hid a second wallet control next to <bwalletx-button>:", el2, ". Use only <bwalletx-button>; don't add your own Get or Connect links.");
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
#emit(type, detail) {
|
|
1981
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
1982
|
+
}
|
|
1983
|
+
#set(s) {
|
|
1984
|
+
this.#state = s;
|
|
1985
|
+
this.setAttribute("state", s);
|
|
1986
|
+
this.#render();
|
|
1987
|
+
}
|
|
1988
|
+
#connected(key) {
|
|
1989
|
+
this.#key = key;
|
|
1990
|
+
this.#set("connected");
|
|
1991
|
+
this.#emit("bwalletx-connected", { identityKey: key });
|
|
1992
|
+
const lookup = this.getAttribute("lookup-url");
|
|
1993
|
+
if (lookup === "off") return;
|
|
1994
|
+
void fetch((lookup ?? LOOKUP) + encodeURIComponent(key)).then((r) => r.ok ? r.json() : null).then((j) => {
|
|
1995
|
+
if (j && typeof j.paymail === "string" && this.#key === key) {
|
|
1996
|
+
this.#paymail = j.paymail;
|
|
1997
|
+
this.#render();
|
|
1998
|
+
}
|
|
1999
|
+
}).catch(() => void 0);
|
|
2000
|
+
}
|
|
2001
|
+
async connect() {
|
|
2002
|
+
if (this.#state === "connecting" || this.#state === "connected") return;
|
|
2003
|
+
const modal = pairingModal({ title: "Connect bWalletX" });
|
|
2004
|
+
this.#set("connecting");
|
|
2005
|
+
try {
|
|
2006
|
+
const { wallet } = await connectBwalletX({ pairing: modal });
|
|
2007
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
2008
|
+
this.#connected(publicKey);
|
|
2009
|
+
} catch (e) {
|
|
2010
|
+
if (e?.code === "NOT_FOUND") this.#set("none");
|
|
2011
|
+
else {
|
|
2012
|
+
this.#set("available");
|
|
2013
|
+
if (e?.name !== "AbortError") console.warn("[bwalletx] connect:", e);
|
|
2014
|
+
}
|
|
2015
|
+
} finally {
|
|
2016
|
+
try {
|
|
2017
|
+
modal.close();
|
|
2018
|
+
} catch {
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
disconnect() {
|
|
2023
|
+
try {
|
|
2024
|
+
clearPairing();
|
|
2025
|
+
} catch {
|
|
2026
|
+
}
|
|
2027
|
+
try {
|
|
2028
|
+
window.postMessage({ type: "bwallet:disconnect" }, location.origin);
|
|
2029
|
+
} catch {
|
|
2030
|
+
}
|
|
2031
|
+
this.#key = null;
|
|
2032
|
+
this.#paymail = null;
|
|
2033
|
+
dropLegacyConnectedKey();
|
|
2034
|
+
this.#set("available");
|
|
2035
|
+
this.#emit("bwalletx-disconnected");
|
|
2036
|
+
}
|
|
2037
|
+
#render() {
|
|
2038
|
+
const esc = (s2) => s2.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
2039
|
+
const size = this.getAttribute("size") ?? "sm";
|
|
2040
|
+
const px = size === "lg" ? 20 : size === "md" ? 17 : 15;
|
|
2041
|
+
const mark = `<span class="mark" part="mark">${markSvg(px)}</span>`;
|
|
2042
|
+
const css = `<style>${SIGNIN_CSS}${CHIP_CSS}</style>`;
|
|
2043
|
+
const s = this.#state;
|
|
2044
|
+
if (s === "none") {
|
|
2045
|
+
const href = this.getAttribute("get-url") ?? getUrlFor(navigator.userAgent, (navigator.maxTouchPoints ?? 0) > 1);
|
|
2046
|
+
this.#root.innerHTML = `${css}<a class="btn" part="button" href="${esc(href)}" target="_blank" rel="noopener">${mark}<span class="txt" part="text"><b part="label">Get bWalletX</b></span></a>`;
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
if (s === "connected" && this.#key) {
|
|
2050
|
+
const name = handleLabel(this.#key, this.#paymail);
|
|
2051
|
+
this.#root.innerHTML = `${css}<button type="button" class="chip" part="button chip" title="Connected to bWalletX. Click to disconnect." aria-label="Connected as ${esc(name)}. Disconnect">${mark}<span class="txt" part="text"><b part="label">${esc(name)}</b></span><span class="dot" aria-hidden="true"></span></button>`;
|
|
2052
|
+
this.#root.querySelector("button").onclick = () => this.disconnect();
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
const busy = s === "connecting" || s === "checking";
|
|
2056
|
+
this.#root.innerHTML = `${css}<button type="button" part="button"${busy ? ' aria-busy="true"' : ""}>${busy ? '<span class="mark" part="mark"><span class="spin" aria-hidden="true"></span></span>' : mark}<span class="txt" part="text"><b part="label">${s === "connecting" ? "Connecting\u2026" : "Connect bWalletX"}</b></span></button>`;
|
|
2057
|
+
this.#root.querySelector("button").onclick = () => void this.connect();
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function defineBwalletxButton(tag = BUTTON_TAG) {
|
|
2062
|
+
if (typeof customElements === "undefined" || customElements.get(tag)) return;
|
|
2063
|
+
customElements.define(tag, createClass2());
|
|
2064
|
+
}
|
|
2065
|
+
function _resetBwalletxButtons() {
|
|
2066
|
+
live.clear();
|
|
2067
|
+
}
|
|
2068
|
+
|
|
928
2069
|
// src/define.ts
|
|
929
2070
|
defineBwalletxSignin();
|
|
2071
|
+
defineBwalletxButton();
|
|
930
2072
|
// Annotate the CommonJS export names for ESM import in node:
|
|
931
2073
|
0 && (module.exports = {
|
|
2074
|
+
BUTTON_TAG,
|
|
2075
|
+
CONNECTED_KEY,
|
|
932
2076
|
GET_URL,
|
|
2077
|
+
MENU_CSS,
|
|
2078
|
+
PENDING_LABEL,
|
|
2079
|
+
PENDING_LABEL_OTHER,
|
|
2080
|
+
PENDING_NOTE,
|
|
933
2081
|
SIGNIN_CSS,
|
|
934
2082
|
SIGNIN_TAG,
|
|
935
2083
|
WEB_WALLET_URL,
|
|
2084
|
+
_resetBwalletxButtons,
|
|
936
2085
|
buildAuthorizeUrl,
|
|
2086
|
+
defineBwalletxButton,
|
|
937
2087
|
defineBwalletxSignin,
|
|
2088
|
+
dropLegacyConnectedKey,
|
|
2089
|
+
getUrlFor,
|
|
2090
|
+
handleLabel,
|
|
938
2091
|
markSvg,
|
|
939
|
-
signinText
|
|
2092
|
+
signinText,
|
|
2093
|
+
stateFor
|
|
940
2094
|
});
|
|
941
2095
|
//# sourceMappingURL=element.cjs.map
|