From 3dd032e238197fcd967f89c51afad40af6cebb00 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Wed, 8 Apr 2026 13:16:36 +0700 Subject: [PATCH] fix: save key and load key --- extension/options.jsx | 27 ++-- extension/output/common.js | 222 ++++++++++++++--------------- extension/output/nostr-provider.js | 220 ++++++++++++++-------------- extension/output/options.build.js | 16 +-- extension/output/popup.build.js | 10 +- extension/popup.jsx | 9 +- 6 files changed, 257 insertions(+), 247 deletions(-) diff --git a/extension/options.jsx b/extension/options.jsx index c50a48c..1d61c4f 100644 --- a/extension/options.jsx +++ b/extension/options.jsx @@ -1,7 +1,7 @@ import browser from 'webextension-polyfill' import { useState, useCallback, useEffect } from 'react' import { render } from 'react-dom' -import { generateSecretKey, nip19, utils } from 'nostr-tools' +import { generateSecretKey, nip19 } from 'nostr-tools' import QRCode from 'react-qr-code' import * as Tabs from '@radix-ui/react-tabs' import { LogoIcon } from './icons' @@ -56,8 +56,11 @@ function Options() { .get(['private_key', 'relays', 'protocol_handler', 'notifications']) .then((results) => { if (results.private_key) { - setPrivKey(nip19.nsecEncode(results.private_key)) + const pkey = results.private_key + const nsec = nip19.nsecEncode(hexToBytes(pkey)) + setPrivKey(nsec) } + if (results.relays) { const relaysList = [] for (const url in results.relays) { @@ -68,11 +71,13 @@ function Options() { } setRelays(relaysList) } + if (results.protocol_handler) { setProtocolHandler(results.protocol_handler) setHandleNostrLinks(true) setShowProtocolHandlerHelp(false) } + if (results.notifications) { setNotifications(true) } @@ -519,8 +524,7 @@ examples: } async function generate() { - const sk = generateSecretKey() - setPrivKey(nip19.nsecEncode(utils.bytesToHex(sk))) + setPrivKey(nip19.nsecEncode(generateSecretKey())) addUnsavedChanges('private_key') } @@ -529,23 +533,26 @@ examples: showMessage('PRIVATE KEY IS INVALID! did not save private key.') return } + let hexOrEmptyKey = privKey + try { const { type, data } = nip19.decode(privKey) if (type === 'nsec') hexOrEmptyKey = bytesToHex(data) - } catch (_) {} + } catch (_) { + showMessage('Invalid private key format.') + return + } + await browser.storage.local.set({ private_key: hexOrEmptyKey }) - if (hexOrEmptyKey !== '') { - setPrivKey(nip19.nsecEncode(hexToBytes(hexOrEmptyKey))) - } - showMessage('saved private key!') + + showMessage('Private Key has been saved.') } function isKeyValid() { if (privKey === '') return true - if (privKey.match(/^[a-f0-9]{64}$/)) return true try { if (nip19.decode(privKey).type === 'nsec') return true } catch (_) {} diff --git a/extension/output/common.js b/extension/output/common.js index 1fbc0e9..eb08e5b 100644 --- a/extension/output/common.js +++ b/extension/output/common.js @@ -1,144 +1,144 @@ -import browser from "webextension-polyfill"; +import browser from 'webextension-polyfill' export const NO_PERMISSIONS_REQUIRED = { - replaceURL: true, - peekPublicKey: true, -}; + replaceURL: true, + peekPublicKey: true +} export const PERMISSION_NAMES = Object.fromEntries([ - ["getPublicKey", "read your public key"], - ["signEvent", "sign events using your private key"], - ["nip04.encrypt", "encrypt messages to peers"], - ["nip04.decrypt", "decrypt messages from peers"], - ["nip44.encrypt", "encrypt messages to peers"], - ["nip44.decrypt", "decrypt messages from peers"], -]); + ['getPublicKey', 'read your public key'], + ['signEvent', 'sign events using your private key'], + ['nip04.encrypt', 'encrypt messages to peers'], + ['nip04.decrypt', 'decrypt messages from peers'], + ['nip44.encrypt', 'encrypt messages to peers'], + ['nip44.decrypt', 'decrypt messages from peers'] +]) function matchConditions(conditions, event) { - if (conditions?.kinds) { - if (event.kind in conditions.kinds) return true; - else return false; - } + if (conditions?.kinds) { + if (event.kind in conditions.kinds) return true + else return false + } - return true; + return true } export async function getPermissionStatus(host, type, event) { - const { policies } = await browser.storage.local.get("policies"); + const { policies } = await browser.storage.local.get('policies') - const answers = [true, false]; - for (let i = 0; i < answers.length; i++) { - const accept = answers[i]; - const { conditions } = policies?.[host]?.[accept]?.[type] || {}; + const answers = [true, false] + for (let i = 0; i < answers.length; i++) { + const accept = answers[i] + const { conditions } = policies?.[host]?.[accept]?.[type] || {} - if (conditions) { - if (type === "signEvent") { - if (matchConditions(conditions, event)) { - return accept; // may be true or false - } else { - } - } else { - return accept; // may be true or false - } - } - } + if (conditions) { + if (type === 'signEvent') { + if (matchConditions(conditions, event)) { + return accept // may be true or false + } else { + } + } else { + return accept // may be true or false + } + } + } - return undefined; + return undefined } export async function updatePermission(host, type, accept, conditions) { - const { policies = {} } = await browser.storage.local.get("policies"); + const { policies = {} } = await browser.storage.local.get('policies') - // if the new conditions is "match everything", override the previous - if (Object.keys(conditions).length === 0) { - conditions = {}; - } else { - // if we already had a policy for this, merge the conditions - const existingConditions = policies[host]?.[accept]?.[type]?.conditions; - if (existingConditions) { - if (existingConditions.kinds && conditions.kinds) { - Object.keys(existingConditions.kinds).forEach((kind) => { - conditions.kinds[kind] = true; - }); - } - } - } + // if the new conditions is "match everything", override the previous + if (Object.keys(conditions).length === 0) { + conditions = {} + } else { + // if we already had a policy for this, merge the conditions + const existingConditions = policies[host]?.[accept]?.[type]?.conditions + if (existingConditions) { + if (existingConditions.kinds && conditions.kinds) { + Object.keys(existingConditions.kinds).forEach((kind) => { + conditions.kinds[kind] = true + }) + } + } + } - // if we have a reverse policy (accept / reject) that is exactly equal to this, remove it - const other = !accept; - const reverse = policies?.[host]?.[other]?.[type]; - if ( - reverse && - JSON.stringify(reverse.conditions) === JSON.stringify(conditions) - ) { - delete policies[host][other][type]; - } + // if we have a reverse policy (accept / reject) that is exactly equal to this, remove it + const other = !accept + const reverse = policies?.[host]?.[other]?.[type] + if ( + reverse && + JSON.stringify(reverse.conditions) === JSON.stringify(conditions) + ) { + delete policies[host][other][type] + } - // insert our new policy - policies[host] = policies[host] || {}; - policies[host][accept] = policies[host][accept] || {}; - policies[host][accept][type] = { - conditions, // filter that must match the event (in case of signEvent) - created_at: Math.round(Date.now() / 1000), - }; + // insert our new policy + policies[host] = policies[host] || {} + policies[host][accept] = policies[host][accept] || {} + policies[host][accept][type] = { + conditions, // filter that must match the event (in case of signEvent) + created_at: Math.round(Date.now() / 1000) + } - browser.storage.local.set({ policies }); + browser.storage.local.set({ policies }) } export async function removePermissions(host, accept, type) { - const { policies = {} } = await browser.storage.local.get("policies"); - delete policies[host]?.[accept]?.[type]; - browser.storage.local.set({ policies }); + const { policies = {} } = await browser.storage.local.get('policies') + delete policies[host]?.[accept]?.[type] + browser.storage.local.set({ policies }) } export async function showNotification(host, answer, type, params) { - const { notifications } = await browser.storage.local.get("notifications"); - if (notifications) { - const action = answer ? "allowed" : "denied"; - browser.notifications.create(undefined, { - type: "basic", - title: `${type} ${action} for ${host}`, - message: JSON.stringify( - params?.event - ? { - kind: params.event.kind, - content: params.event.content, - tags: params.event.tags, - } - : params, - null, - 2, - ), - iconUrl: "icons/48x48.png", - }); - } + const { notifications } = await browser.storage.local.get('notifications') + if (notifications) { + const action = answer ? 'allowed' : 'denied' + browser.notifications.create(undefined, { + type: 'basic', + title: `${type} ${action} for ${host}`, + message: JSON.stringify( + params?.event + ? { + kind: params.event.kind, + content: params.event.content, + tags: params.event.tags + } + : params, + null, + 2 + ), + iconUrl: 'icons/48x48.png' + }) + } } export async function getPosition(width, height) { - let left = 0; - let top = 0; + let left = 0 + let top = 0 - try { - const lastFocused = await browser.windows.getLastFocused(); + try { + const lastFocused = await browser.windows.getLastFocused() - if ( - lastFocused && - lastFocused.top !== undefined && - lastFocused.left !== undefined && - lastFocused.width !== undefined && - lastFocused.height !== undefined - ) { - top = Math.round(lastFocused.top + (lastFocused.height - height) / 2); - left = Math.round(lastFocused.left + (lastFocused.width - width) / 2); - } else { - console.error("Last focused window properties are undefined."); - } - } catch (error) { - console.error("Error getting window position:", error); - } + if ( + lastFocused && + lastFocused.top !== undefined && + lastFocused.left !== undefined && + lastFocused.width !== undefined && + lastFocused.height !== undefined + ) { + top = Math.round(lastFocused.top + (lastFocused.height - height) / 2) + left = Math.round(lastFocused.left + (lastFocused.width - width) / 2) + } else { + console.error('Last focused window properties are undefined.') + } + } catch (error) { + console.error('Error getting window position:', error) + } - return { - top, - left, - }; + return { + top, + left + } } diff --git a/extension/output/nostr-provider.js b/extension/output/nostr-provider.js index 7bd0a30..57afcc2 100644 --- a/extension/output/nostr-provider.js +++ b/extension/output/nostr-provider.js @@ -1,132 +1,130 @@ -const EXTENSION = "nostrconnect"; +const EXTENSION = 'nostrconnect' window.nostr = { - _requests: {}, - _pubkey: null, + _requests: {}, + _pubkey: null, - async getPublicKey() { - if (this._pubkey) return this._pubkey; - this._pubkey = await this._call("getPublicKey", {}); - return this._pubkey; - }, + async getPublicKey() { + if (this._pubkey) return this._pubkey + this._pubkey = await this._call('getPublicKey', {}) + return this._pubkey + }, - async peekPublicKey() { - return this._call("peekPublicKey", {}); - }, + async peekPublicKey() { + return this._call('peekPublicKey', {}) + }, - async signEvent(event) { - return this._call("signEvent", { event }); - }, + async signEvent(event) { + return this._call('signEvent', { event }) + }, - async getRelays() { - return {}; - }, + async getRelays() { + return {} + }, - nip04: { - async encrypt(peer, plaintext) { - return window.nostr._call("nip04.encrypt", { peer, plaintext }); - }, + nip04: { + async encrypt(peer, plaintext) { + return window.nostr._call('nip04.encrypt', { peer, plaintext }) + }, - async decrypt(peer, ciphertext) { - return window.nostr._call("nip04.decrypt", { peer, ciphertext }); - }, - }, + async decrypt(peer, ciphertext) { + return window.nostr._call('nip04.decrypt', { peer, ciphertext }) + } + }, - nip44: { - async encrypt(peer, plaintext) { - return window.nostr._call("nip44.encrypt", { peer, plaintext }); - }, + nip44: { + async encrypt(peer, plaintext) { + return window.nostr._call('nip44.encrypt', { peer, plaintext }) + }, - async decrypt(peer, ciphertext) { - return window.nostr._call("nip44.decrypt", { peer, ciphertext }); - }, - }, + async decrypt(peer, ciphertext) { + return window.nostr._call('nip44.decrypt', { peer, ciphertext }) + } + }, - _call(type, params) { - const id = Math.random().toString().slice(-4); - console.log( - "%c[nostrconnect:%c" + - id + - "%c]%c calling %c" + - type + - "%c with %c" + - JSON.stringify(params || {}), - "background-color:#f1b912;font-weight:bold;color:white", - "background-color:#f1b912;font-weight:bold;color:#a92727", - "background-color:#f1b912;color:white;font-weight:bold", - "color:auto", - "font-weight:bold;color:#08589d;font-family:monospace", - "color:auto", - "font-weight:bold;color:#90b12d;font-family:monospace", - ); - return new Promise((resolve, reject) => { - this._requests[id] = { resolve, reject }; - window.postMessage( - { - id, - ext: EXTENSION, - type, - params, - }, - "*", - ); - }); - }, -}; + _call(type, params) { + const id = Math.random().toString().slice(-4) + console.log( + '%c[nostrconnect:%c' + + id + + '%c]%c calling %c' + + type + + '%c with %c' + + JSON.stringify(params || {}), + 'background-color:#f1b912;font-weight:bold;color:white', + 'background-color:#f1b912;font-weight:bold;color:#a92727', + 'background-color:#f1b912;color:white;font-weight:bold', + 'color:auto', + 'font-weight:bold;color:#08589d;font-family:monospace', + 'color:auto', + 'font-weight:bold;color:#90b12d;font-family:monospace' + ) + return new Promise((resolve, reject) => { + this._requests[id] = { resolve, reject } + window.postMessage( + { + id, + ext: EXTENSION, + type, + params + }, + '*' + ) + }) + } +} -window.addEventListener("message", (message) => { - if ( - !message.data || - message.data.response === null || - message.data.response === undefined || - message.data.ext !== EXTENSION || - !window.nostr._requests[message.data.id] - ) - return; +window.addEventListener('message', (message) => { + if ( + !message.data || + message.data.response === null || + message.data.response === undefined || + message.data.ext !== EXTENSION || + !window.nostr._requests[message.data.id] + ) + return - if (message.data.response.error) { - const error = new Error( - `${EXTENSION}: ${message.data.response.error.message}`, - ); - error.stack = message.data.response.error.stack; - window.nostr._requests[message.data.id].reject(error); - } else { - window.nostr._requests[message.data.id].resolve(message.data.response); - } + if (message.data.response.error) { + const error = new Error( + `${EXTENSION}: ${message.data.response.error.message}` + ) + error.stack = message.data.response.error.stack + window.nostr._requests[message.data.id].reject(error) + } else { + window.nostr._requests[message.data.id].resolve(message.data.response) + } - console.log( - "%c[nostrconnect:%c" + - message.data.id + - "%c]%c result: %c" + - JSON.stringify( - message?.data?.response || - message?.data?.response?.error?.message || - {}, - ), - "background-color:#f1b912;font-weight:bold;color:white", - "background-color:#f1b912;font-weight:bold;color:#a92727", - "background-color:#f1b912;color:white;font-weight:bold", - "color:auto", - "font-weight:bold;color:#08589d", - ); + console.log( + '%c[nostrconnect:%c' + + message.data.id + + '%c]%c result: %c' + + JSON.stringify( + message?.data?.response || message?.data?.response?.error?.message || {} + ), + 'background-color:#f1b912;font-weight:bold;color:white', + 'background-color:#f1b912;font-weight:bold;color:#a92727', + 'background-color:#f1b912;color:white;font-weight:bold', + 'color:auto', + 'font-weight:bold;color:#08589d' + ) - delete window.nostr._requests[message.data.id]; -}); + delete window.nostr._requests[message.data.id] +}) // hack to replace nostr:nprofile.../etc links with something else -let replacing = null; -document.addEventListener("mousedown", replaceNostrSchemeLink); +let replacing = null +document.addEventListener('mousedown', replaceNostrSchemeLink) async function replaceNostrSchemeLink(e) { - if (e.target.tagName !== "A" || !e.target.href.startsWith("nostr:")) return; - if (replacing === false) return; + if (e.target.tagName !== 'A' || !e.target.href.startsWith('nostr:')) return + if (replacing === false) return - const response = await window.nostr._call("replaceURL", { - url: e.target.href, - }); - if (response === false) { - replacing = false; - return; - } + const response = await window.nostr._call('replaceURL', { + url: e.target.href + }) + if (response === false) { + replacing = false + return + } - e.target.href = response; + e.target.href = response } diff --git a/extension/output/options.build.js b/extension/output/options.build.js index 49f2939..bca7a1e 100644 --- a/extension/output/options.build.js +++ b/extension/output/options.build.js @@ -31774,7 +31774,9 @@ For more info, visit https://reactjs.org/link/mock-scheduler`); (0, import_react3.useEffect)(() => { import_webextension_polyfill2.default.storage.local.get(["private_key", "relays", "protocol_handler", "notifications"]).then((results) => { if (results.private_key) { - setPrivKey(nip19_exports.nsecEncode(results.private_key)); + const pkey = results.private_key; + const nsec = nip19_exports.nsecEncode(hexToBytes(pkey)); + setPrivKey(nsec); } if (results.relays) { const relaysList = []; @@ -32327,8 +32329,7 @@ examples: addUnsavedChanges("private_key"); } async function generate() { - const sk = generateSecretKey(); - setPrivKey(nip19_exports.nsecEncode(utils_exports.bytesToHex(sk))); + setPrivKey(nip19_exports.nsecEncode(generateSecretKey())); addUnsavedChanges("private_key"); } async function saveKey() { @@ -32342,20 +32343,17 @@ examples: if (type === "nsec") hexOrEmptyKey = bytesToHex(data); } catch (_) { + showMessage("Invalid private key format."); + return; } await import_webextension_polyfill2.default.storage.local.set({ private_key: hexOrEmptyKey }); - if (hexOrEmptyKey !== "") { - setPrivKey(nip19_exports.nsecEncode(hexToBytes(hexOrEmptyKey))); - } - showMessage("saved private key!"); + showMessage("Private Key has been saved."); } function isKeyValid() { if (privKey === "") return true; - if (privKey.match(/^[a-f0-9]{64}$/)) - return true; try { if (nip19_exports.decode(privKey).type === "nsec") return true; diff --git a/extension/output/popup.build.js b/extension/output/popup.build.js index fb428c3..3737f83 100644 --- a/extension/output/popup.build.js +++ b/extension/output/popup.build.js @@ -31412,6 +31412,10 @@ For more info, visit https://reactjs.org/link/mock-scheduler`); var Trigger = TabsTrigger; var Content = TabsContent; + // node_modules/.pnpm/nostr-tools@2.23.3/node_modules/nostr-tools/lib/esm/utils.js + var utf8Decoder2 = new TextDecoder("utf-8"); + var utf8Encoder2 = new TextEncoder(); + // extension/popup.jsx var import_jsx_runtime = __toESM(require_jsx_runtime()); function Popup() { @@ -31428,9 +31432,9 @@ For more info, visit https://reactjs.org/link/mock-scheduler`); (0, import_react3.useEffect)(() => { import_webextension_polyfill.default.storage.local.get(["private_key", "relays"]).then((results) => { if (results.private_key) { - const hexKey = getPublicKey(results.private_key); - const npubKey = nip19_exports.npubEncode(hexKey); - setKeys({ npub: npubKey, hex: hexKey }); + const hexKey = getPublicKey(hexToBytes(results.private_key)); + const npub = nip19_exports.npubEncode(hexKey); + setKeys({ npub, hex: hexKey }); if (results.relays) { const relaysList = []; for (const url in results.relays) { diff --git a/extension/popup.jsx b/extension/popup.jsx index eeda9af..3a13d22 100644 --- a/extension/popup.jsx +++ b/extension/popup.jsx @@ -6,6 +6,7 @@ import QRCode from 'react-qr-code' import { SettingsIcon } from './icons' import { minidenticon } from 'minidenticons' import * as Tabs from '@radix-ui/react-tabs' +import { hexToBytes } from 'nostr-tools/utils' function Popup() { const [keys, setKeys] = useState(null) @@ -27,19 +28,21 @@ function Popup() { useEffect(() => { browser.storage.local.get(['private_key', 'relays']).then((results) => { if (results.private_key) { - const hexKey = getPublicKey(results.private_key) - const npubKey = nip19.npubEncode(hexKey) + const hexKey = getPublicKey(hexToBytes(results.private_key)) + const npub = nip19.npubEncode(hexKey) - setKeys({ npub: npubKey, hex: hexKey }) + setKeys({ npub: npub, hex: hexKey }) if (results.relays) { const relaysList = [] + for (const url in results.relays) { if (results.relays[url].write) { relaysList.push(url) if (relaysList.length >= 3) break } } + if (relaysList.length) { const nprofileKey = nip19.nprofileEncode({ pubkey: hexKey,