allow to create dm and blossom lists

give a couple templates for happy frens
This commit is contained in:
sakrecoer 2026-08-18 16:18:23 +02:00
parent 57a252b983
commit 6534cad6eb
7 changed files with 729 additions and 204 deletions

View file

@ -1,5 +1,16 @@
import { useState, useEffect, useCallback } from 'react';
// Blast relays - configure these once and they'll be used for publishing
// These are hidden from the UI and used to blast events to reliable relays
const BLAST_RELAYS = [
'wss://relay.primal.net',
'wss://relay.damus.io',
'wss://sendit.nosflare.com',
'wss://nostr.mom',
'wss://relay.ditto.pub',
'wss://nos.lol'
];
export const useNostr = () => {
const [pubkey, setPubkey] = useState(null);
const [isConnected, setIsConnected] = useState(false);
@ -51,10 +62,6 @@ export const useNostr = () => {
return await extension.signEvent(event);
}, [extension]);
/**
* Publish to a single relay using native WebSocket
* Relays are passed in from the template - nothing hardcoded
*/
const publishToSingleRelay = useCallback((url, event) => {
return new Promise((resolve) => {
let ws = null;
@ -62,14 +69,11 @@ export const useNostr = () => {
let timeoutIds = [];
try {
console.log(`Connecting to ${url}...`);
ws = new WebSocket(url);
// Connection timeout
const connectTimeout = setTimeout(() => {
if (!resolved) {
resolved = true;
console.log(`⏰ Connection timeout for ${url}`);
if (ws && ws.readyState !== WebSocket.CLOSED) {
try { ws.close(); } catch (e) {}
}
@ -79,13 +83,9 @@ export const useNostr = () => {
timeoutIds.push(connectTimeout);
ws.onopen = () => {
console.log(`Connected to ${url}, sending event...`);
// Send the event as a JSON-RPC message
const message = JSON.stringify(['EVENT', event]);
ws.send(message);
// Publish timeout
const publishTimeout = setTimeout(() => {
if (!resolved) {
resolved = true;
@ -96,40 +96,24 @@ export const useNostr = () => {
}, 15000);
timeoutIds.push(publishTimeout);
// Listen for responses
ws.onmessage = (msg) => {
try {
const data = JSON.parse(msg.data);
// Check for OK response for our event
if (Array.isArray(data) && data[0] === 'OK' && data[1] === event.id) {
if (!resolved) {
resolved = true;
console.log(`✅ Published to ${url}`);
// Clear all timeouts
timeoutIds.forEach(id => clearTimeout(id));
try { ws.close(); } catch (e) {}
resolve({ url, success: true });
}
}
// Check for error response
if (Array.isArray(data) && data[0] === 'OK' && data[1] !== event.id && data[2] === false) {
if (!resolved) {
resolved = true;
console.log(`❌ Relay rejected event for ${url}:`, data[3]);
timeoutIds.forEach(id => clearTimeout(id));
try { ws.close(); } catch (e) {}
resolve({ url, success: false, error: data[3] || 'Event rejected' });
}
}
} catch (e) {
// Ignore parse errors for non-OK messages
}
} catch (e) {}
};
ws.onerror = (error) => {
ws.onerror = () => {
if (!resolved) {
resolved = true;
console.log(`❌ WebSocket error for ${url}:`, error);
timeoutIds.forEach(id => clearTimeout(id));
try { ws.close(); } catch (e) {}
resolve({ url, success: false, error: 'WebSocket error' });
@ -139,17 +123,15 @@ export const useNostr = () => {
ws.onclose = () => {
if (!resolved) {
resolved = true;
console.log(`⚠️ Connection closed for ${url}`);
timeoutIds.forEach(id => clearTimeout(id));
resolve({ url, success: false, error: 'Connection closed' });
}
};
};
ws.onerror = (error) => {
ws.onerror = () => {
if (!resolved) {
resolved = true;
console.log(`❌ Connection error for ${url}:`, error);
timeoutIds.forEach(id => clearTimeout(id));
resolve({ url, success: false, error: 'Connection error' });
}
@ -158,7 +140,6 @@ export const useNostr = () => {
} catch (err) {
if (!resolved) {
resolved = true;
console.log(`❌ Error with ${url}:`, err.message);
timeoutIds.forEach(id => clearTimeout(id));
if (ws) {
try { ws.close(); } catch (e) {}
@ -170,12 +151,11 @@ export const useNostr = () => {
}, []);
/**
* Publish to multiple relays (all from the user's template)
* Publish to multiple relays with detailed results
*/
const publishToRelays = useCallback(async (event, relayUrls) => {
console.log(`Publishing to ${relayUrls.length} relays...`);
const publishToRelays = useCallback(async (event, relayUrls, label = '') => {
console.log(`Publishing ${label} to ${relayUrls.length} relays...`);
// Filter out invalid URLs
const validUrls = relayUrls.filter(url => {
try {
new URL(url);
@ -189,26 +169,65 @@ export const useNostr = () => {
return [{ url: 'none', success: false, error: 'No valid relay URLs' }];
}
// Publish to each relay in parallel
const publishPromises = validUrls.map(url => publishToSingleRelay(url, event));
const results = await Promise.all(publishPromises);
const successCount = results.filter(r => r.success).length;
console.log(`Published to ${successCount}/${results.length} relays`);
console.log(`Published ${label} to ${successCount}/${results.length} relays`);
return results;
}, [publishToSingleRelay]);
const publishKind10002 = useCallback(async (relays, pubkey) => {
/**
* Generic publish function that publishes to BOTH blast relays AND user relays
*/
const publishKind = useCallback(async (kind, data, pubkey, userRelayUrls = []) => {
if (!extension) {
throw new Error('No Nostr extension connected');
}
// relays comes from the user's template - NOT hardcoded
const event = {
kind: kind,
pubkey: pubkey,
created_at: Math.floor(Date.now() / 1000),
tags: data.tags || [],
content: data.content || ''
};
console.log(`Signing kind ${kind} event...`);
const signedEvent = await extension.signEvent(event);
console.log(`Kind ${kind} event signed successfully`);
// Combine blast relays and user relays (deduplicated)
const allRelays = [...new Set([...BLAST_RELAYS, ...userRelayUrls])];
console.log(`Publishing kind ${kind} to ${allRelays.length} relays (${BLAST_RELAYS.length} blast + ${userRelayUrls.length} user)`);
// Publish to all relays
const results = await publishToRelays(signedEvent, allRelays, `kind ${kind}`);
// Separate results for better reporting
const blastResults = results.filter(r => BLAST_RELAYS.includes(r.url));
const userResults = results.filter(r => userRelayUrls.includes(r.url));
console.log(`Kind ${kind} results:`, {
blast: `${blastResults.filter(r => r.success).length}/${blastResults.length}`,
user: `${userResults.filter(r => r.success).length}/${userResults.length}`
});
return {
event: signedEvent,
results: results,
blastResults: blastResults,
userResults: userResults
};
}, [extension, publishToRelays]);
// Kind 10002 - Relays
const publishKind10002 = useCallback(async (relays, pubkey) => {
const validRelays = relays.filter(r => r.url && r.url.trim() !== '');
if (validRelays.length === 0) {
throw new Error('No valid relays to publish to');
throw new Error('No valid relays to publish');
}
const tags = validRelays.map(relay => {
@ -218,30 +237,41 @@ export const useNostr = () => {
return ['r', relay.url, ...params];
});
const event = {
kind: 10002,
pubkey: pubkey,
created_at: Math.floor(Date.now() / 1000),
tags: tags,
content: ''
};
console.log('Signing kind 10002 event...');
const signedEvent = await extension.signEvent(event);
console.log('Event signed successfully');
// Extract URLs from the user's relay list
const relayUrls = [...new Set(validRelays.map(r => r.url))];
console.log('Publishing to relay URLs:', relayUrls);
// Extract user relay URLs for combined publishing
const userRelayUrls = validRelays.map(r => r.url);
const results = await publishToRelays(signedEvent, relayUrls);
console.log('Final results:', results);
return await publishKind(10002, { tags }, pubkey, userRelayUrls);
}, [publishKind]);
// Kind 10063 - Blossom Servers
const publishKind10063 = useCallback(async (servers, pubkey) => {
const validServers = servers.filter(s => s.url && s.url.trim() !== '');
return {
event: signedEvent,
results: results
};
}, [extension, publishToRelays]);
if (validServers.length === 0) {
throw new Error('No valid blossom servers');
}
const tags = validServers.map(server => ['server', server.url]);
// For blossom servers, we want to publish to blast relays
// The user's main relays are used for kind 10002, not kind 10063
return await publishKind(10063, { tags }, pubkey, []);
}, [publishKind]);
// Kind 10050 - DM Relays
const publishKind10050 = useCallback(async (relays, pubkey) => {
const validRelays = relays.filter(r => r.url && r.url.trim() !== '');
if (validRelays.length === 0) {
throw new Error('No valid DM relays');
}
const tags = validRelays.map(relay => ['relay', relay.url]);
// For DM relays, we want to publish to blast relays
// The user's main relays are used for kind 10002, not kind 10050
return await publishKind(10050, { tags }, pubkey, []);
}, [publishKind]);
return {
pubkey,
@ -251,8 +281,11 @@ export const useNostr = () => {
connect,
signEvent,
publishToRelays,
publishKind10002
publishKind,
publishKind10002,
publishKind10063,
publishKind10050
};
};
export default useNostr;
export default useNostr;