@bwalletx/connect 0.5.6 → 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 +28 -0
- package/README.md +22 -2
- package/dist/bwalletx-connect.global.js +15 -10
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{choice-DCh-tgpT.d.cts → choice-B97AjqF4.d.cts} +87 -3
- package/dist/{choice-DCh-tgpT.d.ts → choice-B97AjqF4.d.ts} +87 -3
- package/dist/{chunk-LS6AYEBO.js → chunk-6EFFOIHK.js} +596 -41
- package/dist/chunk-6EFFOIHK.js.map +1 -0
- package/dist/{chunk-34DKBQNN.js → chunk-DIKJFKND.js} +22 -5
- package/dist/chunk-DIKJFKND.js.map +1 -0
- package/dist/{chunk-IFGOAROM.js → chunk-TNRI7ARM.js} +14 -63
- package/dist/chunk-TNRI7ARM.js.map +1 -0
- package/dist/{element-D9Zl-x12.d.cts → element-BXEoOdXY.d.cts} +11 -4
- package/dist/{element-D9Zl-x12.d.ts → element-BXEoOdXY.d.ts} +11 -4
- package/dist/element.cjs +480 -50
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +7 -2
- package/dist/element.d.ts +7 -2
- package/dist/element.js +8 -2
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +676 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +169 -11
- package/dist/index.d.ts +169 -11
- package/dist/index.js +92 -6
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +683 -103
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +43 -4
- package/dist/react.d.ts +43 -4
- package/dist/react.js +147 -12
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-34DKBQNN.js.map +0 -1
- package/dist/chunk-IFGOAROM.js.map +0 -1
- package/dist/chunk-LS6AYEBO.js.map +0 -1
|
@@ -17,7 +17,7 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
|
17
17
|
return new Promise((resolve) => {
|
|
18
18
|
let done = false;
|
|
19
19
|
let timer;
|
|
20
|
-
const
|
|
20
|
+
const finish2 = (r) => {
|
|
21
21
|
if (done) return;
|
|
22
22
|
done = true;
|
|
23
23
|
if (timer !== void 0) clearTimeout(timer);
|
|
@@ -28,19 +28,19 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
|
28
28
|
const d = e.detail;
|
|
29
29
|
const rdns = d?.info?.rdns ?? "";
|
|
30
30
|
if (d?.wallet && BWALLETX_RDNS.includes(rdns)) {
|
|
31
|
-
|
|
31
|
+
finish2({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
35
35
|
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
36
36
|
if (done) return;
|
|
37
37
|
if (isFramed()) {
|
|
38
|
-
void probeXdm(Math.max(ms, 1200)).then((w) =>
|
|
38
|
+
void probeXdm(Math.max(ms, 1200)).then((w) => finish2(w ? { wallet: w, method: "in-app", rdns: null } : null));
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
timer = setTimeout(() => {
|
|
42
42
|
timer = void 0;
|
|
43
|
-
|
|
43
|
+
finish2(null);
|
|
44
44
|
}, ms);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -733,15 +733,20 @@ function desktopOption(ms = 1500) {
|
|
|
733
733
|
return opt;
|
|
734
734
|
}
|
|
735
735
|
function autoChoice(list, force = false) {
|
|
736
|
-
const
|
|
737
|
-
const
|
|
738
|
-
|
|
736
|
+
const auto = list.filter((w) => !isLocalDesktop(w));
|
|
737
|
+
const detected = auto.filter((w) => w.kind === "brc100");
|
|
738
|
+
const others = auto.filter((w) => w.kind === "handcash" || w.kind === "custom");
|
|
739
|
+
const hasLocal = auto.length !== list.length;
|
|
740
|
+
if (force) return detected[0] ?? auto.find((w) => w.kind !== "pairing") ?? auto[0] ?? null;
|
|
739
741
|
if (!list.length) return null;
|
|
740
|
-
if (list.length === 1) return list[0];
|
|
741
|
-
if (detected.length === 1 && others.length === 0) return detected[0];
|
|
742
|
-
if (detected.length === 0 && others.length === 0) return
|
|
742
|
+
if (list.length === 1) return hasLocal ? void 0 : list[0];
|
|
743
|
+
if (detected.length === 1 && others.length === 0 && !hasLocal) return detected[0];
|
|
744
|
+
if (detected.length === 0 && others.length === 0) return auto[0] ?? void 0;
|
|
743
745
|
return void 0;
|
|
744
746
|
}
|
|
747
|
+
function isLocalDesktop(w) {
|
|
748
|
+
return w.substrate === "json-api" || w.substrate === "cicada" || w.id === "localhost:3321" || w.id === "localhost:3301" || w.id === DESKTOP_OPTION_ID;
|
|
749
|
+
}
|
|
745
750
|
|
|
746
751
|
// src/account.ts
|
|
747
752
|
var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
@@ -777,6 +782,7 @@ function isUserRejection(e) {
|
|
|
777
782
|
}
|
|
778
783
|
function signInErrorText(e) {
|
|
779
784
|
if (isUserRejection(e)) return "Sign-in cancelled";
|
|
785
|
+
if (e?.name === "MetanetUnreachableError") return e.message;
|
|
780
786
|
const code = e?.code;
|
|
781
787
|
if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
|
|
782
788
|
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
@@ -810,6 +816,173 @@ function rememberIdentity(key) {
|
|
|
810
816
|
}
|
|
811
817
|
}
|
|
812
818
|
|
|
819
|
+
// src/device.ts
|
|
820
|
+
function isTouchDevice(env = globalThis) {
|
|
821
|
+
const nav = env.navigator;
|
|
822
|
+
if (!nav) return false;
|
|
823
|
+
const ua = nav.userAgent ?? "";
|
|
824
|
+
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
825
|
+
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
826
|
+
try {
|
|
827
|
+
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
828
|
+
} catch {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/metanet.ts
|
|
834
|
+
import { WalletClient as WalletClient3 } from "@bsv/sdk";
|
|
835
|
+
var METANET_TYPE = "metanet";
|
|
836
|
+
var METANET_OPTION_ID = METANET_TYPE;
|
|
837
|
+
var METANET_JSON_API = "http://localhost:3321";
|
|
838
|
+
var METANET_LABEL = "Use Metanet Desktop or another BRC-100 wallet";
|
|
839
|
+
var METANET_NOTE = "Metanet Desktop must be running on this computer";
|
|
840
|
+
var METANET_REMEMBERED_NOTE = "Signs in with Metanet Desktop on this computer.";
|
|
841
|
+
var NOT_YOU_LABEL = "Not you? Use another wallet";
|
|
842
|
+
var METANET_UNREACHABLE = "We couldn\u2019t reach Metanet Desktop. Open it, then try again.";
|
|
843
|
+
var METANET_ANSWER_MS = 4e3;
|
|
844
|
+
var METANET_SILENT_MS = 2e3;
|
|
845
|
+
var MetanetUnreachableError = class extends Error {
|
|
846
|
+
code = "NOT_FOUND";
|
|
847
|
+
constructor() {
|
|
848
|
+
super(METANET_UNREACHABLE);
|
|
849
|
+
this.name = "MetanetUnreachableError";
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
function inAnyFrame(win = typeof window === "undefined" ? void 0 : window) {
|
|
853
|
+
if (!win) return false;
|
|
854
|
+
try {
|
|
855
|
+
return win.top !== win.self;
|
|
856
|
+
} catch {
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function metanetOffered(env = {}) {
|
|
861
|
+
const win = env.window ?? (typeof window === "undefined" ? void 0 : window);
|
|
862
|
+
if (!win) return false;
|
|
863
|
+
const nav = env.navigator ?? (typeof navigator === "undefined" ? void 0 : navigator);
|
|
864
|
+
const mm = env.matchMedia ?? (typeof matchMedia === "undefined" ? void 0 : (q) => matchMedia(q));
|
|
865
|
+
return !isTouchDevice({ navigator: nav, matchMedia: mm }) && !inAnyFrame(win);
|
|
866
|
+
}
|
|
867
|
+
function client(o) {
|
|
868
|
+
if (o.make) return o.make();
|
|
869
|
+
const originator = o.originator ?? (typeof location !== "undefined" && location.host ? location.host : void 0);
|
|
870
|
+
return new WalletClient3("json-api", originator);
|
|
871
|
+
}
|
|
872
|
+
async function answers(wallet) {
|
|
873
|
+
return Promise.resolve().then(() => wallet.getVersion({})).then((v) => typeof v?.version === "string", () => false);
|
|
874
|
+
}
|
|
875
|
+
async function connectMetanetDesktop(o = {}) {
|
|
876
|
+
let wallet;
|
|
877
|
+
try {
|
|
878
|
+
wallet = client(o);
|
|
879
|
+
} catch {
|
|
880
|
+
throw new MetanetUnreachableError();
|
|
881
|
+
}
|
|
882
|
+
let timer;
|
|
883
|
+
const ok = await Promise.race([answers(wallet), new Promise((r) => timer = setTimeout(() => r(false), o.timeoutMs ?? METANET_ANSWER_MS))]);
|
|
884
|
+
clearTimeout(timer);
|
|
885
|
+
if (!ok) throw new MetanetUnreachableError();
|
|
886
|
+
return wallet;
|
|
887
|
+
}
|
|
888
|
+
function metanetOption(o = {}) {
|
|
889
|
+
const opt = {
|
|
890
|
+
id: METANET_OPTION_ID,
|
|
891
|
+
name: METANET_LABEL,
|
|
892
|
+
kind: "brc100",
|
|
893
|
+
isBwalletX: false,
|
|
894
|
+
substrate: "json-api",
|
|
895
|
+
async connect() {
|
|
896
|
+
const wallet = await connectMetanetDesktop(o);
|
|
897
|
+
opt.wallet = wallet;
|
|
898
|
+
await waitIfLocked(wallet);
|
|
899
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
900
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: opt.id, kind: "brc100" };
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
return opt;
|
|
904
|
+
}
|
|
905
|
+
var KNOWN_KEYS_KEY = "bwx:known-keys";
|
|
906
|
+
var KEY_RE = /^0[23][0-9a-f]{64}$/;
|
|
907
|
+
var MAX_KNOWN = 8;
|
|
908
|
+
function knownIdentityKeys() {
|
|
909
|
+
try {
|
|
910
|
+
const v = JSON.parse(globalThis.localStorage?.getItem(KNOWN_KEYS_KEY) || "[]");
|
|
911
|
+
return Array.isArray(v) ? v.filter((k) => typeof k === "string" && KEY_RE.test(k)) : [];
|
|
912
|
+
} catch {
|
|
913
|
+
return [];
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function isKnownIdentityKey(key) {
|
|
917
|
+
if (!key) return false;
|
|
918
|
+
const k = key.toLowerCase();
|
|
919
|
+
if (!KEY_RE.test(k)) return false;
|
|
920
|
+
return knownIdentityKeys().includes(k) || rememberedIdentity()?.toLowerCase() === k;
|
|
921
|
+
}
|
|
922
|
+
function rememberKnownIdentityKey(key) {
|
|
923
|
+
const k = (key ?? "").toLowerCase();
|
|
924
|
+
if (!KEY_RE.test(k)) return;
|
|
925
|
+
try {
|
|
926
|
+
const list = [k, ...knownIdentityKeys().filter((x) => x !== k)].slice(0, MAX_KNOWN);
|
|
927
|
+
globalThis.localStorage?.setItem(KNOWN_KEYS_KEY, JSON.stringify(list));
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
var triedThisLoad = false;
|
|
932
|
+
function silentAttemptUsed() {
|
|
933
|
+
return triedThisLoad;
|
|
934
|
+
}
|
|
935
|
+
function claimSilentAttempt() {
|
|
936
|
+
if (triedThisLoad) return false;
|
|
937
|
+
triedThisLoad = true;
|
|
938
|
+
return true;
|
|
939
|
+
}
|
|
940
|
+
function resetSilentAttempt() {
|
|
941
|
+
triedThisLoad = false;
|
|
942
|
+
}
|
|
943
|
+
async function trySilentMetanet(o = {}) {
|
|
944
|
+
if (!metanetOffered()) return { ok: false, stop: "not-here" };
|
|
945
|
+
if (!claimSilentAttempt()) return { ok: false, stop: "used" };
|
|
946
|
+
const run = async () => {
|
|
947
|
+
let wallet;
|
|
948
|
+
try {
|
|
949
|
+
wallet = client(o);
|
|
950
|
+
} catch {
|
|
951
|
+
return { ok: false, stop: "unreachable" };
|
|
952
|
+
}
|
|
953
|
+
if (!await answers(wallet)) return { ok: false, stop: "unreachable" };
|
|
954
|
+
const unlocked = await Promise.resolve().then(() => wallet.isAuthenticated({})).then((r2) => r2?.authenticated === true, () => false);
|
|
955
|
+
if (!unlocked) return { ok: false, stop: "locked" };
|
|
956
|
+
const identityKey = await Promise.resolve().then(() => wallet.getPublicKey({ identityKey: true })).then((r2) => r2.publicKey, () => null);
|
|
957
|
+
if (!identityKey) return { ok: false, stop: "unreachable" };
|
|
958
|
+
if (!isKnownIdentityKey(identityKey)) return { ok: false, stop: "unknown-key" };
|
|
959
|
+
return { ok: true, wallet, identityKey };
|
|
960
|
+
};
|
|
961
|
+
let timer;
|
|
962
|
+
const r = await Promise.race([
|
|
963
|
+
run().catch(() => ({ ok: false, stop: "unreachable" })),
|
|
964
|
+
new Promise((res) => timer = setTimeout(() => res({ ok: false, stop: "slow" }), o.windowMs ?? METANET_SILENT_MS))
|
|
965
|
+
]);
|
|
966
|
+
clearTimeout(timer);
|
|
967
|
+
return r;
|
|
968
|
+
}
|
|
969
|
+
var warnedLogout = false;
|
|
970
|
+
var isProduction = () => {
|
|
971
|
+
try {
|
|
972
|
+
return globalThis.process?.env?.NODE_ENV === "production";
|
|
973
|
+
} catch {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
function warnIfNoLogout(o) {
|
|
978
|
+
if (!o.verifyUrl || o.logoutUrl || warnedLogout || isProduction() || typeof console === "undefined") return false;
|
|
979
|
+
warnedLogout = true;
|
|
980
|
+
console.warn(
|
|
981
|
+
`@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.`
|
|
982
|
+
);
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
|
|
813
986
|
// src/choice.ts
|
|
814
987
|
var BWALLETX_TYPE = "bwalletx";
|
|
815
988
|
function substrateOf(o) {
|
|
@@ -851,6 +1024,10 @@ function decideWallet(list, o = {}) {
|
|
|
851
1024
|
const a = autoChoice([...list], o.picker === false);
|
|
852
1025
|
return a === void 0 ? { option: null, reason: "chooser" } : a === null ? { option: null, reason: "none" } : { option: a, reason: "legacy" };
|
|
853
1026
|
}
|
|
1027
|
+
if (o.remembered && !o.preferBwalletX) {
|
|
1028
|
+
const m = matchRemembered(list, o.remembered);
|
|
1029
|
+
if (m) return { option: m, reason: "remembered" };
|
|
1030
|
+
}
|
|
854
1031
|
const b = list.find(isPresentBwalletX);
|
|
855
1032
|
if (b) return { option: b, reason: "bwalletx" };
|
|
856
1033
|
if (o.picker === false) {
|
|
@@ -881,6 +1058,74 @@ function reportAttempt(hook, a) {
|
|
|
881
1058
|
} catch {
|
|
882
1059
|
}
|
|
883
1060
|
}
|
|
1061
|
+
var WALLET_CHOICE_KEY = "bwx:wallet";
|
|
1062
|
+
var SUBSTRATES = ["extension", "in-app", "xdm", "injected", "window.CWI", "json-api", "cicada", "pairing", "adapter"];
|
|
1063
|
+
var TYPE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
1064
|
+
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;
|
|
1065
|
+
function isRememberable(o) {
|
|
1066
|
+
const t = walletTypeOf(o);
|
|
1067
|
+
return substrateOf(o) !== "window.CWI" && TYPE_RE.test(t) && !KEY_LIKE.test(t);
|
|
1068
|
+
}
|
|
1069
|
+
function isExplicitPick(o, reason) {
|
|
1070
|
+
return o.isBwalletX || reason === "chooser" || reason === "requested" || reason === "remembered";
|
|
1071
|
+
}
|
|
1072
|
+
function rememberWalletChoice(o) {
|
|
1073
|
+
try {
|
|
1074
|
+
if (!o) return void globalThis.localStorage?.removeItem(WALLET_CHOICE_KEY);
|
|
1075
|
+
if (!isRememberable(o)) return;
|
|
1076
|
+
const v = { type: walletTypeOf(o), substrate: substrateOf(o), v: 1 };
|
|
1077
|
+
globalThis.localStorage?.setItem(WALLET_CHOICE_KEY, JSON.stringify(v));
|
|
1078
|
+
} catch {
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function rememberedWalletChoice() {
|
|
1082
|
+
try {
|
|
1083
|
+
const raw = globalThis.localStorage?.getItem(WALLET_CHOICE_KEY);
|
|
1084
|
+
if (!raw) return null;
|
|
1085
|
+
const j = JSON.parse(raw);
|
|
1086
|
+
if (typeof j.type !== "string" || !TYPE_RE.test(j.type) || KEY_LIKE.test(j.type)) return null;
|
|
1087
|
+
if (!SUBSTRATES.includes(j.substrate) || j.substrate === "window.CWI") return null;
|
|
1088
|
+
return { type: j.type, substrate: j.substrate };
|
|
1089
|
+
} catch {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
function forgetWalletChoice() {
|
|
1094
|
+
rememberWalletChoice(null);
|
|
1095
|
+
}
|
|
1096
|
+
function needsBwalletX(type) {
|
|
1097
|
+
return !!type && type !== BWALLETX_TYPE;
|
|
1098
|
+
}
|
|
1099
|
+
function tapGoesElsewhere(type) {
|
|
1100
|
+
return needsBwalletX(type) && type !== METANET_TYPE && type !== DESKTOP_OPTION_ID;
|
|
1101
|
+
}
|
|
1102
|
+
function isLocalDesktopType(r) {
|
|
1103
|
+
return r.type === METANET_TYPE || r.type === DESKTOP_OPTION_ID || r.substrate === "json-api" || r.substrate === "cicada";
|
|
1104
|
+
}
|
|
1105
|
+
function matchRemembered(list, r) {
|
|
1106
|
+
if (isLocalDesktopType(r)) return null;
|
|
1107
|
+
if (r.type === BWALLETX_TYPE) {
|
|
1108
|
+
return (r.substrate === "pairing" ? list.find((o) => o.kind === "pairing") : list.find(isPresentBwalletX)) ?? null;
|
|
1109
|
+
}
|
|
1110
|
+
return list.find((o) => !o.isBwalletX && !isLocalDesktop(o) && isRememberable(o) && walletTypeOf(o) === r.type) ?? null;
|
|
1111
|
+
}
|
|
1112
|
+
var ATTACHABLE = ["extension", "in-app", "xdm", "injected"];
|
|
1113
|
+
function planAutoConnect(o) {
|
|
1114
|
+
const r = o.remembered;
|
|
1115
|
+
if (!r || o.enabled === false) return { action: "none", remembered: r, needsBwalletX: false };
|
|
1116
|
+
if (r.type === METANET_TYPE) {
|
|
1117
|
+
return { action: o.metanetHere ?? metanetOffered() ? "silent-metanet" : "none", remembered: r, needsBwalletX: false };
|
|
1118
|
+
}
|
|
1119
|
+
if (isLocalDesktopType(r)) return { action: "none", remembered: r, needsBwalletX: false };
|
|
1120
|
+
const action = o.signedIn && ATTACHABLE.includes(r.substrate) ? "attach" : "wait-for-tap";
|
|
1121
|
+
return { action, remembered: r, needsBwalletX: needsBwalletX(r.type) };
|
|
1122
|
+
}
|
|
1123
|
+
async function attachRemembered(r, o = {}) {
|
|
1124
|
+
if (!ATTACHABLE.includes(r.substrate)) return null;
|
|
1125
|
+
const list = await discoverWallets({ ms: o.ms });
|
|
1126
|
+
const m = matchRemembered(list, r);
|
|
1127
|
+
return m && m.kind === "brc100" && m.wallet && ATTACHABLE.includes(substrateOf(m)) ? m : null;
|
|
1128
|
+
}
|
|
884
1129
|
|
|
885
1130
|
// src/modal.ts
|
|
886
1131
|
import qrcode from "qrcode-generator";
|
|
@@ -971,6 +1216,114 @@ async function requestAccess(o) {
|
|
|
971
1216
|
}
|
|
972
1217
|
}
|
|
973
1218
|
|
|
1219
|
+
// src/connect-flow.ts
|
|
1220
|
+
async function runConnect(o) {
|
|
1221
|
+
const started = Date.now();
|
|
1222
|
+
const { list } = o;
|
|
1223
|
+
let choice = null;
|
|
1224
|
+
let reason = "none";
|
|
1225
|
+
try {
|
|
1226
|
+
if (o.metanet) {
|
|
1227
|
+
if (!metanetOffered()) throw Object.assign(new Error("Metanet Desktop runs on a computer, outside any frame."), { code: "NOT_SUPPORTED" });
|
|
1228
|
+
choice = metanetOption(typeof o.metanet === "object" ? o.metanet : {});
|
|
1229
|
+
reason = "requested";
|
|
1230
|
+
return await finish(o, choice, reason, started);
|
|
1231
|
+
}
|
|
1232
|
+
const d = decideWallet(list, {
|
|
1233
|
+
walletId: o.walletId,
|
|
1234
|
+
chooser: o.chooser,
|
|
1235
|
+
picker: o.picker,
|
|
1236
|
+
fastPath: o.fastPath,
|
|
1237
|
+
remembered: o.remembered,
|
|
1238
|
+
preferBwalletX: o.preferBwalletX
|
|
1239
|
+
});
|
|
1240
|
+
choice = d.option;
|
|
1241
|
+
reason = d.reason;
|
|
1242
|
+
if (!choice && d.reason === "chooser") choice = await o.pick(list);
|
|
1243
|
+
if (!choice) {
|
|
1244
|
+
if (d.reason === "none" || !list.length) throw Object.assign(new Error("No wallet found. Get bWalletX at bwalletx.com."), { code: "NOT_FOUND" });
|
|
1245
|
+
reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
return await finish(o, choice, reason, started);
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
|
|
1251
|
+
throw e;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
async function finish(o, choice, reason, started) {
|
|
1255
|
+
const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
|
|
1256
|
+
const cw = await choice.connect(signIn ? { identity: false } : void 0);
|
|
1257
|
+
let handle = cw.handle ?? null;
|
|
1258
|
+
let paymail = cw.paymail ?? null;
|
|
1259
|
+
let identityKey = cw.identityKey;
|
|
1260
|
+
let signedIn = false;
|
|
1261
|
+
if (cw.wallet && signIn) {
|
|
1262
|
+
const { result, verified } = await signInAndVerify({
|
|
1263
|
+
wallet: cw.wallet,
|
|
1264
|
+
identityKey: cw.identityKey ?? void 0,
|
|
1265
|
+
// Metanet's connect() just checked the lock: don't ask again (rule 7, the minimum).
|
|
1266
|
+
skipWaitForAuthentication: choice.id === METANET_OPTION_ID,
|
|
1267
|
+
// The key that signed in here last time: a repeat sign-in is then the signature alone.
|
|
1268
|
+
identityKeyHint: rememberedIdentity(),
|
|
1269
|
+
challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
|
|
1270
|
+
challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
1271
|
+
verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
1272
|
+
});
|
|
1273
|
+
rememberIdentity(result.identityKey);
|
|
1274
|
+
identityKey = result.identityKey;
|
|
1275
|
+
handle = verified.handle ?? handle;
|
|
1276
|
+
paymail = verified.paymail ?? paymail;
|
|
1277
|
+
signedIn = true;
|
|
1278
|
+
} else if (cw.wallet && !identityKey) {
|
|
1279
|
+
identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
|
|
1280
|
+
}
|
|
1281
|
+
if (o.remember && isExplicitPick(choice, reason)) rememberWalletChoice(choice);
|
|
1282
|
+
if (o.remember && signedIn) rememberKnownIdentityKey(identityKey);
|
|
1283
|
+
reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
|
|
1284
|
+
return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
|
|
1285
|
+
}
|
|
1286
|
+
async function runSilentMetanet(o = {}) {
|
|
1287
|
+
const s = await trySilentMetanet(o);
|
|
1288
|
+
if (!s.ok || o.isAbandoned?.()) return null;
|
|
1289
|
+
const started = Date.now();
|
|
1290
|
+
const opt = metanetOption(o);
|
|
1291
|
+
opt.wallet = s.wallet;
|
|
1292
|
+
const cw = { wallet: s.wallet, payer: s.wallet, identityKey: s.identityKey, optionId: METANET_OPTION_ID, kind: "brc100" };
|
|
1293
|
+
const done = (signedIn, handle = null, paymail = null) => {
|
|
1294
|
+
rememberWalletChoice(opt);
|
|
1295
|
+
if (signedIn) rememberKnownIdentityKey(s.identityKey);
|
|
1296
|
+
reportAttempt(o.onAttempt, attemptFor(opt, signedIn ? "signed-in" : "connected", started));
|
|
1297
|
+
return { connected: cw, identityKey: s.identityKey, handle, paymail, signedIn, walletType: walletTypeOf(opt), substrate: "json-api", reason: "remembered" };
|
|
1298
|
+
};
|
|
1299
|
+
if (o.sessionKey !== void 0 && o.sessionKey !== null) {
|
|
1300
|
+
return o.sessionKey.toLowerCase() === s.identityKey.toLowerCase() ? done(true) : null;
|
|
1301
|
+
}
|
|
1302
|
+
if (!o.challengeUrl || !o.verifyUrl || !o.post) return done(false);
|
|
1303
|
+
if (!isKnownIdentityKey(s.identityKey)) return null;
|
|
1304
|
+
try {
|
|
1305
|
+
o.onSignIn?.();
|
|
1306
|
+
const post = o.post;
|
|
1307
|
+
const { result, verified } = await signInAndVerify({
|
|
1308
|
+
wallet: s.wallet,
|
|
1309
|
+
identityKey: s.identityKey,
|
|
1310
|
+
skipWaitForAuthentication: true,
|
|
1311
|
+
// trySilentMetanet just saw it unlocked
|
|
1312
|
+
challenge: (key) => post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
1313
|
+
verify: (r) => post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
1314
|
+
});
|
|
1315
|
+
if (o.isAbandoned?.()) {
|
|
1316
|
+
await Promise.resolve(o.logout?.()).catch(() => void 0);
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
rememberIdentity(result.identityKey);
|
|
1320
|
+
return done(true, verified.handle ?? null, verified.paymail ?? null);
|
|
1321
|
+
} catch (e) {
|
|
1322
|
+
reportAttempt(o.onAttempt, attemptFor(opt, outcomeOf(e), started));
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
974
1327
|
// src/element.ts
|
|
975
1328
|
var SIGNIN_TAG = "bwalletx-signin";
|
|
976
1329
|
var GET_URL = "https://bwalletx.com/get";
|
|
@@ -1036,6 +1389,11 @@ button[aria-busy=true]{cursor:wait}
|
|
|
1036
1389
|
/* "Other wallets" (0.5.6): one small link under the button */
|
|
1037
1390
|
.ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1038
1391
|
:host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
|
|
1392
|
+
.sw{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#a16207;text-align:center}
|
|
1393
|
+
:host([theme=dark]) .sw,:host-context(.dark) .sw{color:#fcd34d}
|
|
1394
|
+
.mn{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1395
|
+
.mn small{display:block;font-size:11px;opacity:.85}
|
|
1396
|
+
:host([theme=dark]) .mn,:host-context(.dark) .mn{color:#a1a1aa}
|
|
1039
1397
|
.lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
|
|
1040
1398
|
.lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
1041
1399
|
/* error: one line plus Retry */
|
|
@@ -1074,6 +1432,11 @@ var MENU_CSS = `
|
|
|
1074
1432
|
var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
|
|
1075
1433
|
var PENDING_LABEL = "Approve in bWalletX\u2026";
|
|
1076
1434
|
var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
|
|
1435
|
+
var SWITCH_NOTE = "Some features need bWalletX";
|
|
1436
|
+
var CONTINUE_OTHER_LABEL = "Continue with your wallet";
|
|
1437
|
+
function walletGlyph(px) {
|
|
1438
|
+
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>`;
|
|
1439
|
+
}
|
|
1077
1440
|
function b64url2(bytes) {
|
|
1078
1441
|
let s = "";
|
|
1079
1442
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -1113,7 +1476,7 @@ async function postAccess(url, body) {
|
|
|
1113
1476
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
1114
1477
|
return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
|
|
1115
1478
|
}
|
|
1116
|
-
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"];
|
|
1479
|
+
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"];
|
|
1117
1480
|
var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
|
|
1118
1481
|
function createClass() {
|
|
1119
1482
|
return class BwalletxSigninElement extends HTMLElement {
|
|
@@ -1128,14 +1491,21 @@ function createClass() {
|
|
|
1128
1491
|
#error = "";
|
|
1129
1492
|
#restored = false;
|
|
1130
1493
|
#pendingLabel = PENDING_LABEL;
|
|
1494
|
+
/** The wallet type signed in with here (0.6.0), for the Switch note. */
|
|
1495
|
+
#signedType = null;
|
|
1131
1496
|
#menu = null;
|
|
1497
|
+
/** (0.7.0) A Metanet sign-in (tapped or silent) is in flight. */
|
|
1498
|
+
#viaMetanet = false;
|
|
1499
|
+
/** (0.7.0) Bumped by "Not you?": a Metanet sign-in started before it lands nobody. */
|
|
1500
|
+
#run = 0;
|
|
1132
1501
|
constructor() {
|
|
1133
1502
|
super();
|
|
1134
1503
|
this.#root = this.attachShadow({ mode: "open" });
|
|
1135
1504
|
}
|
|
1136
1505
|
connectedCallback() {
|
|
1506
|
+
if (!this.#controlled && this.#stateful) warnIfNoLogout({ verifyUrl: this.#attr("verify-url"), logoutUrl: this.#attr("logout-url"), where: "<bwalletx-signin>" });
|
|
1137
1507
|
this.#render();
|
|
1138
|
-
void this.#restore();
|
|
1508
|
+
void this.#restore().then(() => this.#silentMetanet());
|
|
1139
1509
|
}
|
|
1140
1510
|
disconnectedCallback() {
|
|
1141
1511
|
this.#menu?.close(false);
|
|
@@ -1241,10 +1611,11 @@ function createClass() {
|
|
|
1241
1611
|
const restoring = state === "restoring";
|
|
1242
1612
|
const error = state === "error";
|
|
1243
1613
|
const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
|
|
1244
|
-
const
|
|
1614
|
+
const elsewhere = state === "signed-out" && tapGoesElsewhere(this.#walletType());
|
|
1615
|
+
const text = pending ? pendingLabel : error ? "Retry" : elsewhere ? CONTINUE_OTHER_LABEL : signinText(this.#attr("label") ?? "signin", brand);
|
|
1245
1616
|
const subAttr = this.#attr("subtitle");
|
|
1246
1617
|
const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
|
|
1247
|
-
const subtitle = pending || error ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
1618
|
+
const subtitle = pending || error || elsewhere ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
1248
1619
|
const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
|
|
1249
1620
|
const disabled = this.hasAttribute("disabled");
|
|
1250
1621
|
const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
|
|
@@ -1253,18 +1624,26 @@ function createClass() {
|
|
|
1253
1624
|
const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
|
|
1254
1625
|
const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
|
|
1255
1626
|
const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
|
|
1256
|
-
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
|
|
1627
|
+
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : elsewhere ? walletGlyph(markPx - 4) : markSvg(markPx);
|
|
1257
1628
|
const otherOff = this.#attr("other-wallets") === "off";
|
|
1258
1629
|
const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
|
|
1259
1630
|
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>` : "";
|
|
1260
1631
|
const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
|
|
1632
|
+
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>` : "";
|
|
1261
1633
|
const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
|
|
1634
|
+
const mnHere = this.#metanetShown(size);
|
|
1635
|
+
const mnRemembered = mnHere && (pending ? this.#metanetPending() : !restoring && this.#walletType() === METANET_TYPE);
|
|
1636
|
+
const notYou = `<button type="button" class="lnk" data-act="not-you" part="not-you-link">${esc(NOT_YOU_LABEL)}</button>`;
|
|
1637
|
+
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>`;
|
|
1262
1638
|
const compact = size === "sm" || size === "icon";
|
|
1263
1639
|
const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
|
|
1264
1640
|
const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
|
|
1265
|
-
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}`;
|
|
1641
|
+
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}`;
|
|
1266
1642
|
this.button.addEventListener("click", () => void this.#click());
|
|
1267
1643
|
for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
|
|
1644
|
+
this.#root.querySelector("[data-act=switch]")?.addEventListener("click", () => void this.switchToBwalletX());
|
|
1645
|
+
this.#root.querySelector("[data-act=metanet]")?.addEventListener("click", () => void this.useMetanet());
|
|
1646
|
+
this.#root.querySelector("[data-act=not-you]")?.addEventListener("click", () => this.notYou());
|
|
1268
1647
|
bindPrewarm(this.button);
|
|
1269
1648
|
if (pending && compact) this.#showTip(PENDING_NOTE);
|
|
1270
1649
|
else this.#hideTip();
|
|
@@ -1332,7 +1711,20 @@ function createClass() {
|
|
|
1332
1711
|
close(false);
|
|
1333
1712
|
void this.signOut();
|
|
1334
1713
|
});
|
|
1335
|
-
|
|
1714
|
+
const items = [copy, out];
|
|
1715
|
+
menu.append(who);
|
|
1716
|
+
if (needsBwalletX(this.#walletType())) {
|
|
1717
|
+
const sw = item("Switch to bWalletX", () => {
|
|
1718
|
+
close(false);
|
|
1719
|
+
void this.switchToBwalletX();
|
|
1720
|
+
});
|
|
1721
|
+
const line = document.createElement("div");
|
|
1722
|
+
line.className = "who";
|
|
1723
|
+
line.textContent = SWITCH_NOTE;
|
|
1724
|
+
menu.append(line, sw);
|
|
1725
|
+
items.unshift(sw);
|
|
1726
|
+
}
|
|
1727
|
+
menu.append(copy, out);
|
|
1336
1728
|
root.append(style, menu);
|
|
1337
1729
|
document.body.appendChild(host);
|
|
1338
1730
|
const place = () => {
|
|
@@ -1348,7 +1740,6 @@ function createClass() {
|
|
|
1348
1740
|
menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
|
|
1349
1741
|
};
|
|
1350
1742
|
place();
|
|
1351
|
-
const items = [copy, out];
|
|
1352
1743
|
const focusAt = (i) => items[(i + items.length) % items.length].focus();
|
|
1353
1744
|
const onKey = (e) => {
|
|
1354
1745
|
const i = items.indexOf(root.activeElement);
|
|
@@ -1403,6 +1794,8 @@ function createClass() {
|
|
|
1403
1794
|
if (this.#controlled) return;
|
|
1404
1795
|
const url = this.#attr("logout-url");
|
|
1405
1796
|
if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
1797
|
+
if (this.#remember) forgetWalletChoice();
|
|
1798
|
+
this.#signedType = null;
|
|
1406
1799
|
this.#account = null;
|
|
1407
1800
|
this.#set("signed-out");
|
|
1408
1801
|
this.#emit("bwalletx-signed-out");
|
|
@@ -1422,6 +1815,110 @@ function createClass() {
|
|
|
1422
1815
|
otherWallets() {
|
|
1423
1816
|
return this.#click("chooser");
|
|
1424
1817
|
}
|
|
1818
|
+
/** (0.6.0) "Switch": sign in with bWalletX (its sheet when here, else the chooser with Get bWalletX first). */
|
|
1819
|
+
switchToBwalletX() {
|
|
1820
|
+
return this.#click("bwalletx");
|
|
1821
|
+
}
|
|
1822
|
+
get #remember() {
|
|
1823
|
+
return this.#attr("remember") !== "off";
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* (0.7.0) "Use Metanet Desktop or another BRC-100 wallet" is offered: metanet on, or auto at lg/md when this
|
|
1827
|
+
* element signs in or follows a provider. Never on a phone or inside a frame.
|
|
1828
|
+
*/
|
|
1829
|
+
#metanetShown(size) {
|
|
1830
|
+
const a = this.#attr("metanet");
|
|
1831
|
+
if (a === "off" || !metanetOffered()) return false;
|
|
1832
|
+
if (a === "on") return true;
|
|
1833
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
1834
|
+
}
|
|
1835
|
+
/** A Metanet sign-in is on screen: our own, or (driven) the provider's with Metanet remembered. */
|
|
1836
|
+
#metanetPending() {
|
|
1837
|
+
return this.#controlled ? this.#attr("wallet-type") === METANET_TYPE : this.#viaMetanet;
|
|
1838
|
+
}
|
|
1839
|
+
/** (0.7.0) The Metanet link: connect to Metanet Desktop on localhost:3321 (after this tap, never before). */
|
|
1840
|
+
useMetanet() {
|
|
1841
|
+
return this.#click("metanet");
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* (0.7.0) "Not you? Use another wallet": forget the remembered wallet here, and drop a Metanet sign-in in flight
|
|
1845
|
+
* (if its verify already set a session, logout-url ends it). Emits bwalletx-not-you (cancelable).
|
|
1846
|
+
*/
|
|
1847
|
+
notYou() {
|
|
1848
|
+
if (!this.#emit("bwalletx-not-you", { element: this }, true)) return;
|
|
1849
|
+
this.#run++;
|
|
1850
|
+
if (this.#remember) forgetWalletChoice();
|
|
1851
|
+
this.#signedType = null;
|
|
1852
|
+
if (this.#viaMetanet && !this.#controlled) {
|
|
1853
|
+
this.#viaMetanet = false;
|
|
1854
|
+
this.#busy = false;
|
|
1855
|
+
if (this.#phase === "pending") this.#phase = "signed-out";
|
|
1856
|
+
}
|
|
1857
|
+
this.#render();
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* (0.7.0) Rule 4: back with Metanet remembered and no session, ONE silent attempt per page load (2 s, a known
|
|
1861
|
+
* identity key only), then the signature alone. Any stop or failure is silent: the normal button stays.
|
|
1862
|
+
*/
|
|
1863
|
+
async #silentMetanet() {
|
|
1864
|
+
if (this.#controlled || !this.#stateful || !this.#remember || this.#attr("require-token")) return;
|
|
1865
|
+
if (!this.#metanetShown(this.#attr("size") ?? "lg")) return;
|
|
1866
|
+
const challengeUrl = this.#attr("challenge-url");
|
|
1867
|
+
const verifyUrl = this.#attr("verify-url");
|
|
1868
|
+
if (!challengeUrl || !verifyUrl || this.#phase !== "signed-out" || this.#busy || silentAttemptUsed()) return;
|
|
1869
|
+
if (rememberedWalletChoice()?.type !== METANET_TYPE || !metanetOffered()) return;
|
|
1870
|
+
const run = ++this.#run;
|
|
1871
|
+
const live = () => run === this.#run;
|
|
1872
|
+
const started = Date.now();
|
|
1873
|
+
const r = await runSilentMetanet({
|
|
1874
|
+
challengeUrl,
|
|
1875
|
+
verifyUrl,
|
|
1876
|
+
post: (url, body) => postJson(url, body),
|
|
1877
|
+
isAbandoned: () => !live(),
|
|
1878
|
+
logout: async () => {
|
|
1879
|
+
const out = this.#attr("logout-url");
|
|
1880
|
+
if (out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
1881
|
+
},
|
|
1882
|
+
onSignIn: () => {
|
|
1883
|
+
if (!live() || this.#phase !== "signed-out" || this.#busy) return;
|
|
1884
|
+
this.#viaMetanet = true;
|
|
1885
|
+
this.#busy = true;
|
|
1886
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
1887
|
+
this.#set("pending");
|
|
1888
|
+
},
|
|
1889
|
+
onAttempt: (a) => this.#emit("bwalletx-attempt", a)
|
|
1890
|
+
});
|
|
1891
|
+
if (!live()) return;
|
|
1892
|
+
this.#viaMetanet = false;
|
|
1893
|
+
this.#busy = false;
|
|
1894
|
+
if (!r) {
|
|
1895
|
+
if (this.#phase === "pending") this.#set("signed-out");
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
this.#signedType = r.walletType;
|
|
1899
|
+
this.#signedIn({ identityKey: r.identityKey, handle: r.handle, paymail: r.paymail });
|
|
1900
|
+
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 });
|
|
1901
|
+
}
|
|
1902
|
+
/** The wallet type the Switch note judges: the provider's, else what signed in here, else the remembered one. */
|
|
1903
|
+
#walletType() {
|
|
1904
|
+
if (this.#controlled) return this.#attr("wallet-type");
|
|
1905
|
+
return this.#signedType ?? (this.#remember ? rememberedWalletChoice()?.type ?? null : null);
|
|
1906
|
+
}
|
|
1907
|
+
#switchNoteShown(size) {
|
|
1908
|
+
const a = this.#attr("switch-note");
|
|
1909
|
+
if (a === "off" || !tapGoesElsewhere(this.#walletType())) return false;
|
|
1910
|
+
return a === "on" || size === "lg" || size === "md";
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Signed in: note the wallet type, and remember it for this origin when the user picked it (0.6.0), plus the
|
|
1914
|
+
* identity key as known here (0.7.0, public keys only: Metanet's silent attempt goes on only for these). Pass a key
|
|
1915
|
+
* only after the site's verify route accepted it.
|
|
1916
|
+
*/
|
|
1917
|
+
#picked(option, reason, identityKey) {
|
|
1918
|
+
this.#signedType = walletTypeOf(option);
|
|
1919
|
+
if (this.#remember && isExplicitPick(option, reason)) rememberWalletChoice(option);
|
|
1920
|
+
if (this.#remember) rememberKnownIdentityKey(identityKey);
|
|
1921
|
+
}
|
|
1425
1922
|
#attempt(option, outcome, started) {
|
|
1426
1923
|
this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
|
|
1427
1924
|
}
|
|
@@ -1430,16 +1927,24 @@ function createClass() {
|
|
|
1430
1927
|
* when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
|
|
1431
1928
|
*/
|
|
1432
1929
|
async #choose(mode, ms, pairing) {
|
|
1930
|
+
if (mode === "metanet") return { option: metanetOption(), reason: "requested" };
|
|
1931
|
+
const r = mode === "signin" && this.#remember ? rememberedWalletChoice() : null;
|
|
1932
|
+
if (r && r.type !== METANET_TYPE && !(r.type === BWALLETX_TYPE && r.substrate !== "pairing")) {
|
|
1933
|
+
const m = matchRemembered(await discoverWallets({ ms, pairing: () => pairing }), r);
|
|
1934
|
+
if (m) return { option: m, reason: "remembered" };
|
|
1935
|
+
}
|
|
1433
1936
|
if (mode !== "chooser") {
|
|
1434
1937
|
const f = await discoverBwalletX(ms);
|
|
1435
1938
|
if (f) {
|
|
1436
1939
|
const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
|
|
1437
|
-
|
|
1940
|
+
const option = brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
|
|
1941
|
+
return { option, reason: "bwalletx" };
|
|
1438
1942
|
}
|
|
1439
1943
|
}
|
|
1440
1944
|
const list = await discoverWallets({ ms: 50, pairing: () => pairing });
|
|
1441
1945
|
if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
|
|
1442
|
-
|
|
1946
|
+
const picked = await chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
|
|
1947
|
+
return picked ? { option: picked, reason: "chooser" } : null;
|
|
1443
1948
|
}
|
|
1444
1949
|
async #click(mode = "signin") {
|
|
1445
1950
|
if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
|
|
@@ -1463,9 +1968,12 @@ function createClass() {
|
|
|
1463
1968
|
const started = Date.now();
|
|
1464
1969
|
const pairing = pairingModal();
|
|
1465
1970
|
let option = null;
|
|
1971
|
+
const run = ++this.#run;
|
|
1972
|
+
const live = () => run === this.#run;
|
|
1466
1973
|
this.#notFound = false;
|
|
1467
1974
|
this.#busy = true;
|
|
1468
|
-
this.#
|
|
1975
|
+
this.#viaMetanet = mode === "metanet";
|
|
1976
|
+
this.#pendingLabel = mode === "metanet" ? PENDING_LABEL_OTHER : PENDING_LABEL;
|
|
1469
1977
|
if (own) this.#set("pending");
|
|
1470
1978
|
else this.#render();
|
|
1471
1979
|
try {
|
|
@@ -1484,12 +1992,14 @@ function createClass() {
|
|
|
1484
1992
|
this.#attempt(null, "cancelled", started);
|
|
1485
1993
|
return;
|
|
1486
1994
|
}
|
|
1487
|
-
option = picked;
|
|
1995
|
+
option = picked.option;
|
|
1996
|
+
const reason = picked.reason;
|
|
1488
1997
|
if (!option.isBwalletX) {
|
|
1489
1998
|
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
1490
1999
|
if (own) this.#render();
|
|
1491
2000
|
}
|
|
1492
2001
|
const cw = await option.connect({ identity: false });
|
|
2002
|
+
if (!live()) return;
|
|
1493
2003
|
const wallet = cw.wallet;
|
|
1494
2004
|
if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
|
|
1495
2005
|
const sub = substrateOf(option);
|
|
@@ -1507,6 +2017,7 @@ function createClass() {
|
|
|
1507
2017
|
challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
|
|
1508
2018
|
verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
|
|
1509
2019
|
});
|
|
2020
|
+
this.#picked(option, reason, verifyUrl2 ? a.response.identityKey : null);
|
|
1510
2021
|
this.#attempt(option, "signed-in", started);
|
|
1511
2022
|
this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
|
|
1512
2023
|
} catch (e) {
|
|
@@ -1519,18 +2030,29 @@ function createClass() {
|
|
|
1519
2030
|
const verifyUrl = this.#attr("verify-url");
|
|
1520
2031
|
const { result: r, verified } = await signInAndVerify({
|
|
1521
2032
|
wallet,
|
|
2033
|
+
// Metanet hands over its key on connect (one request at a time), so its sign-in is the signature alone.
|
|
2034
|
+
identityKey: cw.identityKey ?? void 0,
|
|
2035
|
+
skipWaitForAuthentication: option.id === METANET_OPTION_ID,
|
|
2036
|
+
// its connect() just checked the lock
|
|
1522
2037
|
identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
|
|
1523
2038
|
challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
|
|
1524
2039
|
verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
|
|
1525
2040
|
});
|
|
2041
|
+
if (!live()) {
|
|
2042
|
+
const out = this.#attr("logout-url");
|
|
2043
|
+
if (verifyUrl && out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
1526
2046
|
if (verifyUrl) rememberIdentity(r.identityKey);
|
|
1527
2047
|
const response = verifyUrl ? verified : null;
|
|
2048
|
+
this.#picked(option, reason, verifyUrl ? r.identityKey : null);
|
|
1528
2049
|
if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
|
|
1529
2050
|
this.#attempt(option, "signed-in", started);
|
|
1530
2051
|
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
|
|
1531
2052
|
} catch (error) {
|
|
1532
2053
|
const outcome = outcomeOf(error);
|
|
1533
2054
|
this.#attempt(option, outcome, started);
|
|
2055
|
+
if (!live()) return;
|
|
1534
2056
|
const closedQr = outcome === "cancelled" && option?.kind === "pairing";
|
|
1535
2057
|
if (own) {
|
|
1536
2058
|
if (closedQr) this.#phase = prev;
|
|
@@ -1541,12 +2063,15 @@ function createClass() {
|
|
|
1541
2063
|
}
|
|
1542
2064
|
if (!closedQr) this.#emit("bwalletx-error", { error });
|
|
1543
2065
|
} finally {
|
|
1544
|
-
this.#busy = false;
|
|
1545
2066
|
try {
|
|
1546
2067
|
pairing.close();
|
|
1547
2068
|
} catch {
|
|
1548
2069
|
}
|
|
1549
|
-
|
|
2070
|
+
if (live()) {
|
|
2071
|
+
this.#busy = false;
|
|
2072
|
+
this.#viaMetanet = false;
|
|
2073
|
+
this.#render();
|
|
2074
|
+
}
|
|
1550
2075
|
}
|
|
1551
2076
|
}
|
|
1552
2077
|
};
|
|
@@ -1619,18 +2144,6 @@ function qrSvg(text) {
|
|
|
1619
2144
|
q.make();
|
|
1620
2145
|
return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
|
1621
2146
|
}
|
|
1622
|
-
function isTouchDevice(env = globalThis) {
|
|
1623
|
-
const nav = env.navigator;
|
|
1624
|
-
if (!nav) return false;
|
|
1625
|
-
const ua = nav.userAgent ?? "";
|
|
1626
|
-
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
1627
|
-
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
1628
|
-
try {
|
|
1629
|
-
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
1630
|
-
} catch {
|
|
1631
|
-
return false;
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
2147
|
function pairingModal(o = {}) {
|
|
1635
2148
|
const ac = new AbortController();
|
|
1636
2149
|
let ui = null;
|
|
@@ -1791,10 +2304,11 @@ function chooseWallet(options, o = {}) {
|
|
|
1791
2304
|
row2.onclick = () => done(w);
|
|
1792
2305
|
list.append(row2);
|
|
1793
2306
|
}
|
|
1794
|
-
if (o.desktop !== false && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
|
|
2307
|
+
if (o.desktop !== false && metanetOffered() && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
|
|
1795
2308
|
if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
|
|
1796
|
-
const d =
|
|
1797
|
-
const row2 = optionRow(d, false,
|
|
2309
|
+
const d = metanetOption();
|
|
2310
|
+
const row2 = optionRow(d, false, METANET_NOTE);
|
|
2311
|
+
row2.title = "Connects to Metanet Desktop on this computer, never to a browser extension.";
|
|
1798
2312
|
row2.onclick = () => done(d);
|
|
1799
2313
|
list.append(row2);
|
|
1800
2314
|
}
|
|
@@ -1839,6 +2353,7 @@ export {
|
|
|
1839
2353
|
DESKTOP_OPTION_ID,
|
|
1840
2354
|
desktopOption,
|
|
1841
2355
|
autoChoice,
|
|
2356
|
+
isLocalDesktop,
|
|
1842
2357
|
PAYMAIL_LOOKUP_URL,
|
|
1843
2358
|
BAVATAR_URL,
|
|
1844
2359
|
shortKey,
|
|
@@ -1850,6 +2365,31 @@ export {
|
|
|
1850
2365
|
IDENTITY_HINT_KEY,
|
|
1851
2366
|
rememberedIdentity,
|
|
1852
2367
|
rememberIdentity,
|
|
2368
|
+
isTouchDevice,
|
|
2369
|
+
METANET_TYPE,
|
|
2370
|
+
METANET_OPTION_ID,
|
|
2371
|
+
METANET_JSON_API,
|
|
2372
|
+
METANET_LABEL,
|
|
2373
|
+
METANET_NOTE,
|
|
2374
|
+
METANET_REMEMBERED_NOTE,
|
|
2375
|
+
NOT_YOU_LABEL,
|
|
2376
|
+
METANET_UNREACHABLE,
|
|
2377
|
+
METANET_ANSWER_MS,
|
|
2378
|
+
METANET_SILENT_MS,
|
|
2379
|
+
MetanetUnreachableError,
|
|
2380
|
+
inAnyFrame,
|
|
2381
|
+
metanetOffered,
|
|
2382
|
+
connectMetanetDesktop,
|
|
2383
|
+
metanetOption,
|
|
2384
|
+
KNOWN_KEYS_KEY,
|
|
2385
|
+
knownIdentityKeys,
|
|
2386
|
+
isKnownIdentityKey,
|
|
2387
|
+
rememberKnownIdentityKey,
|
|
2388
|
+
silentAttemptUsed,
|
|
2389
|
+
claimSilentAttempt,
|
|
2390
|
+
resetSilentAttempt,
|
|
2391
|
+
trySilentMetanet,
|
|
2392
|
+
warnIfNoLogout,
|
|
1853
2393
|
BWALLETX_TYPE,
|
|
1854
2394
|
substrateOf,
|
|
1855
2395
|
walletTypeOf,
|
|
@@ -1861,8 +2401,19 @@ export {
|
|
|
1861
2401
|
outcomeOf,
|
|
1862
2402
|
attemptFor,
|
|
1863
2403
|
reportAttempt,
|
|
2404
|
+
WALLET_CHOICE_KEY,
|
|
2405
|
+
isRememberable,
|
|
2406
|
+
isExplicitPick,
|
|
2407
|
+
rememberWalletChoice,
|
|
2408
|
+
rememberedWalletChoice,
|
|
2409
|
+
forgetWalletChoice,
|
|
2410
|
+
needsBwalletX,
|
|
2411
|
+
tapGoesElsewhere,
|
|
2412
|
+
isLocalDesktopType,
|
|
2413
|
+
matchRemembered,
|
|
2414
|
+
planAutoConnect,
|
|
2415
|
+
attachRemembered,
|
|
1864
2416
|
qrSvg,
|
|
1865
|
-
isTouchDevice,
|
|
1866
2417
|
pairingModal,
|
|
1867
2418
|
pickWallet,
|
|
1868
2419
|
chooseWallet,
|
|
@@ -1870,6 +2421,8 @@ export {
|
|
|
1870
2421
|
throwIfAccessError,
|
|
1871
2422
|
proveHoldings,
|
|
1872
2423
|
requestAccess,
|
|
2424
|
+
runConnect,
|
|
2425
|
+
runSilentMetanet,
|
|
1873
2426
|
SIGNIN_TAG,
|
|
1874
2427
|
GET_URL,
|
|
1875
2428
|
WEB_WALLET_URL,
|
|
@@ -1880,7 +2433,9 @@ export {
|
|
|
1880
2433
|
PENDING_NOTE,
|
|
1881
2434
|
PENDING_LABEL,
|
|
1882
2435
|
PENDING_LABEL_OTHER,
|
|
2436
|
+
SWITCH_NOTE,
|
|
2437
|
+
CONTINUE_OTHER_LABEL,
|
|
1883
2438
|
buildAuthorizeUrl,
|
|
1884
2439
|
defineBwalletxSignin
|
|
1885
2440
|
};
|
|
1886
|
-
//# sourceMappingURL=chunk-
|
|
2441
|
+
//# sourceMappingURL=chunk-6EFFOIHK.js.map
|