Googstr/extension/prompt.js
Jason Tudisco fcfaec675a Add NIP-46 signer, optional NIP-07 extension, passkey protection, backups
- 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>
2026-09-29 08:33:02 -06:00

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();