feat: trying different relays

This commit is contained in:
complex 2025-04-08 16:03:55 +02:00
parent 873847689c
commit 6cbf92a276
8 changed files with 133 additions and 106 deletions

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Implement strict Content Security Policy -->
<title>HTTP to Kind 21120 Converter</title>
<title>HTTP to Kind 21120 Converter</title>
<!-- Include the Nostr libraries -->
<script src='https://www.unpkg.com/nostr-login@latest/dist/unpkg.js'></script>
<script src="https://cdn.jsdelivr.net/npm/nostr-tools@latest"></script>
@ -18,6 +19,7 @@
padding: 20px;
line-height: 1.6;
}
textarea {
width: 100%;
height: 200px;
@ -27,6 +29,7 @@
border-radius: 4px;
margin-bottom: 15px;
}
button {
background-color: #4CAF50;
color: white;
@ -36,9 +39,11 @@
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
#output {
margin-top: 20px;
padding: 15px;
@ -46,6 +51,7 @@
border-radius: 4px;
background-color: #f9f9f9;
}
pre {
background-color: #f5f5f5;
padding: 10px;
@ -54,52 +60,60 @@
white-space: pre-wrap;
word-wrap: break-word;
}
.info-box {
background-color: #e7f3fe;
border-left: 6px solid #2196F3;
padding: 10px;
margin-bottom: 20px;
}
h1 {
color: #333;
}
h2 {
color: #555;
}
</style>
</head>
<body>
<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>
<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>
<h2>Server Information:</h2>
<div style="margin-bottom: 15px;">
<div style="margin-bottom: 10px;">
<label for="serverPubkey">Server Pubkey:</label><br>
<input type="text" id="serverPubkey" value="npub1r6knexka25dn9w9jnf5kf8xh6gfq7n3p38zfl7nn7cjjjsp4umcqnk0aun" style="width: 100%; padding: 8px;">
<input type="text" id="serverPubkey" value="npub1r6knexka25dn9w9jnf5kf8xh6gfq7n3p38zfl7nn7cjjjsp4umcqnk0aun"
style="width: 100%; padding: 8px;">
</div>
<div>
<label for="relay">Response Relay (optional):</label><br>
<input type="text" id="relay" value="wss://relay.damus.io" style="width: 100%; padding: 8px;">
<input type="text" id="relay" value="wss://relay.degmods.com" style="width: 100%; padding: 8px;">
</div>
</div>
<h2>Enter HTTP Request:</h2>
<textarea id="httpRequest" placeholder="GET /index.html HTTP/1.1
Host: example.com
User-Agent: Browser/1.0
"></textarea>
<button id="convertButton">Convert to Event</button>
<div id="output" hidden>
<h2>Converted Event:</h2>
<pre id="eventOutput"></pre>
</div>
</body>
</html>

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -10,6 +11,7 @@
<!-- Include the Nostr extensions - these will be accessed via window.nostr -->
<script defer src="./bundle.js"></script>
</head>
<body>
<!-- Theme toggle button -->
<div class="theme-toggle-container">
@ -20,62 +22,65 @@
</div>
<h1>HTTP Messages</h1>
<!-- Navigation -->
<div class="navigation">
<a href="./index.html" class="nav-link active">Home</a>
<a href="./help.html" class="nav-link">Documentation</a>
</div>
<!-- Main Content (HTTP Request Converter) -->
<div class="content">
<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>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>
</div>
<h2>Server Information:</h2>
<div style="margin-bottom: 15px;">
<div style="margin-bottom: 10px;">
<label for="serverPubkey">Server Pubkey or Search Term:</label><br>
<div class="server-input-container">
<input type="text" id="serverPubkey" placeholder="npub, username, or NIP-05 identifier" value="npub1r6knexka25dn9w9jnf5kf8xh6gfq7n3p38zfl7nn7cjjjsp4umcqnk0aun" class="server-input">
<input type="text" id="serverPubkey" placeholder="npub, username, or NIP-05 identifier"
value="npub1r6knexka25dn9w9jnf5kf8xh6gfq7n3p38zfl7nn7cjjjsp4umcqnk0aun" class="server-input">
<button id="searchServerBtn" class="server-search-button">Search</button>
</div>
<div id="serverSearchResult" class="server-search-result" style="display: none;">
<!-- Search results will be shown here -->
</div>
</div>
<div>
<label for="relay">Response Relay (optional):</label><br>
<input type="text" id="relay" value="wss://relay.damus.io" style="width: 100%; padding: 8px;">
<input type="text" id="relay" value="wss://relay.degmods.com" style="width: 100%; padding: 8px;">
</div>
</div>
<h2>Enter HTTP Request:</h2>
<textarea id="httpRequest" placeholder="GET /index.html HTTP/1.1
Host: example.com
User-Agent: Browser/1.0
"></textarea>
<button id="convertButton">Convert to Event</button>
<div id="output" hidden>
<h2>Converted Event:</h2>
<pre id="eventOutput"></pre>
<div class="publish-container">
<h2>Publish to Relay:</h2>
<div class="publish-input-container">
<input type="text" id="publishRelay" value="wss://relay.nostrdev.com" placeholder="wss://relay.example.com" class="publish-input">
<input type="text" id="publishRelay" value="wss://relay.degmods.com"
placeholder="wss://relay.example.com" class="publish-input">
<button id="publishButton" class="publish-button">Publish Event</button>
</div>
<div id="publishResult" class="publish-result" style="display: none;">
<!-- Publish results will be shown here -->
</div>
</div>
<div class="qr-container">
<h2>QR Code:</h2>
<div id="qrCode"></div>
@ -84,4 +89,5 @@ User-Agent: Browser/1.0
</div>
</div>
</body>
</html>

@ -7,7 +7,7 @@ export const defaultServerConfig = {
// Server's npub (hard-coded to match the server's config)
serverNpub: "npub1r6knexka25dn9w9jnf5kf8xh6gfq7n3p38zfl7nn7cjjjsp4umcqnk0aun",
// Default relay for responses
defaultRelay: "wss://relay.damus.io"
defaultRelay: "wss://relay.degmods.com"
};
// Application settings

@ -22,17 +22,17 @@ export function isValidHexString(str: string, expectedLength?: number): boolean
if (!hexRegex.test(str)) {
return false;
}
// Check if it has even length (hex strings should have even length)
if (str.length % 2 !== 0) {
return false;
}
// Check expected length if provided
if (expectedLength !== undefined && str.length !== expectedLength) {
return false;
}
return true;
}
@ -50,39 +50,39 @@ export async function publishToRelay(event: NostrEvent, relayUrl: string): Promi
reject(new Error('Invalid event object'));
return;
}
// Check required fields
if (!event.id || !event.pubkey || !event.sig) {
reject(new Error('Event missing required fields (id, pubkey, sig)'));
return;
}
// Validate hex strings
if (!isValidHexString(event.id, 64)) {
reject(new Error(`Invalid event ID: ${event.id} (must be 64 hex chars)`));
return;
}
if (!isValidHexString(event.pubkey, 64)) {
reject(new Error(`Invalid pubkey: ${event.pubkey} (must be 64 hex chars)`));
return;
}
if (!isValidHexString(event.sig, 128)) {
reject(new Error(`Invalid signature: ${event.sig} (must be 128 hex chars)`));
return;
}
// Create a relay pool for publishing
const relayPool = new nostrTools.SimplePool();
// Set a timeout for the publish operation
// eslint-disable-next-line no-undef
const timeout = setTimeout(() => {
relayPool.close([relayUrl]);
reject(new Error(`Timed out connecting to relay: ${relayUrl}`));
}, 10000); // 10 second timeout
// Create a standard formatted event to ensure it follows the relay protocol
const standardEvent = {
id: event.id,
@ -93,114 +93,121 @@ export async function publishToRelay(event: NostrEvent, relayUrl: string): Promi
content: event.content,
sig: event.sig
};
// Make sure all fields are in the correct format
if (typeof standardEvent.created_at !== 'number') {
standardEvent.created_at = Math.floor(Date.now() / 1000);
}
if (typeof standardEvent.kind !== 'number') {
standardEvent.kind = 21120;
}
// Use the standardized event for publishing
const finalEvent = standardEvent;
// Ensure all hex strings are valid (this is a sanity check beyond our validation)
if (!isValidHexString(finalEvent.id, 64)) {
reject(new Error(`ID is not a valid hex string: ${finalEvent.id}`));
return;
}
if (!isValidHexString(finalEvent.pubkey, 64)) {
reject(new Error(`Pubkey is not a valid hex string: ${finalEvent.pubkey}`));
return;
}
if (!isValidHexString(finalEvent.sig, 128)) {
reject(new Error(`Sig is not a valid hex string: ${finalEvent.sig}`));
return;
}
// Try direct WebSocket approach as a fallback
// Try direct WebSocket approach first
let ws: WebSocket | null = null;
let wsTimeout: ReturnType<typeof setTimeout> | null = null;
let responseHandled = false;
try {
// Use the WebSocket API directly
const ws = new WebSocket(relayUrl);
ws = new WebSocket(relayUrl);
// eslint-disable-next-line no-undef
const wsTimeout = setTimeout(() => {
try {
ws.close();
} catch {
// Ignore errors when closing WebSocket
wsTimeout = setTimeout(() => {
if (ws) {
try {
ws.close();
} catch {
// Ignore errors when closing WebSocket
}
}
reject(new Error("WebSocket connection timed out"));
}, 10000);
// Create a flag to track if we've handled response
let responseHandled = false;
ws.onopen = (): void => {
// Send the event directly using the relay protocol format ["EVENT", event]
const messageToSend = JSON.stringify(["EVENT", finalEvent]);
ws.send(messageToSend);
ws?.send(messageToSend);
};
ws.onmessage = (msg): void => {
if (responseHandled) {
return; // Skip if we've already handled a response
}
if (typeof msg.data === 'string' && msg.data.startsWith('["OK"')) {
responseHandled = true;
// eslint-disable-next-line no-undef
clearTimeout(wsTimeout);
if (wsTimeout) clearTimeout(wsTimeout);
resolve(`Event published successfully via WebSocket`);
try {
ws.close();
ws?.close();
} catch {
// Ignore errors when closing WebSocket
}
} else if (typeof msg.data === 'string' && (msg.data.includes('invalid') || msg.data.includes('error'))) {
responseHandled = true;
// eslint-disable-next-line no-undef
clearTimeout(wsTimeout);
if (wsTimeout) clearTimeout(wsTimeout);
reject(new Error(`Relay rejected event: ${msg.data}`));
try {
ws.close();
ws?.close();
} catch {
// Ignore errors when closing WebSocket
}
}
};
ws.onerror = (error): void => {
ws.onerror = (): void => {
if (responseHandled) {
return; // Skip if we've already handled a response
}
responseHandled = true;
// eslint-disable-next-line no-undef
clearTimeout(wsTimeout);
reject(new Error(`WebSocket error: ${String(error)}`));
if (wsTimeout) clearTimeout(wsTimeout);
// Create a more meaningful error message
const errorMessage = 'WebSocket connection failed';
reject(new Error(`WebSocket error: ${errorMessage}`));
try {
ws.close();
ws?.close();
} catch {
// Ignore errors when closing WebSocket
}
};
ws.onclose = (): void => {
// eslint-disable-next-line no-undef
clearTimeout(wsTimeout);
if (wsTimeout) clearTimeout(wsTimeout);
};
} catch {
// If WebSocket fails, try the regular method
} catch (wsError) {
// If WebSocket fails, try the regular method
console.log('WebSocket approach failed, trying nostr-tools:', wsError);
// Use the nostr-tools publish method as a fallback
const publishPromises = relayPool.publish([relayUrl], finalEvent);
// Use Promise.all to wait for all promises to resolve
Promise.all(publishPromises)
.then((relayResults: string[]) => {
@ -235,7 +242,7 @@ export function convertNpubToHex(npub: string): string | null {
if (!npub.startsWith('npub')) {
return null;
}
try {
const decoded = nostrTools.nip19.decode(npub);
if (decoded.type === 'npub') {
@ -244,7 +251,7 @@ export function convertNpubToHex(npub: string): string | null {
} catch {
// Silent error handling to not disturb user experience
}
return null;
}
@ -259,7 +266,7 @@ export function verifyEvent(event: NostrEvent): boolean {
if (!event.id || !event.sig) {
return false;
}
// Type assertion to satisfy the nostrTools.verifyEvent requirements
return nostrTools.verifyEvent(event as nostrTools.Event);
} catch {

@ -4,7 +4,7 @@ import * as nostrTools from 'nostr-tools';
// Define popular relays to search for users
export const POPULAR_RELAYS = [
"wss://relay.damus.io",
"wss://relay.degmods.com",
"wss://relay.nostr.band",
"wss://nos.lol",
"wss://nostr.wine"
@ -15,16 +15,16 @@ export const POPULAR_RELAYS = [
* @param nip05Address The NIP-05 address to look up (e.g. user@example.com)
* @returns Promise that resolves to the public key if found, or null if not found
*/
export async function lookupNip05(nip05Address: string): Promise<{pubkey: string, relays?: string[]} | null> {
export async function lookupNip05(nip05Address: string): Promise<{ pubkey: string, relays?: string[] } | null> {
try {
// Use the NIP-05 lookup function from nostr-tools
const result = await nostrTools.nip05.queryProfile(nip05Address);
// If the result has a pubkey, return it
if (result && result.pubkey) {
return result;
}
return null;
} catch {
return null;
@ -47,10 +47,10 @@ interface NostrEvent {
* @param searchTerm The username or NIP-05 identifier to search for
* @returns Promise that resolves to an array of user profiles
*/
export async function searchUsers(searchTerm: string): Promise<Array<{name: string, pubkey: string, npub: string, nip05?: string}>> {
const results: Array<{name: string, pubkey: string, npub: string, nip05?: string}> = [];
export async function searchUsers(searchTerm: string): Promise<Array<{ name: string, pubkey: string, npub: string, nip05?: string }>> {
const results: Array<{ name: string, pubkey: string, npub: string, nip05?: string }> = [];
const processedPubkeys = new Set<string>();
// First, check if the input might already be an npub
if (searchTerm.startsWith('npub')) {
try {
@ -67,7 +67,7 @@ export async function searchUsers(searchTerm: string): Promise<Array<{name: stri
// Not a valid npub, continue with search
}
}
// Check if the search term looks like a NIP-05 identifier
if (searchTerm.includes('@')) {
try {
@ -86,23 +86,23 @@ export async function searchUsers(searchTerm: string): Promise<Array<{name: stri
// Error handling silent to not disturb the user experience
}
}
// Create a pool of relays to search
const relayPool = new nostrTools.SimplePool();
try {
// Create a filter for the subscription
const filter = {
kinds: [0], // Only metadata events
limit: 20 // Limit to 20 results
};
// Set a timeout to ensure we don't wait forever
const timeoutPromise = new Promise<void>((resolve) => {
// Use window.setTimeout to avoid "not defined" error
window.setTimeout(() => resolve(), 5000); // 5 second timeout
});
// Create an event handler
const eventHandler = (event: NostrEvent): void => {
try {
@ -110,16 +110,16 @@ export async function searchUsers(searchTerm: string): Promise<Array<{name: stri
if (processedPubkeys.has(event.pubkey)) {
return;
}
// Parse the profile metadata from content
const profile = JSON.parse(event.content);
// Check if the profile matches our search term
const searchTermLower = searchTerm.toLowerCase();
const nameLower = (profile.name || '').toLowerCase();
const displayNameLower = (profile.display_name || '').toLowerCase();
const nip05Lower = (profile.nip05 || '').toLowerCase();
if (
nameLower.includes(searchTermLower) ||
displayNameLower.includes(searchTermLower) ||
@ -133,7 +133,7 @@ export async function searchUsers(searchTerm: string): Promise<Array<{name: stri
npub: npub,
nip05: profile.nip05
});
// Mark as processed
processedPubkeys.add(event.pubkey);
}
@ -141,24 +141,24 @@ export async function searchUsers(searchTerm: string): Promise<Array<{name: stri
// Error handling silent to not break the user flow
}
};
// Subscribe to events matching the filter
const sub = relayPool.subscribeMany(
POPULAR_RELAYS,
[filter],
{ onevent: eventHandler }
);
// Wait for timeout
await timeoutPromise;
// Close the subscription
sub.close();
relayPool.close(POPULAR_RELAYS);
} catch {
// Error handling silent to provide a graceful fallback
}
} catch {
// Error handling silent to provide a graceful fallback
}
return results;
}

@ -0,0 +1 @@
# Maybe use event kind 21121 for response event

@ -9,13 +9,11 @@ export const serverKeys = {
// Relay configuration
export const relayUrls = [
'wss://relay.damus.io',
'wss://relay.nostr.info',
'wss://nostr.fmt.wiz.biz'
'wss://relay.degmods.com'
];
// Server configuration
export const serverConfig = {
port: process.env.PORT ? parseInt(process.env.PORT) : 3001,
port: process.env.PORT ? parseInt(process.env.PORT) : 8000,
expirationTime: 3600 // 1 hour in seconds
};

@ -77,8 +77,8 @@ export class NostrHttpServer {
*/
private async subscribeToEvents(): Promise<void> {
const filter: NDKFilter = {
kinds: [21120 as any], // Type assertion to bypass NDKKind type check
'#p': [this.config.pubkey]
//kinds: [21120 as any], // Type assertion to bypass NDKKind type check
//'#p': [this.config.pubkey]
};
// Create a subscription with the filter and options
@ -89,6 +89,7 @@ export class NostrHttpServer {
// Set up event handler
subscription.on('event', (event: NDKEvent) => {
console.log('Received event:', event);
this.processEvent(event).catch(error => {
console.error('Error processing event:', error);
});