diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..2375b78 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cac0e10 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/Shared (Extension)/Resources/background.js b/Shared (Extension)/Resources/background.js index d244b94..7d86548 100644 --- a/Shared (Extension)/Resources/background.js +++ b/Shared (Extension)/Resources/background.js @@ -1,90 +1,98 @@ -import { generatePrivateKey, getPublicKey, signEvent, nip04, nip19 } from "nostr-tools"; +import { + generatePrivateKey, + getPublicKey, + signEvent, + nip04, + nip19, +} from 'nostr-tools'; const storage = browser.storage.local; -browser.runtime.onInstalled.addListener(async ({reason}) => { +browser.runtime.onInstalled.addListener(async ({ reason }) => { // I would like to be able to skip this for development purposes - let ignoreHook = (await storage.get('ignoreInstallHook')).ignoreInstallHook + let ignoreHook = (await storage.get('ignoreInstallHook')).ignoreInstallHook; if (ignoreHook === true) { return; } if (['install'].includes(reason)) { - browser.tabs.create({ - url: 'https://ursus.camp/nostore' - }) + browser.tabs.create({ + url: 'https://ursus.camp/nostore', + }); } }); -browser.runtime.onMessage.addListener(async (message, _sender, sendResponse) => { - console.log(message); +browser.runtime.onMessage.addListener( + async (message, _sender, sendResponse) => { + console.log(message); - switch (message.kind) { - case 'init': - await initialize(); - break; - case 'setProfileIndex': - await setProfileIndex(message.payload); - break; - case 'getProfileIndex': - let profileIndex = await getProfileIndex(); - sendResponse(profileIndex); - break; - case 'getNsecKey': - let nsecKey = await getNsecKey(); - sendResponse(nsecKey); - break; - case 'getNpubKey': - let npubKey = await getNpubKey(); - sendResponse(npubKey); - break; - case 'getPubKey': - let pubKey = await getPubKey(); - sendResponse(pubKey); - break; - case 'getHosts': - let hosts = await getHosts(); - sendResponse(hosts); - break; - case 'getName': - let name = await getName(); - sendResponse(name); - break; - case 'getProfileNames': - let profileNames = await getProfileNames(); - sendResponse(profileNames); - break; - case 'newProfile': - let newIndex = await newProfile(); - sendResponse(newIndex); - break; - case 'saveProfile': - await saveProfile(message.payload); - break; - case 'clearData': - await browser.storage.local.clear(); - break; - case 'deleteProfile': - await deleteProfile(); - break; - case 'signEvent': - let event = await signEvent_(message.payload); - sendResponse(event); - break; - case 'nip04.encrypt': - let cipherText = await nip04Encrypt(message.payload); - sendResponse(cipherText); - break; - case 'nip04.decrypt': - let plainText = await nip04Decrypt(message.payload); - sendResponse(plainText); - break; - case 'getRelays': - sendResponse({}); - break; - default: - break; + switch (message.kind) { + case 'init': + await initialize(); + break; + case 'setProfileIndex': + await setProfileIndex(message.payload); + break; + case 'getProfileIndex': + let profileIndex = await getProfileIndex(); + sendResponse(profileIndex); + break; + case 'getNsecKey': + let nsecKey = await getNsecKey(); + sendResponse(nsecKey); + break; + case 'getNpubKey': + let npubKey = await getNpubKey(); + sendResponse(npubKey); + break; + case 'getPubKey': + let pubKey = await getPubKey(); + sendResponse(pubKey); + break; + case 'getHosts': + let hosts = await getHosts(); + sendResponse(hosts); + break; + case 'getName': + let name = await getName(); + sendResponse(name); + break; + case 'getProfileNames': + let profileNames = await getProfileNames(); + sendResponse(profileNames); + break; + case 'newProfile': + let newIndex = await newProfile(); + sendResponse(newIndex); + break; + case 'saveProfile': + await saveProfile(message.payload); + break; + case 'clearData': + await browser.storage.local.clear(); + break; + case 'deleteProfile': + await deleteProfile(); + break; + case 'signEvent': + let event = await signEvent_(message.payload); + sendResponse(event); + break; + case 'nip04.encrypt': + let cipherText = await nip04Encrypt(message.payload); + sendResponse(cipherText); + break; + case 'nip04.decrypt': + let plainText = await nip04Decrypt(message.payload); + sendResponse(plainText); + break; + case 'getRelays': + sendResponse({}); + break; + default: + break; + } } -}); +); async function get(item) { return (await storage.get(item))[item]; @@ -93,7 +101,7 @@ async function get(item) { async function getOrSetDefault(key, def) { let val = (await storage.get(key))[key]; if (val == null || val == undefined) { - await storage.set({[key]: def}); + await storage.set({ [key]: def }); return def; } @@ -102,7 +110,9 @@ async function getOrSetDefault(key, def) { async function initialize() { await getOrSetDefault('profileIndex', 0); - await getOrSetDefault('profiles', [{name: 'Default', privKey: generatePrivateKey(), hosts: []}]); + await getOrSetDefault('profiles', [ + { name: 'Default', privKey: generatePrivateKey(), hosts: [] }, + ]); } async function getNsecKey() { @@ -145,7 +155,7 @@ async function getProfileNames() { } async function setProfileIndex(profileIndex) { - await storage.set({profileIndex}); + await storage.set({ profileIndex }); } async function getProfileIndex() { @@ -162,9 +172,13 @@ async function currentProfile() { async function newProfile() { let profiles = await get('profiles'); - const newProfile = {name: 'New Profile', privKey: generatePrivateKey(), hosts: []}; + const newProfile = { + name: 'New Profile', + privKey: generatePrivateKey(), + hosts: [], + }; profiles.push(newProfile); - await storage.set({profiles}); + await storage.set({ profiles }); return profiles.length - 1; } @@ -175,7 +189,7 @@ async function saveProfile(profile) { let index = await getProfileIndex(); let profiles = await get('profiles'); profiles[index] = profile; - await storage.set({profiles}); + await storage.set({ profiles }); } async function deleteProfile() { @@ -183,22 +197,22 @@ async function deleteProfile() { let profiles = await get('profiles'); profiles.splice(index, 1); let profileIndex = Math.max(index - 1, 0); - await storage.set({profiles, profileIndex}); + await storage.set({ profiles, profileIndex }); } async function signEvent_(event) { - event = {...event}; + event = { ...event }; let privKey = await getPrivKey(); event.sig = signEvent(event, privKey); return event; } -async function nip04Encrypt({pubKey, plainText}) { +async function nip04Encrypt({ pubKey, plainText }) { let privKey = await getPrivKey(); return nip04.encrypt(privKey, pubKey, plainText); } -async function nip04Decrypt({pubKey, cipherText}) { +async function nip04Decrypt({ pubKey, cipherText }) { let privKey = await getPrivKey(); return nip04.decrypt(privKey, pubKey, cipherText); -} \ No newline at end of file +} diff --git a/Shared (Extension)/Resources/content.js b/Shared (Extension)/Resources/content.js index ff988ab..7dfd747 100644 --- a/Shared (Extension)/Resources/content.js +++ b/Shared (Extension)/Resources/content.js @@ -2,16 +2,20 @@ let script = document.createElement('script'); script.setAttribute('src', browser.runtime.getURL('nostr.build.js')); document.body.appendChild(script); -window.addEventListener('message', async (message) => { - const validEvents = ['getPubKey', 'signEvent', 'getRelays', 'nip04.encrypt', 'nip04.decrypt']; - let {kind, reqId, payload} = message.data; - if (!validEvents.includes(kind)) - return; - - console.log(`Event ${reqId}: Content script received message kind ${kind}, payload: `, payload); - payload = await browser.runtime.sendMessage({kind, payload}); +window.addEventListener('message', async message => { + const validEvents = [ + 'getPubKey', + 'signEvent', + 'getRelays', + 'nip04.encrypt', + 'nip04.decrypt', + ]; + let { kind, reqId, payload } = message.data; + if (!validEvents.includes(kind)) return; - kind = `return_${kind}`; + payload = await browser.runtime.sendMessage({ kind, payload }); - window.postMessage({kind, reqId, payload}, '*'); -}); \ No newline at end of file + kind = `return_${kind}`; + + window.postMessage({ kind, reqId, payload }, '*'); +}); diff --git a/Shared (Extension)/Resources/nostr.js b/Shared (Extension)/Resources/nostr.js index 8684c09..f302f14 100644 --- a/Shared (Extension)/Resources/nostr.js +++ b/Shared (Extension)/Resources/nostr.js @@ -4,7 +4,7 @@ window.nostr = { async getPublicKey() { return await this.broadcast('getPubKey'); }, - + async signEvent(event) { return await this.broadcast('signEvent', event); }, @@ -16,7 +16,7 @@ window.nostr = { // This is here for Alby comatibility. This is not part of the NIP-07 standard. // I have found at least one site, nostr.band, which expects it to be present. async enable() { - return {enabled: true} + return { enabled: true }; }, broadcast(kind, payload) { @@ -24,30 +24,40 @@ window.nostr = { return new Promise((resolve, _reject) => { this.requests[reqId] = resolve; console.log(`Event ${reqId}: ${kind}, payload: `, payload); - window.postMessage({kind, reqId, payload}, '*'); + window.postMessage({ kind, reqId, payload }, '*'); }); }, - nip04: { async encrypt(pubKey, plainText) { - return await window.nostr.broadcast('nip04.encrypt', {pubKey, plainText}); + return await window.nostr.broadcast('nip04.encrypt', { + pubKey, + plainText, + }); }, async decrypt(pubKey, cipherText) { - return await window.nostr.broadcast('nip04.decrypt', {pubKey, cipherText}); - } - } -} + return await window.nostr.broadcast('nip04.decrypt', { + pubKey, + cipherText, + }); + }, + }, +}; -window.addEventListener('message', (message) => { - const validEvents = ['getPubKey', 'signEvent', 'getRelays', 'nip04.encrypt', 'nip04.decrypt'].map(e => `return_${e}`); - let {kind, reqId, payload} = message.data; +window.addEventListener('message', message => { + const validEvents = [ + 'getPubKey', + 'signEvent', + 'getRelays', + 'nip04.encrypt', + 'nip04.decrypt', + ].map(e => `return_${e}`); + let { kind, reqId, payload } = message.data; + + if (!validEvents.includes(kind)) return; - if (!validEvents.includes(kind)) - return; - console.log(`Event ${reqId}: Received payload:`, payload); window.nostr.requests[reqId](payload); delete window.nostr.requests[reqId]; -}); \ No newline at end of file +}); diff --git a/Shared (Extension)/Resources/popup.html b/Shared (Extension)/Resources/popup.html index 313ad9a..ec938fc 100644 --- a/Shared (Extension)/Resources/popup.html +++ b/Shared (Extension)/Resources/popup.html @@ -1,11 +1,13 @@ + +
@@ -16,7 +18,8 @@ - +
@@ -25,7 +28,7 @@ - +
@@ -59,10 +62,11 @@
- +
No user data is collected or transmitted. All private keys are stored in the extension's sequestered local browser storage.
- + + \ No newline at end of file diff --git a/Shared (Extension)/Resources/popup.js b/Shared (Extension)/Resources/popup.js index d7ae055..219c022 100644 --- a/Shared (Extension)/Resources/popup.js +++ b/Shared (Extension)/Resources/popup.js @@ -1,4 +1,4 @@ -import Alpine from "alpinejs"; +import Alpine from 'alpinejs'; window.Alpine = Alpine; Alpine.data('popup', () => ({ @@ -15,8 +15,8 @@ Alpine.data('popup', () => ({ confirmDelete: false, async init() { - console.log("Initializing backend."); - await browser.runtime.sendMessage({kind: 'init'}); + console.log('Initializing backend.'); + await browser.runtime.sendMessage({ kind: 'init' }); this.$watch('profileIndex', async () => { await this.setProfileIndex(); @@ -46,57 +46,71 @@ Alpine.data('popup', () => ({ // Becauset the popup state resets every time it open, we use null as a guard. That way // whenever the user opens the popup, it doesn't automatically reset the current profile if (this.profileIndex !== null) { - await browser.runtime.sendMessage({kind: 'setProfileIndex', payload: this.profileIndex}); + await browser.runtime.sendMessage({ + kind: 'setProfileIndex', + payload: this.profileIndex, + }); } }, async getNsecKey() { - this.privKey = await browser.runtime.sendMessage({kind: 'getNsecKey'}); + this.privKey = await browser.runtime.sendMessage({ + kind: 'getNsecKey', + }); this.pristinePrivKey = this.privKey; }, - + async getNpubKey() { - this.pubKey = await browser.runtime.sendMessage({kind: 'getNpubKey'}); + this.pubKey = await browser.runtime.sendMessage({ kind: 'getNpubKey' }); }, async getHosts() { - this.hosts = await browser.runtime.sendMessage({kind: 'getHosts'}); + this.hosts = await browser.runtime.sendMessage({ kind: 'getHosts' }); }, async getProfileNames() { - this.profileNames = await browser.runtime.sendMessage({kind: 'getProfileNames'}); + this.profileNames = await browser.runtime.sendMessage({ + kind: 'getProfileNames', + }); }, async getName() { - this.name = await browser.runtime.sendMessage({kind: 'getName'}); + this.name = await browser.runtime.sendMessage({ kind: 'getName' }); this.pristineName = this.name; }, async getProfileIndex() { - this.profileIndex = await browser.runtime.sendMessage({kind: 'getProfileIndex'}); + this.profileIndex = await browser.runtime.sendMessage({ + kind: 'getProfileIndex', + }); }, async newProfile() { - let newIndex = await browser.runtime.sendMessage({kind: 'newProfile'}); + let newIndex = await browser.runtime.sendMessage({ + kind: 'newProfile', + }); await this.refreshProfile(); this.profileIndex = newIndex; }, async saveProfile() { - let {name, privKey, hosts} = this; - let profile = {name, privKey, hosts}; - await browser.runtime.sendMessage({kind: 'saveProfile', payload: profile}); + let { name, privKey, hosts } = this; + let profile = { name, privKey, hosts }; + await browser.runtime.sendMessage({ + kind: 'saveProfile', + payload: profile, + }); await this.refreshProfile(); }, async clearData() { - await browser.runtime.sendMessage({kind: 'clearData'}); + await browser.runtime.sendMessage({ kind: 'clearData' }); await this.init(); // Re-initialize after clearing this.confirmClear = false; }, async deleteProfile() { - await browser.runtime.sendMessage({kind: 'deleteProfile'}); + await browser.runtime.sendMessage({ kind: 'deleteProfile' }); await this.init(); this.confirmDelete = false; }, @@ -104,13 +118,15 @@ Alpine.data('popup', () => ({ // Properties get hasValidPubKey() { - return typeof(this.pubKey) === 'string' && this.pubKey.length > 0; + return typeof this.pubKey === 'string' && this.pubKey.length > 0; }, get needsSaving() { - return (this.privKey !== this.pristinePrivKey || this.name !== this.pristineName); - } + return ( + this.privKey !== this.pristinePrivKey || + this.name !== this.pristineName + ); + }, })); - Alpine.start(); diff --git a/package-lock.json b/package-lock.json index fc8bf7e..dbfeb33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,9 @@ "alpinejs": "^3.10.5", "esbuild": "^0.16.17", "nostr-tools": "^1.1.1" + }, + "devDependencies": { + "prettier": "^2.8.3" } }, "node_modules/@esbuild/android-arm": { @@ -492,6 +495,21 @@ "@scure/bip32": "^1.1.1", "@scure/bip39": "^1.1.0" } + }, + "node_modules/prettier": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } } } } diff --git a/package.json b/package.json index ed8e41a..6678558 100644 --- a/package.json +++ b/package.json @@ -19,5 +19,8 @@ "alpinejs": "^3.10.5", "esbuild": "^0.16.17", "nostr-tools": "^1.1.1" + }, + "devDependencies": { + "prettier": "^2.8.3" } }