feat: calculate npub correctly
This commit is contained in:
parent
01e164c119
commit
5300315bf4
@ -43,7 +43,7 @@ export function isValidHexString(str: string, expectedLength?: number): boolean
|
||||
* @returns Promise that resolves to a success or error message
|
||||
*/
|
||||
export async function publishToRelay(event: NostrEvent, relayUrl: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
try {
|
||||
// Additional validation specifically for publishing
|
||||
if (!event || typeof event !== 'object') {
|
||||
@ -77,17 +77,16 @@ export async function publishToRelay(event: NostrEvent, relayUrl: string): Promi
|
||||
const relayPool = new nostrTools.SimplePool();
|
||||
|
||||
// Set a timeout for the publish operation
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
relayPool.close([relayUrl]);
|
||||
reject(new Error(`Timed out connecting to relay: ${relayUrl}`));
|
||||
}, 10000); // 10 second timeout
|
||||
}, 10000);
|
||||
|
||||
// Create a standard formatted event to ensure it follows the relay protocol
|
||||
const standardEvent = {
|
||||
id: event.id,
|
||||
pubkey: event.pubkey,
|
||||
created_at: Number(event.created_at),
|
||||
created_at: Math.floor(Date.now() / 1000), // Always use current timestamp
|
||||
kind: Number(event.kind),
|
||||
tags: event.tags,
|
||||
content: event.content,
|
||||
@ -103,22 +102,27 @@ export async function publishToRelay(event: NostrEvent, relayUrl: string): Promi
|
||||
standardEvent.kind = 21120;
|
||||
}
|
||||
|
||||
// Use the standardized event for publishing
|
||||
const finalEvent = standardEvent;
|
||||
// Since we updated the timestamp, we need to recalculate the event ID and get a new signature
|
||||
// First create an unsigned event
|
||||
const unsignedEvent = {
|
||||
kind: standardEvent.kind,
|
||||
created_at: standardEvent.created_at,
|
||||
tags: standardEvent.tags,
|
||||
content: standardEvent.content,
|
||||
pubkey: standardEvent.pubkey
|
||||
};
|
||||
|
||||
// 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}`));
|
||||
// Get a new signature using the extension
|
||||
if (!window.nostr) {
|
||||
reject(new Error('No Nostr extension available to sign the event'));
|
||||
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}`));
|
||||
let signedEvent;
|
||||
try {
|
||||
signedEvent = await window.nostr.signEvent(unsignedEvent);
|
||||
} catch (signError) {
|
||||
reject(new Error(`Failed to sign event with new timestamp: ${String(signError)}`));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -142,7 +146,7 @@ export async function publishToRelay(event: NostrEvent, relayUrl: string): Promi
|
||||
|
||||
ws.onopen = (): void => {
|
||||
// Send the event directly using the relay protocol format ["EVENT", event]
|
||||
const messageToSend = JSON.stringify(["EVENT", finalEvent]);
|
||||
const messageToSend = JSON.stringify(["EVENT", signedEvent]);
|
||||
ws?.send(messageToSend);
|
||||
};
|
||||
|
||||
@ -195,7 +199,7 @@ export async function publishToRelay(event: NostrEvent, relayUrl: string): Promi
|
||||
console.log('WebSocket approach failed, trying nostr-tools:', wsError);
|
||||
|
||||
// Use the nostr-tools publish method as a fallback
|
||||
const publishPromises = relayPool.publish([relayUrl], finalEvent);
|
||||
const publishPromises = relayPool.publish([relayUrl], signedEvent as nostrTools.Event);
|
||||
|
||||
// Use Promise.all to wait for all promises to resolve
|
||||
Promise.all(publishPromises)
|
||||
|
@ -1,11 +1,8 @@
|
||||
// Hard-coded Nostr keys for the server
|
||||
export const serverKeys = {
|
||||
// These are test keys - DO NOT USE IN PRODUCTION
|
||||
privateKeyHex: "d5d49f5d14c8e59fafb5858dd27712674b8f8c53f1d72dae4ee69bd201068c68",
|
||||
publicKeyHex: "f3c75b9243135a548bc64545dcfd7e8725e20faeacb5c18ac581d7a010036f40",
|
||||
npub: "npub1r6knexka25dn9w9jnf5kf8xh6gfq7n3p38zfl7nn7cjjjsp4umcqnk0aun",
|
||||
nsec: "nsec1hsdfy8ch8y0zw8g0np0mzk3chg94y2t5sek7hh9xpv565h5ucqpqgf4t93"
|
||||
};
|
||||
import * as nostrTools from 'nostr-tools';
|
||||
|
||||
export const privateKey = nostrTools.generateSecretKey();
|
||||
export const publicKey = nostrTools.getPublicKey(privateKey);
|
||||
export const serverNPub = nostrTools.nip19.npubEncode(publicKey);
|
||||
|
||||
// Relay configuration
|
||||
export const relayUrls = [
|
||||
|
@ -2,16 +2,16 @@
|
||||
* Main entry point for the Nostr HTTP Request Server
|
||||
*/
|
||||
|
||||
import { serverKeys, relayUrls, serverConfig } from './config.js';
|
||||
import { privateKey, publicKey, serverNPub, relayUrls, serverConfig } from './config.js';
|
||||
import { NostrHttpServer } from './server.js';
|
||||
|
||||
// Create server configuration
|
||||
const config = {
|
||||
port: serverConfig.port,
|
||||
relayUrls: relayUrls,
|
||||
privateKey: serverKeys.privateKeyHex,
|
||||
pubkey: serverKeys.publicKeyHex,
|
||||
npub: serverKeys.npub
|
||||
privateKey: privateKey,
|
||||
pubkey: publicKey,
|
||||
npub: serverNPub,
|
||||
};
|
||||
|
||||
// Create and start server
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import NDK from '@nostr-dev-kit/ndk';
|
||||
import { NDKUser, NDKEvent, NDKFilter, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
||||
import { NDKUser, NDKEvent, NDKFilter, NDKPrivateKeySigner, NDKSubscription } from '@nostr-dev-kit/ndk';
|
||||
import express from 'express';
|
||||
import { createServer } from 'http';
|
||||
import { ServerConfig, HttpRequest, RateLimitConfig, RateLimitState } from './types';
|
||||
@ -17,6 +17,7 @@ export class NostrHttpServer {
|
||||
private config: ServerConfig;
|
||||
private rateLimits: Map<string, RateLimitState>;
|
||||
private rateLimitConfig: RateLimitConfig;
|
||||
private subscription: NDKSubscription | null = null;
|
||||
|
||||
/**
|
||||
* Create a new Nostr HTTP Server
|
||||
@ -38,19 +39,146 @@ export class NostrHttpServer {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the connection status of all relays
|
||||
* @returns A map of relay URLs to their connection status
|
||||
*/
|
||||
private async checkRelayConnections(): Promise<Map<string, boolean>> {
|
||||
const connectionStatus = new Map<string, boolean>();
|
||||
|
||||
for (const relayUrl of this.config.relayUrls) {
|
||||
try {
|
||||
// Try to connect to the relay
|
||||
await this.ndk.connect();
|
||||
// If we get here, connection was successful
|
||||
connectionStatus.set(relayUrl, true);
|
||||
console.log(`Relay ${relayUrl}: Connected`);
|
||||
} catch (error) {
|
||||
connectionStatus.set(relayUrl, false);
|
||||
console.error(`Error connecting to relay ${relayUrl}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a test event to verify subscription is working
|
||||
*/
|
||||
private async sendTestEvent(): Promise<void> {
|
||||
try {
|
||||
console.log('Sending unencrypted test event to verify subscription...');
|
||||
|
||||
// Create a test event
|
||||
const testEvent = new NDKEvent(this.ndk, {
|
||||
kind: 21120,
|
||||
content: 'Test event for subscription verification',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
pubkey: this.config.pubkey,
|
||||
tags: [
|
||||
['p', this.config.pubkey]
|
||||
]
|
||||
});
|
||||
|
||||
// Sign and publish the event
|
||||
await testEvent.sign();
|
||||
await testEvent.publish();
|
||||
|
||||
console.log('Unencrypted test event published successfully');
|
||||
console.log('Event ID:', testEvent.id);
|
||||
console.log('This event should be received by your subscription without decryption errors');
|
||||
} catch (error) {
|
||||
console.error('Error sending test event:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an encrypted test event to verify encryption/decryption
|
||||
*/
|
||||
private async sendEncryptedTestEvent(): Promise<void> {
|
||||
try {
|
||||
console.log('Sending encrypted test event to verify encryption/decryption...');
|
||||
|
||||
// Create a test event with encrypted content
|
||||
const testContent = 'This is an encrypted test event';
|
||||
|
||||
// Encrypt the content
|
||||
const encryptedContent = await this.encryptContent(this.config.pubkey, testContent);
|
||||
if (!encryptedContent) {
|
||||
throw new Error('Failed to encrypt test content');
|
||||
}
|
||||
|
||||
// Create the event
|
||||
const testEvent = new NDKEvent(this.ndk, {
|
||||
kind: 21120,
|
||||
content: encryptedContent,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
pubkey: this.config.pubkey,
|
||||
tags: [
|
||||
['p', this.config.pubkey]
|
||||
]
|
||||
});
|
||||
|
||||
// Sign and publish the event
|
||||
await testEvent.sign();
|
||||
await testEvent.publish();
|
||||
|
||||
console.log('Encrypted test event published successfully');
|
||||
console.log('Event ID:', testEvent.id);
|
||||
console.log('This event should be received and decrypted by your subscription');
|
||||
} catch (error) {
|
||||
console.error('Error sending encrypted test event:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt content using NIP-44
|
||||
* @param pubkey The public key of the recipient
|
||||
* @param content The content to encrypt
|
||||
*/
|
||||
private async encryptContent(pubkey: string, content: string): Promise<string | null> {
|
||||
try {
|
||||
const user = new NDKUser({ pubkey });
|
||||
return await this.ndk.signer?.encrypt(user, content) || null;
|
||||
} catch (error) {
|
||||
console.error('Encryption error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
try {
|
||||
// Connect to NDK
|
||||
console.log('Connecting to relays:', this.config.relayUrls);
|
||||
await this.ndk.connect();
|
||||
console.log('Connected to Nostr relays');
|
||||
|
||||
// Verify connection status
|
||||
const connectedRelays = this.ndk.explicitRelayUrls;
|
||||
console.log('Connected to relays:', connectedRelays);
|
||||
|
||||
if (connectedRelays.length === 0) {
|
||||
console.warn('Warning: No relays connected. Check your relay URLs.');
|
||||
}
|
||||
|
||||
// Check individual relay connections
|
||||
const relayStatus = await this.checkRelayConnections();
|
||||
const connectedCount = Array.from(relayStatus.values()).filter(status => status).length;
|
||||
console.log(`Connected to ${connectedCount} out of ${this.config.relayUrls.length} relays`);
|
||||
|
||||
// Subscribe to events
|
||||
await this.subscribeToEvents();
|
||||
console.log('Subscribed to kind 21120 events');
|
||||
|
||||
// Send a test event to verify subscription
|
||||
await this.sendTestEvent();
|
||||
|
||||
// Send an encrypted test event to verify encryption/decryption
|
||||
await this.sendEncryptedTestEvent();
|
||||
|
||||
// Set up Express server for status endpoint
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
@ -60,9 +188,51 @@ export class NostrHttpServer {
|
||||
<h1>Nostr HTTP Request Server</h1>
|
||||
<p>Server npub: ${this.config.npub}</p>
|
||||
<p>Status: Running</p>
|
||||
<p>Connected to ${this.ndk.explicitRelayUrls.length} relays</p>
|
||||
<p>Subscription: ${this.subscription ? 'Active' : 'Inactive'}</p>
|
||||
<p><a href="/test">Send Unencrypted Test Event</a></p>
|
||||
<p><a href="/test-encrypted">Send Encrypted Test Event</a></p>
|
||||
`);
|
||||
});
|
||||
|
||||
// Add test endpoint
|
||||
app.get('/test', async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
await this.sendTestEvent();
|
||||
res.send(`
|
||||
<h1>Unencrypted Test Event Sent</h1>
|
||||
<p>An unencrypted test event has been sent to verify the subscription.</p>
|
||||
<p>Check the server logs for details.</p>
|
||||
<p><a href="/">Back to Home</a></p>
|
||||
`);
|
||||
} catch (error) {
|
||||
res.status(500).send(`
|
||||
<h1>Error</h1>
|
||||
<p>Failed to send unencrypted test event: ${error instanceof Error ? error.message : String(error)}</p>
|
||||
<p><a href="/">Back to Home</a></p>
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
// Add encrypted test endpoint
|
||||
app.get('/test-encrypted', async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
await this.sendEncryptedTestEvent();
|
||||
res.send(`
|
||||
<h1>Encrypted Test Event Sent</h1>
|
||||
<p>An encrypted test event has been sent to verify encryption/decryption.</p>
|
||||
<p>Check the server logs for details.</p>
|
||||
<p><a href="/">Back to Home</a></p>
|
||||
`);
|
||||
} catch (error) {
|
||||
res.status(500).send(`
|
||||
<h1>Error</h1>
|
||||
<p>Failed to send encrypted test event: ${error instanceof Error ? error.message : String(error)}</p>
|
||||
<p><a href="/">Back to Home</a></p>
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(this.config.port, () => {
|
||||
console.log(`Server running on http://localhost:${this.config.port}`);
|
||||
});
|
||||
@ -77,23 +247,49 @@ 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] // Filter for events tagged with our pubkey
|
||||
};
|
||||
|
||||
console.log('Subscribing with filter:', JSON.stringify(filter, null, 2));
|
||||
console.log('Connected to relays:', this.ndk.explicitRelayUrls);
|
||||
|
||||
// Create a subscription with the filter and options
|
||||
const subscription = this.ndk.subscribe(filter, {
|
||||
closeOnEose: false,
|
||||
groupable: false
|
||||
});
|
||||
|
||||
// Log subscription status
|
||||
console.log('Subscription created');
|
||||
|
||||
// Set up event handler
|
||||
subscription.on('event', (event: NDKEvent) => {
|
||||
console.log('Received event:', event);
|
||||
console.log('Event details:');
|
||||
console.log(` ID: ${event.id}`);
|
||||
console.log(` Kind: ${event.kind}`);
|
||||
console.log(` Pubkey: ${event.pubkey}`);
|
||||
console.log(` Created at: ${new Date(event.created_at * 1000).toISOString()}`);
|
||||
console.log(` Content: ${event.content.substring(0, 100)}${event.content.length > 100 ? '...' : ''}`);
|
||||
|
||||
// Log tags
|
||||
console.log(' Tags:');
|
||||
event.tags.forEach(tag => {
|
||||
console.log(` ${tag.join(', ')}`);
|
||||
});
|
||||
|
||||
this.processEvent(event).catch(error => {
|
||||
console.error('Error processing event:', error);
|
||||
});
|
||||
});
|
||||
|
||||
// Add EOSE handler to confirm subscription is working
|
||||
subscription.on('eose', () => {
|
||||
console.log('EOSE received - subscription is active');
|
||||
});
|
||||
|
||||
// Store subscription for later reference
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,10 +311,28 @@ export class NostrHttpServer {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt content
|
||||
const decryptedContent = await this.decryptContent(event.pubkey, event.content);
|
||||
if (!decryptedContent) {
|
||||
throw new Error('Failed to decrypt content');
|
||||
// Check if this is a test event (unencrypted)
|
||||
if (event.content === 'Test event for subscription verification') {
|
||||
console.log('Received test event, no decryption needed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to decrypt content
|
||||
let decryptedContent: string | null = null;
|
||||
try {
|
||||
decryptedContent = await this.decryptContent(event.pubkey, event.content);
|
||||
if (!decryptedContent) {
|
||||
throw new Error('Failed to decrypt content');
|
||||
}
|
||||
} catch (decryptError) {
|
||||
console.error('Decryption error:', decryptError);
|
||||
// If decryption fails, check if this is a test event or other unencrypted content
|
||||
if (event.content.startsWith('HTTP/')) {
|
||||
console.log('Content appears to be an unencrypted HTTP request');
|
||||
decryptedContent = event.content;
|
||||
} else {
|
||||
throw new Error('Failed to decrypt content and content is not a recognized unencrypted format');
|
||||
}
|
||||
}
|
||||
|
||||
// Parse HTTP request
|
||||
|
@ -31,7 +31,7 @@ export interface HttpResponse {
|
||||
export interface ServerConfig {
|
||||
port: number;
|
||||
relayUrls: string[];
|
||||
privateKey: string;
|
||||
privateKey: Uint8Array<ArrayBufferLike>;
|
||||
pubkey: string;
|
||||
npub: string;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user