Files
nostash/Shared (Extension)/Resources/utils.js
Ryan Breen c832aa22af Permissions UI with user requests and modification post-saving.
Commits:

Basic, functional permission tab when requesting getPubKey. Only allows one time deny.

getPubKey and getRelays for sure working after prompt.

Changed the prompt to use a query string instead of background script queue. This should prevent any disconnect between the user expecting one thing and getting a different prompt.

It is not working using query string and working quite nicely.

Finally figured out the secret sauce to only open one prompt at a time.

Allow and deny now both work, with the option to remember the request next time.

Still tweaking lots of events to try and get the prompts working smoothly

Nice rendering for the event query message.

Tweaking the migration setup and the tab opening/closing code.

When remembering signing events, it is now scoped by event kind, as well!

Include extra event information in event signing dialogs.

Change confirm buttons to confirm dialog box.

Update nostr-tools to 1.2.1

The interface for app permissions looks good. Ready to work on functionality now.

Don't show app permissions section unless there are things to show.

Fix bug when saving a "Deny".

Additional formatting changes to options page for permissions UI.

Fine permissions seem to be working nicely!

Quick usability fix so that App Permissions section appears on Options page, even when there are no options selected.

Bumping build to #5. Preparing for new build release.
2023-02-05 20:40:30 -05:00

209 lines
6.9 KiB
JavaScript

const DB_VERSION = 1;
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'),
];
// prettier-ignore
export const KINDS = [
[0, 'Metadata', 'https://github.com/nostr-protocol/nips/blob/master/01.md'],
[1, 'Text', 'https://github.com/nostr-protocol/nips/blob/master/01.md'],
[2, 'Recommend Relay', 'https://github.com/nostr-protocol/nips/blob/master/01.md'],
[3, 'Contacts', 'https://github.com/nostr-protocol/nips/blob/master/02.md'],
[4, 'Encrypted Direct Messages', 'https://github.com/nostr-protocol/nips/blob/master/04.md'],
[5, 'Event Deletion', 'https://github.com/nostr-protocol/nips/blob/master/09.md'],
[7, 'Reaction', 'https://github.com/nostr-protocol/nips/blob/master/25.md'],
[40, 'Channel Creation', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[41, 'Channel Metadata', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[42, 'Channel Message', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[43, 'Channel Hide Message', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[44, 'Channel Mute User', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
];
export async function initialize() {
await getOrSetDefault('profileIndex', 0);
await getOrSetDefault('profiles', [await generateProfile()]);
let version = (await storage.get({ version: 0 })).version;
console.log('DB version: ', version);
while (version < DB_VERSION) {
version = await migrate(version, DB_VERSION);
await storage.set({ version });
}
}
async function migrate(version, goal) {
if (version === 0) {
console.log('Migrating to version 1.');
let profiles = await getProfiles();
profiles.forEach(profile => (profile.hosts = {}));
await storage.set({ profiles });
return version + 1;
}
}
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];
}
export async function getPermissions(index = null) {
if (!index) {
index = await getProfileIndex();
}
let profile = await getProfile(index);
let hosts = await profile.hosts;
return hosts;
}
export async function getPermission(host, action) {
let index = await getProfileIndex();
let profile = await getProfile(index);
console.log(host, action);
console.log('profile: ', profile);
return profile.hosts?.[host]?.[action] || 'ask';
}
export async function setPermission(host, action, perm, index = null) {
let profiles = await getProfiles();
if (!index) {
index = await getProfileIndex();
}
let profile = profiles[index];
let newPerms = profile.hosts[host] || {};
newPerms = { ...newPerms, [action]: perm };
profile.hosts[host] = newPerms;
profiles[index] = profile;
await storage.set({ profiles });
}
export function humanPermission(p) {
// Handle special case where event signing includes a kind number
if (p.startsWith('signEvent:')) {
let [e, n] = p.split(':');
n = parseInt(n);
let nname = KINDS.find(k => k[0] === n)?.[1] || `Unknown (Kind ${n})`;
return `Sign event: ${nname}`;
}
switch (p) {
case 'getPubKey':
return 'Read public key';
case 'signEvent':
return 'Sign event';
case 'getRelays':
return 'Read relay list';
case 'nip04.encrypt':
return 'Encrypt private message';
case 'nip04.decrypt':
return 'Decrypt private message';
default:
return 'Unknown';
}
}