Moved profile management into options UI.

This commit is contained in:
Ryan Breen
2023-01-27 10:38:37 -05:00
parent 0ea7f11356
commit b0faf6146e
6 changed files with 237 additions and 282 deletions

View File

@@ -28,6 +28,7 @@ browser.runtime.onMessage.addListener(
log(message);
switch (message.kind) {
// General
case 'log':
console.log(
message.payload.module ? `${module}: ` : '',
@@ -44,14 +45,31 @@ browser.runtime.onMessage.addListener(
let profileIndex = await getProfileIndex();
sendResponse(profileIndex);
break;
case 'getNsecKey':
let nsecKey = await getNsecKey();
sendResponse(nsecKey);
case 'getProfileNames':
let profileNames = await getProfileNames();
sendResponse(profileNames);
break;
case 'getNpubKey':
let npubKey = await getNpubKey();
sendResponse(npubKey);
// Options page
case 'newProfile':
let newIndex = await newProfile();
sendResponse(newIndex);
break;
case 'savePrivateKey':
await savePrivateKey(message.payload);
break;
case 'saveProfileName':
await saveProfileName(message.payload);
break;
case 'getNpub':
let npub = await getNpub(message.payload);
sendResponse(npub);
break;
case 'getNsec':
let nsec = await getNsec(message.payload);
sendResponse(nsec);
break;
case 'getPubKey':
let pubKey = await getPubKey();
sendResponse(pubKey);
@@ -60,27 +78,26 @@ browser.runtime.onMessage.addListener(
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();
await clearData();
break;
case 'deleteProfile':
await deleteProfile();
await deleteProfile(message.payload);
break;
case 'getRelaysForProfile':
let profileRelays = await getRelaysForProfile(message.payload);
sendResponse(profileRelays);
break;
case 'saveRelaysForProfile':
let [srfpIndex, srfpRelays] = message.payload;
await saveRelaysForProfile(srfpIndex, srfpRelays);
break;
case 'getNameForProfile':
let nameForProfile = await getNameForProfile(message.payload);
sendResponse(nameForProfile);
break;
// window.nostr
case 'signEvent':
let event = await signEvent_(message.payload);
sendResponse(event);
@@ -97,26 +114,7 @@ browser.runtime.onMessage.addListener(
let relays = await getRelays();
sendResponse(relays);
break;
case 'getRelaysForProfile':
let profileRelays = await getRelaysForProfile(message.payload);
sendResponse(profileRelays);
break;
case 'saveRelaysForProfile':
let [srfpIndex, srfpRelays] = message.payload;
await saveRelaysForProfile(srfpIndex, srfpRelays);
break;
case 'getNameForProfile':
let nameForProfile = await getNameForProfile(message.payload);
sendResponse(nameForProfile);
break;
case 'getPubKeyForProfile':
let pubKeyForProfile = await getNpubKey(message.payload);
sendResponse(pubKeyForProfile);
break;
case 'getPrivKeyForProfile':
let privKeyForProfile = await getNsecKey(message.payload);
sendResponse(privKeyForProfile);
break;
default:
break;
}
@@ -124,19 +122,7 @@ browser.runtime.onMessage.addListener(
}
);
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;
}
return val;
}
// General
async function initialize() {
await getOrSetDefault('profileIndex', 0);
@@ -150,22 +136,47 @@ async function initialize() {
]);
}
async function getProfile(index) {
async function setProfileIndex(profileIndex) {
await storage.set({ profileIndex });
}
async function getProfileIndex() {
return await get('profileIndex');
}
// Options
async function clearData() {
let ignoreInstallHook = await storage.get({ ignoreInstallHook: false });
await storage.clear();
await storage.set(ignoreInstallHook);
}
async function savePrivateKey([index, privKey]) {
if (privKey.startsWith('nsec')) {
privKey = nip19.decode(privKey).data;
}
let profiles = await get('profiles');
return profiles[index];
profiles[index].privKey = privKey;
await storage.set({ profiles });
}
async function getNsecKey(index) {
async function saveProfileName([index, profileName]) {
let profiles = await get('profiles');
profiles[index].name = profileName;
await storage.set({ profiles });
}
async function getNsec(index) {
let profile = await getProfile(index);
let nsecKey = nip19.nsecEncode(profile.privKey);
return nsecKey;
let nsec = nip19.nsecEncode(profile.privKey);
return nsec;
}
async function getNpubKey(index) {
async function getNpub(index) {
let profile = await getProfile(index);
let pubKey = getPublicKey(profile.privKey);
let npubKey = nip19.npubEncode(pubKey);
return npubKey;
let npub = nip19.npubEncode(pubKey);
return npub;
}
async function getPrivKey() {
@@ -179,31 +190,13 @@ async function getPubKey() {
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);
@@ -216,29 +209,28 @@ async function newProfile() {
name: 'New Profile',
privKey: generatePrivateKey(),
hosts: [],
relays: [],
};
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();
async function deleteProfile(index) {
let profiles = await get('profiles');
profiles.splice(index, 1);
let profileIndex = Math.max(index - 1, 0);
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 profileIndex =
this.profileIndex === index
? Math.max(index - 1, 0)
: this.profileIndex;
await storage.set({ profiles, profileIndex });
}
}
async function signEvent_(event) {
event = { ...event };
@@ -285,3 +277,24 @@ async function getNameForProfile(profileIndex) {
let profile = profiles[profileIndex];
return profile.name;
}
// Utilities
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;
}
return val;
}
async function getProfile(index) {
let profiles = await get('profiles');
return profiles[index];
}

View File

@@ -4,7 +4,7 @@
@layer components {
.button {
@apply rounded-lg p-1 lg:p-1.5 bg-fuchsia-900 hover:bg-fuchsia-800 active:bg-fuchsia-700 text-fuchsia-200 w-24 text-center;
@apply rounded-lg p-1 lg:p-1.5 bg-fuchsia-900 hover:bg-fuchsia-800 active:bg-fuchsia-700 text-fuchsia-200 w-24 min-w-fit text-center disabled:bg-gray-200 disabled:text-black;
}
.input {

View File

@@ -17,26 +17,29 @@
<label for="profiles">Profile</label>
<br>
<select class="input" x-model.number="profileIndex" id="profiles">
<template x-for="(profileName, index) in profileNames" :key="profileName">
<option x-text="profileName" :value="index"></option>
<template x-for="(name, index) in profileNames" :key="index">
<option x-text="name" :value="index"></option>
</template>
</select>
<button class="button" @click="await newProfile()">New</button>
<button class="button" @click="confirmDelete = true" x-show="!confirmDelete">Delete</button>
<button class="button" @click="deleteProfile" x-show="confirmDelete">Confirm Delete</button>
</div>
<!-- KEYS -->
<div class="section">
<h2 class="section-header">Keys</h2>
<form @submit.prevent="saveProfile">
<div class="mt-3">
<label for="profile-name">Profile Name</label>
<br>
<input x-model="profileName" type="text" class="input">
<input x-model="profileName" type="text" class="input" autocapitalize="off" autocomplete="off" spellcheck="off">
</div>
<div class="mt-3">
<label for="priv-key">Private Key</label>
<br>
<input x-model="privKey" type="text" class="input">
<input x-model="privKey" type="text" class="input" autocapitalize="off" autocomplete="off" spellcheck="off">
</div>
<div class="mt-3">
@@ -46,8 +49,10 @@
</div>
<div class="mt-3">
<button class="button" :disabled="!needsSave">Save</button>
<button class="button" :disabled="!needsSave" @click="saveProfile">Save</button>
</div>
</form>
</div>
<!-- RELAYS -->
@@ -102,6 +107,8 @@
<div class="mt-6">
<button class="button" @click="window.close()">Close</button>
<button class="button" @click="confirmClear = true" x-show="!confirmClear">Clear Data</button>
<button class="button" @click="clearData" x-show="confirmClear">Confirm Clear</button>
</div>
</body>

View File

@@ -7,8 +7,10 @@ const RECOMMENDED_RELAYS = [
new URL('wss://relay.snort.social'),
];
const log = console.log;
Alpine.data('options', () => ({
profileNames: ['Default'],
profileNames: ['Poop'],
profileIndex: 0,
profileName: '',
pristineProfileName: '',
@@ -19,12 +21,16 @@ Alpine.data('options', () => ({
newRelay: '',
urlError: '',
recommendedRelay: '',
confirmDelete: false,
confirmClear: false,
async init() {
await this.refreshInfo();
async init(watch = true) {
log('Initialize backend.');
await browser.runtime.sendMessage({ kind: 'init' });
if (watch) {
this.$watch('profileIndex', async () => {
await this.refreshInfo();
await this.refreshProfile();
});
this.$watch('recommendedRelay', async () => {
@@ -32,22 +38,94 @@ Alpine.data('options', () => ({
await this.addRelay(this.recommendedRelay);
this.recommendedRelay = '';
});
}
log('Setting active index.');
await this.getActiveIndex();
await this.refreshProfile();
},
async refreshInfo() {
async refreshProfile() {
await this.getProfileNames();
await this.getNameForProfile();
await this.getPrivKeyForProfile();
await this.getPubKeyForProfile();
await this.getRelaysForProfile();
await this.getProfileName();
await this.getNsec();
await this.getNpub();
this.confirmClear = false;
this.confirmDelete = false;
},
// Profile functions
async getProfileNames() {
this.profileNames = await browser.runtime.sendMessage({
kind: 'getProfileNames',
});
},
async getProfileName() {
this.profileName = await browser.runtime.sendMessage({
kind: 'getNameForProfile',
payload: this.profileIndex,
});
this.pristineProfileName = this.profileName;
},
async getActiveIndex() {
this.profileIndex = await browser.runtime.sendMessage({
kind: 'getProfileIndex',
});
},
async newProfile() {
let newIndex = await browser.runtime.sendMessage({
kind: 'newProfile',
});
await this.getProfileNames();
this.profileIndex = newIndex;
},
async deleteProfile() {
await browser.runtime.sendMessage({
kind: 'deleteProfile',
payload: this.profileIndex,
});
await this.init(false);
},
// Key functions
async saveProfile() {
if (!this.needsSave) return;
await browser.runtime.sendMessage({
kind: 'savePrivateKey',
payload: [this.profileIndex, this.privKey],
});
await browser.runtime.sendMessage({
kind: 'saveProfileName',
payload: [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 getRelaysForProfile() {
this.relays = await browser.runtime.sendMessage({
kind: 'getRelaysForProfile',
@@ -95,27 +173,9 @@ Alpine.data('options', () => ({
}, 3000);
},
async getNameForProfile() {
this.profileName = await browser.runtime.sendMessage({
kind: 'getNameForProfile',
payload: this.profileIndex,
});
this.pristineProfileName = this.profileName;
},
async getPubKeyForProfile() {
this.pubKey = await browser.runtime.sendMessage({
kind: 'getPubKeyForProfile',
payload: this.profileIndex,
});
},
async getPrivKeyForProfile() {
this.privKey = await browser.runtime.sendMessage({
kind: 'getPrivKeyForProfile',
payload: this.profileIndex,
});
this.pristinePrivKey = this.privKey;
async clearData() {
await browser.runtime.sendMessage({ kind: 'clearData' });
await this.init(false);
},
// Properties

View File

@@ -17,48 +17,9 @@
<option x-text="prof" :value="index"></option>
</template>
</select>
<button @click="newProfile">New</button>
<button @click="confirmDelete = true" x-show="!confirmDelete"
:disabled="profileNames.length <= 1">Delete</button>
<button @click="await deleteProfile()" x-show="confirmDelete">Confirm Delete</button>
</div>
</div>
<div class="profile-name">
<label for="profile-name">Profile Name</label>
<input type="text" id="profile-name" x-model="name">
</div>
<div class="key">
<label for="priv-key">Private Key</label>
<input id="priv-key" x-model="privKey" :type="visibleKey ? 'text' : 'password'">
</div>
<div class="buttons">
<button @click="visibleKey = !visibleKey" x-text="visibleKey ? 'Hide' : 'Show'"></button>
<button @click="await saveProfile()" :disabled="!needsSaving">Save</button>
<button @click="confirmClear = true" x-show="!confirmClear">Clear Data</button>
<button @click="await clearData()" x-show="confirmClear">Confirm Clear</button>
</div>
<div x-show="hasValidPubKey">
<label for="pub-key">Pub Key:</label>
<input type="text" id="pub-key" x-model="pubKey" disabled>
</div>
<div class="allowed-sites" x-show="hosts.length > 0">
<h3>Allowed Sites</h3>
<table>
<template x-for="(host, index) in hosts" :key="host.host">
<tr>
<td class="allowed" x-text="host.allowed ? 'Yes' : 'No'"></td>
<td x-text="host.host"></td>
<td><button @click="deleteSite(index)">Delete</button></td>
</tr>
</template>
</table>
</div>
<div class="help">
<button @click='window.open("https://ursus.camp/nostore", "_blank")'>Get Help</button>
<button @click="await openOptions()">Advanced</button>

View File

@@ -5,27 +5,16 @@ window.Alpine = Alpine;
const log = msg => bglog(msg, 'popup');
Alpine.data('popup', () => ({
privKey: '',
pubKey: '',
pristinePrivKey: '',
hosts: [],
name: '',
pristineName: '',
profileNames: ['Default'],
profileIndex: 0,
visibleKey: false,
confirmClear: false,
confirmDelete: false,
async init() {
log('Initializing backend.');
await browser.runtime.sendMessage({ kind: 'init' });
this.$watch('profileIndex', async () => {
await this.getProfileNames();
await this.setProfileIndex();
await this.refreshProfile();
this.confirmClear = false;
this.confirmDelete = false;
});
// Even though getProfileIndex will immediately trigger a profile refresh, we still
@@ -33,16 +22,8 @@ Alpine.data('popup', () => ({
// 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.getProfileIndex();
},
async setProfileIndex() {
@@ -56,89 +37,22 @@ Alpine.data('popup', () => ({
}
},
async getNsecKey() {
this.privKey = await browser.runtime.sendMessage({
kind: 'getNsecKey',
});
this.pristinePrivKey = this.privKey;
},
async getNpubKey() {
this.pubKey = await browser.runtime.sendMessage({
kind: 'getNpubKey',
});
},
async getHosts() {
this.hosts = await browser.runtime.sendMessage({
kind: 'getHosts',
});
},
async getProfileNames() {
this.profileNames = await browser.runtime.sendMessage({
kind: 'getProfileNames',
});
},
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;
},
async openOptions() {
await browser.runtime.openOptionsPage();
window.close();
},
// Properties
get hasValidPubKey() {
return typeof this.pubKey === 'string' && this.pubKey.length > 0;
},
get needsSaving() {
return (
this.privKey !== this.pristinePrivKey ||
this.name !== this.pristineName
);
},
}));
Alpine.start();