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.
This commit is contained in:
@@ -5,81 +5,197 @@ import {
|
||||
nip04,
|
||||
nip19,
|
||||
} from 'nostr-tools';
|
||||
|
||||
import { getProfileIndex, get, getProfile } from './utils';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import {
|
||||
getProfileIndex,
|
||||
get,
|
||||
getProfile,
|
||||
getPermission,
|
||||
setPermission,
|
||||
} from './utils';
|
||||
|
||||
const storage = browser.storage.local;
|
||||
const log = msg => console.log('Background: ', msg);
|
||||
const validations = {};
|
||||
let prompt = { mutex: new Mutex(), release: null, tabId: null };
|
||||
|
||||
browser.runtime.onInstalled.addListener(async ({ reason }) => {
|
||||
// I would like to be able to skip this for development purposes
|
||||
let ignoreHook = (await storage.get({ ignoreInstallHook: false }))
|
||||
.ignoreInstallHook;
|
||||
if (ignoreHook === true) {
|
||||
return;
|
||||
}
|
||||
if (['install'].includes(reason)) {
|
||||
browser.tabs.create({
|
||||
url: 'https://ursus.camp/nostore',
|
||||
});
|
||||
// let ignoreHook = (await storage.get({ ignoreInstallHook: false }))
|
||||
// .ignoreInstallHook;
|
||||
// if (ignoreHook === true) {
|
||||
// return;
|
||||
// }
|
||||
// if (['install'].includes(reason)) {
|
||||
// browser.tabs.create({
|
||||
// url: 'https://ursus.camp/nostore',
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
log(message);
|
||||
let uuid = crypto.randomUUID();
|
||||
let sr;
|
||||
|
||||
switch (message.kind) {
|
||||
// General
|
||||
case 'closePrompt':
|
||||
prompt.release?.();
|
||||
return Promise.resolve(true);
|
||||
case 'allowed':
|
||||
complete(message);
|
||||
return Promise.resolve(true);
|
||||
case 'denied':
|
||||
deny(message);
|
||||
return Promise.resolve(true);
|
||||
case 'generatePrivateKey':
|
||||
return Promise.resolve(generatePrivateKey());
|
||||
case 'savePrivateKey':
|
||||
return savePrivateKey(message.payload);
|
||||
case 'getNpub':
|
||||
return getNpub(message.payload);
|
||||
case 'getNsec':
|
||||
return getNsec(message.payload);
|
||||
|
||||
// window.nostr
|
||||
case 'getPubKey':
|
||||
case 'signEvent':
|
||||
case 'nip04.encrypt':
|
||||
case 'nip04.decrypt':
|
||||
case 'getRelays':
|
||||
console.log('asking');
|
||||
validations[uuid] = sendResponse;
|
||||
ask(uuid, message);
|
||||
setTimeout(() => {
|
||||
console.log('timeout release');
|
||||
prompt.release?.();
|
||||
}, 10_000);
|
||||
return true;
|
||||
default:
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener(
|
||||
async (message, _sender, sendResponse) => {
|
||||
log(message);
|
||||
async function forceRelease() {
|
||||
if (prompt.tabId !== null) {
|
||||
try {
|
||||
// If the previous prompt is still open, then this won't do anything.
|
||||
// If it's not open, it will throw an error and get caught.
|
||||
await browser.tabs.get(prompt.tabId);
|
||||
} catch (error) {
|
||||
// If the tab is closed, but somehow escaped our event handling, we can clean it up here
|
||||
// before attempting to open the next tab.
|
||||
prompt.release?.();
|
||||
prompt.tabId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.kind) {
|
||||
// General
|
||||
case 'log':
|
||||
console.log(
|
||||
message.payload.module ? `${module}: ` : '',
|
||||
message.payload.msg
|
||||
);
|
||||
break;
|
||||
case 'generatePrivateKey':
|
||||
sendResponse(generatePrivateKey());
|
||||
break;
|
||||
case 'savePrivateKey':
|
||||
await savePrivateKey(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;
|
||||
async function ask(uuid, { kind, host, payload }) {
|
||||
await forceRelease(); // Clean up previous tab if it closed without cleaning itself up
|
||||
prompt.release = await prompt.mutex.acquire();
|
||||
|
||||
let mKind = kind === 'signEvent' ? `signEvent:${payload.kind}` : kind;
|
||||
let permission = await getPermission(host, mKind);
|
||||
console.log('existing permission: ', permission);
|
||||
if (permission === 'allow') {
|
||||
console.log('already allowed');
|
||||
complete({
|
||||
payload: uuid,
|
||||
origKind: kind,
|
||||
event: payload,
|
||||
remember: false,
|
||||
host,
|
||||
});
|
||||
prompt.release();
|
||||
return;
|
||||
}
|
||||
|
||||
if (permission === 'deny') {
|
||||
console.log('already denied');
|
||||
deny({ payload: uuid, origKind: kind, host });
|
||||
prompt.release();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('creating asking popup');
|
||||
let qs = new URLSearchParams({
|
||||
uuid,
|
||||
kind,
|
||||
host,
|
||||
payload: JSON.stringify(payload || false),
|
||||
});
|
||||
let tab = await browser.tabs.getCurrent();
|
||||
let p = await browser.tabs.create({
|
||||
url: `/permission.html?${qs.toString()}`,
|
||||
openerTabId: tab.id,
|
||||
});
|
||||
prompt.tabId = p.id;
|
||||
return true;
|
||||
}
|
||||
|
||||
function complete({ payload, origKind, event, remember, host }) {
|
||||
console.log('complete');
|
||||
sendResponse = validations[payload];
|
||||
|
||||
if (remember) {
|
||||
console.log('saving permission');
|
||||
let mKind =
|
||||
origKind === 'signEvent' ? `signEvent:${event.kind}` : origKind;
|
||||
setPermission(host, mKind, 'allow');
|
||||
}
|
||||
|
||||
if (sendResponse) {
|
||||
console.log('sendResponse found');
|
||||
switch (origKind) {
|
||||
case 'getPubKey':
|
||||
let pubKey = await getPubKey();
|
||||
sendResponse(pubKey);
|
||||
getPubKey().then(pk => {
|
||||
console.log(pk);
|
||||
sendResponse(pk);
|
||||
});
|
||||
break;
|
||||
|
||||
// window.nostr
|
||||
case 'signEvent':
|
||||
let event = await signEvent_(message.payload);
|
||||
sendResponse(event);
|
||||
signEvent_(event).then(e => sendResponse(e));
|
||||
break;
|
||||
case 'nip04.encrypt':
|
||||
let cipherText = await nip04Encrypt(message.payload);
|
||||
sendResponse(cipherText);
|
||||
nip04Encrypt(event).then(e => sendResponse(e));
|
||||
break;
|
||||
case 'nip04.decrypt':
|
||||
let plainText = await nip04Decrypt(message.payload);
|
||||
sendResponse(plainText);
|
||||
nip04Decrypt(event).then(e => sendResponse(e));
|
||||
break;
|
||||
case 'getRelays':
|
||||
let relays = await getRelays();
|
||||
sendResponse(relays);
|
||||
break;
|
||||
|
||||
default:
|
||||
getRelays().then(e => sendResponse(e));
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function deny({ origKind, host, payload, remember, event }) {
|
||||
console.log('denied');
|
||||
sendResponse = validations[payload];
|
||||
|
||||
if (remember) {
|
||||
console.log('saving permission');
|
||||
let mKind =
|
||||
origKind === 'signEvent' ? `signEvent:${event.kind}` : origKind;
|
||||
setPermission(host, mKind, 'deny');
|
||||
}
|
||||
|
||||
sendResponse?.(undefined);
|
||||
return false;
|
||||
}
|
||||
|
||||
function keyDeleter(key) {
|
||||
return new Promise(resolver => {
|
||||
setTimeout(() => {
|
||||
console.log('Validations: ', validations);
|
||||
console.log('Deleting key validations: ', key);
|
||||
resolver();
|
||||
delete validations[key];
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// Options
|
||||
async function savePrivateKey([index, privKey]) {
|
||||
@@ -89,6 +205,7 @@ async function savePrivateKey([index, privKey]) {
|
||||
let profiles = await get('profiles');
|
||||
profiles[index].privKey = privKey;
|
||||
await storage.set({ profiles });
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getNsec(index) {
|
||||
|
||||
Reference in New Issue
Block a user