diff --git a/Nostore.xcodeproj/project.pbxproj b/Nostore.xcodeproj/project.pbxproj
index 50fb97a..f069b5d 100644
--- a/Nostore.xcodeproj/project.pbxproj
+++ b/Nostore.xcodeproj/project.pbxproj
@@ -71,6 +71,12 @@
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 */; };
+ 944A6DD32988BA200032C2E3 /* permission.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DD22988BA200032C2E3 /* permission.html */; };
+ 944A6DD42988BA200032C2E3 /* permission.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DD22988BA200032C2E3 /* permission.html */; };
+ 944A6DD62988BD230032C2E3 /* permission.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DD52988BD230032C2E3 /* permission.js */; };
+ 944A6DD72988BD230032C2E3 /* permission.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DD52988BD230032C2E3 /* permission.js */; };
+ 944A6DD92988D7900032C2E3 /* permission.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DD82988D7900032C2E3 /* permission.build.js */; };
+ 944A6DDA2988D7900032C2E3 /* permission.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DD82988D7900032C2E3 /* permission.build.js */; };
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 */; };
@@ -171,6 +177,9 @@
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 = ""; };
+ 944A6DD22988BA200032C2E3 /* permission.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = permission.html; sourceTree = ""; };
+ 944A6DD52988BD230032C2E3 /* permission.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = permission.js; sourceTree = ""; };
+ 944A6DD82988D7900032C2E3 /* permission.build.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = permission.build.js; 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 = ""; };
@@ -258,6 +267,9 @@
941B03A2296FA90400CA291E /* Resources */ = {
isa = PBXGroup;
children = (
+ 944A6DD82988D7900032C2E3 /* permission.build.js */,
+ 944A6DD52988BD230032C2E3 /* permission.js */,
+ 944A6DD22988BA200032C2E3 /* permission.html */,
948C69E4297F8BA600FB3574 /* options.build.css */,
948C69E1297F891F00FB3574 /* options.build.js */,
948C69DB297F88A200FB3574 /* options.css */,
@@ -501,7 +513,9 @@
941B0413297110F100CA291E /* background.build.js in Resources */,
948C69E82982DFE900FB3574 /* background.html in Resources */,
948C69DF297F88A200FB3574 /* options.js in Resources */,
+ 944A6DD32988BA200032C2E3 /* permission.html in Resources */,
948C69DD297F88A200FB3574 /* options.css in Resources */,
+ 944A6DD92988D7900032C2E3 /* permission.build.js in Resources */,
941B03F2296FA90400CA291E /* background.js in Resources */,
948C69E2297F891F00FB3574 /* options.build.js in Resources */,
941B03F8296FA90400CA291E /* popup.css in Resources */,
@@ -512,6 +526,7 @@
941B040D296FAD6900CA291E /* nostr.js in Resources */,
941B03EE296FA90400CA291E /* images in Resources */,
941B03F0296FA90400CA291E /* manifest.json in Resources */,
+ 944A6DD62988BD230032C2E3 /* permission.js in Resources */,
941B04312978CDF900CA291E /* Icon-32.png in Resources */,
941B041A2971139000CA291E /* content.build.js in Resources */,
941B041C2971139000CA291E /* popup.build.js in Resources */,
@@ -535,7 +550,9 @@
941B0414297110F100CA291E /* background.build.js in Resources */,
948C69E92982DFE900FB3574 /* background.html in Resources */,
948C69E0297F88A200FB3574 /* options.js in Resources */,
+ 944A6DD42988BA200032C2E3 /* permission.html in Resources */,
948C69DE297F88A200FB3574 /* options.css in Resources */,
+ 944A6DDA2988D7900032C2E3 /* permission.build.js in Resources */,
941B03F3296FA90400CA291E /* background.js in Resources */,
948C69E3297F891F00FB3574 /* options.build.js in Resources */,
941B03F9296FA90400CA291E /* popup.css in Resources */,
@@ -546,6 +563,7 @@
941B040E296FAD6900CA291E /* nostr.js in Resources */,
941B03EF296FA90400CA291E /* images in Resources */,
941B03F1296FA90400CA291E /* manifest.json in Resources */,
+ 944A6DD72988BD230032C2E3 /* permission.js in Resources */,
941B04322978CDF900CA291E /* Icon-32.png in Resources */,
941B041B2971139000CA291E /* content.build.js in Resources */,
941B041D2971139000CA291E /* popup.build.js in Resources */,
@@ -764,7 +782,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
@@ -795,7 +813,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
@@ -830,7 +848,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
@@ -872,7 +890,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
@@ -913,7 +931,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/nostore.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -945,7 +963,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/nostore.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -980,7 +998,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "macOS (App)/nostore.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -1016,7 +1034,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "macOS (App)/nostore.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 4;
+ CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 5SD834TD9W;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
diff --git a/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist b/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist
index 2fa6e1a..1e154e6 100644
--- a/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,12 +7,12 @@
Nostore (iOS).xcscheme_^#shared#^_
orderHint
- 1
+ 0
Nostore (macOS).xcscheme_^#shared#^_
orderHint
- 0
+ 1
nostore (iOS).xcscheme_^#shared#^_
diff --git a/Shared (Extension)/Resources/background.js b/Shared (Extension)/Resources/background.js
index a55816a..68e37cf 100644
--- a/Shared (Extension)/Resources/background.js
+++ b/Shared (Extension)/Resources/background.js
@@ -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) {
diff --git a/Shared (Extension)/Resources/content.js b/Shared (Extension)/Resources/content.js
index 7dfd747..b81a3d5 100644
--- a/Shared (Extension)/Resources/content.js
+++ b/Shared (Extension)/Resources/content.js
@@ -13,7 +13,12 @@ window.addEventListener('message', async message => {
let { kind, reqId, payload } = message.data;
if (!validEvents.includes(kind)) return;
- payload = await browser.runtime.sendMessage({ kind, payload });
+ payload = await browser.runtime.sendMessage({
+ kind,
+ payload,
+ host: window.location.host,
+ });
+ console.log(payload);
kind = `return_${kind}`;
diff --git a/Shared (Extension)/Resources/nostr.js b/Shared (Extension)/Resources/nostr.js
index 83371fd..92dd87a 100644
--- a/Shared (Extension)/Resources/nostr.js
+++ b/Shared (Extension)/Resources/nostr.js
@@ -56,6 +56,6 @@ window.addEventListener('message', message => {
if (!validEvents.includes(kind)) return;
- window.nostr.requests[reqId](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
index bdc4b3d..66a7c3b 100644
--- a/Shared (Extension)/Resources/options.css
+++ b/Shared (Extension)/Resources/options.css
@@ -34,4 +34,12 @@
.section-header {
@apply text-2xl lg:text-5xl font-bold;
}
+
+ .subsection-header {
+ @apply text-xl lg:text-4xl font-bold;
+ }
+
+ a {
+ @apply border-2 border-dotted text-fuchsia-800 border-fuchsia-800 hover:border-transparent;
+ }
}
\ No newline at end of file
diff --git a/Shared (Extension)/Resources/options.html b/Shared (Extension)/Resources/options.html
index cd57dd4..b520413 100644
--- a/Shared (Extension)/Resources/options.html
+++ b/Shared (Extension)/Resources/options.html
@@ -22,9 +22,8 @@
-
-
-
+
+
@@ -53,7 +52,7 @@
-
+
@@ -81,7 +80,7 @@
-
+
|
@@ -111,10 +110,52 @@
+
+
+
+
+
+ Permissions granted to individual applications.
+ Everything defaults to Ask unless you have saved a different option.
+
+
+
+
+
+
+
+
+
You have not remembered any app requests yet.
+
+
+
+ | App Request |
+ Action |
+
+
+
+ |
+
+
+ |
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+ The host
+
+ is requesting the following permission:
+ .
+
+
+ Event kind is .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Shared (Extension)/Resources/options.js b/Shared (Extension)/Resources/options.js
index 7e7f5f7..8e34260 100644
--- a/Shared (Extension)/Resources/options.js
+++ b/Shared (Extension)/Resources/options.js
@@ -11,12 +11,16 @@ import {
saveProfileName,
saveRelays,
RECOMMENDED_RELAYS,
+ getPermissions,
+ setPermission,
+ KINDS,
+ humanPermission,
} from './utils';
const log = console.log;
Alpine.data('options', () => ({
- profileNames: ['Poop'],
+ profileNames: ['---'],
profileIndex: 0,
profileName: '',
pristineProfileName: '',
@@ -27,8 +31,11 @@ Alpine.data('options', () => ({
newRelay: '',
urlError: '',
recommendedRelay: '',
- confirmDelete: false,
- confirmClear: false,
+ permissions: {},
+ host: '',
+ permHosts: [],
+ hostPerms: [],
+ setPermission,
async init(watch = true) {
log('Initialize backend.');
@@ -37,6 +44,11 @@ Alpine.data('options', () => ({
if (watch) {
this.$watch('profileIndex', async () => {
await this.refreshProfile();
+ this.host = '';
+ });
+
+ this.$watch('host', () => {
+ this.calcHostPerms();
});
this.$watch('recommendedRelay', async () => {
@@ -59,8 +71,7 @@ Alpine.data('options', () => ({
await this.getNsec();
await this.getNpub();
await this.getRelays();
- this.confirmClear = false;
- this.confirmDelete = false;
+ await this.getPermissions();
},
// Profile functions
@@ -87,8 +98,14 @@ Alpine.data('options', () => ({
},
async deleteProfile() {
- await deleteProfile(this.profileIndex);
- await this.init(false);
+ if (
+ confirm(
+ 'This will delete this profile and all associated data. Are you sure you wish to continue?'
+ )
+ ) {
+ await deleteProfile(this.profileIndex);
+ await this.init(false);
+ }
},
// Key functions
@@ -96,9 +113,13 @@ Alpine.data('options', () => ({
async saveProfile() {
if (!this.needsSave) return;
+ console.log('saving private key');
await savePrivateKey(this.profileIndex, this.privKey);
+ console.log('saving profile name');
await saveProfileName(this.profileIndex, this.profileName);
+ console.log('getting profile name');
await this.getProfileNames();
+ console.log('refreshing profile');
await this.refreshProfile();
},
@@ -161,11 +182,57 @@ Alpine.data('options', () => ({
}, 3000);
},
+ // Permissions
+
+ async getPermissions() {
+ this.permissions = await getPermissions(this.profileIndex);
+
+ // Set the convenience variables
+ this.calcPermHosts();
+ this.calcHostPerms();
+ },
+
+ calcPermHosts() {
+ let hosts = Object.keys(this.permissions);
+ hosts.sort();
+ this.permHosts = hosts;
+ },
+
+ calcHostPerms() {
+ let hp = this.permissions[this.host] || {};
+ let keys = Object.keys(hp);
+ keys.sort();
+ this.hostPerms = keys.map(k => [k, humanPermission(k), hp[k]]);
+ console.log(this.hostPerms);
+ },
+
+ permTypes(hostPerms) {
+ let k = Object.keys(hostPerms);
+ k = Object.keys.sort();
+ k = k.map(p => {
+ let e = [p, hostPerms[p]];
+ if (p.startsWith('signEvent')) {
+ let n = parseInt(p.split(':')[1]);
+ let name =
+ KINDS.find(kind => kind[0] === n) || `Unknown (Kind ${n})`;
+ e = [name, hostPerms[p]];
+ }
+ return e;
+ });
+ return k;
+ },
+
// General
async clearData() {
- await clearData();
- await this.init(false);
+ if (
+ confirm(
+ 'This will remove your private keys and all associated data. Are you sure you wish to continue?'
+ )
+ ) {
+ await clearData();
+ await this.init(false);
+ }
},
// Properties
diff --git a/Shared (Extension)/Resources/permission.html b/Shared (Extension)/Resources/permission.html
new file mode 100644
index 0000000..846f0ab
--- /dev/null
+++ b/Shared (Extension)/Resources/permission.html
@@ -0,0 +1,43 @@
+
+
+
+