diff --git a/.gitignore b/.gitignore index 8af9f9d..41a6b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -*.build.js \ No newline at end of file +*.build.js +Shared (Extension)/Resources/options.build.css diff --git a/Nostore.xcodeproj/project.pbxproj b/Nostore.xcodeproj/project.pbxproj index a53719e..97ecc4c 100644 --- a/Nostore.xcodeproj/project.pbxproj +++ b/Nostore.xcodeproj/project.pbxproj @@ -71,6 +71,18 @@ 941B04342978CDF900CA291E /* Icon-16.png in Resources */ = {isa = PBXBuildFile; fileRef = 941B042F2978CDF900CA291E /* Icon-16.png */; }; 941B04352978CDF900CA291E /* Icon-64.png in Resources */ = {isa = PBXBuildFile; fileRef = 941B04302978CDF900CA291E /* Icon-64.png */; }; 941B04362978CDF900CA291E /* Icon-64.png in Resources */ = {isa = PBXBuildFile; fileRef = 941B04302978CDF900CA291E /* Icon-64.png */; }; + 948C69D9297F887600FB3574 /* options.html in Resources */ = {isa = PBXBuildFile; fileRef = 948C69D8297F887600FB3574 /* options.html */; }; + 948C69DA297F887600FB3574 /* options.html in Resources */ = {isa = PBXBuildFile; fileRef = 948C69D8297F887600FB3574 /* options.html */; }; + 948C69DD297F88A200FB3574 /* options.css in Resources */ = {isa = PBXBuildFile; fileRef = 948C69DB297F88A200FB3574 /* options.css */; }; + 948C69DE297F88A200FB3574 /* options.css in Resources */ = {isa = PBXBuildFile; fileRef = 948C69DB297F88A200FB3574 /* options.css */; }; + 948C69DF297F88A200FB3574 /* options.js in Resources */ = {isa = PBXBuildFile; fileRef = 948C69DC297F88A200FB3574 /* options.js */; }; + 948C69E0297F88A200FB3574 /* options.js in Resources */ = {isa = PBXBuildFile; fileRef = 948C69DC297F88A200FB3574 /* options.js */; }; + 948C69E2297F891F00FB3574 /* options.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 948C69E1297F891F00FB3574 /* options.build.js */; }; + 948C69E3297F891F00FB3574 /* options.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 948C69E1297F891F00FB3574 /* options.build.js */; }; + 948C69E5297F8BA600FB3574 /* options.build.css in Resources */ = {isa = PBXBuildFile; fileRef = 948C69E4297F8BA600FB3574 /* options.build.css */; }; + 948C69E6297F8BA600FB3574 /* options.build.css in Resources */ = {isa = PBXBuildFile; fileRef = 948C69E4297F8BA600FB3574 /* options.build.css */; }; + 948C69E82982DFE900FB3574 /* background.html in Resources */ = {isa = PBXBuildFile; fileRef = 948C69E72982DFE900FB3574 /* background.html */; }; + 948C69E92982DFE900FB3574 /* background.html in Resources */ = {isa = PBXBuildFile; fileRef = 948C69E72982DFE900FB3574 /* background.html */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -159,6 +171,12 @@ 941B042E2978CDF900CA291E /* Icon-32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-32.png"; sourceTree = ""; }; 941B042F2978CDF900CA291E /* Icon-16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-16.png"; sourceTree = ""; }; 941B04302978CDF900CA291E /* Icon-64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-64.png"; sourceTree = ""; }; + 948C69D8297F887600FB3574 /* options.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = options.html; sourceTree = ""; }; + 948C69DB297F88A200FB3574 /* options.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = options.css; sourceTree = ""; }; + 948C69DC297F88A200FB3574 /* options.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = options.js; sourceTree = ""; }; + 948C69E1297F891F00FB3574 /* options.build.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = options.build.js; sourceTree = ""; }; + 948C69E4297F8BA600FB3574 /* options.build.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = options.build.css; sourceTree = ""; }; + 948C69E72982DFE900FB3574 /* background.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = background.html; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -240,6 +258,11 @@ 941B03A2296FA90400CA291E /* Resources */ = { isa = PBXGroup; children = ( + 948C69E4297F8BA600FB3574 /* options.build.css */, + 948C69E1297F891F00FB3574 /* options.build.js */, + 948C69DB297F88A200FB3574 /* options.css */, + 948C69DC297F88A200FB3574 /* options.js */, + 948C69D8297F887600FB3574 /* options.html */, 941B04162971138F00CA291E /* content.build.js */, 941B04152971138F00CA291E /* nostr.build.js */, 941B04172971138F00CA291E /* popup.build.js */, @@ -253,6 +276,7 @@ 941B03A8296FA90400CA291E /* popup.html */, 941B03A9296FA90400CA291E /* popup.css */, 941B03AA296FA90400CA291E /* popup.js */, + 948C69E72982DFE900FB3574 /* background.html */, ); path = Resources; sourceTree = ""; @@ -475,7 +499,11 @@ buildActionMask = 2147483647; files = ( 941B0413297110F100CA291E /* background.build.js in Resources */, + 948C69E82982DFE900FB3574 /* background.html in Resources */, + 948C69DF297F88A200FB3574 /* options.js in Resources */, + 948C69DD297F88A200FB3574 /* options.css in Resources */, 941B03F2296FA90400CA291E /* background.js in Resources */, + 948C69E2297F891F00FB3574 /* options.build.js in Resources */, 941B03F8296FA90400CA291E /* popup.css in Resources */, 941B04292977A28700CA291E /* Icon-128.png in Resources */, 941B04182971138F00CA291E /* nostr.build.js in Resources */, @@ -489,6 +517,8 @@ 941B041C2971139000CA291E /* popup.build.js in Resources */, 941B03EC296FA90400CA291E /* _locales in Resources */, 941B04222977A25700CA291E /* Icon-512.png in Resources */, + 948C69E5297F8BA600FB3574 /* options.build.css in Resources */, + 948C69D9297F887600FB3574 /* options.html in Resources */, 941B03F4296FA90400CA291E /* content.js in Resources */, 941B04262977A25700CA291E /* Icon-1024.png in Resources */, 941B04352978CDF900CA291E /* Icon-64.png in Resources */, @@ -503,7 +533,11 @@ buildActionMask = 2147483647; files = ( 941B0414297110F100CA291E /* background.build.js in Resources */, + 948C69E92982DFE900FB3574 /* background.html in Resources */, + 948C69E0297F88A200FB3574 /* options.js in Resources */, + 948C69DE297F88A200FB3574 /* options.css in Resources */, 941B03F3296FA90400CA291E /* background.js in Resources */, + 948C69E3297F891F00FB3574 /* options.build.js in Resources */, 941B03F9296FA90400CA291E /* popup.css in Resources */, 941B042A2977A28700CA291E /* Icon-128.png in Resources */, 941B04192971138F00CA291E /* nostr.build.js in Resources */, @@ -517,6 +551,8 @@ 941B041D2971139000CA291E /* popup.build.js in Resources */, 941B03ED296FA90400CA291E /* _locales in Resources */, 941B04232977A25700CA291E /* Icon-512.png in Resources */, + 948C69E6297F8BA600FB3574 /* options.build.css in Resources */, + 948C69DA297F887600FB3574 /* options.html in Resources */, 941B03F5296FA90400CA291E /* content.js in Resources */, 941B04272977A25700CA291E /* Icon-1024.png in Resources */, 941B04362978CDF900CA291E /* Icon-64.png in Resources */, @@ -805,7 +841,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -847,7 +883,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/README.md b/README.md index c83a27f..2e4fbf8 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ This extension does not collect any user data, or transmit any data over a netwo 2. Open project folder in terminal. 3. Run `npm install` to install the dependencies. 4. Run `npm run watch` to watch and build the necessary extension files. -5. After every rebuild, execute Run in XCode to deploy the latest changes to Safari. +5. Run `npm run watch-tailwind` to watch and build the pages with tailwinds CSS. +6. After every rebuild, execute Run in XCode to deploy the latest changes to Safari. If you do not see the Nostore extension in your Safari toolbar, you need to activate unsigned extensions and Nostore: diff --git a/Shared (Extension)/Resources/background.html b/Shared (Extension)/Resources/background.html new file mode 100644 index 0000000..bb54024 --- /dev/null +++ b/Shared (Extension)/Resources/background.html @@ -0,0 +1 @@ + diff --git a/Shared (Extension)/Resources/background.js b/Shared (Extension)/Resources/background.js index 7d86548..a55816a 100644 --- a/Shared (Extension)/Resources/background.js +++ b/Shared (Extension)/Resources/background.js @@ -6,11 +6,15 @@ import { nip19, } from 'nostr-tools'; +import { getProfileIndex, get, getProfile } from './utils'; + const storage = browser.storage.local; +const log = msg => console.log('Background: ', msg); 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: false })) + .ignoreInstallHook; if (ignoreHook === true) { return; } @@ -23,56 +27,36 @@ browser.runtime.onInstalled.addListener(async ({ reason }) => { browser.runtime.onMessage.addListener( async (message, _sender, sendResponse) => { - console.log(message); + log(message); switch (message.kind) { - case 'init': - await initialize(); + // General + case 'log': + console.log( + message.payload.module ? `${module}: ` : '', + message.payload.msg + ); break; - case 'setProfileIndex': - await setProfileIndex(message.payload); + case 'generatePrivateKey': + sendResponse(generatePrivateKey()); break; - case 'getProfileIndex': - let profileIndex = await getProfileIndex(); - sendResponse(profileIndex); + case 'savePrivateKey': + await savePrivateKey(message.payload); break; - case 'getNsecKey': - let nsecKey = await getNsecKey(); - sendResponse(nsecKey); + case 'getNpub': + let npub = await getNpub(message.payload); + sendResponse(npub); break; - case 'getNpubKey': - let npubKey = await getNpubKey(); - sendResponse(npubKey); + case 'getNsec': + let nsec = await getNsec(message.payload); + sendResponse(nsec); 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; + + // window.nostr case 'signEvent': let event = await signEvent_(message.payload); sendResponse(event); @@ -86,38 +70,38 @@ browser.runtime.onMessage.addListener( sendResponse(plainText); break; case 'getRelays': - sendResponse({}); + let relays = await getRelays(); + sendResponse(relays); break; + default: break; } + return false; } ); -async function get(item) { - return (await storage.get(item))[item]; -} - -async function getOrSetDefault(key, def) { - let val = (await storage.get(key))[key]; - if (val == null || val == undefined) { - await storage.set({ [key]: def }); - return def; +// Options +async function savePrivateKey([index, privKey]) { + if (privKey.startsWith('nsec')) { + privKey = nip19.decode(privKey).data; } - - return val; + let profiles = await get('profiles'); + profiles[index].privKey = privKey; + await storage.set({ profiles }); } -async function initialize() { - await getOrSetDefault('profileIndex', 0); - await getOrSetDefault('profiles', [ - { name: 'Default', privKey: generatePrivateKey(), hosts: [] }, - ]); +async function getNsec(index) { + let profile = await getProfile(index); + let nsec = nip19.nsecEncode(profile.privKey); + return nsec; } -async function getNsecKey() { - let profile = await currentProfile(); - return profile.nsecKey; +async function getNpub(index) { + let profile = await getProfile(index); + let pubKey = getPublicKey(profile.privKey); + let npub = nip19.npubEncode(pubKey); + return npub; } async function getPrivKey() { @@ -125,81 +109,18 @@ async function getPrivKey() { return profile.privKey; } -async function getNpubKey() { - let pubKey = await getPubKey(); - console.log('pubKey: ', pubKey); - let npubKey = nip19.npubEncode(pubKey); - console.log('npub key: ', npubKey); - return npubKey; -} - async function getPubKey() { let privKey = await getPrivKey(); let pubKey = getPublicKey(privKey); return pubKey; } -async function getHosts() { - let profile = await currentProfile(); - return profile.hosts; -} - -async function getName() { - let profile = await currentProfile(); - return profile.name; -} - -async function getProfileNames() { - let profiles = await get('profiles'); - return profiles.map(p => p.name); -} - -async function setProfileIndex(profileIndex) { - await storage.set({ profileIndex }); -} - -async function getProfileIndex() { - return await get('profileIndex'); -} - async function currentProfile() { - let index = await get('profileIndex'); + let index = await getProfileIndex(); let profiles = await get('profiles'); - let currentProfile = profiles[index]; - currentProfile.nsecKey = nip19.nsecEncode(currentProfile.privKey); return profiles[index]; } -async function newProfile() { - let profiles = await get('profiles'); - const newProfile = { - name: 'New Profile', - privKey: generatePrivateKey(), - hosts: [], - }; - profiles.push(newProfile); - await storage.set({ profiles }); - return profiles.length - 1; -} - -async function saveProfile(profile) { - if (profile.privKey.startsWith('nsec')) { - profile.privKey = nip19.decode(profile.privKey).data; - } - let index = await getProfileIndex(); - let profiles = await get('profiles'); - profiles[index] = profile; - await storage.set({ profiles }); -} - -async function deleteProfile() { - let index = await getProfileIndex(); - let profiles = await get('profiles'); - profiles.splice(index, 1); - let profileIndex = Math.max(index - 1, 0); - await storage.set({ profiles, profileIndex }); -} - async function signEvent_(event) { event = { ...event }; let privKey = await getPrivKey(); @@ -216,3 +137,15 @@ async function nip04Decrypt({ pubKey, cipherText }) { let privKey = await getPrivKey(); return nip04.decrypt(privKey, pubKey, cipherText); } + +async function getRelays() { + let profile = await currentProfile(); + let relays = profile.relays; + let relayObj = {}; + // The getRelays call expects this to be returned as an object, not array + relays.forEach(relay => { + let { url, read, write } = relay; + relayObj[url] = { read, write }; + }); + return relayObj; +} diff --git a/Shared (Extension)/Resources/manifest.json b/Shared (Extension)/Resources/manifest.json index 28ae599..0e68c68 100644 --- a/Shared (Extension)/Resources/manifest.json +++ b/Shared (Extension)/Resources/manifest.json @@ -1,11 +1,9 @@ { "manifest_version": 3, "default_locale": "en", - "name": "__MSG_extension_name__", "description": "__MSG_extension_description__", "version": "1.0", - "icons": { "48": "images/icon-48.png", "96": "images/icon-96.png", @@ -13,17 +11,19 @@ "256": "images/icon-256.png", "512": "images/icon-512.png" }, - "background": { - "service_worker": "background.build.js", - "type": "module" + "page": "background.html" }, - - "content_scripts": [{ - "js": [ "content.build.js" ], - "matches": [ "" ] - }], - + "content_scripts": [ + { + "js": [ + "content.build.js" + ], + "matches": [ + "" + ] + } + ], "action": { "default_popup": "popup.html", "default_icon": { @@ -35,17 +35,32 @@ "72": "images/toolbar-72.png" } }, - - "permissions": [ "storage" ], - - "web_accessible_resources": [ - { - "resources": ["nostr.build.js", "popup.build.js"], - "matches": [""] - } + "options_ui": { + "page": "options.html" + }, + "permissions": [ + "storage" + ], + "web_accessible_resources": [ + { + "resources": [ + "nostr.build.js", + "popup.build.js", + "options.build.js", + "options.build.css", + "options.html" + ], + "matches": [ + "" + ] + } ], - "content_security_policy": { "extension_pages": "script-src 'self' 'unsafe-eval'" + }, + "browser_specific_settings": { + "safari": { + "strict_min_version": "15.4" + } } } diff --git a/Shared (Extension)/Resources/nostr.js b/Shared (Extension)/Resources/nostr.js index f302f14..83371fd 100644 --- a/Shared (Extension)/Resources/nostr.js +++ b/Shared (Extension)/Resources/nostr.js @@ -23,7 +23,6 @@ window.nostr = { let reqId = Math.random().toString(); return new Promise((resolve, _reject) => { this.requests[reqId] = resolve; - console.log(`Event ${reqId}: ${kind}, payload: `, payload); window.postMessage({ kind, reqId, payload }, '*'); }); }, @@ -57,7 +56,6 @@ window.addEventListener('message', message => { if (!validEvents.includes(kind)) return; - console.log(`Event ${reqId}: Received payload:`, payload); window.nostr.requests[reqId](payload); delete window.nostr.requests[reqId]; }); diff --git a/Shared (Extension)/Resources/options.css b/Shared (Extension)/Resources/options.css new file mode 100644 index 0000000..bdc4b3d --- /dev/null +++ b/Shared (Extension)/Resources/options.css @@ -0,0 +1,37 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer components { + .button { + /* Colors */ + @apply bg-fuchsia-900 hover:bg-fuchsia-800 active:bg-fuchsia-700 text-fuchsia-200 disabled:bg-gray-200 disabled:text-black; + + /* Sizing and padding */ + @apply rounded-lg p-1 md:p-1.5 md:w-24 min-w-fit text-center; + } + + .input { + /* Colors */ + @apply bg-fuchsia-200 text-fuchsia-800 disabled:bg-gray-200 disabled:text-black focus:border-fuchsia-800; + + /* Sizing and padding */ + @apply rounded-lg p-1 lg:p-1.5 w-full md:w-64; + } + + .checkbox { + /* Colors */ + @apply text-fuchsia-800 bg-fuchsia-200 rounded-full accent-fuchsia-200; + + /* Sizing and padding */ + @apply w-4 h-4 lg:w-5 lg:h-5; + } + + .section { + @apply border-2 border-fuchsia-700 rounded-lg p-1 md:p-5 mt-6 shadow-md; + } + + .section-header { + @apply text-2xl lg:text-5xl font-bold; + } +} \ No newline at end of file diff --git a/Shared (Extension)/Resources/options.html b/Shared (Extension)/Resources/options.html new file mode 100644 index 0000000..cd57dd4 --- /dev/null +++ b/Shared (Extension)/Resources/options.html @@ -0,0 +1,121 @@ + + + + + + + + + + + + +

Settings

+ + +
+ +
+ +
+ + + +
+
+ + +
+

Keys

+

Provide your nsec or legacy (hexadecimal) private keys. +

+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ + +
+

Relays

+

Add relay suggestions for clients.

+ + + + +
+ +
+ + +
+ +
+
+
+ +
+ + + +
+ + + \ No newline at end of file diff --git a/Shared (Extension)/Resources/options.js b/Shared (Extension)/Resources/options.js new file mode 100644 index 0000000..7e7f5f7 --- /dev/null +++ b/Shared (Extension)/Resources/options.js @@ -0,0 +1,196 @@ +import Alpine from 'alpinejs'; +import { + clearData, + deleteProfile, + getProfileIndex, + getProfileNames, + getRelays, + initialize, + newProfile, + savePrivateKey, + saveProfileName, + saveRelays, + RECOMMENDED_RELAYS, +} from './utils'; + +const log = console.log; + +Alpine.data('options', () => ({ + profileNames: ['Poop'], + profileIndex: 0, + profileName: '', + pristineProfileName: '', + privKey: '', + pristinePrivKey: '', + pubKey: '', + relays: [], + newRelay: '', + urlError: '', + recommendedRelay: '', + confirmDelete: false, + confirmClear: false, + + async init(watch = true) { + log('Initialize backend.'); + await initialize(); + + if (watch) { + this.$watch('profileIndex', async () => { + await this.refreshProfile(); + }); + + this.$watch('recommendedRelay', async () => { + if (this.recommendedRelay.length == 0) return; + await this.addRelay(this.recommendedRelay); + this.recommendedRelay = ''; + }); + } + + // We need to refresh the names BEFORE setting the profile index, or it won't work + // on init to set the correct profile. + await this.getProfileNames(); + await this.getProfileIndex(); + await this.refreshProfile(); + }, + + async refreshProfile() { + await this.getProfileNames(); + await this.getProfileName(); + await this.getNsec(); + await this.getNpub(); + await this.getRelays(); + this.confirmClear = false; + this.confirmDelete = false; + }, + + // Profile functions + + async getProfileNames() { + this.profileNames = await getProfileNames(); + }, + + async getProfileName() { + let names = await getProfileNames(); + let name = names[this.profileIndex]; + this.profileName = name; + this.pristineProfileName = name; + }, + + async getProfileIndex() { + this.profileIndex = await getProfileIndex(); + }, + + async newProfile() { + let newIndex = await newProfile(); + await this.getProfileNames(); + this.profileIndex = newIndex; + }, + + async deleteProfile() { + await deleteProfile(this.profileIndex); + await this.init(false); + }, + + // Key functions + + async saveProfile() { + if (!this.needsSave) return; + + await savePrivateKey(this.profileIndex, this.privKey); + await saveProfileName(this.profileIndex, this.profileName); + await this.getProfileNames(); + await this.refreshProfile(); + }, + + async getNpub() { + this.pubKey = await browser.runtime.sendMessage({ + kind: 'getNpub', + payload: this.profileIndex, + }); + }, + + async getNsec() { + this.privKey = await browser.runtime.sendMessage({ + kind: 'getNsec', + payload: this.profileIndex, + }); + this.pristinePrivKey = this.privKey; + }, + + // Relay functions + + async getRelays() { + this.relays = await getRelays(this.profileIndex); + }, + + async saveRelays() { + await saveRelays(this.profileIndex, this.relays); + await this.getRelays(); + }, + + async addRelay(relayToAdd = null) { + let newRelay = relayToAdd || this.newRelay; + try { + let url = new URL(newRelay); + if (url.protocol !== 'wss:') { + this.setUrlError('Must be a websocket url'); + return; + } + let urls = this.relays.map(v => v.url); + if (urls.includes(url.href)) { + this.setUrlError('URL already exists'); + return; + } + this.relays.push({ url: url.href, read: true, write: true }); + await this.saveRelays(); + this.newRelay = ''; + } catch (error) { + this.setUrlError('Invalid websocket URL'); + } + }, + + async deleteRelay(index) { + this.relays.splice(index, 1); + await this.saveRelays(); + }, + + setUrlError(message) { + this.urlError = message; + setTimeout(() => { + this.urlError = ''; + }, 3000); + }, + + // General + + async clearData() { + await clearData(); + await this.init(false); + }, + + // Properties + + get recommendedRelays() { + let relays = this.relays.map(r => new URL(r.url)).map(r => r.href); + return RECOMMENDED_RELAYS.filter(r => !relays.includes(r.href)).map( + r => r.href + ); + }, + + get hasRelays() { + return this.relays.length > 0; + }, + + get hasRecommendedRelays() { + return this.recommendedRelays.length > 0; + }, + + get needsSave() { + return ( + this.privKey !== this.pristinePrivKey || + this.profileName !== this.pristineProfileName + ); + }, +})); + +Alpine.start(); diff --git a/Shared (Extension)/Resources/popup.css b/Shared (Extension)/Resources/popup.css index 57438a1..ae5bfb7 100644 --- a/Shared (Extension)/Resources/popup.css +++ b/Shared (Extension)/Resources/popup.css @@ -5,61 +5,23 @@ body { width: 300px; - padding: 10px; + padding: 15px; font-family: system-ui; } -label { - display: inline-block; - width: 200px; -} - -input { - width: 100%; -} - -.profile-buttons { - width: 100%; -} - -#priv-key, #pub-key { - font-family: monospace; -} - -.profiles { - margin-bottom: 15px; -} - -.profile-name { - margin-bottom: 15px; -} - -.key { - margin-bottom: 15px; -} - -.buttons { - margin-bottom: 15px; -} - -td:first-child { - width: 50px; -} - -td:nth-child(2) { - width: 100px; -} - -tr { - margin-bottom: 10px; +.relay { + margin-top: 10px; + font-size: 80%; + color: darkred; } .help { - margin-top: 15px; + margin-top: 10px; } .disclaimer { + margin-top: 10px; font-size: 50%; color: green; } \ No newline at end of file diff --git a/Shared (Extension)/Resources/popup.html b/Shared (Extension)/Resources/popup.html index ec938fc..63aefe5 100644 --- a/Shared (Extension)/Resources/popup.html +++ b/Shared (Extension)/Resources/popup.html @@ -17,50 +17,20 @@ - - - -
- - -
- -
- - -
- -
- - - - -
- -
- - -
- -
-

Allowed Sites

- - -
+
+ + You do not have any relays setup for this profile. Would you like to add some recommended + relays now? + +
+
- +
diff --git a/Shared (Extension)/Resources/popup.js b/Shared (Extension)/Resources/popup.js index 219c022..0097618 100644 --- a/Shared (Extension)/Resources/popup.js +++ b/Shared (Extension)/Resources/popup.js @@ -1,131 +1,76 @@ +import { + bglog, + getProfileNames, + setProfileIndex, + getProfileIndex, + getRelays, + RECOMMENDED_RELAYS, + saveRelays, +} from './utils'; import Alpine from 'alpinejs'; window.Alpine = Alpine; +const log = console.log; + Alpine.data('popup', () => ({ - privKey: '', - pubKey: '', - pristinePrivKey: '', - hosts: [], - name: '', - pristineName: '', profileNames: ['Default'], profileIndex: 0, - visibleKey: false, - confirmClear: false, - confirmDelete: false, + relayCount: 0, async init() { - console.log('Initializing backend.'); + log('Initializing backend.'); await browser.runtime.sendMessage({ kind: 'init' }); this.$watch('profileIndex', async () => { + await this.loadNames(); await this.setProfileIndex(); - await this.refreshProfile(); - this.confirmClear = false; - this.confirmDelete = false; + await this.countRelays(); }); - // Even though getProfileIndex will immediately trigger a profile refresh, we still + // Even though loadProfileIndex will immediately trigger a profile refresh, we still // need to do an initial profile refresh first. This will pull the latest data from // the background scripts. Specifically, this pulls the list of profile names, // otherwise it generates a rendering error where it may not show the correct selected // profile when first loading the popup. - await this.refreshProfile(); - await this.getProfileIndex(); - }, - - async refreshProfile() { - await this.getNsecKey(); - await this.getNpubKey(); - await this.getHosts(); - await this.getName(); - await this.getProfileNames(); + await this.loadNames(); + await this.loadProfileIndex(); + await this.countRelays(); }, async setProfileIndex() { // 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 setProfileIndex(this.profileIndex); } }, - async getNsecKey() { - this.privKey = await browser.runtime.sendMessage({ - kind: 'getNsecKey', - }); - this.pristinePrivKey = this.privKey; + async loadNames() { + this.profileNames = await getProfileNames(); }, - async getNpubKey() { - this.pubKey = await browser.runtime.sendMessage({ kind: 'getNpubKey' }); + async loadProfileIndex() { + this.profileIndex = await getProfileIndex(); }, - async getHosts() { - this.hosts = await browser.runtime.sendMessage({ kind: 'getHosts' }); + async openOptions() { + await browser.runtime.openOptionsPage(); + window.close(); }, - async getProfileNames() { - this.profileNames = await browser.runtime.sendMessage({ - kind: 'getProfileNames', - }); + async countRelays() { + let relays = await getRelays(this.profileIndex); + this.relayCount = relays.length; }, - async getName() { - this.name = await browser.runtime.sendMessage({ kind: 'getName' }); - this.pristineName = this.name; - }, - - async getProfileIndex() { - this.profileIndex = await browser.runtime.sendMessage({ - kind: 'getProfileIndex', - }); - }, - - async 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, - }); - await this.refreshProfile(); - }, - - async 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 this.init(); - this.confirmDelete = false; - }, - - // Properties - - get hasValidPubKey() { - return typeof this.pubKey === 'string' && this.pubKey.length > 0; - }, - - get needsSaving() { - return ( - this.privKey !== this.pristinePrivKey || - this.name !== this.pristineName - ); + async addRelays() { + let relays = RECOMMENDED_RELAYS.map(r => ({ + url: r.href, + read: true, + write: true, + })); + await saveRelays(this.profileIndex, relays); + await this.countRelays(); }, })); diff --git a/Shared (Extension)/Resources/utils.js b/Shared (Extension)/Resources/utils.js new file mode 100644 index 0000000..494aa79 --- /dev/null +++ b/Shared (Extension)/Resources/utils.js @@ -0,0 +1,129 @@ +const storage = browser.storage.local; +export const RECOMMENDED_RELAYS = [ + new URL('wss://relay.damus.io'), + new URL('wss://eden.nostr.land'), + new URL('wss://nostr-relay.derekross.me'), + new URL('wss://relay.snort.social'), +]; + +export async function initialize() { + await getOrSetDefault('profileIndex', 0); + await getOrSetDefault('profiles', [await generateProfile()]); + await getOrSetDefault('version', 0); +} + +export async function bglog(msg, module = null) { + await browser.runtime.sendMessage({ + kind: 'log', + payload: { msg, module }, + }); +} + +export async function getProfiles() { + let profiles = await storage.get({ profiles: [] }); + return profiles.profiles; +} + +export async function getProfile(index) { + let profiles = await getProfiles(); + return profiles[index]; +} + +export async function getProfileNames() { + let profiles = await getProfiles(); + return profiles.map(p => p.name); +} + +export async function getProfileIndex() { + const index = await storage.get({ profileIndex: 0 }); + return index.profileIndex; +} + +export async function setProfileIndex(profileIndex) { + await storage.set({ profileIndex }); +} + +export async function deleteProfile(index) { + let profiles = await getProfiles(); + let profileIndex = await getProfileIndex(); + profiles.splice(index, 1); + if (profiles.length == 0) { + await clearData(); // If we have deleted all of the profiles, let's just start fresh with all new data + await initialize(); + } else { + // If the index deleted was the active profile, change the active profile to the next one + let newIndex = + profileIndex === index ? Math.max(index - 1, 0) : this.profileIndex; + await storage.set({ profiles, profileIndex: newIndex }); + } +} + +export async function clearData() { + let ignoreInstallHook = await storage.get({ ignoreInstallHook: false }); + await storage.clear(); + await storage.set(ignoreInstallHook); +} + +async function generatePrivateKey() { + return await browser.runtime.sendMessage({ kind: 'generatePrivateKey' }); +} + +export async function generateProfile(name = 'Default') { + return { + name, + privKey: await generatePrivateKey(), + hosts: [], + relays: [], + }; +} + +async function getOrSetDefault(key, def) { + let val = (await storage.get(key))[key]; + if (val == null || val == undefined) { + await storage.set({ [key]: def }); + return def; + } + + return val; +} + +export async function saveProfileName(index, profileName) { + let profiles = await getProfiles(); + profiles[index].name = profileName; + await storage.set({ profiles }); +} + +export async function savePrivateKey(index, privateKey) { + await browser.runtime.sendMessage({ + kind: 'savePrivateKey', + payload: [index, privateKey], + }); +} + +export async function newProfile() { + let profiles = await getProfiles(); + const newProfile = await generateProfile('New Profile'); + profiles.push(newProfile); + await storage.set({ profiles }); + return profiles.length - 1; +} + +export async function getRelays(profileIndex) { + let profile = await getProfile(profileIndex); + return profile.relays || []; +} + +export async function saveRelays(profileIndex, relays) { + // Having an Alpine proxy object as a sub-object does not serialize correctly in storage, + // so we are pre-serializing here before assigning it to the profile, so the proxy + // obj doesn't bug out. + let fixedRelays = JSON.parse(JSON.stringify(relays)); + let profiles = await getProfiles(); + let profile = profiles[profileIndex]; + profile.relays = fixedRelays; + await storage.set({ profiles }); +} + +export async function get(item) { + return (await storage.get(item))[item]; +} diff --git a/build.js b/build.js index b0af100..e37c035 100755 --- a/build.js +++ b/build.js @@ -1,21 +1,28 @@ #!/usr/bin/env node -let watch = process.argv[2] === 'watch' ? { - onRebuild(error, result) { - if (error) console.error('watch rebuild failed: ', error) - else console.log('watch rebuild succeeded: ', result) - } -} : false; +let watch = + process.argv[2] === 'watch' + ? { + onRebuild(error, result) { + if (error) console.error('watch rebuild failed: ', error); + else console.log('watch rebuild succeeded: ', result); + }, + } + : false; -require('esbuild').build({ - entryPoints: { - 'background.build': './Shared (Extension)/Resources/background.js', - 'content.build': './Shared (Extension)/Resources/content.js', - 'nostr.build': './Shared (Extension)/Resources/nostr.js', - 'popup.build': './Shared (Extension)/Resources/popup.js', - }, - outdir: './Shared (Extension)/Resources', - sourcemap: 'inline', - bundle: true, - watch -}).catch(() => process.exit(1)) \ No newline at end of file +require('esbuild') + .build({ + entryPoints: { + 'background.build': './Shared (Extension)/Resources/background.js', + 'content.build': './Shared (Extension)/Resources/content.js', + 'nostr.build': './Shared (Extension)/Resources/nostr.js', + 'popup.build': './Shared (Extension)/Resources/popup.js', + 'options.build': './Shared (Extension)/Resources/options.js', + }, + outdir: './Shared (Extension)/Resources', + sourcemap: 'inline', + bundle: true, + // minify: true, + watch, + }) + .catch(() => process.exit(1)); diff --git a/iOS (Extension)/Info.plist b/iOS (Extension)/Info.plist index 9ee504d..56a3ef0 100644 --- a/iOS (Extension)/Info.plist +++ b/iOS (Extension)/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + NSExtension NSExtensionPointIdentifier diff --git a/macOS (Extension)/Info.plist b/macOS (Extension)/Info.plist index 9ee504d..56a3ef0 100644 --- a/macOS (Extension)/Info.plist +++ b/macOS (Extension)/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index dbfeb33..e12aa96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,13 @@ "license": "ISC", "dependencies": { "alpinejs": "^3.10.5", - "esbuild": "^0.16.17", "nostr-tools": "^1.1.1" }, "devDependencies": { - "prettier": "^2.8.3" + "@tailwindcss/forms": "^0.5.3", + "esbuild": "^0.16.17", + "prettier": "^2.8.3", + "tailwindcss": "^3.2.4" } }, "node_modules/@esbuild/android-arm": { @@ -24,6 +26,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -39,6 +42,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -54,6 +58,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -69,6 +74,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -84,6 +90,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -99,6 +106,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -114,6 +122,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -129,6 +138,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -144,6 +154,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -159,6 +170,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -174,6 +186,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -189,6 +202,7 @@ "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -204,6 +218,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -219,6 +234,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -234,6 +250,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -249,6 +266,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -264,6 +282,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -279,6 +298,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -294,6 +314,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -309,6 +330,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -324,6 +346,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -339,6 +362,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -363,6 +387,41 @@ } ] }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -427,6 +486,18 @@ } ] }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz", + "integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==", + "dev": true, + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" + } + }, "node_modules/@vue/reactivity": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", @@ -440,6 +511,38 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/alpinejs": { "version": "3.10.5", "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.10.5.tgz", @@ -448,10 +551,155 @@ "@vue/reactivity": "~3.1.1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/esbuild": { "version": "0.16.17", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -484,6 +732,223 @@ "@esbuild/win32-x64": "0.16.17" } }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nostr-tools": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.1.1.tgz", @@ -496,6 +961,175 @@ "@scure/bip39": "^1.1.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", + "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prettier": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", @@ -510,6 +1144,207 @@ "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz", + "integrity": "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==", + "dev": true, + "dependencies": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.18", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "6.0.0", + "postcss-selector-parser": "^6.0.10", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } } } } diff --git a/package.json b/package.json index 6678558..083f71b 100644 --- a/package.json +++ b/package.json @@ -11,16 +11,19 @@ "scripts": { "build": "./build.js", "watch": "./build.js watch", + "watch-tailwind": "tailwindcss -i './Shared (Extension)/Resources/options.css' -o './Shared (Extension)/Resources/options.build.css' --watch", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "alpinejs": "^3.10.5", - "esbuild": "^0.16.17", "nostr-tools": "^1.1.1" }, "devDependencies": { - "prettier": "^2.8.3" + "esbuild": "^0.16.17", + "@tailwindcss/forms": "^0.5.3", + "prettier": "^2.8.3", + "tailwindcss": "^3.2.4" } -} +} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..5d2e96b --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./Shared (Extension)/**/*.{html,js}'], + theme: { + extend: {}, + }, + plugins: [require('@tailwindcss/forms')], +};