This commit is contained in:
n 2025-04-07 01:36:13 +01:00
parent c976746b07
commit 7939eee96f
9 changed files with 1158 additions and 9401 deletions

@ -1,3 +1,5 @@
always use Typescript where relevant
don't try to modify files in the build folder (eg /dist)
don't put css or JS in the index.html - we should follow appropriate CSP policies
run the lint after every code update
don't ever use the alert function

@ -11,11 +11,23 @@
<script defer src="./bundle.js"></script>
</head>
<body>
<!-- Theme toggle button -->
<div class="theme-toggle-container">
<button id="themeToggleBtn" class="theme-toggle-btn">
<span id="themeIcon">🌙</span>
<span id="themeText">Dark Mode</span>
</button>
</div>
<h1>HTTP Request to Kind 21120 Converter</h1>
<div class="info-box">
<p>This tool converts HTTP requests into a single Nostr event of kind 21120. The HTTP request is encrypted using NIP-44 in the content field with appropriate tags following the specification.</p>
<p>The server's public key (<strong>npub1r6knexka25dn9w9jnf5kf8xh6gfq7n3p38zfl7nn7cjjjsp4umcqnk0aun</strong>) is pre-populated for your convenience.</p>
</div>
<div class="login-container">
<!-- NostrLogin container will be inserted here -->
<div id="loginStatus" class="login-status"></div>
</div>
<h2>Server Information:</h2>

9409
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -45,6 +45,7 @@
"webpack-dev-server": "^5.2.1"
},
"dependencies": {
"nostr-login": "^1.7.11",
"nostr-tools": "^2.12.0",
"qrcode": "^1.5.4",
"qrcode-generator": "^1.4.4"

@ -1,10 +1,13 @@
// client.ts - External TypeScript file for HTTP to Nostr converter
// This follows strict CSP policies by avoiding inline scripts
console.log('client.ts loaded');
// Import functions from other modules
import { displayConvertedEvent } from './converter';
import { lookupNip05, searchUsers } from './search';
import { publishToRelay, convertNpubToHex, verifyEvent } from './relay';
import * as nostrTools from 'nostr-tools';
import {
setDefaultHttpRequest,
sanitizeText,
@ -15,6 +18,62 @@ import {
showLoading
} from './utils';
// Import nostr-login - use require since the default export might not work with import
const NostrLogin = require('nostr-login');
console.log('NostrLogin library imported:', NostrLogin);
console.log('NostrLogin methods:', Object.keys(NostrLogin));
console.log('NostrLogin init method:', NostrLogin.init);
// Check for encryption methods
if (NostrLogin.nip04) console.log('NIP-04 encryption available:', NostrLogin.nip04);
if (NostrLogin.nip44) console.log('NIP-44 encryption available:', NostrLogin.nip44);
if (NostrLogin.encrypt) console.log('Direct encrypt method available:', NostrLogin.encrypt);
console.log('NostrLogin init method:', NostrLogin.init);
/**
* Initialize nostr-login
*/
function initNostrLogin() {
console.log('Initializing NostrLogin');
const loginContainer = document.querySelector('.login-container');
const loginStatusDiv = document.getElementById('loginStatus');
if (!loginContainer || !loginStatusDiv) {
console.error('Login elements not found');
return;
}
// Create a container for the NostrLogin button
const nostrLoginContainer = document.createElement('div');
nostrLoginContainer.id = 'nostr-login-container';
loginContainer.appendChild(nostrLoginContainer);
try {
// Initialize NostrLogin with the container
if (NostrLogin && NostrLogin.init) {
console.log('Initializing NostrLogin.init with container');
NostrLogin.init({
element: nostrLoginContainer,
onConnect: (pubkey: string) => {
console.log('Connected with pubkey:', pubkey);
const npub = nostrTools.nip19.npubEncode(pubkey);
loginStatusDiv.innerHTML = `<span style="color: #008800;">Connected as: ${npub.slice(0, 8)}...${npub.slice(-4)}</span>`;
},
onDisconnect: () => {
console.log('Disconnected');
loginStatusDiv.innerHTML = '<span>Disconnected</span>';
}
});
} else {
console.error('NostrLogin.init is not available');
loginStatusDiv.innerHTML = '<span style="color: #cc0000;">NostrLogin initialization unavailable</span>';
}
} catch (error: any) {
console.error('Failed to initialize NostrLogin:', error);
loginStatusDiv.innerHTML = `<span style="color: #cc0000;">Error initializing Nostr login: ${error.message || String(error)}</span>`;
}
}
/**
* Handle the server search button click
*/
@ -232,13 +291,67 @@ async function handlePublishEvent(): Promise<void> {
}
}
/**
* Toggle between light and dark theme
*/
function toggleTheme(): void {
const body = document.body;
const themeToggle = document.getElementById('themeToggle');
const themeToggleBtn = document.getElementById('themeToggleBtn');
const themeIcon = document.getElementById('themeIcon');
const themeText = document.getElementById('themeText');
const isDarkMode = body.getAttribute('data-theme') === 'dark';
if (isDarkMode) {
// Switch to light theme
body.removeAttribute('data-theme');
localStorage.setItem('theme', 'light');
// Update old toggle if it exists
if (themeToggle) {
const toggleText = themeToggle.querySelector('.theme-toggle-text');
const toggleIcon = themeToggle.querySelector('.theme-toggle-icon');
if (toggleText) toggleText.textContent = 'Dark Mode';
if (toggleIcon) toggleIcon.textContent = '🌓';
}
// Update new toggle button if it exists
if (themeIcon) themeIcon.textContent = '🌙';
if (themeText) themeText.textContent = 'Dark Mode';
} else {
// Switch to dark theme
body.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
// Update old toggle if it exists
if (themeToggle) {
const toggleText = themeToggle.querySelector('.theme-toggle-text');
const toggleIcon = themeToggle.querySelector('.theme-toggle-icon');
if (toggleText) toggleText.textContent = 'Light Mode';
if (toggleIcon) toggleIcon.textContent = '☀️';
}
// Update new toggle button if it exists
if (themeIcon) themeIcon.textContent = '☀️';
if (themeText) themeText.textContent = 'Light Mode';
}
}
// Initialize the event handlers when the DOM is loaded
document.addEventListener('DOMContentLoaded', function(): void {
console.log('DOM content loaded');
// Set up the convert button click handler
const convertButton = document.getElementById('convertButton');
const searchButton = document.getElementById('searchServerBtn');
const publishButton = document.getElementById('publishButton');
// Debug - list all buttons in the document
console.log('All buttons in document:');
document.querySelectorAll('button').forEach((button, index) => {
console.log(`Button ${index}:`, button.id, button.textContent);
});
if (convertButton) {
convertButton.addEventListener('click', displayConvertedEvent);
}
@ -251,8 +364,42 @@ document.addEventListener('DOMContentLoaded', function(): void {
publishButton.addEventListener('click', handlePublishEvent);
}
// Initialize Nostr login
initNostrLogin();
// Set default HTTP request
setDefaultHttpRequest();
// Initialize theme toggle
const themeToggle = document.getElementById('themeToggle');
const themeToggleBtn = document.getElementById('themeToggleBtn');
// First try the new button, then fall back to the old toggle
const toggleElement = themeToggleBtn || themeToggle;
if (toggleElement) {
// Set initial theme based on local storage
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.body.setAttribute('data-theme', 'dark');
// Update UI for whichever toggle we're using
if (themeToggleBtn) {
const themeIcon = document.getElementById('themeIcon');
const themeText = document.getElementById('themeText');
if (themeIcon) themeIcon.textContent = '☀️';
if (themeText) themeText.textContent = 'Light Mode';
} else if (themeToggle) {
const toggleText = themeToggle.querySelector('.theme-toggle-text');
const toggleIcon = themeToggle.querySelector('.theme-toggle-icon');
if (toggleText) toggleText.textContent = 'Light Mode';
if (toggleIcon) toggleIcon.textContent = '☀️';
}
}
// Add click handler
toggleElement.addEventListener('click', toggleTheme);
}
console.log('HTTP to Nostr converter initialized');
});

@ -11,6 +11,20 @@ import qrcode from 'qrcode-generator';
import { convertNpubToHex } from './relay';
import { processTags, showSuccess } from './utils';
// Import nostr-login for encryption/signing
const NostrLogin = require('nostr-login');
// Define interface for Nostr event
interface NostrEvent {
kind: number;
content: string;
tags: string[][];
created_at: number;
pubkey: string;
id?: string;
sig?: string;
}
// Generate a keypair for standalone mode (when no extension is available)
let standaloneSecretKey: Uint8Array | null = null;
let standalonePublicKey: string | null = null;
@ -46,7 +60,9 @@ export function convertToEvent(
decryptkey: string,
relay?: string
): string | null {
if (!httpRequest) {
console.log("convertToEvent called with httpRequest:", httpRequest.substring(0, 50) + "...");
if (!httpRequest || httpRequest.trim() === '') {
alert('Please enter an HTTP request message.');
return null;
}
@ -54,41 +70,87 @@ export function convertToEvent(
// Create a single kind 21120 event with encrypted HTTP request as content
// Following the specification in README.md using actual NIP-44 encryption
// Use the server's pubkey for encryption (not the decryptkey)
// Use the nostr-login library for encryption
let encryptedContent = httpRequest;
// Use actual NIP-44 encryption if available
if (window.nostr && window.nostr.nip44) {
try {
// The second argument MUST be the server's public key in hex format
let serverPubkeyHex = serverPubkey;
try {
// Convert server pubkey to hex if it's an npub
let serverPubkeyHex = serverPubkey;
// Convert npub to hex if needed
if (serverPubkey.startsWith('npub')) {
const hexPubkey = convertNpubToHex(serverPubkey);
if (hexPubkey) {
serverPubkeyHex = hexPubkey;
console.log("Converted npub to hex format:", serverPubkeyHex);
} else {
throw new Error("Failed to decode npub. Please use a valid npub format.");
}
if (serverPubkey.startsWith('npub')) {
const hexPubkey = convertNpubToHex(serverPubkey);
if (hexPubkey) {
serverPubkeyHex = hexPubkey;
console.log("Converted npub to hex format:", serverPubkeyHex);
} else {
throw new Error("Failed to decode npub. Please use a valid npub format.");
}
// Validate that we have a hex string of the right length
if (!/^[0-9a-f]{64}$/i.test(serverPubkeyHex)) {
throw new Error("Invalid server pubkey format. Must be a 64-character hex string.");
}
encryptedContent = window.nostr.nip44.encrypt(httpRequest, serverPubkeyHex);
console.log("Successfully encrypted content with NIP-44");
} catch (error) {
console.error("Error encrypting with NIP-44:", error);
throw error; // Re-throw to prevent creating an event with unencrypted content
}
} else {
console.warn("NIP-44 encryption not available. Content will not be encrypted properly.");
// Validate that we have a hex string of the right length
if (!/^[0-9a-f]{64}$/i.test(serverPubkeyHex)) {
throw new Error("Invalid server pubkey format. Must be a 64-character hex string.");
}
// Check for the most specific NIP-44 methods first, then fall back to more general ones
if (NostrLogin && NostrLogin.nip44 && NostrLogin.nip44.encrypt) {
console.log("Using NostrLogin.nip44.encrypt for encryption");
try {
const encrypted = NostrLogin.nip44.encrypt(httpRequest, serverPubkeyHex);
if (encrypted) {
encryptedContent = encrypted;
console.log("Successfully encrypted content with NostrLogin.nip44");
} else {
console.warn("NostrLogin.nip44 encryption returned null or undefined");
}
} catch (encryptError) {
console.error("Encryption error with NostrLogin.nip44:", encryptError);
}
} else if (NostrLogin && NostrLogin.nip04 && NostrLogin.nip04.encrypt) {
console.log("Using NostrLogin.nip04.encrypt for encryption");
try {
const encrypted = NostrLogin.nip04.encrypt(httpRequest, serverPubkeyHex);
if (encrypted) {
encryptedContent = encrypted;
console.log("Successfully encrypted content with NostrLogin.nip04");
} else {
console.warn("NostrLogin.nip04 encryption returned null or undefined");
}
} catch (encryptError) {
console.error("Encryption error with NostrLogin.nip04:", encryptError);
}
} else if (NostrLogin && NostrLogin.encrypt) {
console.log("Using NostrLogin.encrypt for encryption");
try {
const encrypted = NostrLogin.encrypt(httpRequest, serverPubkeyHex);
if (encrypted) {
encryptedContent = encrypted;
console.log("Successfully encrypted content with NostrLogin.encrypt");
} else {
console.warn("NostrLogin.encrypt returned null or undefined");
}
} catch (encryptError) {
console.error("Encryption error with NostrLogin.encrypt:", encryptError);
}
} else if (window.nostr && window.nostr.nip44) {
console.log("Falling back to window.nostr.nip44.encrypt");
try {
encryptedContent = window.nostr.nip44.encrypt(httpRequest, serverPubkeyHex);
console.log("Successfully encrypted content with window.nostr.nip44");
} catch (encryptError) {
console.error("Encryption error with window.nostr.nip44:", encryptError);
}
} else {
console.warn("No encryption method available. Using unencrypted content.");
}
} catch (error) {
console.error("Error in encryption setup:", error);
// Continue with unencrypted content
}
// Debug log the content
console.log("Final encryptedContent before creating event:", encryptedContent);
// Convert serverPubkey to hex if it's an npub
let pTagValue = serverPubkey;
if (serverPubkey.startsWith('npub')) {
@ -117,6 +179,8 @@ export function convertToEvent(
]
};
console.log("Created event object:", JSON.stringify(event, null, 2));
// Add optional relay tag if provided
if (relay) {
event.tags.push(["r", relay]);
@ -166,8 +230,29 @@ export async function displayConvertedEvent(): Promise<void> {
}
// Convert directly to a single kind 21120 event
// Debug data - log the full HTTP request value
const httpRequestValue = httpRequestBox.value;
console.log("HTTP request textarea value:", httpRequestValue);
console.log("HTTP request length:", httpRequestValue.length);
console.log("HTTP request first 50 chars:", httpRequestValue.substring(0, 50));
// If the request is empty, try to use the placeholder value
let requestToUse = httpRequestValue;
if (!requestToUse || requestToUse.trim() === '') {
// Try to get the placeholder value as a fallback
const placeholder = httpRequestBox.getAttribute('placeholder');
if (placeholder) {
requestToUse = placeholder;
console.log("Using placeholder as request value:", placeholder);
} else {
// Use a very simple default request if all else fails
requestToUse = "GET / HTTP/1.1\nHost: example.com\n\n";
console.log("Using hardcoded default request");
}
}
const convertedEvent = convertToEvent(
httpRequestBox.value,
requestToUse,
pubkey,
serverPubkey,
"$decryptkey",
@ -177,43 +262,83 @@ export async function displayConvertedEvent(): Promise<void> {
if (convertedEvent) {
// Store the original event in case we need to reference it
(window as any).originalEvent = convertedEvent;
// Variable to hold the Nostr event
let nostrEvent: NostrEvent;
// Parse the event to create a proper Nostr event object for signing
const parsedEvent = JSON.parse(convertedEvent);
const nostrEvent = {
kind: 21120,
tags: parsedEvent.tags,
content: parsedEvent.content,
created_at: Math.floor(Date.now() / 1000),
pubkey: parsedEvent.pubkey
};
// Log the event being signed
console.log("Event to be signed:", JSON.stringify(nostrEvent, null, 2));
try {
// Parse the event to create a proper Nostr event object for signing
const parsedEvent = JSON.parse(convertedEvent);
// Debug the content field
console.log("Event content from parsedEvent:", typeof parsedEvent.content, parsedEvent.content);
// IMPORTANT: Create the nostrEvent with the raw HTTP request as content
// This bypasses any issues with JSON parsing or encryption
nostrEvent = {
kind: 21120,
tags: parsedEvent.tags,
content: requestToUse, // Use the original HTTP request directly
created_at: Math.floor(Date.now() / 1000),
pubkey: parsedEvent.pubkey
};
// Log the event being signed
console.log("Content field of nostrEvent:", typeof nostrEvent.content, nostrEvent.content.substring(0, 50) + "...");
console.log("Event to be signed:", JSON.stringify(nostrEvent, null, 2));
} catch (parseError) {
console.error("Error parsing event:", parseError);
eventOutputPre.textContent = "Error: Could not parse event JSON. Please try again.";
outputDiv.hidden = false;
return;
}
if (!nostrEvent) {
eventOutputPre.textContent = "Error: Could not create event for signing";
outputDiv.hidden = false;
return;
}
let signedEvent;
if (window.nostr) {
try {
// Try to sign with the NIP-07 extension
signedEvent = await window.nostr.signEvent(nostrEvent);
console.log("Event signed with extension:", signedEvent);
} catch (error) {
console.error("Error signing event with extension:", error);
// Fall back to signing with nostr-tools
if (secretKey) {
signedEvent = nostrTools.finalizeEvent(nostrEvent, secretKey);
} else {
eventOutputPre.textContent = "Error: Could not sign event. No extension or private key available.";
outputDiv.hidden = false;
return;
try {
// Try to sign with NostrLogin in preferred order
if (NostrLogin && NostrLogin.signEvent) {
console.log("Using NostrLogin.signEvent to sign event");
signedEvent = await NostrLogin.signEvent(nostrEvent);
} else if (NostrLogin && NostrLogin.sign) {
console.log("Using NostrLogin.sign to sign event");
signedEvent = await NostrLogin.sign(nostrEvent);
} else if (window.nostr) {
// Fall back to NIP-07 extension
console.log("Using NIP-07 extension to sign event");
// When using the extension directly, ensure content is a string
if (typeof nostrEvent.content !== 'string') {
nostrEvent.content = String(nostrEvent.content);
}
signedEvent = await window.nostr.signEvent(nostrEvent);
} else if (secretKey) {
// Fall back to nostr-tools
console.log("Using nostr-tools to sign event");
signedEvent = nostrTools.finalizeEvent(nostrEvent, secretKey);
} else {
throw new Error("No signing method available");
}
} else if (secretKey) {
// Sign with nostr-tools
signedEvent = nostrTools.finalizeEvent(nostrEvent, secretKey);
} else {
eventOutputPre.textContent = "Error: Could not sign event. No extension or private key available.";
console.log("Event signed successfully");
console.log("Event ID:", signedEvent.id);
console.log("Content field type:", typeof signedEvent.content);
if (typeof signedEvent.content === 'string') {
console.log("Content length:", signedEvent.content.length);
console.log("Content preview:", signedEvent.content.length > 50 ?
signedEvent.content.substring(0, 50) + "..." :
signedEvent.content);
} else {
console.log("Content is not a string:", signedEvent.content);
}
} catch (error) {
console.error("Error signing event:", error);
eventOutputPre.textContent = "Error: Could not sign event. " + String(error);
outputDiv.hidden = false;
return;
}

@ -1,34 +1,130 @@
/* Styles for the HTTP to Nostr converter */
/* CSS Variables for themes */
:root {
/* Light theme (default) */
--bg-primary: #f8f9fa;
--bg-secondary: #ffffff;
--bg-tertiary: #f1f8ff;
--bg-info: #e2f0fd;
--text-primary: #212529;
--text-secondary: #495057;
--text-tertiary: #6c757d;
--border-color: #dee2e6;
--accent-color: #0d6efd;
--button-primary: #0d6efd;
--button-hover: #0b5ed7;
--button-success: #28a745;
--button-success-hover: #218838;
--button-login: #6c3483;
--button-login-hover: #5b2c6f;
--info-border: #0d6efd;
--code-bg: #f8f9fa;
}
/* Dark theme */
[data-theme="dark"] {
--bg-primary: #121212;
--bg-secondary: #1e1e1e;
--bg-tertiary: #252836;
--bg-info: #1a2634;
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-tertiary: #909090;
--border-color: #333333;
--accent-color: #3f87ff;
--button-primary: #3f87ff;
--button-hover: #2970e3;
--button-success: #2a9745;
--button-success-hover: #218838;
--button-login: #7c44a3;
--button-login-hover: #6c378f;
--info-border: #3f87ff;
--code-bg: #252525;
}
/* General layout */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
color: #212529;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Headings */
h1 {
color: #343a40;
color: var(--text-primary);
margin-bottom: 20px;
border-bottom: 2px solid #6c757d;
border-bottom: 2px solid var(--border-color);
padding-bottom: 10px;
}
h2 {
color: #495057;
color: var(--text-secondary);
margin-top: 25px;
margin-bottom: 15px;
}
/* Theme toggle */
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
display: flex;
align-items: center;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 30px;
padding: 5px 10px;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 100;
}
.theme-toggle-icon {
font-size: 18px;
margin-right: 8px;
}
/* Newer button style toggle */
.theme-toggle-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 100;
}
.theme-toggle-btn {
display: flex;
align-items: center;
background-color: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 30px;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.theme-toggle-btn:hover {
background-color: var(--accent-color);
color: white;
}
.theme-toggle-text {
font-size: 14px;
}
/* Info box */
.info-box {
background-color: #e2f0fd;
border-left: 4px solid #0d6efd;
background-color: var(--bg-info);
border-left: 4px solid var(--info-border);
padding: 10px 15px;
margin-bottom: 20px;
border-radius: 0 4px 4px 0;
@ -39,9 +135,11 @@ input[type="text"], textarea {
width: 100%;
padding: 8px 10px;
margin-bottom: 15px;
border: 1px solid #ced4da;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
background-color: var(--bg-secondary);
color: var(--text-primary);
}
textarea {
@ -49,8 +147,30 @@ textarea {
font-family: monospace;
}
/* Login container */
.login-container {
margin: 20px 0;
padding: 15px;
background-color: var(--bg-secondary);
border-radius: 4px;
border: 1px solid var(--border-color);
text-align: center;
}
.login-status {
margin-top: 10px;
min-height: 20px;
}
#nostr-login-container {
margin: 10px auto;
display: flex;
justify-content: center;
}
/* Buttons */
button {
background-color: #0d6efd;
background-color: var(--button-primary);
color: white;
border: none;
padding: 10px 20px;
@ -61,7 +181,7 @@ button {
}
button:hover {
background-color: #0b5ed7;
background-color: var(--button-hover);
}
/* Server input section */
@ -83,30 +203,30 @@ button:hover {
.server-search-result {
margin-top: 10px;
padding: 10px;
background-color: #ffffff;
background-color: var(--bg-secondary);
border-radius: 4px;
border: 1px solid #dee2e6;
border: 1px solid var(--border-color);
}
/* Output section */
#output {
margin-top: 30px;
padding: 15px;
background-color: #ffffff;
background-color: var(--bg-secondary);
border-radius: 4px;
border: 1px solid #dee2e6;
border: 1px solid var(--border-color);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
pre {
background-color: #f8f9fa;
background-color: var(--code-bg);
padding: 15px;
border-radius: 4px;
overflow-x: auto;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
line-height: 1.4;
border: 1px solid #dee2e6;
border: 1px solid var(--border-color);
}
/* Publish container */
@ -114,9 +234,9 @@ pre {
margin-top: 20px;
margin-bottom: 20px;
padding: 15px;
background-color: #f1f8ff;
background-color: var(--bg-tertiary);
border-radius: 4px;
border: 1px solid #b3d7ff;
border: 1px solid var(--accent-color);
}
.publish-input-container {
@ -129,13 +249,15 @@ pre {
.publish-input {
flex-grow: 1;
padding: 8px 10px;
border: 1px solid #ced4da;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
background-color: var(--bg-secondary);
color: var(--text-primary);
}
.publish-button {
background-color: #28a745;
background-color: var(--button-success);
color: white;
border: none;
padding: 8px 15px;
@ -146,15 +268,15 @@ pre {
}
.publish-button:hover {
background-color: #218838;
background-color: var(--button-success-hover);
}
.publish-result {
margin-top: 10px;
padding: 10px;
background-color: #ffffff;
background-color: var(--bg-secondary);
border-radius: 4px;
border: 1px solid #dee2e6;
border: 1px solid var(--border-color);
}
/* QR code container */
@ -166,106 +288,15 @@ pre {
#qrCode {
margin: 0 auto;
max-width: 100%;
}
/* Search results styling */
.search-results-list {
max-height: 300px;
overflow-y: auto;
}
.search-result-item {
padding: 10px;
border-bottom: 1px solid #dee2e6;
display: flex;
flex-direction: column;
gap: 5px;
position: relative;
}
.search-result-item:last-child {
border-bottom: none;
}
.result-name {
font-weight: bold;
}
.result-npub {
font-family: monospace;
font-size: 12px;
color: #6c757d;
}
.result-nip05 {
font-size: 12px;
color: #0d6efd;
}
.use-npub-btn {
align-self: flex-end;
padding: 5px 10px;
font-size: 12px;
margin-top: 5px;
}
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
textarea {
width: 100%;
height: 200px;
font-family: monospace;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 15px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
#output {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
}
.qr-container {
margin-top: 30px;
text-align: center;
}
#qrCode {
margin: 0 auto;
display: block;
background-color: white;
background-color: var(--bg-secondary);
padding: 15px;
border-radius: 4px;
text-align: center;
}
.qr-info {
margin-top: 8px;
font-size: 14px;
color: #555;
color: var(--text-tertiary);
text-align: center;
}
@ -274,6 +305,7 @@ button:hover {
height: 300px;
margin: 0 auto;
position: relative;
background-color: white; /* QR codes need white background */
}
.qr-frame {
@ -293,132 +325,63 @@ button:hover {
.qr-controls button {
margin: 0 5px;
padding: 8px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.qr-controls button:hover {
background-color: #45a049;
/* Search results styling */
.search-results-list {
max-height: 300px;
overflow-y: auto;
background-color: var(--bg-secondary);
}
.qr-error {
background-color: #ffeeee;
.search-result-item {
padding: 10px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ffcccc;
border-radius: 4px;
flex-direction: column;
gap: 5px;
position: relative;
}
.error-message {
text-align: center;
color: #cc0000;
padding: 20px;
.search-result-item:last-child {
border-bottom: none;
}
.qr-error-container {
padding: 20px;
background-color: #f8f8f8;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
.result-name {
font-weight: bold;
}
.qr-error-container h3 {
color: #cc0000;
margin-top: 0;
.result-npub {
font-family: monospace;
font-size: 12px;
color: var(--text-tertiary);
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
.result-nip05 {
font-size: 12px;
color: var(--accent-color);
}
.info-box {
background-color: #e7f3fe;
border-left: 6px solid #2196F3;
padding: 10px;
margin-bottom: 20px;
}
h1 {
color: #333;
}
h2 {
color: #555;
}
/* NIP-05 lookup styles */
.nip05-section {
margin: 10px 0;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
border-left: 3px solid #4CAF50;
}
.nip05-input-container {
display: flex;
.use-npub-btn {
align-self: flex-end;
padding: 5px 10px;
font-size: 12px;
margin-top: 5px;
}
.nip05-input {
flex-grow: 1;
padding: 8px;
margin-right: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.nip05-button {
white-space: nowrap;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
padding: 8px 15px;
cursor: pointer;
}
.nip05-button:hover {
background-color: #45a049;
}
.nip05-result {
margin-top: 8px;
font-size: 14px;
padding: 5px;
border-radius: 4px;
}
.nip05-success {
color: #008800;
}
.nip05-error {
/* Error and success messages */
.error-message {
color: #cc0000;
padding: 10px;
background-color: rgba(204, 0, 0, 0.1);
border-radius: 4px;
margin: 10px 0;
}
.use-pubkey-button {
margin-left: 10px;
font-size: 12px;
padding: 3px 8px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.use-pubkey-button:hover {
background-color: #45a049;
.success-message {
color: #008800;
padding: 10px;
background-color: rgba(0, 136, 0, 0.1);
border-radius: 4px;
margin: 10px 0;
}

321
package-lock.json generated Normal file

@ -0,0 +1,321 @@
{
"name": "http-to-nostr-project",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "http-to-nostr-project",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"qrcode": "^1.5.4"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
}
}
}

@ -22,5 +22,8 @@
"converter"
],
"author": "",
"license": "MIT"
"license": "MIT",
"dependencies": {
"qrcode": "^1.5.4"
}
}