Merge pull request #21 from fishcakeday/ios18-updates

Make extension work on the latest iOS (18)
This commit is contained in:
Ryan Breen
2024-09-22 14:38:36 -04:00
committed by GitHub
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,20 @@
{ {
"colors" : [ "colors": [
{ {
"color" : { "color": {
"color-space" : "display-p3", "color-space": "display-p3",
"components" : { "components": {
"alpha" : "1.000", "alpha": "1.000",
"blue" : "0.665", "blue": "0.665",
"green" : "0.271", "green": "0.271",
"red" : "0.509" "red": "0.509"
}
},
"idiom": "universal"
} }
}, ],
"idiom" : "universal" "info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,74 +1,74 @@
{ {
"images" : [ "images": [
{ {
"filename" : "iOS-Icon-1024.png", "filename": "iOS-Icon-1024.png",
"idiom" : "universal", "idiom": "universal",
"platform" : "ios", "platform": "ios",
"size" : "1024x1024" "size": "1024x1024"
}, },
{ {
"filename" : "Icon-16.png", "filename": "Icon-16.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "1x", "scale": "1x",
"size" : "16x16" "size": "16x16"
}, },
{ {
"filename" : "Icon-32.png", "filename": "Icon-32.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "2x", "scale": "2x",
"size" : "16x16" "size": "16x16"
}, },
{ {
"filename" : "Icon-32 1.png", "filename": "Icon-32 1.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "1x", "scale": "1x",
"size" : "32x32" "size": "32x32"
}, },
{ {
"filename" : "Icon-64.png", "filename": "Icon-64.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "2x", "scale": "2x",
"size" : "32x32" "size": "32x32"
}, },
{ {
"filename" : "Icon-128.png", "filename": "Icon-128.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "1x", "scale": "1x",
"size" : "128x128" "size": "128x128"
}, },
{ {
"filename" : "Icon-256.png", "filename": "Icon-256.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "2x", "scale": "2x",
"size" : "128x128" "size": "128x128"
}, },
{ {
"filename" : "Icon-256 1.png", "filename": "Icon-256 1.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "1x", "scale": "1x",
"size" : "256x256" "size": "256x256"
}, },
{ {
"filename" : "Icon-512.png", "filename": "Icon-512.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "2x", "scale": "2x",
"size" : "256x256" "size": "256x256"
}, },
{ {
"filename" : "Icon-512 1.png", "filename": "Icon-512 1.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "1x", "scale": "1x",
"size" : "512x512" "size": "512x512"
}, },
{ {
"filename" : "Icon-1024.png", "filename": "Icon-1024.png",
"idiom" : "mac", "idiom": "mac",
"scale" : "2x", "scale": "2x",
"size" : "512x512" "size": "512x512"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,6 +1,6 @@
{ {
"info" : { "info": {
"author" : "xcode", "author": "xcode",
"version" : 1 "version": 1
} }
} }

View File

@@ -1,20 +1,20 @@
{ {
"images" : [ "images": [
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "Icon-512.png", "filename": "Icon-512.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,6 +1,6 @@
{ {
"info" : { "info": {
"author" : "xcode", "author": "xcode",
"version" : 1 "version": 1
} }
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "ipad-menu.png", "filename": "ipad-menu.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "ipad-popup.png", "filename": "ipad-popup.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "ipad-url-bar.png", "filename": "ipad-url-bar.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,6 +1,6 @@
{ {
"info" : { "info": {
"author" : "xcode", "author": "xcode",
"version" : 1 "version": 1
} }
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "iphone-menu.png", "filename": "iphone-menu.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "iphone-popup.png", "filename": "iphone-popup.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "iphone-url-bar.png", "filename": "iphone-url-bar.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,6 +1,6 @@
{ {
"info" : { "info": {
"author" : "xcode", "author": "xcode",
"version" : 1 "version": 1
} }
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "default-popup.png", "filename": "default-popup.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

View File

@@ -1,21 +1,21 @@
{ {
"images" : [ "images": [
{ {
"filename" : "toolbar-inactive.png", "filename": "toolbar-inactive.png",
"idiom" : "universal", "idiom": "universal",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"scale" : "3x" "scale": "3x"
}
],
"info": {
"author": "xcode",
"version": 1
} }
],
"info" : {
"author" : "xcode",
"version" : 1
}
} }

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

View File

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

View File

@@ -2,46 +2,48 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
[x-cloak] { display: none; } [x-cloak] {
display: none;
}
@layer components { @layer components {
.button { .button {
/* Colors */ /* Colors */
@apply bg-fuchsia-900 hover:bg-fuchsia-800 active:bg-fuchsia-700 text-fuchsia-200 disabled:bg-gray-200 disabled:text-black; @apply bg-fuchsia-900 hover:bg-fuchsia-800 active:bg-fuchsia-700 text-fuchsia-200 disabled:bg-gray-200 disabled:text-black;
/* Sizing and padding */ /* Sizing and padding */
@apply rounded-lg p-1.5 md:w-24 min-w-fit text-center; @apply rounded-lg p-1.5 md:w-24 min-w-fit text-center;
} }
.input { .input {
/* Colors */ /* Colors */
@apply bg-fuchsia-200 text-fuchsia-800 disabled:bg-gray-200 disabled:text-black focus:border-fuchsia-800; @apply bg-fuchsia-200 text-fuchsia-800 disabled:bg-gray-200 disabled:text-black focus:border-fuchsia-800;
/* Sizing and padding */ /* Sizing and padding */
@apply rounded-lg p-1.5 lg:p-1.5 w-full md:w-64; @apply rounded-lg p-1.5 lg:p-1.5 w-full md:w-64;
} }
.checkbox { .checkbox {
/* Colors */ /* Colors */
@apply text-fuchsia-800 bg-fuchsia-200 rounded-full accent-fuchsia-200; @apply text-fuchsia-800 bg-fuchsia-200 rounded-full accent-fuchsia-200;
/* Sizing and padding */ /* Sizing and padding */
@apply w-4 h-4 lg:w-5 lg:h-5; @apply w-4 h-4 lg:w-5 lg:h-5;
} }
.section { .section {
@apply border-2 border-fuchsia-700 rounded-lg p-1 md:p-5 mt-6 shadow-md; @apply border-2 border-fuchsia-700 rounded-lg p-1 md:p-5 mt-6 shadow-md;
} }
.section-header { .section-header {
@apply text-2xl lg:text-5xl font-bold; @apply text-2xl lg:text-5xl font-bold;
} }
.subsection-header { .subsection-header {
@apply text-xl lg:text-4xl font-bold; @apply text-xl lg:text-4xl font-bold;
} }
a { a {
@apply border-2 border-dotted text-fuchsia-800 border-fuchsia-800 hover:border-transparent; @apply border-2 border-dotted text-fuchsia-800 border-fuchsia-800 hover:border-transparent;
} }
} }

View File

@@ -1,184 +1,261 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="options.build.css" />
<script defer src="options.build.js"></script>
</head>
<head> <body x-data="options" class="text-fuchsia-900 p-3.5 lg:p-32">
<meta charset="UTF-8"> <h1 class="text-3xl lg:text-6xl font-bold md:text-center">Settings</h1>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="options.build.css">
<script defer src="options.build.js"></script>
</head> <!-- PROFILES -->
<div class="mt-6">
<body x-data="options" class="text-fuchsia-900 p-3.5 lg:p-32"> <label for="profiles">Profile</label>
<h1 class="text-3xl lg:text-6xl font-bold md:text-center">Settings</h1> <br />
<select class="input" x-model.number="profileIndex" id="profiles">
<!-- PROFILES --> <template x-for="(name, index) in profileNames" :key="index">
<div class="mt-6"> <option x-text="name" :value="index"></option>
<label for="profiles">Profile</label> </template>
<br> </select>
<select class="input" x-model.number="profileIndex" id="profiles"> <div class="block md:inline p-3 pl-0 md:p-0">
<template x-for="(name, index) in profileNames" :key="index"> <button class="button" @click.prevent="newProfile">New</button>
<option x-text="name" :value="index"></option> <button class="button" @click.prevent="deleteProfile">
</template> Delete
</select> </button>
<div class="block md:inline p-3 pl-0 md:p-0"> </div>
<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">Delete</button>
</div>
</div>
<!-- KEYS -->
<div class="section">
<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>
<form @submit.prevent="saveProfile">
<div class="mt-3">
<label for="profile-name">Profile Name</label>
<br>
<input x-model="profileName" type="text" class="input" autocapitalize="off" autocomplete="off" spellcheck="off">
</div>
<div class="mt-3" x-show="delegate" x-cloak>
<div class="mb-1">
<span class="text-red-700 font-bold">This is is a delegated profile.</span>
</div> </div>
<label for="delegator-pub-key">Delegator Public Key</label> <!-- KEYS -->
<br> <div class="section">
<input id="delegator-pub-key" x-model="delegator" class="input" autocapitalize="off" autocomplete="off" <h2 class="section-header">Keys</h2>
spellcheck="off" :disabled="delegate"> <p class="text-sm italic">
</div> Provide your <code class="not-italic">nsec</code> or legacy
(hexadecimal) private keys.
</p>
<form @submit.prevent="saveProfile">
<div class="mt-3">
<label for="profile-name">Profile Name</label>
<br />
<input
x-model="profileName"
type="text"
class="input"
autocapitalize="off"
autocomplete="off"
spellcheck="off"
/>
</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"
</div> 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 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"
</div> type="text"
class="input"
disabled
/>
<a
href=""
class="border-none"
@click.prevent="copyPubKey"
x-text="copied ? 'Copied!' : 'Copy'"
></a>
</div>
<div class="mt-3"> <div class="mt-3">
<button class="button" :disabled="!needsSave || !validKey" @click.prevent="saveProfile">Save</button> <button
</div> class="button"
</form> :disabled="!needsSave || !validKey"
@click.prevent="saveProfile"
>
Save
</button>
</div>
</form>
</div>
</div> <!-- RELAYS -->
<div class="section">
<h2 class="section-header">Relays</h2>
<p class="text-sm italic">Add relay suggestions for clients.</p>
<template x-if="hasRelays">
<table
class="mt-3 text-xs md:text-base table-auto md:table-fixed"
>
<thead class="font-bold text-lg">
<td class="p-2 text-center">URL</td>
<td class="p-2 text-center">Read</td>
<td class="p-2 text-center">Write</td>
<td class="p-2 text-center">Actions</td>
</thead>
<template x-for="(relay, index) in relays" :key="index">
<tr>
<td class="p-2 w-1/3" x-text="relay.url"></td>
<td class="p-2 text-center">
<input
class="checkbox"
type="checkbox"
x-model="relay.read"
@change="await saveRelays()"
/>
</td>
<td class="p-2 text-center">
<input
class="checkbox"
type="checkbox"
x-model="relay.write"
@change="await saveRelays()"
/>
</td>
<td class="p-2 text-center">
<button
class="button"
@click.prevent="await deleteRelay(index)"
>
Delete
</button>
</td>
</tr>
</template>
</table>
</template>
<!-- RELAYS --> <template x-if="!hasRelays">
<div class="section"> <div class="mt-3">
<h2 class="section-header">Relays</h2> There are no relays assigned to this profile.
<p class="text-sm italic">Add relay suggestions for clients.</p> </div>
<template x-if="hasRelays"> </template>
<table class="mt-3 text-xs md:text-base table-auto md:table-fixed">
<thead class="font-bold text-lg">
<td class="p-2 text-center">URL</td>
<td class="p-2 text-center">Read</td>
<td class="p-2 text-center">Write</td>
<td class="p-2 text-center">Actions</td>
</thead>
<template x-for="(relay, index) in relays" :key="index">
<tr>
<td class="p-2 w-1/3" x-text="relay.url"></td>
<td class="p-2 text-center">
<input class="checkbox" type="checkbox" x-model="relay.read" @change="await saveRelays()">
</td>
<td class="p-2 text-center">
<input class="checkbox" type="checkbox" x-model="relay.write" @change="await saveRelays()">
</td>
<td class="p-2 text-center">
<button class="button" @click.prevent="await deleteRelay(index)">Delete</button>
</td>
</tr>
</template>
</table>
</template>
<template x-if="!hasRelays"> <div class="mt-3" x-show="hasRecommendedRelays" x-cloak>
<div class="mt-3"> <select x-model="recommendedRelay" class="input">
There are no relays assigned to this profile. <option value="" disabled selected>
</div> Recommended Relays
</template> </option>
<template x-for="relay in recommendedRelays">
<option :value="relay" x-text="relay"></option>
</template>
</select>
</div>
<div class="mt-3" x-show="hasRecommendedRelays" x-cloak> <input
<select x-model="recommendedRelay" class="input"> class="mt-3 input"
<option value="" disabled selected>Recommended Relays</option> x-model="newRelay"
<template x-for="relay in recommendedRelays"> type="text"
<option :value="relay" x-text="relay"></option> @keyup.enter="await addRelay()"
</template> placeholder="wss://..."
</select> autocomplete="off"
</div> autocorrect="off"
autocapitalize="off"
spellcheck="off"
/>
<div class="block md:inline p-3 pl-0 md:p-0">
<button class="button" @click="await addRelay()">Add</button>
</div>
<div
class="text-red-500 font-bold"
x-show="urlError.length > 0"
x-text="urlError"
x-cloak
></div>
</div>
<input class="mt-3 input" x-model="newRelay" type="text" @keyup.enter="await addRelay()" placeholder="wss://..." <!-- PERMISSIONS -->
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="off"> <div class="section">
<div class="block md:inline p-3 pl-0 md:p-0"> <h2 class="section-header">App Permissions</h2>
<button class="button" @click="await addRelay()">Add</button> <p class="text-sm italic">
</div> Permissions granted to individual applications. Everything
<div class="text-red-500 font-bold" x-show="urlError.length > 0" x-text="urlError" x-cloak></div> defaults to <span class="font-bold">Ask</span> unless you have
</div> saved a different option.
</p>
<div class="mt-3" x-show="permHosts.length > 0">
<label for="app">Apps</label>
<br />
<select id="app" class="input" x-model="host">
<option value=""></option>
<template x-for="permHost in permHosts">
<option :value="permHost" x-text="permHost"></option>
</template>
</select>
</div>
<!-- PERMISSIONS --> <p x-show="permHosts.length === 0" x-cloak class="font-bold mt-3">
<div class="section"> You have not remembered any app requests yet.
<h2 class="section-header">App Permissions</h2> </p>
<p class="text-sm italic">
Permissions granted to individual applications.
Everything defaults to <span class="font-bold">Ask</span> unless you have saved a different option.
</p>
<div class="mt-3" x-show="permHosts.length > 0"> <table
<label for="app">Apps</label> class="mt-3 text-xs md:text-base table-fixed"
<br> x-show="hostPerms.length > 0"
<select id="app" class="input" x-model="host"> x-cloak
<option value=""></option> >
<template x-for="permHost in permHosts"> <thead class="font-bold text-lg">
<option :value="permHost" x-text="permHost"></option> <td class="p-2 text-center">App Request</td>
</template> <td class="p-2 text-center">Action</td>
</select> </thead>
</div> <template
x-for="[etype, humanName, perm] in hostPerms"
:key="etype"
>
<tr>
<td class="p-2 w-1/3 md:w-2/4" x-text="humanName"></td>
<td class="p-2 text-center">
<select
class="input"
:value="perm"
@change="await setPermission(host, etype, $event.target.value, profileIndex)"
>
<option value="ask">Ask</option>
<option value="allow">Allow</option>
<option value="deny">Deny</option>
</select>
</td>
</tr>
</template>
</table>
</div>
<p x-show="permHosts.length === 0" x-cloak class="font-bold mt-3">You have not remembered any app requests yet.</p> <div class="mt-6">
<button class="button" @click.prevent="closeOptions">Close</button>
<button class="button" @click.prevent="clearData">
Clear Data
</button>
</div>
<table class="mt-3 text-xs md:text-base table-fixed" x-show="hostPerms.length > 0" x-cloak> <div class="mt-6">
<thead class="font-bold text-lg"> <a
<td class="p-2 text-center">App Request</td> href="experimental/experimental.html"
<td class="p-2 text-center">Action</td> class="border-none hover:underline"
</thead> >Experimental features →</a
<template x-for="[etype, humanName, perm] in hostPerms" :key="etype"> >
<tr> <br />
<td class="p-2 w-1/3 md:w-2/4" x-text="humanName"></td> <a
<td class="p-2 text-center"> href="event_history/event_history.html"
<select class="input" :value="perm" class="border-none hover:underline"
@change="await setPermission(host, etype, $event.target.value, profileIndex)"> >Event history →</a
<option value="ask">Ask</option> >
<option value="allow">Allow</option> </div>
<option value="deny">Deny</option> </body>
</select> </html>
</td>
</tr>
</template>
</table>
</div>
<div class="mt-6">
<button class="button" @click.prevent="closeOptions">Close</button>
<button class="button" @click.prevent="clearData">Clear Data</button>
</div>
<div class="mt-6">
<a href="experimental/experimental.html" class="border-none hover:underline">Experimental
features →</a>
<br>
<a href="event_history/event_history.html" class="border-none hover:underline">Event history →</a>
</div>
</body>
</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,43 +1,58 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <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="permission.build.js"></script>
<link rel="stylesheet" href="/options.build.css" />
<title>Permission Requested</title>
</head>
<head> <body x-data="permission">
<meta charset="UTF-8"> <div class="text-center">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <h1 class="section-header mt-5 text-center">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> App is requesting permission
<script defer src="permission.build.js"></script> </h1>
<link rel="stylesheet" href="/options.build.css"> <p class="mt-6 text-center">
<title>Permission Requested</title> The host
</head> <span class="text-lg font-bold" x-text="host"></span>
is requesting the following permission:
<span class="text-lg font-bold" x-text="humanPermission"></span
>.
</p>
<p x-show="isSigningEvent">
Event kind is
<a
:href="eventInfo.nip"
class="text-lg font-bold"
x-text="eventInfo.desc"
@click.prevent="await openNip()"
></a
>.
</p>
<body x-data="permission"> <template x-if="isSigningEvent">
<div class="text-center"> <div class="inline-block text-left">
<pre class="mt-6" x-html="humanEvent"></pre>
<h1 class="section-header mt-5 text-center">App is requesting permission</h1> </div>
<p class="mt-6 text-center"> </template>
The host
<span class="text-lg font-bold" x-text="host"></span>
is requesting the following permission:
<span class="text-lg font-bold" x-text="humanPermission"></span>.
</p>
<p x-show="isSigningEvent">
Event kind is <a :href="eventInfo.nip" class="text-lg font-bold" x-text="eventInfo.desc"
@click.prevent="await openNip()"></a>.
</p>
<template x-if="isSigningEvent">
<div class="inline-block text-left">
<pre class="mt-6" x-html="humanEvent"></pre>
</div>
</template>
<div class="mt-6 text-center">
<button class="button" @click="await allow()">Allow</button>
<button class="button" @click="await deny()">Deny</button>
<input 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>
</body>
<div class="mt-6 text-center">
<button class="button" @click="await allow()">Allow</button>
<button class="button" @click="await deny()">Deny</button>
<input
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>
</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;
@@ -24,4 +23,4 @@ body {
margin-top: 10px; margin-top: 10px;
font-size: 50%; font-size: 50%;
color: green; color: green;
} }

View File

@@ -1,55 +1,87 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="popup.css" />
<link rel="stylesheet" href="options.build.css" />
<script defer src="popup.build.js"></script>
</head>
<head> <body x-data="popup">
<meta charset="UTF-8"> <div class="profiles">
<meta name="viewport" content="width=device-width,initial-scale=1" /> <label for="profile">Active Profile</label>
<link rel="stylesheet" href="popup.css"> <div class="profile-buttons flex flex-row gap-2">
<link rel="stylesheet" href="options.build.css"> <div class="grow">
<script defer src="popup.build.js"></script> <select
</head> x-model.number="profileIndex"
name="profile"
<body x-data="popup"> id="profile"
<div class="profiles"> class="input"
<label for="profile">Active Profile</label> >
<div class="profile-buttons flex flex-row gap-2"> <template
<div class="grow"> x-for="(prof, index) in profileNames"
<select x-model.number="profileIndex" name="profile" id="profile" class="input"> :key="index"
<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>
<button
class="button p-1.5"
@click.prevent="await copyNpub()"
style="display: block"
title="Copy npub"
>
<?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>
</button>
</div> </div>
<button class="button p-1.5" @click.prevent="await copyNpub()" style="display: block;" title="Copy npub"> </div>
<?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"> <div class="relay" x-show="relayCount < 1 && showRelayReminder" x-cloak>
<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" <span>
stroke="#f5d0fe" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> You do not have any relays setup for this profile. Would you
<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" like to add some recommended relays now?
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </span>
</svg> <br />
<button class="button" @click="await addRelays()">
Add Relays
</button>
<button class="button" @click="noThanks">No Thanks</button>
</div>
<div class="help">
<button class="button p-1.5" @click="await openOptions()">
Settings
</button> </button>
</div> </div>
</div>
<div class="relay" x-show="relayCount < 1 && showRelayReminder" x-cloak> <div class="disclaimer">
<span> No user data is collected or transmitted. All private keys are
You do not have any relays setup for this profile. Would you like to add some recommended stored in the extension's sequestered local browser storage.
relays now? </div>
</span> </body>
<br> </html>
<button class="button" @click="await addRelays()">Add Relays</button>
<button class="button" @click="noThanks">No Thanks</button>
</div>
<div class="help">
<button class="button p-1.5" @click="await openOptions()">Settings</button>
</div>
<div class="disclaimer">
No user data is collected or transmitted.
All private keys are stored in the extension's sequestered local browser storage.
</div>
</body>
</html>

View File

@@ -9,59 +9,59 @@ export const RECOMMENDED_RELAYS = [
]; ];
// prettier-ignore // prettier-ignore
export const KINDS = [ export const KINDS = [
[0, 'Metadata', 'https://github.com/nostr-protocol/nips/blob/master/01.md'], [0, 'Metadata', 'https://github.com/nostr-protocol/nips/blob/master/01.md'],
[1, 'Text', 'https://github.com/nostr-protocol/nips/blob/master/01.md'], [1, 'Text', 'https://github.com/nostr-protocol/nips/blob/master/01.md'],
[2, 'Recommend Relay', 'https://github.com/nostr-protocol/nips/blob/master/01.md'], [2, 'Recommend Relay', 'https://github.com/nostr-protocol/nips/blob/master/01.md'],
[3, 'Contacts', 'https://github.com/nostr-protocol/nips/blob/master/02.md'], [3, 'Contacts', 'https://github.com/nostr-protocol/nips/blob/master/02.md'],
[4, 'Encrypted Direct Messages', 'https://github.com/nostr-protocol/nips/blob/master/04.md'], [4, 'Encrypted Direct Messages', 'https://github.com/nostr-protocol/nips/blob/master/04.md'],
[5, 'Event Deletion', 'https://github.com/nostr-protocol/nips/blob/master/09.md'], [5, 'Event Deletion', 'https://github.com/nostr-protocol/nips/blob/master/09.md'],
[6, 'Repost', 'https://github.com/nostr-protocol/nips/blob/master/18.md'], [6, 'Repost', 'https://github.com/nostr-protocol/nips/blob/master/18.md'],
[7, 'Reaction', 'https://github.com/nostr-protocol/nips/blob/master/25.md'], [7, 'Reaction', 'https://github.com/nostr-protocol/nips/blob/master/25.md'],
[8, 'Badge Award', 'https://github.com/nostr-protocol/nips/blob/master/58.md'], [8, 'Badge Award', 'https://github.com/nostr-protocol/nips/blob/master/58.md'],
[16, 'Generic Repost', 'https://github.com/nostr-protocol/nips/blob/master/18.md'], [16, 'Generic Repost', 'https://github.com/nostr-protocol/nips/blob/master/18.md'],
[40, 'Channel Creation', 'https://github.com/nostr-protocol/nips/blob/master/28.md'], [40, 'Channel Creation', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[41, 'Channel Metadata', 'https://github.com/nostr-protocol/nips/blob/master/28.md'], [41, 'Channel Metadata', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[42, 'Channel Message', 'https://github.com/nostr-protocol/nips/blob/master/28.md'], [42, 'Channel Message', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[43, 'Channel Hide Message', 'https://github.com/nostr-protocol/nips/blob/master/28.md'], [43, 'Channel Hide Message', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[44, 'Channel Mute User', 'https://github.com/nostr-protocol/nips/blob/master/28.md'], [44, 'Channel Mute User', 'https://github.com/nostr-protocol/nips/blob/master/28.md'],
[1063, 'File Metadata', 'https://github.com/nostr-protocol/nips/blob/master/94.md'], [1063, 'File Metadata', 'https://github.com/nostr-protocol/nips/blob/master/94.md'],
[1311, 'Live Chat Message', 'https://github.com/nostr-protocol/nips/blob/master/53.md'], [1311, 'Live Chat Message', 'https://github.com/nostr-protocol/nips/blob/master/53.md'],
[1984, 'Reporting', 'https://github.com/nostr-protocol/nips/blob/master/56.md'], [1984, 'Reporting', 'https://github.com/nostr-protocol/nips/blob/master/56.md'],
[1985, 'Label', 'https://github.com/nostr-protocol/nips/blob/master/32.md'], [1985, 'Label', 'https://github.com/nostr-protocol/nips/blob/master/32.md'],
[4550, 'Community Post Approval', 'https://github.com/nostr-protocol/nips/blob/master/72.md'], [4550, 'Community Post Approval', 'https://github.com/nostr-protocol/nips/blob/master/72.md'],
[7000, 'Job Feedback', 'https://github.com/nostr-protocol/nips/blob/master/90.md'], [7000, 'Job Feedback', 'https://github.com/nostr-protocol/nips/blob/master/90.md'],
[9041, 'Zap Goal', 'https://github.com/nostr-protocol/nips/blob/master/75.md'], [9041, 'Zap Goal', 'https://github.com/nostr-protocol/nips/blob/master/75.md'],
[9734, 'Zap Request', 'https://github.com/nostr-protocol/nips/blob/master/57.md'], [9734, 'Zap Request', 'https://github.com/nostr-protocol/nips/blob/master/57.md'],
[9735, 'Zap', 'https://github.com/nostr-protocol/nips/blob/master/57.md'], [9735, 'Zap', 'https://github.com/nostr-protocol/nips/blob/master/57.md'],
[10000, 'Mute List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'], [10000, 'Mute List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'],
[10001, 'Pin List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'], [10001, 'Pin List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'],
[10002, 'Relay List Metadata', 'https://github.com/nostr-protocol/nips/blob/master/65.md'], [10002, 'Relay List Metadata', 'https://github.com/nostr-protocol/nips/blob/master/65.md'],
[13194, 'Wallet Info', 'https://github.com/nostr-protocol/nips/blob/master/47.md'], [13194, 'Wallet Info', 'https://github.com/nostr-protocol/nips/blob/master/47.md'],
[22242, 'Client Authentication', 'https://github.com/nostr-protocol/nips/blob/master/42.md'], [22242, 'Client Authentication', 'https://github.com/nostr-protocol/nips/blob/master/42.md'],
[23194, 'Wallet Request', 'https://github.com/nostr-protocol/nips/blob/master/47.md'], [23194, 'Wallet Request', 'https://github.com/nostr-protocol/nips/blob/master/47.md'],
[23195, 'Wallet Response', 'https://github.com/nostr-protocol/nips/blob/master/47.md'], [23195, 'Wallet Response', 'https://github.com/nostr-protocol/nips/blob/master/47.md'],
[24133, 'Nostr Connect', 'https://github.com/nostr-protocol/nips/blob/master/46.md'], [24133, 'Nostr Connect', 'https://github.com/nostr-protocol/nips/blob/master/46.md'],
[27235, 'HTTP Auth', 'https://github.com/nostr-protocol/nips/blob/master/98.md'], [27235, 'HTTP Auth', 'https://github.com/nostr-protocol/nips/blob/master/98.md'],
[30000, 'Categorized People List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'], [30000, 'Categorized People List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'],
[30001, 'Categorized Bookmark List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'], [30001, 'Categorized Bookmark List', 'https://github.com/nostr-protocol/nips/blob/master/51.md'],
[30008, 'Profile Badges', 'https://github.com/nostr-protocol/nips/blob/master/58.md'], [30008, 'Profile Badges', 'https://github.com/nostr-protocol/nips/blob/master/58.md'],
[30009, 'Badge Definition', 'https://github.com/nostr-protocol/nips/blob/master/58.md'], [30009, 'Badge Definition', 'https://github.com/nostr-protocol/nips/blob/master/58.md'],
[30017, 'Create or update a stall', 'https://github.com/nostr-protocol/nips/blob/master/15.md'], [30017, 'Create or update a stall', 'https://github.com/nostr-protocol/nips/blob/master/15.md'],
[30018, 'Create or update a product', 'https://github.com/nostr-protocol/nips/blob/master/15.md'], [30018, 'Create or update a product', 'https://github.com/nostr-protocol/nips/blob/master/15.md'],
[30023, 'Long-Form Content', 'https://github.com/nostr-protocol/nips/blob/master/23.md'], [30023, 'Long-Form Content', 'https://github.com/nostr-protocol/nips/blob/master/23.md'],
[30024, 'Draft Long-form Content', 'https://github.com/nostr-protocol/nips/blob/master/23.md'], [30024, 'Draft Long-form Content', 'https://github.com/nostr-protocol/nips/blob/master/23.md'],
[30078, 'Application-specific Data', 'https://github.com/nostr-protocol/nips/blob/master/78.md'], [30078, 'Application-specific Data', 'https://github.com/nostr-protocol/nips/blob/master/78.md'],
[30311, 'Live Event', 'https://github.com/nostr-protocol/nips/blob/master/53.md'], [30311, 'Live Event', 'https://github.com/nostr-protocol/nips/blob/master/53.md'],
[30315, 'User Statuses', 'https://github.com/nostr-protocol/nips/blob/master/38.md'], [30315, 'User Statuses', 'https://github.com/nostr-protocol/nips/blob/master/38.md'],
[30402, 'Classified Listing', 'https://github.com/nostr-protocol/nips/blob/master/99.md'], [30402, 'Classified Listing', 'https://github.com/nostr-protocol/nips/blob/master/99.md'],
[30403, 'Draft Classified Listing', 'https://github.com/nostr-protocol/nips/blob/master/99.md'], [30403, 'Draft Classified Listing', 'https://github.com/nostr-protocol/nips/blob/master/99.md'],
[31922, 'Date-Based Calendar Event', 'https://github.com/nostr-protocol/nips/blob/master/52.md'], [31922, 'Date-Based Calendar Event', 'https://github.com/nostr-protocol/nips/blob/master/52.md'],
[31923, 'Time-Based Calendar Event', 'https://github.com/nostr-protocol/nips/blob/master/52.md'], [31923, 'Time-Based Calendar Event', 'https://github.com/nostr-protocol/nips/blob/master/52.md'],
[31924, 'Calendar', 'https://github.com/nostr-protocol/nips/blob/master/52.md'], [31924, 'Calendar', 'https://github.com/nostr-protocol/nips/blob/master/52.md'],
[31925, 'Calendar Event RSVP', 'https://github.com/nostr-protocol/nips/blob/master/52.md'], [31925, 'Calendar Event RSVP', 'https://github.com/nostr-protocol/nips/blob/master/52.md'],
[31989, 'Handler recommendation', 'https://github.com/nostr-protocol/nips/blob/master/89.md'], [31989, 'Handler recommendation', 'https://github.com/nostr-protocol/nips/blob/master/89.md'],
[31990, 'Handler information', 'https://github.com/nostr-protocol/nips/blob/master/89.md'], [31990, 'Handler information', 'https://github.com/nostr-protocol/nips/blob/master/89.md'],
[34550, 'Community Definition', 'https://github.com/nostr-protocol/nips/blob/master/72.md'], [34550, 'Community Definition', 'https://github.com/nostr-protocol/nips/blob/master/72.md'],
]; ];
export async function initialize() { export async function initialize() {
@@ -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));

3396
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,33 @@
{ {
"name": "nostore", "name": "nostore",
"version": "1.2.0", "version": "1.2.0",
"description": "", "description": "",
"source": [ "source": [
"background.js", "background.js",
"content.js", "content.js",
"nostr.js", "nostr.js",
"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"
}, },
"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",
"json-format-highlight": "^1.0.4", "idb": "^8.0.0",
"nostr-tools": "^1.3.0" "json-format-highlight": "^1.0.4",
}, "nostr-tools": "^2.7.2"
"devDependencies": { },
"@tailwindcss/forms": "^0.5.3", "devDependencies": {
"esbuild": "^0.16.17", "@tailwindcss/forms": "^0.5.9",
"prettier": "^2.8.3", "esbuild": "^0.23.1",
"tailwindcss": "^3.2.4" "prettier": "^3.3.3",
} "tailwindcss": "^3.4.12"
} }
}