allow to create dm and blossom lists
give a couple templates for happy frens
This commit is contained in:
parent
57a252b983
commit
6534cad6eb
7 changed files with 729 additions and 204 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue