prettier, update to the dependencies (latest), remove nip26 and its artifacts, cleanup and renew

This commit is contained in:
Fishcake
2024-09-20 20:31:58 +09:00
parent 0477cc5cdf
commit e1c83597dd
37 changed files with 3136 additions and 2488 deletions

View File

@@ -188,7 +188,6 @@
941B042F2978CDF900CA291E /* Icon-16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-16.png"; sourceTree = "<group>"; }; 941B042F2978CDF900CA291E /* Icon-16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-16.png"; sourceTree = "<group>"; };
941B04302978CDF900CA291E /* Icon-64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-64.png"; sourceTree = "<group>"; }; 941B04302978CDF900CA291E /* Icon-64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-64.png"; sourceTree = "<group>"; };
944A6E02299F2FBB0032C2E3 /* experimental */ = {isa = PBXFileReference; lastKnownFileType = folder; path = experimental; sourceTree = "<group>"; }; 944A6E02299F2FBB0032C2E3 /* experimental */ = {isa = PBXFileReference; lastKnownFileType = folder; path = experimental; sourceTree = "<group>"; };
944A6E0D299F32070032C2E3 /* wizards */ = {isa = PBXFileReference; lastKnownFileType = folder; path = wizards; sourceTree = "<group>"; };
944A6E12299F39D30032C2E3 /* permission */ = {isa = PBXFileReference; lastKnownFileType = folder; path = permission; sourceTree = "<group>"; }; 944A6E12299F39D30032C2E3 /* permission */ = {isa = PBXFileReference; lastKnownFileType = folder; path = permission; sourceTree = "<group>"; };
944A6E38299F46270032C2E3 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; }; 944A6E38299F46270032C2E3 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
944A6E3E299F46D30032C2E3 /* NostoreApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostoreApp.swift; sourceTree = "<group>"; }; 944A6E3E299F46D30032C2E3 /* NostoreApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostoreApp.swift; sourceTree = "<group>"; };
@@ -294,7 +293,6 @@
children = ( children = (
9471E91E29A470C700EA623B /* event_history */, 9471E91E29A470C700EA623B /* event_history */,
944A6E12299F39D30032C2E3 /* permission */, 944A6E12299F39D30032C2E3 /* permission */,
944A6E0D299F32070032C2E3 /* wizards */,
944A6E02299F2FBB0032C2E3 /* experimental */, 944A6E02299F2FBB0032C2E3 /* experimental */,
948C69E4297F8BA600FB3574 /* options.build.css */, 948C69E4297F8BA600FB3574 /* options.build.css */,
948C69E1297F891F00FB3574 /* options.build.js */, 948C69E1297F891F00FB3574 /* options.build.js */,
@@ -461,7 +459,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1420; LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1500; LastUpgradeCheck = 1600;
TargetAttributes = { TargetAttributes = {
941B03AE296FA90400CA291E = { 941B03AE296FA90400CA291E = {
CreatedOnToolsVersion = 14.2; CreatedOnToolsVersion = 14.2;
@@ -503,7 +501,6 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
944A6E0E299F32070032C2E3 /* wizards in Resources */,
941B03E0296FA90400CA291E /* Icon.png in Resources */, 941B03E0296FA90400CA291E /* Icon.png in Resources */,
944A6E03299F2FBB0032C2E3 /* experimental in Resources */, 944A6E03299F2FBB0032C2E3 /* experimental in Resources */,
941B03DE296FA90400CA291E /* Main.html in Resources */, 941B03DE296FA90400CA291E /* Main.html in Resources */,
@@ -520,7 +517,6 @@
files = ( files = (
941B03E1296FA90400CA291E /* Icon.png in Resources */, 941B03E1296FA90400CA291E /* Icon.png in Resources */,
941B03E3296FA90400CA291E /* Style.css in Resources */, 941B03E3296FA90400CA291E /* Style.css in Resources */,
944A6E0F299F32070032C2E3 /* wizards in Resources */,
941B03E5296FA90400CA291E /* Script.js in Resources */, 941B03E5296FA90400CA291E /* Script.js in Resources */,
944A6E14299F39D30032C2E3 /* permission in Resources */, 944A6E14299F39D30032C2E3 /* permission in Resources */,
941B03E9296FA90400CA291E /* Assets.xcassets in Resources */, 941B03E9296FA90400CA291E /* Assets.xcassets in Resources */,
@@ -534,7 +530,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
9471E91F29A470C700EA623B /* event_history in Resources */, 9471E91F29A470C700EA623B /* event_history in Resources */,
944A6E10299F32070032C2E3 /* wizards in Resources */,
941B0413297110F100CA291E /* background.build.js in Resources */, 941B0413297110F100CA291E /* background.build.js in Resources */,
944A6E15299F39D30032C2E3 /* permission in Resources */, 944A6E15299F39D30032C2E3 /* permission in Resources */,
948C69E82982DFE900FB3574 /* background.html in Resources */, 948C69E82982DFE900FB3574 /* background.html in Resources */,
@@ -572,7 +567,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
9471E92029A470C700EA623B /* event_history in Resources */, 9471E92029A470C700EA623B /* event_history in Resources */,
944A6E11299F32070032C2E3 /* wizards in Resources */,
941B0414297110F100CA291E /* background.build.js in Resources */, 941B0414297110F100CA291E /* background.build.js in Resources */,
944A6E16299F39D30032C2E3 /* permission in Resources */, 944A6E16299F39D30032C2E3 /* permission in Resources */,
948C69E92982DFE900FB3574 /* background.html in Resources */, 948C69E92982DFE900FB3574 /* background.html in Resources */,
@@ -860,7 +854,6 @@
941B0403296FA90400CA291E /* Debug */ = { 941B0403296FA90400CA291E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@@ -902,7 +895,6 @@
941B0404296FA90400CA291E /* Release */ = { 941B0404296FA90400CA291E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@@ -960,7 +952,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.0;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-framework", "-framework",
@@ -993,7 +985,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.0;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-framework", "-framework",
@@ -1011,7 +1003,6 @@
941B040A296FA90400CA291E /* Debug */ = { 941B040A296FA90400CA291E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "macOS (App)/nostore.entitlements"; CODE_SIGN_ENTITLEMENTS = "macOS (App)/nostore.entitlements";
@@ -1048,7 +1039,6 @@
941B040B296FA90400CA291E /* Release */ = { 941B040B296FA90400CA291E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "macOS (App)/nostore.entitlements"; CODE_SIGN_ENTITLEMENTS = "macOS (App)/nostore.entitlements";

View File

@@ -4,10 +4,10 @@ This is a [NIP-07][nip07] compatible extension for signing nostr events.
## Features ## Features
* Login with nostr (`getPublicKey`). - Login with nostr (`getPublicKey`).
* Post nostr event (`signEvent`). - Post nostr event (`signEvent`).
* Encrypted direct messages (`nip04.encrypt` and `nip04.decrypt`). - Encrypted direct messages (`nip04.encrypt` and `nip04.decrypt`).
* Multiple profiles. - Multiple profiles.
## Installation ## Installation

View File

@@ -1,20 +1,39 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'"> <meta
http-equiv="Content-Security-Policy"
content="default-src 'self'"
/>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<link rel="stylesheet" href="../Style.css"> <link rel="stylesheet" href="../Style.css" />
<script src="../Script.js" defer></script> <script src="../Script.js" defer></script>
</head> </head>
<body> <body>
<img src="../Icon.png" width="128" height="128" alt="Nostore Icon"> <img src="../Icon.png" width="128" height="128" alt="Nostore Icon" />
<p class="platform-ios">You can turn on Nostores Safari extension in Settings.</p> <p class="platform-ios">
<p class="platform-mac state-unknown">You can turn on Nostores extension in Safari Extensions preferences.</p> You can turn on Nostores Safari extension in Settings.
<p class="platform-mac state-on">Nostores extension is currently on. You can turn it off in Safari Extensions preferences.</p> </p>
<p class="platform-mac state-off">Nostores extension is currently off. You can turn it on in Safari Extensions preferences.</p> <p class="platform-mac state-unknown">
<button class="platform-mac open-preferences">Quit and Open Safari Extensions Preferences…</button> You can turn on Nostores extension in Safari Extensions
preferences.
</p>
<p class="platform-mac state-on">
Nostores extension is currently on. You can turn it off in Safari
Extensions preferences.
</p>
<p class="platform-mac state-off">
Nostores extension is currently off. You can turn it on in Safari
Extensions preferences.
</p>
<button class="platform-mac open-preferences">
Quit and Open Safari Extensions Preferences…
</button>
</body> </body>
</html> </html>

View File

@@ -2,13 +2,20 @@ function show(platform, enabled, useSettingsInsteadOfPreferences) {
document.body.classList.add(`platform-${platform}`); document.body.classList.add(`platform-${platform}`);
if (useSettingsInsteadOfPreferences) { if (useSettingsInsteadOfPreferences) {
document.getElementsByClassName('platform-mac state-on')[0].innerText = "Nostores extension is currently on. You can turn it off in the Extensions section of Safari Settings."; document.getElementsByClassName('platform-mac state-on')[0].innerText =
document.getElementsByClassName('platform-mac state-off')[0].innerText = "Nostores extension is currently off. You can turn it on in the Extensions section of Safari Settings."; 'Nostores extension is currently on. You can turn it off in the Extensions section of Safari Settings.';
document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on Nostores extension in the Extensions section of Safari Settings."; document.getElementsByClassName('platform-mac state-off')[0].innerText =
document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…"; 'Nostores extension is currently off. You can turn it on in the Extensions section of Safari Settings.';
document.getElementsByClassName(
'platform-mac state-unknown'
)[0].innerText =
'You can turn on Nostores extension in the Extensions section of Safari Settings.';
document.getElementsByClassName(
'platform-mac open-preferences'
)[0].innerText = 'Quit and Open Safari Settings…';
} }
if (typeof enabled === "boolean") { if (typeof enabled === 'boolean') {
document.body.classList.toggle(`state-on`, enabled); document.body.classList.toggle(`state-on`, enabled);
document.body.classList.toggle(`state-off`, !enabled); document.body.classList.toggle(`state-off`, !enabled);
} else { } else {
@@ -18,7 +25,9 @@ function show(platform, enabled, useSettingsInsteadOfPreferences) {
} }
function openPreferences() { function openPreferences() {
webkit.messageHandlers.controller.postMessage("open-preferences"); webkit.messageHandlers.controller.postMessage('open-preferences');
} }
document.querySelector("button.open-preferences").addEventListener("click", openPreferences); document
.querySelector('button.open-preferences')
.addEventListener('click', openPreferences);

View File

@@ -1,12 +1,11 @@
import { import {
generatePrivateKey,
getPublicKey,
signEvent,
nip04, nip04,
nip19, nip19,
nip26, generateSecretKey,
getEventHash, getPublicKey,
finalizeEvent,
} from 'nostr-tools'; } from 'nostr-tools';
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import { import {
getProfileIndex, getProfileIndex,
@@ -14,7 +13,6 @@ import {
getProfile, getProfile,
getPermission, getPermission,
setPermission, setPermission,
feature,
} from './utilities/utils'; } from './utilities/utils';
import { saveEvent } from './utilities/db'; import { saveEvent } from './utilities/db';
@@ -40,15 +38,13 @@ browser.runtime.onMessage.addListener((message, _sender, sendResponse) => {
deny(message); deny(message);
return Promise.resolve(true); return Promise.resolve(true);
case 'generatePrivateKey': case 'generatePrivateKey':
return Promise.resolve(generatePrivateKey()); return Promise.resolve(generatePrivateKey_());
case 'savePrivateKey': case 'savePrivateKey':
return savePrivateKey(message.payload); return savePrivateKey(message.payload);
case 'getNpub': case 'getNpub':
return getNpub(message.payload); return getNpub(message.payload);
case 'getNsec': case 'getNsec':
return getNsec(message.payload); return getNsec(message.payload);
case 'createDelegation':
return createDelegation(message.payload);
case 'calcPubKey': case 'calcPubKey':
return Promise.resolve(getPublicKey(message.payload)); return Promise.resolve(getPublicKey(message.payload));
case 'npubEncode': case 'npubEncode':
@@ -63,7 +59,7 @@ browser.runtime.onMessage.addListener((message, _sender, sendResponse) => {
case 'nip04.decrypt': case 'nip04.decrypt':
case 'getRelays': case 'getRelays':
validations[uuid] = sendResponse; validations[uuid] = sendResponse;
setDelegation(message).then(() => ask(uuid, message)); ask(uuid, message);
setTimeout(() => { setTimeout(() => {
prompt.release?.(); prompt.release?.();
}, 10_000); }, 10_000);
@@ -88,6 +84,11 @@ async function forceRelease() {
} }
} }
async function generatePrivateKey_() {
const sk = generateSecretKey();
return bytesToHex(sk);
}
async function ask(uuid, { kind, host, payload }) { async function ask(uuid, { kind, host, payload }) {
await forceRelease(); // Clean up previous tab if it closed without cleaning itself up await forceRelease(); // Clean up previous tab if it closed without cleaning itself up
prompt.release = await prompt.mutex.acquire(); prompt.release = await prompt.mutex.acquire();
@@ -178,35 +179,32 @@ async function savePrivateKey([index, privKey]) {
privKey = nip19.decode(privKey).data; privKey = nip19.decode(privKey).data;
} }
let profiles = await get('profiles'); let profiles = await get('profiles');
profiles[index].privKey = privKey; profiles[index].privKey = bytesToHex(privKey);
await storage.set({ profiles }); await storage.set({ profiles });
return true; return true;
} }
async function getNsec(index) { async function getNsec(index) {
let profile = await getProfile(index); let profile = await getProfile(index);
let nsec = nip19.nsecEncode(profile.privKey); let nsec = nip19.nsecEncode(hexToBytes(profile.privKey));
return nsec; return nsec;
} }
async function getNpub(index) { async function getNpub(index) {
let profile = await getProfile(index); let profile = await getProfile(index);
let pubKey = getPublicKey(profile.privKey); let pubKey = getPublicKey(hexToBytes(profile.privKey));
let npub = nip19.npubEncode(pubKey); let npub = nip19.npubEncode(pubKey);
return npub; return npub;
} }
async function getPrivKey() { async function getPrivKey() {
let profile = await currentProfile(); let profile = await currentProfile();
return profile.privKey; return hexToBytes(profile.privKey);
} }
async function getPubKey() { async function getPubKey() {
let pi = await getProfileIndex(); let pi = await getProfileIndex();
let profile = await getProfile(pi); let profile = await getProfile(pi);
if (profile.delegate) {
return profile.delegator;
}
let privKey = await getPrivKey(); let privKey = await getPrivKey();
let pubKey = getPublicKey(privKey); let pubKey = getPublicKey(privKey);
return pubKey; return pubKey;
@@ -220,11 +218,8 @@ async function currentProfile() {
async function signEvent_(event, host) { async function signEvent_(event, host) {
event = JSON.parse(JSON.stringify(event)); event = JSON.parse(JSON.stringify(event));
let privKey = await getPrivKey(); let sk = await getPrivKey();
let pubKey = getPublicKey(privKey); event = finalizeEvent(event, sk);
event.pubkey = pubKey;
event.id = getEventHash(event);
event.sig = signEvent(event, privKey);
saveEvent({ saveEvent({
event, event,
metadata: { host, signed_at: Math.round(Date.now() / 1000) }, metadata: { host, signed_at: Math.round(Date.now() / 1000) },
@@ -253,42 +248,3 @@ async function getRelays() {
}); });
return relayObj; 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,
]);
}

View File

@@ -1,12 +1,11 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script defer src="event_history.build.js"></script> <script defer src="event_history.build.js"></script>
<link rel="stylesheet" href="/options.build.css"> <link rel="stylesheet" href="/options.build.css" />
<title>Event History</title> <title>Event History</title>
<style> <style>
@@ -18,7 +17,9 @@
<body class="text-fuchsia-900 p-3.5 lg:p-32" x-data="eventLog"> <body class="text-fuchsia-900 p-3.5 lg:p-32" x-data="eventLog">
<p> <p>
<a href="/options.html" class="border-none hover:underline">← Back</a> <a href="/options.html" class="border-none hover:underline"
>← Back</a
>
</p> </p>
<h1 class="section-header">Event History</h1> <h1 class="section-header">Event History</h1>
@@ -29,7 +30,12 @@
<div class="grid grid-cols-2 xl:grid-cols-4 gap-4"> <div class="grid grid-cols-2 xl:grid-cols-4 gap-4">
<div> <div>
<label for="view">View</label> <label for="view">View</label>
<select id="view" class="input" x-model="view" @change="reload"> <select
id="view"
class="input"
x-model="view"
@change="reload"
>
<option value="created_at">created_at</option> <option value="created_at">created_at</option>
<option value="kind">kind</option> <option value="kind">kind</option>
<option value="host">host</option> <option value="host">host</option>
@@ -39,7 +45,12 @@
<div> <div>
<label for="sort">Order</label> <label for="sort">Order</label>
<select id="sort" x-model="sort" class="input" @change="reload"> <select
id="sort"
x-model="sort"
class="input"
@change="reload"
>
<option value="asc">Ascending</option> <option value="asc">Ascending</option>
<option value="desc">Descending</option> <option value="desc">Descending</option>
</select> </select>
@@ -47,24 +58,47 @@
<div> <div>
<label for="max">Max</label> <label for="max">Max</label>
<input type="number" x-model.number="max" @input.debounce.750ms="reload" class="input" min="10"> <input
type="number"
x-model.number="max"
@input.debounce.750ms="reload"
class="input"
min="10"
/>
</div> </div>
<div></div> <div></div>
<div x-show="view === 'created_at'" x-cloak> <div x-show="view === 'created_at'" x-cloak>
<label for="fromCreatedAt">From</label> <label for="fromCreatedAt">From</label>
<input type="date" id="fromCreatedAt" x-model="fromCreatedAt" class="input" @change="reload"> <input
type="date"
id="fromCreatedAt"
x-model="fromCreatedAt"
class="input"
@change="reload"
/>
</div> </div>
<div x-show="view === 'created_at'" x-cloak> <div x-show="view === 'created_at'" x-cloak>
<label for="toCreatedAt">To</label> <label for="toCreatedAt">To</label>
<input type="date" id="toCreatedAt" x-model="toCreatedAt" class="input" @change="reload"> <input
type="date"
id="toCreatedAt"
x-model="toCreatedAt"
class="input"
@change="reload"
/>
</div> </div>
<div x-show="view === 'kind'" x-cloak> <div x-show="view === 'kind'" x-cloak>
<label for="kindShortcut">Quick Select</label> <label for="kindShortcut">Quick Select</label>
<select id="kindShortcut" class="input" @change="quickKindSelect" x-model="quickKind"> <select
id="kindShortcut"
class="input"
@change="quickKindSelect"
x-model="quickKind"
>
<option></option> <option></option>
<template x-for="k in kinds"> <template x-for="k in kinds">
<option :value="k[0]" x-text="k[1]"></option> <option :value="k[0]" x-text="k[1]"></option>
@@ -74,17 +108,34 @@
<div x-show="view === 'kind'" x-cloak> <div x-show="view === 'kind'" x-cloak>
<label for="fromKind">From</label> <label for="fromKind">From</label>
<input type="number" id="fromKind" x-model.number="fromKind" class="input" @change="reload"> <input
type="number"
id="fromKind"
x-model.number="fromKind"
class="input"
@change="reload"
/>
</div> </div>
<div x-show="view === 'kind'" x-cloak> <div x-show="view === 'kind'" x-cloak>
<label for="toKind">To</label> <label for="toKind">To</label>
<input type="number" id="toKind" x-model.number="toKind" class="input" @change="reload"> <input
type="number"
id="toKind"
x-model.number="toKind"
class="input"
@change="reload"
/>
</div> </div>
<div x-show="view === 'host'" x-cloak> <div x-show="view === 'host'" x-cloak>
<label for="host">Host</label> <label for="host">Host</label>
<select id="host" class="input" x-model="host" @change="reload"> <select
id="host"
class="input"
x-model="host"
@change="reload"
>
<option value=""></option> <option value=""></option>
<template x-for="h in allHosts"> <template x-for="h in allHosts">
<option :value="h" x-text="h"></option> <option :value="h" x-text="h"></option>
@@ -94,7 +145,12 @@
<div x-show="view === 'pubkey'" x-cloak> <div x-show="view === 'pubkey'" x-cloak>
<label for="profiles">Profiles</label> <label for="profiles">Profiles</label>
<select id="profiles" class="input" x-model="profile" @change="pkFromProfile"> <select
id="profiles"
class="input"
x-model="profile"
@change="pkFromProfile"
>
<option value=""></option> <option value=""></option>
<template x-for="p in profileNames"> <template x-for="p in profileNames">
<option :value="p" x-text="p"></option> <option :value="p" x-text="p"></option>
@@ -104,39 +160,69 @@
<div x-show="view === 'pubkey'" x-cloak> <div x-show="view === 'pubkey'" x-cloak>
<label for="pubkey">Pubkey</label> <label for="pubkey">Pubkey</label>
<input type="text" class="input" x-model="pubkey" @input.debounce="reload"> <input
type="text"
class="input"
x-model="pubkey"
@input.debounce="reload"
/>
</div> </div>
</div> </div>
<div> <div>
<button class="button mt-3" @click="saveAll">Save all</button> <button class="button mt-3" @click="saveAll">Save all</button>
<button class="button mt-3" @click="deleteAll">Delete all</button> <button class="button mt-3" @click="deleteAll">
Delete all
</button>
</div> </div>
</div> </div>
<div class="text-sm italic mt-1 text-right">Click/tap event body to copy the raw event.</div> <div class="text-sm italic mt-1 text-right">
Click/tap event body to copy the raw event.
</div>
<template x-for="(event, index) in events"> <template x-for="(event, index) in events">
<div class="mt-3 border-solid border border-fuchsia-700 rounded-lg"> <div class="mt-3 border-solid border border-fuchsia-700 rounded-lg">
<div class="select-none flex cursor-pointer text-sm md:text-xl" <div
@click="selected = selected === index ? null : index"> class="select-none flex cursor-pointer text-sm md:text-xl"
<div class="flex-none w-14 p-4 font-extrabold" x-text="selected === index ? '-' : '+'"></div> @click="selected = selected === index ? null : index"
<div class="flex-1 w-64 p-4" x-text="formatDate(event.metadata.signed_at)"></div> >
<div class="flex-1 w-64 p-4" x-text="event.metadata.host"></div> <div
<div class="flex-1 w-64 p-4" x-text="formatKind(event.event.kind)"></div> class="flex-none w-14 p-4 font-extrabold"
x-text="selected === index ? '-' : '+'"
></div>
<div
class="flex-1 w-64 p-4"
x-text="formatDate(event.metadata.signed_at)"
></div>
<div
class="flex-1 w-64 p-4"
x-text="event.metadata.host"
></div>
<div
class="flex-1 w-64 p-4"
x-text="formatKind(event.event.kind)"
></div>
</div> </div>
<div @click.prevent="copyEvent(index)" class="cursor-pointer"> <div @click.prevent="copyEvent(index)" class="cursor-pointer">
<pre class="rounded-b-lg bg-slate-200 text-sm md:text-xl" x-html="highlight(event)" x-show="selected === index" <pre
x-transition:enter.opacity.delay.75ms x-transition:leave.opacity x-cloak> class="rounded-b-lg bg-slate-200 text-sm md:text-xl"
</pre> x-html="highlight(event)"
x-show="selected === index"
x-transition:enter.opacity.delay.75ms
x-transition:leave.opacity
x-cloak
></pre>
</div> </div>
</div> </div>
</template> </template>
<div class="fixed top-0 right-0 bg-fuchsia-800 rounded-md p-4 text-white m-8 drop-shadow-md" x-show="copied" x-cloack> <div
class="fixed top-0 right-0 bg-fuchsia-800 rounded-md p-4 text-white m-8 drop-shadow-md"
x-show="copied"
x-cloack
>
Event copied! Event copied!
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,34 +1,44 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/options.build.css"> <link rel="stylesheet" href="/options.build.css" />
<script src="/experimental/experimental.build.js" defer></script> <script src="/experimental/experimental.build.js" defer></script>
<title>Experimental Features</title> <title>Experimental Features</title>
</head> </head>
<body class="text-fuchsia-900 p-3.5 lg:p-32" x-data="experimental"> <body class="text-fuchsia-900 p-3.5 lg:p-32" x-data="experimental">
<p> <p>
<a href="/options.html" class="border-none hover:underline">← Back</a> <a href="/options.html" class="border-none hover:underline"
>← Back</a
>
</p> </p>
<h1 class="text-3xl lg:text-6xl font-bold md:text-center">Experimental Features</h1> <h1 class="text-3xl lg:text-6xl font-bold md:text-center">
Experimental Features
</h1>
<p class="mt-3 text-center font-bold italic"> <p class="mt-3 text-center font-bold italic">
These things may only work partially, or not These things may only work partially, or not work at all. Caveat
work at all. Caveat
emptor! emptor!
</p> </p>
<template x-for="feature in features" :key="feature[0]"> <template x-for="feature in features" :key="feature[0]">
<div class="mt-4"> <div class="mt-4">
<input class="checkbox" type="checkbox" :id="feature[0]" x-model="feature[1]" <input
@change="change(feature[0], feature[1])"> class="checkbox"
<label :for="feature[0]" x-text="feature[2]" class="font-bold"></label> 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> <p x-text="feature[3]" class="italic"></p>
</div> </div>
</template> </template>
</body> </body>
</html> </html>

View File

@@ -2,9 +2,9 @@ import Alpine from 'alpinejs';
const FEATURES = [ const FEATURES = [
[ [
'delegation', 'none',
'NIP-26 Delegation Profiles', 'NIP-XX: None',
'Allow user to create delegated profiles that obey the NIP-26 standard. Requires client support.', 'Reserved for the future use.',
], ],
]; ];

View File

@@ -17,12 +17,8 @@
}, },
"content_scripts": [ "content_scripts": [
{ {
"js": [ "js": ["content.build.js"],
"content.build.js" "matches": ["<all_urls>"]
],
"matches": [
"<all_urls>"
]
} }
], ],
"action": { "action": {
@@ -39,10 +35,7 @@
"options_ui": { "options_ui": {
"page": "options.html" "page": "options.html"
}, },
"permissions": [ "permissions": ["storage", "clipboardWrite"],
"storage",
"clipboardWrite"
],
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": [ "resources": [
@@ -51,21 +44,16 @@
"options.build.js", "options.build.js",
"options.build.css", "options.build.css",
"options.html", "options.html",
"wizards/delegation/delegation.html",
"wizards/delegation/delegation.build.js",
"wizards/delegation/delegation.build.css",
"experimental/experimental.html", "experimental/experimental.html",
"experimental/experimental.build.js", "experimental/experimental.build.js",
"event_history/event_history.html", "event_history/event_history.html",
"event_history/event_history.build.js" "event_history/event_history.build.js"
], ],
"matches": [ "matches": ["<all_urls>"]
"<all_urls>"
]
} }
], ],
"content_security_policy": { "content_security_policy": {
"extension_pages": "script-src 'self' 'unsafe-eval'" "extension_pages": "object-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-src 'self'; font-src 'self'; media-src 'self'; child-src 'self';"
}, },
"browser_specific_settings": { "browser_specific_settings": {
"safari": { "safari": {

View File

@@ -2,7 +2,9 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
[x-cloak] { display: none; } [x-cloak] {
display: none;
}
@layer components { @layer components {
.button { .button {

View File

@@ -1,12 +1,10 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="options.build.css"> <link rel="stylesheet" href="options.build.css" />
<script defer src="options.build.js"></script> <script defer src="options.build.js"></script>
</head> </head>
<body x-data="options" class="text-fuchsia-900 p-3.5 lg:p-32"> <body x-data="options" class="text-fuchsia-900 p-3.5 lg:p-32">
@@ -15,7 +13,7 @@
<!-- PROFILES --> <!-- PROFILES -->
<div class="mt-6"> <div class="mt-6">
<label for="profiles">Profile</label> <label for="profiles">Profile</label>
<br> <br />
<select class="input" x-model.number="profileIndex" id="profiles"> <select class="input" x-model.number="profileIndex" id="profiles">
<template x-for="(name, index) in profileNames" :key="index"> <template x-for="(name, index) in profileNames" :key="index">
<option x-text="name" :value="index"></option> <option x-text="name" :value="index"></option>
@@ -23,54 +21,80 @@
</select> </select>
<div class="block md:inline p-3 pl-0 md:p-0"> <div class="block md:inline p-3 pl-0 md:p-0">
<button class="button" @click.prevent="newProfile">New</button> <button class="button" @click.prevent="newProfile">New</button>
<button class="button" @click.prevent="newDelegated" x-show="delegationActive" x-cloak>New Delegate</button> <button class="button" @click.prevent="deleteProfile">
<button class="button" @click.prevent="deleteProfile">Delete</button> Delete
</button>
</div> </div>
</div> </div>
<!-- KEYS --> <!-- KEYS -->
<div class="section"> <div class="section">
<h2 class="section-header">Keys</h2> <h2 class="section-header">Keys</h2>
<p class="text-sm italic">Provide your <code class="not-italic">nsec</code> or legacy (hexadecimal) private keys. <p class="text-sm italic">
Provide your <code class="not-italic">nsec</code> or legacy
(hexadecimal) private keys.
</p> </p>
<form @submit.prevent="saveProfile"> <form @submit.prevent="saveProfile">
<div class="mt-3"> <div class="mt-3">
<label for="profile-name">Profile Name</label> <label for="profile-name">Profile Name</label>
<br> <br />
<input x-model="profileName" type="text" class="input" autocapitalize="off" autocomplete="off" spellcheck="off"> <input
</div> x-model="profileName"
type="text"
<div class="mt-3" x-show="delegate" x-cloak> class="input"
<div class="mb-1"> autocapitalize="off"
<span class="text-red-700 font-bold">This is is a delegated profile.</span> autocomplete="off"
</div> spellcheck="off"
/>
<label for="delegator-pub-key">Delegator Public Key</label>
<br>
<input id="delegator-pub-key" x-model="delegator" class="input" autocapitalize="off" autocomplete="off"
spellcheck="off" :disabled="delegate">
</div> </div>
<div class="mt-3"> <div class="mt-3">
<label for="priv-key">Private Key</label> <label for="priv-key">Private Key</label>
<br> <br />
<input x-model="privKey" :type="visibilityClass" class="input" :class="validKeyClass" autocapitalize="off" <input
autocomplete="off" spellcheck="off" :disabled="delegate"> x-model="privKey"
<a href="" @click.prevent="visible = !visible" x-text="(visible && 'Hide') || 'Show'" class="border-none"></a> :type="visibilityClass"
class="input"
:class="validKeyClass"
autocapitalize="off"
autocomplete="off"
spellcheck="off"
/>
<a
href=""
@click.prevent="visible = !visible"
x-text="(visible && 'Hide') || 'Show'"
class="border-none"
></a>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<label for="pub-key">Public Key</label> <label for="pub-key">Public Key</label>
<br> <br />
<input x-model="pubKey" type="text" class="input" disabled> <input
<a href="" class="border-none" @click.prevent="copyPubKey" x-text="copied ? 'Copied!' : 'Copy'"></a> x-model="pubKey"
type="text"
class="input"
disabled
/>
<a
href=""
class="border-none"
@click.prevent="copyPubKey"
x-text="copied ? 'Copied!' : 'Copy'"
></a>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<button class="button" :disabled="!needsSave || !validKey" @click.prevent="saveProfile">Save</button> <button
class="button"
:disabled="!needsSave || !validKey"
@click.prevent="saveProfile"
>
Save
</button>
</div> </div>
</form> </form>
</div> </div>
<!-- RELAYS --> <!-- RELAYS -->
@@ -78,7 +102,9 @@
<h2 class="section-header">Relays</h2> <h2 class="section-header">Relays</h2>
<p class="text-sm italic">Add relay suggestions for clients.</p> <p class="text-sm italic">Add relay suggestions for clients.</p>
<template x-if="hasRelays"> <template x-if="hasRelays">
<table class="mt-3 text-xs md:text-base table-auto md:table-fixed"> <table
class="mt-3 text-xs md:text-base table-auto md:table-fixed"
>
<thead class="font-bold text-lg"> <thead class="font-bold text-lg">
<td class="p-2 text-center">URL</td> <td class="p-2 text-center">URL</td>
<td class="p-2 text-center">Read</td> <td class="p-2 text-center">Read</td>
@@ -89,13 +115,28 @@
<tr> <tr>
<td class="p-2 w-1/3" x-text="relay.url"></td> <td class="p-2 w-1/3" x-text="relay.url"></td>
<td class="p-2 text-center"> <td class="p-2 text-center">
<input class="checkbox" type="checkbox" x-model="relay.read" @change="await saveRelays()"> <input
class="checkbox"
type="checkbox"
x-model="relay.read"
@change="await saveRelays()"
/>
</td> </td>
<td class="p-2 text-center"> <td class="p-2 text-center">
<input class="checkbox" type="checkbox" x-model="relay.write" @change="await saveRelays()"> <input
class="checkbox"
type="checkbox"
x-model="relay.write"
@change="await saveRelays()"
/>
</td> </td>
<td class="p-2 text-center"> <td class="p-2 text-center">
<button class="button" @click.prevent="await deleteRelay(index)">Delete</button> <button
class="button"
@click.prevent="await deleteRelay(index)"
>
Delete
</button>
</td> </td>
</tr> </tr>
</template> </template>
@@ -110,33 +151,49 @@
<div class="mt-3" x-show="hasRecommendedRelays" x-cloak> <div class="mt-3" x-show="hasRecommendedRelays" x-cloak>
<select x-model="recommendedRelay" class="input"> <select x-model="recommendedRelay" class="input">
<option value="" disabled selected>Recommended Relays</option> <option value="" disabled selected>
Recommended Relays
</option>
<template x-for="relay in recommendedRelays"> <template x-for="relay in recommendedRelays">
<option :value="relay" x-text="relay"></option> <option :value="relay" x-text="relay"></option>
</template> </template>
</select> </select>
</div> </div>
<input class="mt-3 input" x-model="newRelay" type="text" @keyup.enter="await addRelay()" placeholder="wss://..." <input
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="off"> class="mt-3 input"
x-model="newRelay"
type="text"
@keyup.enter="await addRelay()"
placeholder="wss://..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="off"
/>
<div class="block md:inline p-3 pl-0 md:p-0"> <div class="block md:inline p-3 pl-0 md:p-0">
<button class="button" @click="await addRelay()">Add</button> <button class="button" @click="await addRelay()">Add</button>
</div> </div>
<div class="text-red-500 font-bold" x-show="urlError.length > 0" x-text="urlError" x-cloak></div> <div
class="text-red-500 font-bold"
x-show="urlError.length > 0"
x-text="urlError"
x-cloak
></div>
</div> </div>
<!-- PERMISSIONS --> <!-- PERMISSIONS -->
<div class="section"> <div class="section">
<h2 class="section-header">App Permissions</h2> <h2 class="section-header">App Permissions</h2>
<p class="text-sm italic"> <p class="text-sm italic">
Permissions granted to individual applications. Permissions granted to individual applications. Everything
Everything defaults to <span class="font-bold">Ask</span> unless you have saved a different option. defaults to <span class="font-bold">Ask</span> unless you have
saved a different option.
</p> </p>
<div class="mt-3" x-show="permHosts.length > 0"> <div class="mt-3" x-show="permHosts.length > 0">
<label for="app">Apps</label> <label for="app">Apps</label>
<br> <br />
<select id="app" class="input" x-model="host"> <select id="app" class="input" x-model="host">
<option value=""></option> <option value=""></option>
<template x-for="permHost in permHosts"> <template x-for="permHost in permHosts">
@@ -145,19 +202,31 @@
</select> </select>
</div> </div>
<p x-show="permHosts.length === 0" x-cloak class="font-bold mt-3">You have not remembered any app requests yet.</p> <p x-show="permHosts.length === 0" x-cloak class="font-bold mt-3">
You have not remembered any app requests yet.
</p>
<table class="mt-3 text-xs md:text-base table-fixed" x-show="hostPerms.length > 0" x-cloak> <table
class="mt-3 text-xs md:text-base table-fixed"
x-show="hostPerms.length > 0"
x-cloak
>
<thead class="font-bold text-lg"> <thead class="font-bold text-lg">
<td class="p-2 text-center">App Request</td> <td class="p-2 text-center">App Request</td>
<td class="p-2 text-center">Action</td> <td class="p-2 text-center">Action</td>
</thead> </thead>
<template x-for="[etype, humanName, perm] in hostPerms" :key="etype"> <template
x-for="[etype, humanName, perm] in hostPerms"
:key="etype"
>
<tr> <tr>
<td class="p-2 w-1/3 md:w-2/4" x-text="humanName"></td> <td class="p-2 w-1/3 md:w-2/4" x-text="humanName"></td>
<td class="p-2 text-center"> <td class="p-2 text-center">
<select class="input" :value="perm" <select
@change="await setPermission(host, etype, $event.target.value, profileIndex)"> class="input"
:value="perm"
@change="await setPermission(host, etype, $event.target.value, profileIndex)"
>
<option value="ask">Ask</option> <option value="ask">Ask</option>
<option value="allow">Allow</option> <option value="allow">Allow</option>
<option value="deny">Deny</option> <option value="deny">Deny</option>
@@ -170,15 +239,23 @@
<div class="mt-6"> <div class="mt-6">
<button class="button" @click.prevent="closeOptions">Close</button> <button class="button" @click.prevent="closeOptions">Close</button>
<button class="button" @click.prevent="clearData">Clear Data</button> <button class="button" @click.prevent="clearData">
Clear Data
</button>
</div> </div>
<div class="mt-6"> <div class="mt-6">
<a href="experimental/experimental.html" class="border-none hover:underline">Experimental <a
features →</a> href="experimental/experimental.html"
<br> class="border-none hover:underline"
<a href="event_history/event_history.html" class="border-none hover:underline">Event history</a> >Experimental features</a
>
<br />
<a
href="event_history/event_history.html"
class="border-none hover:underline"
>Event history →</a
>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -16,8 +16,6 @@ import {
KINDS, KINDS,
humanPermission, humanPermission,
validateKey, validateKey,
feature,
getDelegator,
} from './utilities/utils'; } from './utilities/utils';
const log = console.log; const log = console.log;
@@ -42,10 +40,7 @@ Alpine.data('options', () => ({
host: '', host: '',
permHosts: [], permHosts: [],
hostPerms: [], hostPerms: [],
delegationActive: false,
visible: false, visible: false,
delegate: false,
delegator: '',
copied: false, copied: false,
setPermission, setPermission,
go, go,
@@ -77,8 +72,6 @@ Alpine.data('options', () => ({
await this.getProfileIndex(); await this.getProfileIndex();
this.setProfileIndexFromSearch(); this.setProfileIndexFromSearch();
await this.refreshProfile(); await this.refreshProfile();
this.delegationActive = await feature('delegation');
}, },
async refreshProfile() { async refreshProfile() {
@@ -88,7 +81,6 @@ Alpine.data('options', () => ({
await this.getNpub(); await this.getNpub();
await this.getRelays(); await this.getRelays();
await this.getPermissions(); await this.getPermissions();
await this.getDelegate();
}, },
// Profile functions // Profile functions
@@ -117,27 +109,12 @@ Alpine.data('options', () => ({
this.profileIndex = await getProfileIndex(); this.profileIndex = await getProfileIndex();
}, },
async getDelegate() {
let [delegate, delegator] = await getDelegator(this.profileIndex);
this.delegate = delegate;
this.delegator = await browser.runtime.sendMessage({
kind: 'npubEncode',
payload: delegator,
});
},
async newProfile() { async newProfile() {
let newIndex = await newProfile(); let newIndex = await newProfile();
await this.getProfileNames(); await this.getProfileNames();
this.profileIndex = newIndex; this.profileIndex = newIndex;
}, },
newDelegated() {
window.location = browser.runtime.getURL(
'wizards/delegation/delegation.html'
);
},
async deleteProfile() { async deleteProfile() {
if ( if (
confirm( confirm(

View File

@@ -1,28 +1,35 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script defer src="permission.build.js"></script> <script defer src="permission.build.js"></script>
<link rel="stylesheet" href="/options.build.css"> <link rel="stylesheet" href="/options.build.css" />
<title>Permission Requested</title> <title>Permission Requested</title>
</head> </head>
<body x-data="permission"> <body x-data="permission">
<div class="text-center"> <div class="text-center">
<h1 class="section-header mt-5 text-center">
<h1 class="section-header mt-5 text-center">App is requesting permission</h1> App is requesting permission
</h1>
<p class="mt-6 text-center"> <p class="mt-6 text-center">
The host The host
<span class="text-lg font-bold" x-text="host"></span> <span class="text-lg font-bold" x-text="host"></span>
is requesting the following permission: is requesting the following permission:
<span class="text-lg font-bold" x-text="humanPermission"></span>. <span class="text-lg font-bold" x-text="humanPermission"></span
>.
</p> </p>
<p x-show="isSigningEvent"> <p x-show="isSigningEvent">
Event kind is <a :href="eventInfo.nip" class="text-lg font-bold" x-text="eventInfo.desc" Event kind is
@click.prevent="await openNip()"></a>. <a
:href="eventInfo.nip"
class="text-lg font-bold"
x-text="eventInfo.desc"
@click.prevent="await openNip()"
></a
>.
</p> </p>
<template x-if="isSigningEvent"> <template x-if="isSigningEvent">
@@ -34,10 +41,18 @@
<div class="mt-6 text-center"> <div class="mt-6 text-center">
<button class="button" @click="await allow()">Allow</button> <button class="button" @click="await allow()">Allow</button>
<button class="button" @click="await deny()">Deny</button> <button class="button" @click="await deny()">Deny</button>
<input class="checkbox" type="checkbox" id="remember" x-model="remember"> <input
<label for="remember">Remember selection<span x-show="isSigningEvent" x-cloak> (by event kind)</span></label> class="checkbox"
type="checkbox"
id="remember"
x-model="remember"
/>
<label for="remember"
>Remember selection<span x-show="isSigningEvent" x-cloak>
(by event kind)</span
></label
>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -2,7 +2,6 @@
color-scheme: light dark; color-scheme: light dark;
} }
body { body {
width: 300px; width: 300px;
padding: 15px; padding: 15px;

View File

@@ -1,11 +1,10 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="popup.css"> <link rel="stylesheet" href="popup.css" />
<link rel="stylesheet" href="options.build.css"> <link rel="stylesheet" href="options.build.css" />
<script defer src="popup.build.js"></script> <script defer src="popup.build.js"></script>
</head> </head>
@@ -14,19 +13,49 @@
<label for="profile">Active Profile</label> <label for="profile">Active Profile</label>
<div class="profile-buttons flex flex-row gap-2"> <div class="profile-buttons flex flex-row gap-2">
<div class="grow"> <div class="grow">
<select x-model.number="profileIndex" name="profile" id="profile" class="input"> <select
<template x-for="(prof, index) in profileNames" :key="index"> x-model.number="profileIndex"
name="profile"
id="profile"
class="input"
>
<template
x-for="(prof, index) in profileNames"
:key="index"
>
<option x-text="prof" :value="index"></option> <option x-text="prof" :value="index"></option>
</template> </template>
</select> </select>
</div> </div>
<button class="button p-1.5" @click.prevent="await copyNpub()" style="display: block;" title="Copy npub"> <button
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" class="button p-1.5"
viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"> @click.prevent="await copyNpub()"
<path d="M19.4 20H9.6a.6.6 0 01-.6-.6V9.6a.6.6 0 01.6-.6h9.8a.6.6 0 01.6.6v9.8a.6.6 0 01-.6.6z" style="display: block"
stroke="#f5d0fe" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> title="Copy npub"
<path d="M15 9V4.6a.6.6 0 00-.6-.6H4.6a.6.6 0 00-.6.6v9.8a.6.6 0 00.6.6H9" stroke="#f5d0fe" >
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <?xml version="1.0" encoding="UTF-8"?><svg
width="24px"
height="24px"
stroke-width="1.5"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
color="#000000"
>
<path
d="M19.4 20H9.6a.6.6 0 01-.6-.6V9.6a.6.6 0 01.6-.6h9.8a.6.6 0 01.6.6v9.8a.6.6 0 01-.6.6z"
stroke="#f5d0fe"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M15 9V4.6a.6.6 0 00-.6-.6H4.6a.6.6 0 00-.6.6v9.8a.6.6 0 00.6.6H9"
stroke="#f5d0fe"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</svg> </svg>
</button> </button>
</div> </div>
@@ -34,22 +63,25 @@
<div class="relay" x-show="relayCount < 1 && showRelayReminder" x-cloak> <div class="relay" x-show="relayCount < 1 && showRelayReminder" x-cloak>
<span> <span>
You do not have any relays setup for this profile. Would you like to add some recommended You do not have any relays setup for this profile. Would you
relays now? like to add some recommended relays now?
</span> </span>
<br> <br />
<button class="button" @click="await addRelays()">Add Relays</button> <button class="button" @click="await addRelays()">
Add Relays
</button>
<button class="button" @click="noThanks">No Thanks</button> <button class="button" @click="noThanks">No Thanks</button>
</div> </div>
<div class="help"> <div class="help">
<button class="button p-1.5" @click="await openOptions()">Settings</button> <button class="button p-1.5" @click="await openOptions()">
Settings
</button>
</div> </div>
<div class="disclaimer"> <div class="disclaimer">
No user data is collected or transmitted. No user data is collected or transmitted. All private keys are
All private keys are stored in the extension's sequestered local browser storage. stored in the extension's sequestered local browser storage.
</div> </div>
</body> </body>
</html> </html>

View File

@@ -87,7 +87,6 @@ async function migrate(version, goal) {
if (version === 1) { if (version === 1) {
console.log('migrating to version 2.'); console.log('migrating to version 2.');
let profiles = await getProfiles(); let profiles = await getProfiles();
profiles.forEach(profile => (profile.delegate = false));
await storage.set({ profiles }); await storage.set({ profiles });
return version + 1; return version + 1;
} }
@@ -156,7 +155,6 @@ export async function generateProfile(name = 'Default') {
privKey: await generatePrivateKey(), privKey: await generatePrivateKey(),
hosts: {}, hosts: {},
relays: [], relays: [],
delegate: false,
relayReminder: true, relayReminder: true,
}; };
} }
@@ -293,13 +291,6 @@ export async function toggleRelayReminder() {
await storage.set({ profiles }); await storage.set({ profiles });
} }
export async function getDelegator(index) {
let profiles = await getProfiles();
let profile = profiles[index];
console.log(profile);
return [profile.delegate, profile.delegator];
}
export async function getNpub() { export async function getNpub() {
let index = await getProfileIndex(); let index = await getProfileIndex();
return await browser.runtime.sendMessage({ return await browser.runtime.sendMessage({

View File

@@ -1,59 +0,0 @@
<!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.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>

View File

@@ -1,86 +0,0 @@
import Alpine from 'alpinejs';
import {
generateProfile,
getProfiles,
validateKey,
} from '../../utilities/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();

View File

@@ -1,15 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
let watch =
process.argv[2] === 'watch'
? {
onRebuild(error, result) {
if (error) console.error('watch rebuild failed: ', error);
else console.log('watch rebuild succeeded: ', result);
},
}
: false;
require('esbuild') require('esbuild')
.build({ .build({
entryPoints: { entryPoints: {
@@ -22,8 +12,6 @@ require('esbuild')
'./Shared (Extension)/Resources/permission/permission.js', './Shared (Extension)/Resources/permission/permission.js',
'experimental/experimental.build': 'experimental/experimental.build':
'./Shared (Extension)/Resources/experimental/experimental.js', './Shared (Extension)/Resources/experimental/experimental.js',
'wizards/delegation/delegation.build':
'./Shared (Extension)/Resources/wizards/delegation/delegation.js',
'event_history/event_history.build': 'event_history/event_history.build':
'./Shared (Extension)/Resources/event_history/event_history.js', './Shared (Extension)/Resources/event_history/event_history.js',
}, },
@@ -31,6 +19,5 @@ require('esbuild')
sourcemap: 'inline', sourcemap: 'inline',
bundle: true, bundle: true,
// minify: true, // minify: true,
watch,
}) })
.catch(() => process.exit(1)); .catch(() => process.exit(1));

1428
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
"popup.html" "popup.html"
], ],
"scripts": { "scripts": {
"build": "./build.js", "build": "tailwindcss -i './Shared (Extension)/Resources/options.css' -o './Shared (Extension)/Resources/options.build.css' && ./build.js",
"watch": "./build.js watch", "watch": "./build.js watch",
"watch-tailwind": "tailwindcss -i './Shared (Extension)/Resources/options.css' -o './Shared (Extension)/Resources/options.build.css' --watch", "watch-tailwind": "tailwindcss -i './Shared (Extension)/Resources/options.css' -o './Shared (Extension)/Resources/options.build.css' --watch",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@@ -17,16 +17,17 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"alpinejs": "^3.10.5", "@alpinejs/csp": "^3.14.1",
"async-mutex": "^0.4.0", "alpinejs": "^3.14.1",
"idb": "^7.1.1", "async-mutex": "^0.5.0",
"idb": "^8.0.0",
"json-format-highlight": "^1.0.4", "json-format-highlight": "^1.0.4",
"nostr-tools": "^1.3.0" "nostr-tools": "^2.7.2"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.9",
"esbuild": "^0.16.17", "esbuild": "^0.23.1",
"prettier": "^2.8.3", "prettier": "^3.3.3",
"tailwindcss": "^3.2.4" "tailwindcss": "^3.4.12"
} }
} }