- NIP-46 remote signer in the page (bunker:// and nostrconnect:// pairing) - Chrome extension (NIP-07) with per-site permission prompts, unlocked by the site via externally_connectable; downloadable from the page - Passkey (WebAuthn PRF) encryption of the Drive key file - NIP-49 backups: download or save to Drive; reveal nsec - Fix: oldest key file wins when two devices sign up at once - Fix: never overwrite an existing kind-0 because a relay was slow - Fix: EOSE tracking; relays reconnect with backoff; verify event signatures - One-click re-sign-in (consent only the first time) - Vendored, pinned nostr-tools; CSP; no third-party scripts but Google's Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
1.8 KiB
JavaScript
47 lines
1.8 KiB
JavaScript
const $ = (id) => document.getElementById(id);
|
|
const id = new URLSearchParams(location.search).get("id");
|
|
|
|
// Keeps the background worker alive while the user decides.
|
|
chrome.runtime.connect({ name: "prompt" });
|
|
|
|
const WHAT = {
|
|
getPublicKey: "wants to know your nostr public key.",
|
|
signEvent: "wants to sign an event as you.",
|
|
getRelays: "wants your relay list.",
|
|
"nip04.encrypt": "wants to encrypt a message as you (NIP-04).",
|
|
"nip04.decrypt": "wants to decrypt a message sent to you (NIP-04).",
|
|
"nip44.encrypt": "wants to encrypt a message as you (NIP-44).",
|
|
"nip44.decrypt": "wants to decrypt a message sent to you (NIP-44).",
|
|
};
|
|
|
|
async function refreshLock() {
|
|
const { sk } = await chrome.storage.session.get("sk");
|
|
const locked = !sk;
|
|
$("locked-box").classList.toggle("hidden", !locked);
|
|
for (const b of ["once", "always"]) $(b).disabled = locked;
|
|
}
|
|
|
|
async function init() {
|
|
const req = (await chrome.storage.session.get(`prompt:${id}`))[`prompt:${id}`];
|
|
if (!req) return window.close();
|
|
$("origin").textContent = req.origin;
|
|
$("what").textContent = WHAT[req.method] || `wants to call ${req.method}.`;
|
|
if (req.method === "signEvent") {
|
|
const e = req.params.event || {};
|
|
$("details").textContent = `kind ${e.kind}\n\n${String(e.content ?? "").slice(0, 1000)}`;
|
|
$("details").classList.remove("hidden");
|
|
}
|
|
await refreshLock();
|
|
}
|
|
|
|
const decide = async (decision) => {
|
|
await chrome.runtime.sendMessage({ type: "prompt-decision", id, decision });
|
|
window.close();
|
|
};
|
|
$("once").onclick = () => decide("once");
|
|
$("always").onclick = () => decide("always");
|
|
$("deny").onclick = () => decide("deny");
|
|
$("unlock").onclick = () => chrome.runtime.sendMessage({ type: "open-goostr" });
|
|
chrome.storage.session.onChanged.addListener(refreshLock);
|
|
init();
|