183 lines
6.3 KiB
JavaScript
183 lines
6.3 KiB
JavaScript
|
|
// Wiring for the sign-in and registration screens.
|
||
|
|
//
|
||
|
|
// This file contains no cryptography — wallet.js does all of that. What it
|
||
|
|
// does is decide which of the three ways to sign is on offer and get out of
|
||
|
|
// the way of the other two:
|
||
|
|
//
|
||
|
|
// 1. A wallet the browser already has (Theseus exposes one). Best case: the
|
||
|
|
// key never comes near this page.
|
||
|
|
// 2. A recovery phrase typed here, used once, and wiped.
|
||
|
|
// 3. A signature produced somewhere else entirely and pasted in. This one
|
||
|
|
// works with JavaScript switched off, which is why the markup ships with
|
||
|
|
// the paste box present and the buttons hidden until this script decides
|
||
|
|
// they are usable.
|
||
|
|
//
|
||
|
|
// The progressive-enhancement direction matters: buttons start hidden and are
|
||
|
|
// revealed, rather than starting visible and being disabled. A script that
|
||
|
|
// fails to load leaves a page that still works.
|
||
|
|
|
||
|
|
(() => {
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
const CONFIG = window.SIRIUS_PRESS || {};
|
||
|
|
const strings = CONFIG.strings || {};
|
||
|
|
|
||
|
|
function setStatus(block, text, kind = "") {
|
||
|
|
const el = block.querySelector(".sirius-wallet__status");
|
||
|
|
if (!el) return;
|
||
|
|
el.textContent = text || "";
|
||
|
|
el.className = "sirius-wallet__status" + (kind ? " is-" + kind : "");
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Submit the form the block sits in, once a signature is in place. */
|
||
|
|
function submitForm(block) {
|
||
|
|
const form = block.closest("form");
|
||
|
|
if (!form) return;
|
||
|
|
if (typeof form.requestSubmit === "function") {
|
||
|
|
form.requestSubmit();
|
||
|
|
} else {
|
||
|
|
form.submit();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function setSignature(block, signature) {
|
||
|
|
const field = block.querySelector(".sirius-wallet__signature");
|
||
|
|
if (field) field.value = signature;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function signWithPhrase(block) {
|
||
|
|
const input = block.querySelector(".sirius-wallet__phrase-input");
|
||
|
|
const message = block.querySelector(".sirius-wallet__message").value;
|
||
|
|
const phrase = input ? input.value : "";
|
||
|
|
|
||
|
|
const check = window.SiriusWallet.validatePhrase(phrase);
|
||
|
|
if (!check.ok) {
|
||
|
|
setStatus(block, check.error, "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setStatus(block, strings.signing || "Signing…");
|
||
|
|
let wallet;
|
||
|
|
try {
|
||
|
|
wallet = await window.SiriusWallet.fromPhrase(phrase, {
|
||
|
|
prefix: CONFIG.prefix,
|
||
|
|
path: CONFIG.path,
|
||
|
|
});
|
||
|
|
setSignature(block, await wallet.sign(message));
|
||
|
|
} catch (err) {
|
||
|
|
setStatus(block, err.message || String(err), "error");
|
||
|
|
return;
|
||
|
|
} finally {
|
||
|
|
// The phrase has done its one job. Clear it from the field and from the
|
||
|
|
// wallet object before anything else on the page runs.
|
||
|
|
if (input) input.value = "";
|
||
|
|
if (wallet && wallet.forget) wallet.forget();
|
||
|
|
}
|
||
|
|
submitForm(block);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function signWithExternal(block) {
|
||
|
|
const external = window.SiriusWallet.external();
|
||
|
|
if (!external) {
|
||
|
|
setStatus(block, strings.noWallet || "No wallet found in this browser.", "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const message = block.querySelector(".sirius-wallet__message").value;
|
||
|
|
setStatus(block, strings.signing || "Signing…");
|
||
|
|
try {
|
||
|
|
setSignature(block, await external.sign(message));
|
||
|
|
} catch (err) {
|
||
|
|
// A user declining the wallet's approval dialog is a normal outcome, not
|
||
|
|
// an error worth shouting about.
|
||
|
|
const text = err && err.message ? err.message : String(err);
|
||
|
|
setStatus(block, /reject/i.test(text) ? "" : text, /reject/i.test(text) ? "" : "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
submitForm(block);
|
||
|
|
}
|
||
|
|
|
||
|
|
function enhance(block) {
|
||
|
|
const signButton = block.querySelector(".sirius-wallet__sign");
|
||
|
|
const externalButton = block.querySelector(".sirius-wallet__external");
|
||
|
|
const phraseArea = block.querySelector(".sirius-wallet__phrase");
|
||
|
|
|
||
|
|
if (!window.SiriusWallet || !window.crypto || !window.crypto.subtle) {
|
||
|
|
// No usable crypto — for instance an http:// origin, where WebCrypto is
|
||
|
|
// unavailable. Say so, and leave the paste path alone: it still works.
|
||
|
|
setStatus(
|
||
|
|
block,
|
||
|
|
"This browser cannot sign here (a secure https connection is required). Paste a signature from your own wallet instead.",
|
||
|
|
"error",
|
||
|
|
);
|
||
|
|
const manual = block.querySelector(".sirius-wallet__manual");
|
||
|
|
if (manual) manual.open = true;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (phraseArea) phraseArea.hidden = false;
|
||
|
|
if (signButton) {
|
||
|
|
signButton.hidden = false;
|
||
|
|
signButton.addEventListener("click", () => signWithPhrase(block));
|
||
|
|
}
|
||
|
|
if (externalButton && window.SiriusWallet.external()) {
|
||
|
|
externalButton.hidden = false;
|
||
|
|
externalButton.addEventListener("click", () => signWithExternal(block));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Enter in the phrase box should sign, not submit an unsigned form.
|
||
|
|
const input = block.querySelector(".sirius-wallet__phrase-input");
|
||
|
|
if (input) {
|
||
|
|
input.addEventListener("keydown", (event) => {
|
||
|
|
if (event.key === "Enter" && !event.shiftKey) {
|
||
|
|
event.preventDefault();
|
||
|
|
signWithPhrase(block);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** The "which address is this phrase?" helper on the recovery page. */
|
||
|
|
function enhanceDerive() {
|
||
|
|
const button = document.getElementById("sirius_derive_go");
|
||
|
|
const input = document.getElementById("sirius_derive_phrase");
|
||
|
|
const out = document.querySelector(".sirius-derive__out");
|
||
|
|
if (!button || !input || !out) return;
|
||
|
|
|
||
|
|
button.addEventListener("click", async () => {
|
||
|
|
const check = window.SiriusWallet.validatePhrase(input.value);
|
||
|
|
if (!check.ok) {
|
||
|
|
out.textContent = check.error;
|
||
|
|
out.className = "sirius-derive__out is-error";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
out.textContent = strings.signing || "Working…";
|
||
|
|
out.className = "sirius-derive__out";
|
||
|
|
try {
|
||
|
|
const wallet = await window.SiriusWallet.fromPhrase(input.value, {
|
||
|
|
prefix: CONFIG.prefix,
|
||
|
|
path: CONFIG.path,
|
||
|
|
});
|
||
|
|
out.textContent = wallet.address;
|
||
|
|
out.className = "sirius-derive__out is-address";
|
||
|
|
wallet.forget();
|
||
|
|
} catch (err) {
|
||
|
|
out.textContent = err.message || String(err);
|
||
|
|
out.className = "sirius-derive__out is-error";
|
||
|
|
} finally {
|
||
|
|
input.value = "";
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function start() {
|
||
|
|
document.querySelectorAll(".sirius-wallet").forEach(enhance);
|
||
|
|
enhanceDerive();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (document.readyState === "loading") {
|
||
|
|
document.addEventListener("DOMContentLoaded", start);
|
||
|
|
} else {
|
||
|
|
start();
|
||
|
|
}
|
||
|
|
})();
|