fix: save key and load key

This commit is contained in:
Ren Amamiya
2026-04-08 13:16:36 +07:00
parent 4050afe93f
commit 3dd032e238
6 changed files with 257 additions and 247 deletions

View File

@@ -1,7 +1,7 @@
import browser from 'webextension-polyfill' import browser from 'webextension-polyfill'
import { useState, useCallback, useEffect } from 'react' import { useState, useCallback, useEffect } from 'react'
import { render } from 'react-dom' 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 QRCode from 'react-qr-code'
import * as Tabs from '@radix-ui/react-tabs' import * as Tabs from '@radix-ui/react-tabs'
import { LogoIcon } from './icons' import { LogoIcon } from './icons'
@@ -56,8 +56,11 @@ function Options() {
.get(['private_key', 'relays', 'protocol_handler', 'notifications']) .get(['private_key', 'relays', 'protocol_handler', 'notifications'])
.then((results) => { .then((results) => {
if (results.private_key) { 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) { if (results.relays) {
const relaysList = [] const relaysList = []
for (const url in results.relays) { for (const url in results.relays) {
@@ -68,11 +71,13 @@ function Options() {
} }
setRelays(relaysList) setRelays(relaysList)
} }
if (results.protocol_handler) { if (results.protocol_handler) {
setProtocolHandler(results.protocol_handler) setProtocolHandler(results.protocol_handler)
setHandleNostrLinks(true) setHandleNostrLinks(true)
setShowProtocolHandlerHelp(false) setShowProtocolHandlerHelp(false)
} }
if (results.notifications) { if (results.notifications) {
setNotifications(true) setNotifications(true)
} }
@@ -519,8 +524,7 @@ examples:
} }
async function generate() { async function generate() {
const sk = generateSecretKey() setPrivKey(nip19.nsecEncode(generateSecretKey()))
setPrivKey(nip19.nsecEncode(utils.bytesToHex(sk)))
addUnsavedChanges('private_key') addUnsavedChanges('private_key')
} }
@@ -529,23 +533,26 @@ examples:
showMessage('PRIVATE KEY IS INVALID! did not save private key.') showMessage('PRIVATE KEY IS INVALID! did not save private key.')
return return
} }
let hexOrEmptyKey = privKey let hexOrEmptyKey = privKey
try { try {
const { type, data } = nip19.decode(privKey) const { type, data } = nip19.decode(privKey)
if (type === 'nsec') hexOrEmptyKey = bytesToHex(data) if (type === 'nsec') hexOrEmptyKey = bytesToHex(data)
} catch (_) {} } catch (_) {
showMessage('Invalid private key format.')
return
}
await browser.storage.local.set({ await browser.storage.local.set({
private_key: hexOrEmptyKey private_key: hexOrEmptyKey
}) })
if (hexOrEmptyKey !== '') {
setPrivKey(nip19.nsecEncode(hexToBytes(hexOrEmptyKey))) showMessage('Private Key has been saved.')
}
showMessage('saved private key!')
} }
function isKeyValid() { function isKeyValid() {
if (privKey === '') return true if (privKey === '') return true
if (privKey.match(/^[a-f0-9]{64}$/)) return true
try { try {
if (nip19.decode(privKey).type === 'nsec') return true if (nip19.decode(privKey).type === 'nsec') return true
} catch (_) {} } catch (_) {}

View File

@@ -1,125 +1,125 @@
import browser from "webextension-polyfill"; import browser from 'webextension-polyfill'
export const NO_PERMISSIONS_REQUIRED = { export const NO_PERMISSIONS_REQUIRED = {
replaceURL: true, replaceURL: true,
peekPublicKey: true, peekPublicKey: true
}; }
export const PERMISSION_NAMES = Object.fromEntries([ export const PERMISSION_NAMES = Object.fromEntries([
["getPublicKey", "read your public key"], ['getPublicKey', 'read your public key'],
["signEvent", "sign events using your private key"], ['signEvent', 'sign events using your private key'],
["nip04.encrypt", "encrypt messages to peers"], ['nip04.encrypt', 'encrypt messages to peers'],
["nip04.decrypt", "decrypt messages from peers"], ['nip04.decrypt', 'decrypt messages from peers'],
["nip44.encrypt", "encrypt messages to peers"], ['nip44.encrypt', 'encrypt messages to peers'],
["nip44.decrypt", "decrypt messages from peers"], ['nip44.decrypt', 'decrypt messages from peers']
]); ])
function matchConditions(conditions, event) { function matchConditions(conditions, event) {
if (conditions?.kinds) { if (conditions?.kinds) {
if (event.kind in conditions.kinds) return true; if (event.kind in conditions.kinds) return true
else return false; else return false
} }
return true; return true
} }
export async function getPermissionStatus(host, type, event) { 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]; const answers = [true, false]
for (let i = 0; i < answers.length; i++) { for (let i = 0; i < answers.length; i++) {
const accept = answers[i]; const accept = answers[i]
const { conditions } = policies?.[host]?.[accept]?.[type] || {}; const { conditions } = policies?.[host]?.[accept]?.[type] || {}
if (conditions) { if (conditions) {
if (type === "signEvent") { if (type === 'signEvent') {
if (matchConditions(conditions, event)) { if (matchConditions(conditions, event)) {
return accept; // may be true or false return accept // may be true or false
} else { } else {
} }
} else { } else {
return accept; // may be true or false return accept // may be true or false
} }
} }
} }
return undefined; return undefined
} }
export async function updatePermission(host, type, accept, conditions) { 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 the new conditions is "match everything", override the previous
if (Object.keys(conditions).length === 0) { if (Object.keys(conditions).length === 0) {
conditions = {}; conditions = {}
} else { } else {
// if we already had a policy for this, merge the conditions // if we already had a policy for this, merge the conditions
const existingConditions = policies[host]?.[accept]?.[type]?.conditions; const existingConditions = policies[host]?.[accept]?.[type]?.conditions
if (existingConditions) { if (existingConditions) {
if (existingConditions.kinds && conditions.kinds) { if (existingConditions.kinds && conditions.kinds) {
Object.keys(existingConditions.kinds).forEach((kind) => { Object.keys(existingConditions.kinds).forEach((kind) => {
conditions.kinds[kind] = true; conditions.kinds[kind] = true
}); })
} }
} }
} }
// if we have a reverse policy (accept / reject) that is exactly equal to this, remove it // if we have a reverse policy (accept / reject) that is exactly equal to this, remove it
const other = !accept; const other = !accept
const reverse = policies?.[host]?.[other]?.[type]; const reverse = policies?.[host]?.[other]?.[type]
if ( if (
reverse && reverse &&
JSON.stringify(reverse.conditions) === JSON.stringify(conditions) JSON.stringify(reverse.conditions) === JSON.stringify(conditions)
) { ) {
delete policies[host][other][type]; delete policies[host][other][type]
} }
// insert our new policy // insert our new policy
policies[host] = policies[host] || {}; policies[host] = policies[host] || {}
policies[host][accept] = policies[host][accept] || {}; policies[host][accept] = policies[host][accept] || {}
policies[host][accept][type] = { policies[host][accept][type] = {
conditions, // filter that must match the event (in case of signEvent) conditions, // filter that must match the event (in case of signEvent)
created_at: Math.round(Date.now() / 1000), created_at: Math.round(Date.now() / 1000)
}; }
browser.storage.local.set({ policies }); browser.storage.local.set({ policies })
} }
export async function removePermissions(host, accept, type) { export async function removePermissions(host, accept, type) {
const { policies = {} } = await browser.storage.local.get("policies"); const { policies = {} } = await browser.storage.local.get('policies')
delete policies[host]?.[accept]?.[type]; delete policies[host]?.[accept]?.[type]
browser.storage.local.set({ policies }); browser.storage.local.set({ policies })
} }
export async function showNotification(host, answer, type, params) { export async function showNotification(host, answer, type, params) {
const { notifications } = await browser.storage.local.get("notifications"); const { notifications } = await browser.storage.local.get('notifications')
if (notifications) { if (notifications) {
const action = answer ? "allowed" : "denied"; const action = answer ? 'allowed' : 'denied'
browser.notifications.create(undefined, { browser.notifications.create(undefined, {
type: "basic", type: 'basic',
title: `${type} ${action} for ${host}`, title: `${type} ${action} for ${host}`,
message: JSON.stringify( message: JSON.stringify(
params?.event params?.event
? { ? {
kind: params.event.kind, kind: params.event.kind,
content: params.event.content, content: params.event.content,
tags: params.event.tags, tags: params.event.tags
} }
: params, : params,
null, null,
2, 2
), ),
iconUrl: "icons/48x48.png", iconUrl: 'icons/48x48.png'
}); })
} }
} }
export async function getPosition(width, height) { export async function getPosition(width, height) {
let left = 0; let left = 0
let top = 0; let top = 0
try { try {
const lastFocused = await browser.windows.getLastFocused(); const lastFocused = await browser.windows.getLastFocused()
if ( if (
lastFocused && lastFocused &&
@@ -128,17 +128,17 @@ export async function getPosition(width, height) {
lastFocused.width !== undefined && lastFocused.width !== undefined &&
lastFocused.height !== undefined lastFocused.height !== undefined
) { ) {
top = Math.round(lastFocused.top + (lastFocused.height - height) / 2); top = Math.round(lastFocused.top + (lastFocused.height - height) / 2)
left = Math.round(lastFocused.left + (lastFocused.width - width) / 2); left = Math.round(lastFocused.left + (lastFocused.width - width) / 2)
} else { } else {
console.error("Last focused window properties are undefined."); console.error('Last focused window properties are undefined.')
} }
} catch (error) { } catch (error) {
console.error("Error getting window position:", error); console.error('Error getting window position:', error)
} }
return { return {
top, top,
left, left
}; }
} }

View File

@@ -1,80 +1,80 @@
const EXTENSION = "nostrconnect"; const EXTENSION = 'nostrconnect'
window.nostr = { window.nostr = {
_requests: {}, _requests: {},
_pubkey: null, _pubkey: null,
async getPublicKey() { async getPublicKey() {
if (this._pubkey) return this._pubkey; if (this._pubkey) return this._pubkey
this._pubkey = await this._call("getPublicKey", {}); this._pubkey = await this._call('getPublicKey', {})
return this._pubkey; return this._pubkey
}, },
async peekPublicKey() { async peekPublicKey() {
return this._call("peekPublicKey", {}); return this._call('peekPublicKey', {})
}, },
async signEvent(event) { async signEvent(event) {
return this._call("signEvent", { event }); return this._call('signEvent', { event })
}, },
async getRelays() { async getRelays() {
return {}; return {}
}, },
nip04: { nip04: {
async encrypt(peer, plaintext) { async encrypt(peer, plaintext) {
return window.nostr._call("nip04.encrypt", { peer, plaintext }); return window.nostr._call('nip04.encrypt', { peer, plaintext })
}, },
async decrypt(peer, ciphertext) { async decrypt(peer, ciphertext) {
return window.nostr._call("nip04.decrypt", { peer, ciphertext }); return window.nostr._call('nip04.decrypt', { peer, ciphertext })
}, }
}, },
nip44: { nip44: {
async encrypt(peer, plaintext) { async encrypt(peer, plaintext) {
return window.nostr._call("nip44.encrypt", { peer, plaintext }); return window.nostr._call('nip44.encrypt', { peer, plaintext })
}, },
async decrypt(peer, ciphertext) { async decrypt(peer, ciphertext) {
return window.nostr._call("nip44.decrypt", { peer, ciphertext }); return window.nostr._call('nip44.decrypt', { peer, ciphertext })
}, }
}, },
_call(type, params) { _call(type, params) {
const id = Math.random().toString().slice(-4); const id = Math.random().toString().slice(-4)
console.log( console.log(
"%c[nostrconnect:%c" + '%c[nostrconnect:%c' +
id + id +
"%c]%c calling %c" + '%c]%c calling %c' +
type + type +
"%c with %c" + '%c with %c' +
JSON.stringify(params || {}), JSON.stringify(params || {}),
"background-color:#f1b912;font-weight:bold;color:white", 'background-color:#f1b912;font-weight:bold;color:white',
"background-color:#f1b912;font-weight:bold;color:#a92727", 'background-color:#f1b912;font-weight:bold;color:#a92727',
"background-color:#f1b912;color:white;font-weight:bold", 'background-color:#f1b912;color:white;font-weight:bold',
"color:auto", 'color:auto',
"font-weight:bold;color:#08589d;font-family:monospace", 'font-weight:bold;color:#08589d;font-family:monospace',
"color:auto", 'color:auto',
"font-weight:bold;color:#90b12d;font-family:monospace", 'font-weight:bold;color:#90b12d;font-family:monospace'
); )
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._requests[id] = { resolve, reject }; this._requests[id] = { resolve, reject }
window.postMessage( window.postMessage(
{ {
id, id,
ext: EXTENSION, ext: EXTENSION,
type, type,
params, params
}, },
"*", '*'
); )
}); })
}, }
}; }
window.addEventListener("message", (message) => { window.addEventListener('message', (message) => {
if ( if (
!message.data || !message.data ||
message.data.response === null || message.data.response === null ||
@@ -82,51 +82,49 @@ window.addEventListener("message", (message) => {
message.data.ext !== EXTENSION || message.data.ext !== EXTENSION ||
!window.nostr._requests[message.data.id] !window.nostr._requests[message.data.id]
) )
return; return
if (message.data.response.error) { if (message.data.response.error) {
const error = new Error( const error = new Error(
`${EXTENSION}: ${message.data.response.error.message}`, `${EXTENSION}: ${message.data.response.error.message}`
); )
error.stack = message.data.response.error.stack; error.stack = message.data.response.error.stack
window.nostr._requests[message.data.id].reject(error); window.nostr._requests[message.data.id].reject(error)
} else { } else {
window.nostr._requests[message.data.id].resolve(message.data.response); window.nostr._requests[message.data.id].resolve(message.data.response)
} }
console.log( console.log(
"%c[nostrconnect:%c" + '%c[nostrconnect:%c' +
message.data.id + message.data.id +
"%c]%c result: %c" + '%c]%c result: %c' +
JSON.stringify( JSON.stringify(
message?.data?.response || message?.data?.response || message?.data?.response?.error?.message || {}
message?.data?.response?.error?.message ||
{},
), ),
"background-color:#f1b912;font-weight:bold;color:white", 'background-color:#f1b912;font-weight:bold;color:white',
"background-color:#f1b912;font-weight:bold;color:#a92727", 'background-color:#f1b912;font-weight:bold;color:#a92727',
"background-color:#f1b912;color:white;font-weight:bold", 'background-color:#f1b912;color:white;font-weight:bold',
"color:auto", 'color:auto',
"font-weight:bold;color:#08589d", '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 // hack to replace nostr:nprofile.../etc links with something else
let replacing = null; let replacing = null
document.addEventListener("mousedown", replaceNostrSchemeLink); document.addEventListener('mousedown', replaceNostrSchemeLink)
async function replaceNostrSchemeLink(e) { async function replaceNostrSchemeLink(e) {
if (e.target.tagName !== "A" || !e.target.href.startsWith("nostr:")) return; if (e.target.tagName !== 'A' || !e.target.href.startsWith('nostr:')) return
if (replacing === false) return; if (replacing === false) return
const response = await window.nostr._call("replaceURL", { const response = await window.nostr._call('replaceURL', {
url: e.target.href, url: e.target.href
}); })
if (response === false) { if (response === false) {
replacing = false; replacing = false
return; return
} }
e.target.href = response; e.target.href = response
} }

View File

@@ -31774,7 +31774,9 @@ For more info, visit https://reactjs.org/link/mock-scheduler`);
(0, import_react3.useEffect)(() => { (0, import_react3.useEffect)(() => {
import_webextension_polyfill2.default.storage.local.get(["private_key", "relays", "protocol_handler", "notifications"]).then((results) => { import_webextension_polyfill2.default.storage.local.get(["private_key", "relays", "protocol_handler", "notifications"]).then((results) => {
if (results.private_key) { 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) { if (results.relays) {
const relaysList = []; const relaysList = [];
@@ -32327,8 +32329,7 @@ examples:
addUnsavedChanges("private_key"); addUnsavedChanges("private_key");
} }
async function generate() { async function generate() {
const sk = generateSecretKey(); setPrivKey(nip19_exports.nsecEncode(generateSecretKey()));
setPrivKey(nip19_exports.nsecEncode(utils_exports.bytesToHex(sk)));
addUnsavedChanges("private_key"); addUnsavedChanges("private_key");
} }
async function saveKey() { async function saveKey() {
@@ -32342,20 +32343,17 @@ examples:
if (type === "nsec") if (type === "nsec")
hexOrEmptyKey = bytesToHex(data); hexOrEmptyKey = bytesToHex(data);
} catch (_) { } catch (_) {
showMessage("Invalid private key format.");
return;
} }
await import_webextension_polyfill2.default.storage.local.set({ await import_webextension_polyfill2.default.storage.local.set({
private_key: hexOrEmptyKey private_key: hexOrEmptyKey
}); });
if (hexOrEmptyKey !== "") { showMessage("Private Key has been saved.");
setPrivKey(nip19_exports.nsecEncode(hexToBytes(hexOrEmptyKey)));
}
showMessage("saved private key!");
} }
function isKeyValid() { function isKeyValid() {
if (privKey === "") if (privKey === "")
return true; return true;
if (privKey.match(/^[a-f0-9]{64}$/))
return true;
try { try {
if (nip19_exports.decode(privKey).type === "nsec") if (nip19_exports.decode(privKey).type === "nsec")
return true; return true;

View File

@@ -31412,6 +31412,10 @@ For more info, visit https://reactjs.org/link/mock-scheduler`);
var Trigger = TabsTrigger; var Trigger = TabsTrigger;
var Content = TabsContent; 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 // extension/popup.jsx
var import_jsx_runtime = __toESM(require_jsx_runtime()); var import_jsx_runtime = __toESM(require_jsx_runtime());
function Popup() { function Popup() {
@@ -31428,9 +31432,9 @@ For more info, visit https://reactjs.org/link/mock-scheduler`);
(0, import_react3.useEffect)(() => { (0, import_react3.useEffect)(() => {
import_webextension_polyfill.default.storage.local.get(["private_key", "relays"]).then((results) => { import_webextension_polyfill.default.storage.local.get(["private_key", "relays"]).then((results) => {
if (results.private_key) { if (results.private_key) {
const hexKey = getPublicKey(results.private_key); const hexKey = getPublicKey(hexToBytes(results.private_key));
const npubKey = nip19_exports.npubEncode(hexKey); const npub = nip19_exports.npubEncode(hexKey);
setKeys({ npub: npubKey, hex: hexKey }); setKeys({ npub, hex: hexKey });
if (results.relays) { if (results.relays) {
const relaysList = []; const relaysList = [];
for (const url in results.relays) { for (const url in results.relays) {

View File

@@ -6,6 +6,7 @@ import QRCode from 'react-qr-code'
import { SettingsIcon } from './icons' import { SettingsIcon } from './icons'
import { minidenticon } from 'minidenticons' import { minidenticon } from 'minidenticons'
import * as Tabs from '@radix-ui/react-tabs' import * as Tabs from '@radix-ui/react-tabs'
import { hexToBytes } from 'nostr-tools/utils'
function Popup() { function Popup() {
const [keys, setKeys] = useState(null) const [keys, setKeys] = useState(null)
@@ -27,19 +28,21 @@ function Popup() {
useEffect(() => { useEffect(() => {
browser.storage.local.get(['private_key', 'relays']).then((results) => { browser.storage.local.get(['private_key', 'relays']).then((results) => {
if (results.private_key) { if (results.private_key) {
const hexKey = getPublicKey(results.private_key) const hexKey = getPublicKey(hexToBytes(results.private_key))
const npubKey = nip19.npubEncode(hexKey) const npub = nip19.npubEncode(hexKey)
setKeys({ npub: npubKey, hex: hexKey }) setKeys({ npub: npub, hex: hexKey })
if (results.relays) { if (results.relays) {
const relaysList = [] const relaysList = []
for (const url in results.relays) { for (const url in results.relays) {
if (results.relays[url].write) { if (results.relays[url].write) {
relaysList.push(url) relaysList.push(url)
if (relaysList.length >= 3) break if (relaysList.length >= 3) break
} }
} }
if (relaysList.length) { if (relaysList.length) {
const nprofileKey = nip19.nprofileEncode({ const nprofileKey = nip19.nprofileEncode({
pubkey: hexKey, pubkey: hexKey,