Added page for Experimental features, with NIP-26 support.
This commit is contained in:
@@ -77,6 +77,18 @@
|
||||
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 */; };
|
||||
944A6DE02991DFC60032C2E3 /* delegation_wizard.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DDC2991DFC60032C2E3 /* delegation_wizard.build.js */; };
|
||||
944A6DE12991DFC60032C2E3 /* delegation_wizard.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DDC2991DFC60032C2E3 /* delegation_wizard.build.js */; };
|
||||
944A6DE22991DFC60032C2E3 /* delegation_wizard.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DDD2991DFC60032C2E3 /* delegation_wizard.js */; };
|
||||
944A6DE32991DFC60032C2E3 /* delegation_wizard.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DDD2991DFC60032C2E3 /* delegation_wizard.js */; };
|
||||
944A6DE52991E4550032C2E3 /* delegation_wizard.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE42991E4550032C2E3 /* delegation_wizard.html */; };
|
||||
944A6DE62991E4550032C2E3 /* delegation_wizard.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE42991E4550032C2E3 /* delegation_wizard.html */; };
|
||||
944A6DEA299682EF0032C2E3 /* experimental.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE7299682EF0032C2E3 /* experimental.html */; };
|
||||
944A6DEB299682EF0032C2E3 /* experimental.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE7299682EF0032C2E3 /* experimental.html */; };
|
||||
944A6DEC299682EF0032C2E3 /* experimental.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE8299682EF0032C2E3 /* experimental.js */; };
|
||||
944A6DED299682EF0032C2E3 /* experimental.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE8299682EF0032C2E3 /* experimental.js */; };
|
||||
944A6DEE299682EF0032C2E3 /* experimental.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE9299682EF0032C2E3 /* experimental.build.js */; };
|
||||
944A6DEF299682EF0032C2E3 /* experimental.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE9299682EF0032C2E3 /* experimental.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 */; };
|
||||
@@ -180,6 +192,12 @@
|
||||
944A6DD22988BA200032C2E3 /* permission.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = permission.html; sourceTree = "<group>"; };
|
||||
944A6DD52988BD230032C2E3 /* permission.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = permission.js; sourceTree = "<group>"; };
|
||||
944A6DD82988D7900032C2E3 /* permission.build.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = permission.build.js; sourceTree = "<group>"; };
|
||||
944A6DDC2991DFC60032C2E3 /* delegation_wizard.build.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = delegation_wizard.build.js; sourceTree = "<group>"; };
|
||||
944A6DDD2991DFC60032C2E3 /* delegation_wizard.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = delegation_wizard.js; sourceTree = "<group>"; };
|
||||
944A6DE42991E4550032C2E3 /* delegation_wizard.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = delegation_wizard.html; sourceTree = "<group>"; };
|
||||
944A6DE7299682EF0032C2E3 /* experimental.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = experimental.html; sourceTree = "<group>"; };
|
||||
944A6DE8299682EF0032C2E3 /* experimental.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = experimental.js; sourceTree = "<group>"; };
|
||||
944A6DE9299682EF0032C2E3 /* experimental.build.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = experimental.build.js; sourceTree = "<group>"; };
|
||||
948C69D8297F887600FB3574 /* options.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = options.html; sourceTree = "<group>"; };
|
||||
948C69DB297F88A200FB3574 /* options.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = options.css; sourceTree = "<group>"; };
|
||||
948C69DC297F88A200FB3574 /* options.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = options.js; sourceTree = "<group>"; };
|
||||
@@ -267,6 +285,12 @@
|
||||
941B03A2296FA90400CA291E /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
944A6DE9299682EF0032C2E3 /* experimental.build.js */,
|
||||
944A6DE7299682EF0032C2E3 /* experimental.html */,
|
||||
944A6DE8299682EF0032C2E3 /* experimental.js */,
|
||||
944A6DE42991E4550032C2E3 /* delegation_wizard.html */,
|
||||
944A6DDC2991DFC60032C2E3 /* delegation_wizard.build.js */,
|
||||
944A6DDD2991DFC60032C2E3 /* delegation_wizard.js */,
|
||||
944A6DD82988D7900032C2E3 /* permission.build.js */,
|
||||
944A6DD52988BD230032C2E3 /* permission.js */,
|
||||
944A6DD22988BA200032C2E3 /* permission.html */,
|
||||
@@ -510,8 +534,12 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
944A6DEE299682EF0032C2E3 /* experimental.build.js in Resources */,
|
||||
941B0413297110F100CA291E /* background.build.js in Resources */,
|
||||
944A6DE22991DFC60032C2E3 /* delegation_wizard.js in Resources */,
|
||||
944A6DE52991E4550032C2E3 /* delegation_wizard.html in Resources */,
|
||||
948C69E82982DFE900FB3574 /* background.html in Resources */,
|
||||
944A6DEA299682EF0032C2E3 /* experimental.html in Resources */,
|
||||
948C69DF297F88A200FB3574 /* options.js in Resources */,
|
||||
944A6DD32988BA200032C2E3 /* permission.html in Resources */,
|
||||
948C69DD297F88A200FB3574 /* options.css in Resources */,
|
||||
@@ -524,12 +552,14 @@
|
||||
941B042C2978CD8E00CA291E /* iOS-Icon-1024.png in Resources */,
|
||||
941B03F6296FA90400CA291E /* popup.html in Resources */,
|
||||
941B040D296FAD6900CA291E /* nostr.js in Resources */,
|
||||
944A6DE02991DFC60032C2E3 /* delegation_wizard.build.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 */,
|
||||
944A6DEC299682EF0032C2E3 /* experimental.js in Resources */,
|
||||
941B03EC296FA90400CA291E /* _locales in Resources */,
|
||||
941B04222977A25700CA291E /* Icon-512.png in Resources */,
|
||||
948C69E5297F8BA600FB3574 /* options.build.css in Resources */,
|
||||
@@ -547,8 +577,12 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
944A6DEF299682EF0032C2E3 /* experimental.build.js in Resources */,
|
||||
941B0414297110F100CA291E /* background.build.js in Resources */,
|
||||
944A6DE32991DFC60032C2E3 /* delegation_wizard.js in Resources */,
|
||||
944A6DE62991E4550032C2E3 /* delegation_wizard.html in Resources */,
|
||||
948C69E92982DFE900FB3574 /* background.html in Resources */,
|
||||
944A6DEB299682EF0032C2E3 /* experimental.html in Resources */,
|
||||
948C69E0297F88A200FB3574 /* options.js in Resources */,
|
||||
944A6DD42988BA200032C2E3 /* permission.html in Resources */,
|
||||
948C69DE297F88A200FB3574 /* options.css in Resources */,
|
||||
@@ -561,12 +595,14 @@
|
||||
941B042D2978CD8E00CA291E /* iOS-Icon-1024.png in Resources */,
|
||||
941B03F7296FA90400CA291E /* popup.html in Resources */,
|
||||
941B040E296FAD6900CA291E /* nostr.js in Resources */,
|
||||
944A6DE12991DFC60032C2E3 /* delegation_wizard.build.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 */,
|
||||
944A6DED299682EF0032C2E3 /* experimental.js in Resources */,
|
||||
941B03ED296FA90400CA291E /* _locales in Resources */,
|
||||
941B04232977A25700CA291E /* Icon-512.png in Resources */,
|
||||
948C69E6297F8BA600FB3574 /* options.build.css in Resources */,
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
signEvent,
|
||||
nip04,
|
||||
nip19,
|
||||
nip26,
|
||||
getEventHash,
|
||||
} from 'nostr-tools';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import {
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
getProfile,
|
||||
getPermission,
|
||||
setPermission,
|
||||
feature,
|
||||
} from './utils';
|
||||
|
||||
const storage = browser.storage.local;
|
||||
@@ -19,20 +22,6 @@ 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',
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
log(message);
|
||||
let uuid = crypto.randomUUID();
|
||||
@@ -57,6 +46,10 @@ browser.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
return getNpub(message.payload);
|
||||
case 'getNsec':
|
||||
return getNsec(message.payload);
|
||||
case 'createDelegation':
|
||||
return createDelegation(message.payload);
|
||||
case 'calcPubKey':
|
||||
return Promise.resolve(getPublicKey(message.payload));
|
||||
|
||||
// window.nostr
|
||||
case 'getPubKey':
|
||||
@@ -64,11 +57,9 @@ browser.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
case 'nip04.encrypt':
|
||||
case 'nip04.decrypt':
|
||||
case 'getRelays':
|
||||
console.log('asking');
|
||||
validations[uuid] = sendResponse;
|
||||
ask(uuid, message);
|
||||
setDelegation(message).then(() => ask(uuid, message));
|
||||
setTimeout(() => {
|
||||
console.log('timeout release');
|
||||
prompt.release?.();
|
||||
}, 10_000);
|
||||
return true;
|
||||
@@ -98,9 +89,7 @@ async function ask(uuid, { kind, host, payload }) {
|
||||
|
||||
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,
|
||||
@@ -113,13 +102,11 @@ async function ask(uuid, { kind, host, payload }) {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -136,22 +123,18 @@ async function ask(uuid, { kind, host, payload }) {
|
||||
}
|
||||
|
||||
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':
|
||||
getPubKey().then(pk => {
|
||||
console.log(pk);
|
||||
sendResponse(pk);
|
||||
});
|
||||
break;
|
||||
@@ -172,11 +155,9 @@ function complete({ payload, origKind, event, remember, host }) {
|
||||
}
|
||||
|
||||
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');
|
||||
@@ -186,17 +167,6 @@ function deny({ origKind, host, payload, remember, event }) {
|
||||
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]) {
|
||||
if (privKey.startsWith('nsec')) {
|
||||
@@ -227,6 +197,11 @@ async function getPrivKey() {
|
||||
}
|
||||
|
||||
async function getPubKey() {
|
||||
let pi = await getProfileIndex();
|
||||
let profile = await getProfile(pi);
|
||||
if (profile.delegate) {
|
||||
return profile.delegator;
|
||||
}
|
||||
let privKey = await getPrivKey();
|
||||
let pubKey = getPublicKey(privKey);
|
||||
return pubKey;
|
||||
@@ -239,9 +214,13 @@ async function currentProfile() {
|
||||
}
|
||||
|
||||
async function signEvent_(event) {
|
||||
event = { ...event };
|
||||
event = JSON.parse(JSON.stringify(event));
|
||||
let privKey = await getPrivKey();
|
||||
let pubKey = getPublicKey(privKey);
|
||||
event.pubkey = pubKey;
|
||||
event.id = getEventHash(event);
|
||||
event.sig = signEvent(event, privKey);
|
||||
console.log(JSON.stringify(event));
|
||||
return event;
|
||||
}
|
||||
|
||||
@@ -266,3 +245,42 @@ async function getRelays() {
|
||||
});
|
||||
return relayObj;
|
||||
}
|
||||
|
||||
function createDelegation({
|
||||
kind,
|
||||
delegatorPrivKey,
|
||||
delegateePubKey,
|
||||
until,
|
||||
since,
|
||||
}) {
|
||||
delegatorPrivKey = nip19.decode(delegatorPrivKey).data;
|
||||
let delegation = nip26.createDelegation(delegatorPrivKey, {
|
||||
pubkey: delegateePubKey,
|
||||
until,
|
||||
// kind,
|
||||
// since,
|
||||
});
|
||||
return Promise.resolve(delegation);
|
||||
}
|
||||
|
||||
async function setDelegation({ payload }) {
|
||||
if (!payload) return;
|
||||
let active = await feature('delegation');
|
||||
if (!active) return;
|
||||
|
||||
let { delegate, delegation } = await currentProfile();
|
||||
|
||||
// Nothing to do if this is not a delegate
|
||||
if (!delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
payload.tags = payload.tags || [];
|
||||
|
||||
payload.tags.push([
|
||||
'delegation',
|
||||
delegation.from,
|
||||
delegation.cond,
|
||||
delegation.sig,
|
||||
]);
|
||||
}
|
||||
|
||||
59
Shared (Extension)/Resources/delegation_wizard.html
Normal file
59
Shared (Extension)/Resources/delegation_wizard.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script defer src="delegation_wizard.build.js"></script>
|
||||
<link rel="stylesheet" href="options.build.css">
|
||||
|
||||
<title>Delegation Wizard</title>
|
||||
</head>
|
||||
|
||||
<body x-data="delegated" class="text-fuchsia-900 p-3.5 lg:p-32">
|
||||
<h1 class="text-3xl lg:text-6xl font-bold md:text-center">New Delegated Profile</h1>
|
||||
|
||||
<p class="mt-6">
|
||||
A delegated user, as laid out in the
|
||||
<a href="https://github.com/nostr-protocol/nips/blob/master/26.md" @click.prevent="openNip($event)">NIP-26
|
||||
specification</a>,
|
||||
is a user that is authorized to sign events for a different user. Additional limits can be
|
||||
put on the delegated account, such as time limits.
|
||||
</p>
|
||||
|
||||
<p class="mt-3">
|
||||
This may be useful if you wish to keep your main key stored safely offline, but still post as that user
|
||||
with a throwaway key.
|
||||
</p>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-header">Delegator</h2>
|
||||
<p class="text-small italic">The secure account to be delegated.</p>
|
||||
|
||||
<div class="mt-3">
|
||||
<label for="delegator">Delegator Private Key</label>
|
||||
<br>
|
||||
<input type="text" class="input" x-model="privKey" :class="validKeyClass" autocapitalize="off" autocomplete="off"
|
||||
spellcheck="off">
|
||||
<p class="text-xs italic">This is not stored, but only used to sign a token which is stored instead.</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label for="duration">Delegated Duration</label>
|
||||
<br>
|
||||
<select x-model.number="duration" class="input">
|
||||
<option value="1">1 day</option>
|
||||
<option value="7">7 days</option>
|
||||
<option value="30">30 days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button class="button" @click.prevent="goBack">Back</button>
|
||||
<button class="button" @click.prevent="save" :disabled="!isKeyValid">Save</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
82
Shared (Extension)/Resources/delegation_wizard.js
Normal file
82
Shared (Extension)/Resources/delegation_wizard.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import Alpine from 'alpinejs';
|
||||
import { generateProfile, getProfiles, validateKey } from './utils';
|
||||
import { getPublicKey, nip26, nip19 } from 'nostr-tools';
|
||||
|
||||
const storage = browser.storage.local;
|
||||
|
||||
Alpine.data('delegated', () => ({
|
||||
privKey: '',
|
||||
duration: 7,
|
||||
profile: {},
|
||||
|
||||
async init() {
|
||||
this.profile = await generateProfile('New Delegate');
|
||||
this.profile.delegate = true;
|
||||
},
|
||||
|
||||
openNip(event) {
|
||||
browser.tabs.create({ url: event.target.href, active: true });
|
||||
},
|
||||
|
||||
goBack() {
|
||||
window.location = browser.runtime.getURL('options.html');
|
||||
},
|
||||
|
||||
async save() {
|
||||
let profiles = await getProfiles();
|
||||
|
||||
// We need to jankify this Alpine proxy object so it's in the right format
|
||||
// when we save it to storage
|
||||
let profile = JSON.parse(JSON.stringify(this.profile));
|
||||
profile.delegator = getPublicKey(this.decodedPrivKey);
|
||||
profile.delegation = this.getDelegation();
|
||||
|
||||
profiles.push(profile);
|
||||
let profileIndex = profiles.length - 1;
|
||||
await storage.set({ profiles, profileIndex });
|
||||
|
||||
window.location = `${browser.runtime.getURL(
|
||||
'options.html'
|
||||
)}?index=${profileIndex}`;
|
||||
},
|
||||
|
||||
getDelegation() {
|
||||
let pubkey = getPublicKey(this.profile.privKey);
|
||||
|
||||
let delegation = nip26.createDelegation(this.decodedPrivKey, {
|
||||
pubkey,
|
||||
until: this.until,
|
||||
since: Math.round(Date.now() / 1000) - 1,
|
||||
});
|
||||
console.log(delegation);
|
||||
return delegation;
|
||||
},
|
||||
|
||||
// Properties
|
||||
|
||||
get isKeyValid() {
|
||||
return validateKey(this.privKey);
|
||||
},
|
||||
|
||||
get validKeyClass() {
|
||||
return this.isKeyValid
|
||||
? ''
|
||||
: 'ring-2 ring-rose-500 focus:ring-2 focus:ring-rose-500 border-transparent focus:border-transparent';
|
||||
},
|
||||
|
||||
get until() {
|
||||
return Math.round(Date.now() / 1000) + 60 * 60 * 24 * this.duration;
|
||||
},
|
||||
|
||||
get decodedPrivKey() {
|
||||
if (!this.isKeyValid) {
|
||||
return null;
|
||||
}
|
||||
if (this.privKey.startsWith('nsec')) {
|
||||
return nip19.decode(this.privKey).data;
|
||||
}
|
||||
return this.privKey;
|
||||
},
|
||||
}));
|
||||
|
||||
Alpine.start();
|
||||
34
Shared (Extension)/Resources/experimental.html
Normal file
34
Shared (Extension)/Resources/experimental.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="options.build.css">
|
||||
<script src="experimental.build.js" defer></script>
|
||||
<title>Experimental Features</title>
|
||||
</head>
|
||||
|
||||
<body class="text-fuchsia-900 p-3.5 lg:p-32" x-data="experimental">
|
||||
<p>
|
||||
<a href="options.html" class="border-none hover:underline">← Back</a>
|
||||
</p>
|
||||
<h1 class="text-3xl lg:text-6xl font-bold md:text-center">Experimental Features</h1>
|
||||
<p class="mt-3 text-center font-bold italic">
|
||||
These things may only work partially, or not
|
||||
work at all. Caveat
|
||||
emptor!
|
||||
</p>
|
||||
|
||||
<template x-for="feature in features" :key="feature[0]">
|
||||
<div class="mt-4">
|
||||
<input class="checkbox" type="checkbox" :id="feature[0]" x-model="feature[1]"
|
||||
@change="change(feature[0], feature[1])">
|
||||
<label :for="feature[0]" x-text="feature[2]" class="font-bold"></label>
|
||||
<p x-text="feature[3]" class="italic"></p>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
40
Shared (Extension)/Resources/experimental.js
Normal file
40
Shared (Extension)/Resources/experimental.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import Alpine from 'alpinejs';
|
||||
|
||||
const FEATURES = [
|
||||
[
|
||||
'delegation',
|
||||
'NIP-26 Delegation Profiles',
|
||||
'Allow user to create delegated profiles that obey the NIP-26 standard. Requires client support.',
|
||||
],
|
||||
];
|
||||
|
||||
Alpine.data('experimental', () => ({
|
||||
features: [],
|
||||
|
||||
async init() {
|
||||
await this.reloadFeatures();
|
||||
|
||||
console.log(this.features);
|
||||
},
|
||||
|
||||
async reloadFeatures() {
|
||||
this.features = await Promise.all(
|
||||
FEATURES.map(async ([name, shortDesc, longDesc]) => {
|
||||
name = `feature:${name}`;
|
||||
let active = await browser.storage.local.get({
|
||||
[name]: false,
|
||||
});
|
||||
active = active[name];
|
||||
return [name, active, shortDesc, longDesc];
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
async change(feature, active) {
|
||||
console.log(feature, active);
|
||||
await browser.storage.local.set({ [feature]: active });
|
||||
await this.reloadFeatures();
|
||||
},
|
||||
}));
|
||||
|
||||
Alpine.start();
|
||||
@@ -49,7 +49,10 @@
|
||||
"popup.build.js",
|
||||
"options.build.js",
|
||||
"options.build.css",
|
||||
"options.html"
|
||||
"options.html",
|
||||
"delegation_wizard.html",
|
||||
"delegation_wizard.build.js",
|
||||
"delegation_wizard.build.css"
|
||||
],
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
</template>
|
||||
</select>
|
||||
<div class="block md:inline p-3 pl-0 md:p-0">
|
||||
<button class="button" @click.prevent="await newProfile()">New</button>
|
||||
<button class="button" @click.prevent="newProfile">New</button>
|
||||
<button class="button" @click.prevent="newDelegated" x-show="delegationActive">New Delegate</button>
|
||||
<button class="button" @click.prevent="deleteProfile">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,6 +159,10 @@
|
||||
<button class="button" @click.prevent="window.close()">Close</button>
|
||||
<button class="button" @click.prevent="clearData">Clear Data</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<a href="experimental.html" class="border-none hover:underline">Experimental features →</a>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
KINDS,
|
||||
humanPermission,
|
||||
validateKey,
|
||||
feature,
|
||||
} from './utils';
|
||||
|
||||
const log = console.log;
|
||||
@@ -36,6 +37,7 @@ Alpine.data('options', () => ({
|
||||
host: '',
|
||||
permHosts: [],
|
||||
hostPerms: [],
|
||||
delegationActive: false,
|
||||
setPermission,
|
||||
|
||||
async init(watch = true) {
|
||||
@@ -63,7 +65,10 @@ Alpine.data('options', () => ({
|
||||
// on init to set the correct profile.
|
||||
await this.getProfileNames();
|
||||
await this.getProfileIndex();
|
||||
this.setProfileIndexFromSearch();
|
||||
await this.refreshProfile();
|
||||
|
||||
this.delegationActive = await feature('delegation');
|
||||
},
|
||||
|
||||
async refreshProfile() {
|
||||
@@ -77,6 +82,15 @@ Alpine.data('options', () => ({
|
||||
|
||||
// Profile functions
|
||||
|
||||
setProfileIndexFromSearch() {
|
||||
let p = new URLSearchParams(window.location.search);
|
||||
let index = p.get('index');
|
||||
if (!index) {
|
||||
return;
|
||||
}
|
||||
this.profileIndex = parseInt(index);
|
||||
},
|
||||
|
||||
async getProfileNames() {
|
||||
this.profileNames = await getProfileNames();
|
||||
},
|
||||
@@ -98,6 +112,10 @@ Alpine.data('options', () => ({
|
||||
this.profileIndex = newIndex;
|
||||
},
|
||||
|
||||
newDelegated() {
|
||||
window.location = browser.runtime.getURL('delegation_wizard.html');
|
||||
},
|
||||
|
||||
async deleteProfile() {
|
||||
if (
|
||||
confirm(
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
relays now?
|
||||
</span>
|
||||
<br>
|
||||
<button @click="await addRelays()">Add Relays</button>
|
||||
<button class="button" @click="await addRelays()">Add Relays</button>
|
||||
</div>
|
||||
|
||||
<div class="help">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const DB_VERSION = 1;
|
||||
const DB_VERSION = 2;
|
||||
const storage = browser.storage.local;
|
||||
export const RECOMMENDED_RELAYS = [
|
||||
new URL('wss://relay.damus.io'),
|
||||
@@ -41,6 +41,14 @@ async function migrate(version, goal) {
|
||||
await storage.set({ profiles });
|
||||
return version + 1;
|
||||
}
|
||||
|
||||
if (version === 1) {
|
||||
console.log('migrating to version 2.');
|
||||
let profiles = await getProfiles();
|
||||
profiles.forEach(profile => (profile.delegate = false));
|
||||
await storage.set({ profiles });
|
||||
return version + 1;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProfiles() {
|
||||
@@ -98,6 +106,7 @@ export async function generateProfile(name = 'Default') {
|
||||
privKey: await generatePrivateKey(),
|
||||
hosts: {},
|
||||
relays: [],
|
||||
delegate: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -213,3 +222,9 @@ export function validateKey(key) {
|
||||
|
||||
return hexMatch || b32Match;
|
||||
}
|
||||
|
||||
export async function feature(name) {
|
||||
let fname = `feature:${name}`;
|
||||
let f = await browser.storage.local.get({ [fname]: false });
|
||||
return f[fname];
|
||||
}
|
||||
|
||||
4
build.js
4
build.js
@@ -19,6 +19,10 @@ require('esbuild')
|
||||
'popup.build': './Shared (Extension)/Resources/popup.js',
|
||||
'options.build': './Shared (Extension)/Resources/options.js',
|
||||
'permission.build': './Shared (Extension)/Resources/permission.js',
|
||||
'experimental.build':
|
||||
'./Shared (Extension)/Resources/experimental.js',
|
||||
'delegation_wizard.build':
|
||||
'./Shared (Extension)/Resources/delegation_wizard.js',
|
||||
},
|
||||
outdir: './Shared (Extension)/Resources',
|
||||
sourcemap: 'inline',
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"alpinejs": "^3.10.5",
|
||||
"async-mutex": "^0.4.0",
|
||||
"json-format-highlight": "^1.0.4",
|
||||
"nostr-tools": "^1.2.1"
|
||||
"nostr-tools": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
@@ -965,9 +965,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-tools": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.2.1.tgz",
|
||||
"integrity": "sha512-SL0sst29mrQ7oUPGQn+NMH4yuTe69a8S4bliNpYB2IG0fDl3Cx+xSLnuCTb4nZiNalatYsA5l+751wQiDGA3+A==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.3.0.tgz",
|
||||
"integrity": "sha512-AJosFolIhMFfs34sAX1V9kXV2/kDDGzfIVVEFK3u92Q96zTn9dekED6PybZXmKGNniUKfuy1h76oBPeK1MRVQw==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^0.5.7",
|
||||
"@noble/secp256k1": "^1.7.0",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"alpinejs": "^3.10.5",
|
||||
"async-mutex": "^0.4.0",
|
||||
"json-format-highlight": "^1.0.4",
|
||||
"nostr-tools": "^1.2.1"
|
||||
"nostr-tools": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
|
||||
Reference in New Issue
Block a user