cleaner flow
This commit is contained in:
parent
3dd1cf29da
commit
980aaa501d
5 changed files with 346 additions and 286 deletions
|
|
@ -8,9 +8,9 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"nostr-tools": "^2.24.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"nostr-tools": "^2.0.0",
|
|
||||||
"react-router-dom": "^6.20.0"
|
"react-router-dom": "^6.20.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -4,44 +4,83 @@ const NostrConnect = ({ setConnectedPubkey }) => {
|
||||||
const [pubkey, setPubkey] = useState(null);
|
const [pubkey, setPubkey] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// Check if already connected
|
||||||
|
useEffect(() => {
|
||||||
|
if (window.nostr?.getPublicKey) {
|
||||||
|
window.nostr.getPublicKey()
|
||||||
|
.then(pk => {
|
||||||
|
setPubkey(pk);
|
||||||
|
setConnectedPubkey(pk);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const connect = async () => {
|
const connect = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (!window.nostr) {
|
if (!window.nostr) {
|
||||||
throw new Error('Please install a Nostr extension (Alby, Nos2x, etc.)');
|
throw new Error('Please install a Nostr extension (Alby, Nos2x, etc.)');
|
||||||
}
|
}
|
||||||
|
const pk = await window.nostr.getPublicKey();
|
||||||
const pubkey = await window.nostr.getPublicKey();
|
setPubkey(pk);
|
||||||
setPubkey(pubkey);
|
setConnectedPubkey(pk);
|
||||||
setConnectedPubkey(pubkey);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('Failed to connect: ' + error.message);
|
alert('Failed to connect: ' + error.message);
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const disconnect = () => {
|
||||||
// Check if already connected
|
setPubkey(null);
|
||||||
if (window.nostr) {
|
setConnectedPubkey(null);
|
||||||
window.nostr.getPublicKey().then(pk => {
|
};
|
||||||
setPubkey(pk);
|
|
||||||
setConnectedPubkey(pk);
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
return (
|
return (
|
||||||
<div className="nostr-connected">
|
<div className="nostr-connected" style={{
|
||||||
✅ Connected: {pubkey.slice(0, 8)}...{pubkey.slice(-8)}
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '12px 16px',
|
||||||
|
background: '#e8f5e9',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #4caf50',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
|
<span>
|
||||||
|
✅ Connected: <code>{pubkey.slice(0, 8)}...{pubkey.slice(-8)}</code>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={disconnect}
|
||||||
|
className="btn-secondary"
|
||||||
|
style={{ fontSize: '12px', padding: '4px 12px' }}
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button onClick={connect} disabled={loading} className="btn-nostr">
|
<button
|
||||||
{loading ? 'Connecting...' : '🔑 Connect Nostr'}
|
onClick={connect}
|
||||||
|
disabled={loading}
|
||||||
|
className="btn-nostr"
|
||||||
|
style={{
|
||||||
|
padding: '10px 20px',
|
||||||
|
background: loading ? '#ccc' : '#ff6b00',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontWeight: '600',
|
||||||
|
cursor: loading ? 'not-allowed' : 'pointer',
|
||||||
|
transition: 'background 0.2s'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? '⏳ Connecting...' : '🔑 Connect with Extension'}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,52 @@ import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { decodeTemplate, validateTemplate } from '../utils/templates';
|
import { decodeTemplate, validateTemplate } from '../utils/templates';
|
||||||
import useNostr from '../hooks/useNostr';
|
import useNostr from '../hooks/useNostr';
|
||||||
|
import NostrConnect from './NostrConnect';
|
||||||
|
|
||||||
// Define BLAST_RELAYS here
|
const TemplateApplier = ({ setConnectedPubkey }) => {
|
||||||
const BLAST_RELAYS = [
|
|
||||||
'wss://relay.primal.net',
|
|
||||||
'wss://relay.damus.io',
|
|
||||||
'wss://nos.lol'
|
|
||||||
];
|
|
||||||
|
|
||||||
const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
|
||||||
const { encoded } = useParams();
|
const { encoded } = useParams();
|
||||||
|
const nostr = useNostr();
|
||||||
|
|
||||||
const [template, setTemplate] = useState(null);
|
const [template, setTemplate] = useState(null);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [applying, setApplying] = useState(false);
|
const [applying, setApplying] = useState(false);
|
||||||
const [results, setResults] = useState(null);
|
const [results, setResults] = useState(null);
|
||||||
const [isConnecting, setIsConnecting] = useState(false);
|
|
||||||
const { publishKind10002, publishKind10050, publishKind10063, isConnected, connect, pubkey } = useNostr();
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// CONNECTION CHECK
|
||||||
|
// ============================================================
|
||||||
|
useEffect(() => {
|
||||||
|
const checkConnection = async () => {
|
||||||
|
if (nostr.pubkey) {
|
||||||
|
setConnectedPubkey(nostr.pubkey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.nostr?.getPublicKey) {
|
||||||
|
try {
|
||||||
|
const pk = await window.nostr.getPublicKey();
|
||||||
|
if (pk) {
|
||||||
|
await nostr.connect();
|
||||||
|
setConnectedPubkey(pk);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Extension not authorized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkConnection();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (nostr.pubkey) {
|
||||||
|
setConnectedPubkey(nostr.pubkey);
|
||||||
|
}
|
||||||
|
}, [nostr.pubkey, setConnectedPubkey]);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// LOAD TEMPLATE
|
||||||
|
// ============================================================
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!encoded) {
|
if (!encoded) {
|
||||||
setError('No template provided');
|
setError('No template provided');
|
||||||
|
|
@ -36,25 +65,9 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
}
|
}
|
||||||
}, [encoded]);
|
}, [encoded]);
|
||||||
|
|
||||||
useEffect(() => {
|
// ============================================================
|
||||||
if (pubkey && !connectedPubkey) {
|
// PUBLISH FUNCTIONS
|
||||||
setConnectedPubkey(pubkey);
|
// ============================================================
|
||||||
}
|
|
||||||
}, [pubkey, connectedPubkey, setConnectedPubkey]);
|
|
||||||
|
|
||||||
const handleConnect = async () => {
|
|
||||||
setIsConnecting(true);
|
|
||||||
try {
|
|
||||||
const pk = await connect();
|
|
||||||
setConnectedPubkey(pk);
|
|
||||||
setError('');
|
|
||||||
} catch (err) {
|
|
||||||
setError('Failed to connect: ' + err.message);
|
|
||||||
} finally {
|
|
||||||
setIsConnecting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleApplyTemplate = async () => {
|
const handleApplyTemplate = async () => {
|
||||||
if (!template) return;
|
if (!template) return;
|
||||||
|
|
||||||
|
|
@ -63,20 +76,12 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
setResults(null);
|
setResults(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let pubkeyToUse = connectedPubkey || pubkey;
|
if (!nostr.pubkey) {
|
||||||
|
throw new Error('Please connect your Nostr signer first');
|
||||||
if (!pubkeyToUse) {
|
|
||||||
try {
|
|
||||||
pubkeyToUse = await connect();
|
|
||||||
setConnectedPubkey(pubkeyToUse);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('Please connect your Nostr extension first');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pubkeyToUse) {
|
// ✅ Collect ALL user relay URLs from the main relays
|
||||||
throw new Error('No Nostr public key available. Please connect your extension.');
|
const allUserRelays = template.relays.map(r => r.url);
|
||||||
}
|
|
||||||
|
|
||||||
const allResults = [];
|
const allResults = [];
|
||||||
const publishConfigs = [];
|
const publishConfigs = [];
|
||||||
|
|
@ -86,28 +91,25 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
publishConfigs.push({
|
publishConfigs.push({
|
||||||
name: 'Relays (kind 10002)',
|
name: 'Relays (kind 10002)',
|
||||||
kind: '10002',
|
kind: '10002',
|
||||||
relays: template.relays,
|
publishFn: () => nostr.publishKind10002(template.relays, nostr.pubkey)
|
||||||
publishFn: () => publishKind10002(template.relays, pubkeyToUse)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Publish kind 10063 (blossom servers)
|
// 2. Publish kind 10063 (blossom servers) - ✅ Pass all user relays
|
||||||
if (template.blossomServers && template.blossomServers.length > 0) {
|
if (template.blossomServers && template.blossomServers.length > 0) {
|
||||||
publishConfigs.push({
|
publishConfigs.push({
|
||||||
name: 'Blossom Servers (kind 10063)',
|
name: 'Blossom Servers (kind 10063)',
|
||||||
kind: '10063',
|
kind: '10063',
|
||||||
relays: template.blossomServers.map(s => ({ url: s.url, read: true, write: true })),
|
publishFn: () => nostr.publishKind10063(template.blossomServers, nostr.pubkey, allUserRelays)
|
||||||
publishFn: () => publishKind10063(template.blossomServers, pubkeyToUse)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Publish kind 10050 (DM relays)
|
// 3. Publish kind 10050 (DM relays) - ✅ Pass all user relays
|
||||||
if (template.dmRelays && template.dmRelays.length > 0) {
|
if (template.dmRelays && template.dmRelays.length > 0) {
|
||||||
publishConfigs.push({
|
publishConfigs.push({
|
||||||
name: 'DM Relays (kind 10050)',
|
name: 'DM Relays (kind 10050)',
|
||||||
kind: '10050',
|
kind: '10050',
|
||||||
relays: template.dmRelays.map(r => ({ url: r.url, read: true, write: true })),
|
publishFn: () => nostr.publishKind10050(template.dmRelays, nostr.pubkey, allUserRelays)
|
||||||
publishFn: () => publishKind10050(template.dmRelays, pubkeyToUse)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +162,10 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// RENDER
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
if (error && !template) {
|
if (error && !template) {
|
||||||
return (
|
return (
|
||||||
<div className="template-applier">
|
<div className="template-applier">
|
||||||
|
|
@ -183,6 +189,8 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isConnected = nostr.isConnected && nostr.pubkey;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="template-applier">
|
<div className="template-applier">
|
||||||
<h2>Apply Relay Template</h2>
|
<h2>Apply Relay Template</h2>
|
||||||
|
|
@ -204,8 +212,9 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<span className="relay-url">{relay.url}</span>
|
<span className="relay-url">{relay.url}</span>
|
||||||
<span className="relay-permissions">
|
<span className="relay-permissions">
|
||||||
{relay.read ? '📖 Read' : ''}
|
{relay.read && relay.write ? '📖✍️' :
|
||||||
{relay.write ? ' ✍️ Write' : ''}
|
relay.read ? '📖' :
|
||||||
|
relay.write ? '✍️' : ''}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
@ -221,7 +230,7 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<span className="relay-url">{server.url}</span>
|
<span className="relay-url">{server.url}</span>
|
||||||
<span style={{ fontSize: '12px', color: '#999' }}>
|
<span style={{ fontSize: '12px', color: '#999' }}>
|
||||||
#{index + 1} {index === 0 ? '⭐ Perferred' : ''}
|
#{index + 1} {index === 0 ? '⭐ Preferred' : ''}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
@ -244,29 +253,56 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Connection Status */}
|
{/* AUTH SECTION */}
|
||||||
<div className="connection-status" style={{ marginBottom: '16px' }}>
|
{!isConnected && (
|
||||||
{(connectedPubkey || pubkey) ? (
|
<div className="auth-section" style={{
|
||||||
<div className="info-box">
|
padding: '20px',
|
||||||
✅ Connected as: <code>{(connectedPubkey || pubkey).slice(0, 8)}...{(connectedPubkey || pubkey).slice(-8)}</code>
|
background: '#f8f9fa',
|
||||||
</div>
|
borderRadius: '8px',
|
||||||
) : (
|
border: '1px solid #e9ecef',
|
||||||
<div className="warning" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '10px' }}>
|
marginBottom: '16px'
|
||||||
<span>⚠️ Not connected to Nostr. Please connect your extension to publish.</span>
|
}}>
|
||||||
<button
|
<h4 style={{ marginBottom: '12px' }}>🔑 Connect Your Nostr Signer</h4>
|
||||||
onClick={handleConnect}
|
<p style={{ fontSize: '14px', color: '#666', marginBottom: '12px' }}>
|
||||||
disabled={isConnecting}
|
You need to connect a signer to publish this template.
|
||||||
className="btn-nostr"
|
</p>
|
||||||
style={{ width: 'auto' }}
|
<NostrConnect setConnectedPubkey={setConnectedPubkey} />
|
||||||
>
|
|
||||||
{isConnecting ? '⏳ Connecting...' : '🔑 Connect Nostr'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Publish Button */}
|
{/* CONNECTED STATUS */}
|
||||||
{!results && (connectedPubkey || pubkey) && (
|
{isConnected && (
|
||||||
|
<div className="connection-status" style={{ marginBottom: '16px' }}>
|
||||||
|
<div className="info-box" style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '12px 16px',
|
||||||
|
background: '#e8f5e9',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '1px solid #4caf50',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
|
<span>
|
||||||
|
✅ Connected: <code>{nostr.pubkey.slice(0, 8)}...{nostr.pubkey.slice(-8)}</code>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
nostr.disconnect();
|
||||||
|
setConnectedPubkey(null);
|
||||||
|
}}
|
||||||
|
className="btn-secondary"
|
||||||
|
style={{ fontSize: '12px', padding: '4px 12px' }}
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* PUBLISH BUTTON */}
|
||||||
|
{!results && isConnected && (
|
||||||
<button
|
<button
|
||||||
onClick={handleApplyTemplate}
|
onClick={handleApplyTemplate}
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
|
|
@ -277,11 +313,7 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
marginTop: '10px'
|
marginTop: '10px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{applying ? (
|
{applying ? '⏳ Publishing...' : '✅ Apply Template & Publish to Relays'}
|
||||||
'⏳ Publishing...'
|
|
||||||
) : (
|
|
||||||
'✅ Apply Template & Publish to Relays'
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -291,7 +323,7 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Results */}
|
{/* RESULTS */}
|
||||||
{results && results.events && (
|
{results && results.events && (
|
||||||
<div className="success-box" style={{ marginTop: '20px' }}>
|
<div className="success-box" style={{ marginTop: '20px' }}>
|
||||||
<h3>
|
<h3>
|
||||||
|
|
@ -343,7 +375,6 @@ const TemplateApplier = ({ connectedPubkey, setConnectedPubkey }) => {
|
||||||
}}>
|
}}>
|
||||||
{result.success ? '✅' : '❌'} {result.url}
|
{result.success ? '✅' : '❌'} {result.url}
|
||||||
{result.error && ` - ${result.error}`}
|
{result.error && ` - ${result.error}`}
|
||||||
{BLAST_RELAYS.includes(result.url) && ' 🔥'}
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,21 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { encodeTemplate, generateTemplateId, validateTemplate, getCommunityTemplates } from '../utils/templates';
|
import { encodeTemplate, generateTemplateId, validateTemplate, getCommunityTemplates } from '../utils/templates';
|
||||||
import RelayList from './RelayList';
|
import RelayList from './RelayList';
|
||||||
import NostrConnect from './NostrConnect';
|
|
||||||
|
|
||||||
// Configure your blast relays here (hidden from UI)
|
const TemplateCreator = () => {
|
||||||
// These will be used to publish the events, but won't appear in the template
|
|
||||||
const BLAST_RELAYS = [
|
|
||||||
'wss://relay.primal.net',
|
|
||||||
'wss://relay.damus.io',
|
|
||||||
'wss://nos.lol'
|
|
||||||
// Add your preferred blast relays here
|
|
||||||
];
|
|
||||||
|
|
||||||
const TemplateCreator = ({ setConnectedPubkey }) => {
|
|
||||||
const [templateName, setTemplateName] = useState('');
|
const [templateName, setTemplateName] = useState('');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
const [relays, setRelays] = useState([
|
const [relays, setRelays] = useState([
|
||||||
{ url: 'wss://relay.damus.io', read: true, write: true },
|
{ url: 'wss://your-cool.relay', read: true, write: true }
|
||||||
{ url: 'wss://nos.lol', read: true, write: true }
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Blossom servers (kind 10063)
|
|
||||||
const [blossomServers, setBlossomServers] = useState([
|
const [blossomServers, setBlossomServers] = useState([
|
||||||
{ url: 'https://cdn.satellite.earth' }
|
{ url: 'https://your-blossom.community' }
|
||||||
]);
|
]);
|
||||||
const [showBlossom, setShowBlossom] = useState(true);
|
const [showBlossom, setShowBlossom] = useState(true);
|
||||||
|
|
||||||
// DM relays (kind 10050)
|
|
||||||
const [dmRelays, setDmRelays] = useState([
|
const [dmRelays, setDmRelays] = useState([
|
||||||
{ url: 'wss://relay.private-msgs.com' }
|
{ url: 'wss://your-dm.relay' }
|
||||||
]);
|
]);
|
||||||
const [showDmRelays, setShowDmRelays] = useState(true);
|
const [showDmRelays, setShowDmRelays] = useState(true);
|
||||||
|
|
||||||
|
|
@ -64,31 +51,6 @@ const TemplateCreator = ({ setConnectedPubkey }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadCommunityTemplate = (templateKey) => {
|
|
||||||
const template = communityTemplates[templateKey];
|
|
||||||
if (template) {
|
|
||||||
setRelays(template.relays);
|
|
||||||
setTemplateName(template.name);
|
|
||||||
setDescription(template.description);
|
|
||||||
if (template.blossomServers && template.blossomServers.length > 0) {
|
|
||||||
setBlossomServers(template.blossomServers);
|
|
||||||
setShowBlossom(true);
|
|
||||||
} else {
|
|
||||||
setBlossomServers([{ url: '' }]);
|
|
||||||
setShowBlossom(false);
|
|
||||||
}
|
|
||||||
if (template.dmRelays && template.dmRelays.length > 0) {
|
|
||||||
setDmRelays(template.dmRelays);
|
|
||||||
setShowDmRelays(true);
|
|
||||||
} else {
|
|
||||||
setDmRelays([{ url: '' }]);
|
|
||||||
setShowDmRelays(false);
|
|
||||||
}
|
|
||||||
setSelectedTemplate(templateKey);
|
|
||||||
setError('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Relay functions
|
// Relay functions
|
||||||
const addRelay = () => {
|
const addRelay = () => {
|
||||||
setRelays([...relays, { url: '', read: true, write: true }]);
|
setRelays([...relays, { url: '', read: true, write: true }]);
|
||||||
|
|
@ -104,6 +66,12 @@ const TemplateCreator = ({ setConnectedPubkey }) => {
|
||||||
setRelays(updated);
|
setRelays(updated);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addCommonRelay = (url) => {
|
||||||
|
if (!relays.some(r => r.url === url)) {
|
||||||
|
setRelays([...relays, { url, read: true, write: true }]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Blossom server functions
|
// Blossom server functions
|
||||||
const addBlossomServer = () => {
|
const addBlossomServer = () => {
|
||||||
setBlossomServers([...blossomServers, { url: '' }]);
|
setBlossomServers([...blossomServers, { url: '' }]);
|
||||||
|
|
@ -134,9 +102,28 @@ const TemplateCreator = ({ setConnectedPubkey }) => {
|
||||||
setDmRelays(updated);
|
setDmRelays(updated);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addCommonRelay = (url) => {
|
const loadCommunityTemplate = (templateKey) => {
|
||||||
if (!relays.some(r => r.url === url)) {
|
const template = communityTemplates[templateKey];
|
||||||
setRelays([...relays, { url, read: true, write: true }]);
|
if (template) {
|
||||||
|
setRelays(template.relays);
|
||||||
|
setTemplateName(template.name);
|
||||||
|
setDescription(template.description);
|
||||||
|
if (template.blossomServers && template.blossomServers.length > 0) {
|
||||||
|
setBlossomServers(template.blossomServers);
|
||||||
|
setShowBlossom(true);
|
||||||
|
} else {
|
||||||
|
setBlossomServers([{ url: '' }]);
|
||||||
|
setShowBlossom(false);
|
||||||
|
}
|
||||||
|
if (template.dmRelays && template.dmRelays.length > 0) {
|
||||||
|
setDmRelays(template.dmRelays);
|
||||||
|
setShowDmRelays(true);
|
||||||
|
} else {
|
||||||
|
setDmRelays([{ url: '' }]);
|
||||||
|
setShowDmRelays(false);
|
||||||
|
}
|
||||||
|
setSelectedTemplate(templateKey);
|
||||||
|
setError('');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -144,8 +131,6 @@ const TemplateCreator = ({ setConnectedPubkey }) => {
|
||||||
<div className="template-creator">
|
<div className="template-creator">
|
||||||
<h2>Create Relay Template</h2>
|
<h2>Create Relay Template</h2>
|
||||||
|
|
||||||
<NostrConnect setConnectedPubkey={setConnectedPubkey} />
|
|
||||||
|
|
||||||
{/* Community Templates Section */}
|
{/* Community Templates Section */}
|
||||||
<div className="form-group" style={{
|
<div className="form-group" style={{
|
||||||
background: '#f8f9fa',
|
background: '#f8f9fa',
|
||||||
|
|
@ -243,7 +228,7 @@ const TemplateCreator = ({ setConnectedPubkey }) => {
|
||||||
|
|
||||||
{/* Main Relays Section */}
|
{/* Main Relays Section */}
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Relays *</label>
|
<label>📡 Relays *</label>
|
||||||
<RelayList
|
<RelayList
|
||||||
relays={relays}
|
relays={relays}
|
||||||
onUpdate={updateRelay}
|
onUpdate={updateRelay}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
// Blast relays - configure these once and they'll be used for publishing
|
// Blast relays - configured once, used for publishing
|
||||||
// These are hidden from the UI and used to blast events to reliable relays
|
|
||||||
const BLAST_RELAYS = [
|
const BLAST_RELAYS = [
|
||||||
'wss://relay.primal.net',
|
'wss://relay.primal.net',
|
||||||
'wss://relay.damus.io',
|
'wss://relay.damus.io',
|
||||||
|
'wss://relay.ditto.pub',
|
||||||
|
'wss://offchain.pub',
|
||||||
'wss://sendit.nosflare.com',
|
'wss://sendit.nosflare.com',
|
||||||
'wss://nostr.mom',
|
'wss://nostr.mom',
|
||||||
'wss://relay.ditto.pub',
|
|
||||||
'wss://nos.lol'
|
'wss://nos.lol'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -16,143 +16,144 @@ export const useNostr = () => {
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [extension, setExtension] = useState(null);
|
const [extension, setExtension] = useState(null);
|
||||||
|
const [isConnecting, setIsConnecting] = useState(false);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INIT - Check for existing extension
|
||||||
|
// ============================================================
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkExtension = async () => {
|
const checkConnection = async () => {
|
||||||
|
if (isConnected && pubkey) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (window.nostr) {
|
if (window.nostr?.getPublicKey) {
|
||||||
setExtension(window.nostr);
|
|
||||||
try {
|
try {
|
||||||
const pk = await window.nostr.getPublicKey();
|
const pk = await window.nostr.getPublicKey();
|
||||||
|
setExtension(window.nostr);
|
||||||
setPubkey(pk);
|
setPubkey(pk);
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
} catch (err) {
|
console.log('✅ Connected via extension:', pk.slice(0, 8) + '...');
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
console.log('Extension available but not authorized');
|
console.log('Extension available but not authorized');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
console.log('ℹ️ No extension found');
|
||||||
setError('No Nostr extension found');
|
} catch (e) {
|
||||||
|
console.error('Init error:', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
checkExtension();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
checkConnection();
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (window.nostr?.getPublicKey && !isConnected) {
|
||||||
|
window.nostr.getPublicKey().then(pk => {
|
||||||
|
setExtension(window.nostr);
|
||||||
|
setPubkey(pk);
|
||||||
|
setIsConnected(true);
|
||||||
|
console.log('✅ Extension connected:', pk.slice(0, 8) + '...');
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [isConnected, pubkey]);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// CONNECT
|
||||||
|
// ============================================================
|
||||||
const connect = useCallback(async () => {
|
const connect = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
setIsConnecting(true);
|
||||||
if (!window.nostr) {
|
if (!window.nostr) {
|
||||||
throw new Error('Please install a Nostr extension (Alby, Nos2x, etc.)');
|
throw new Error('Please install a Nostr extension (Alby, Nos2x, etc.)');
|
||||||
}
|
}
|
||||||
const pk = await window.nostr.getPublicKey();
|
const pk = await window.nostr.getPublicKey();
|
||||||
|
setExtension(window.nostr);
|
||||||
setPubkey(pk);
|
setPubkey(pk);
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
setExtension(window.nostr);
|
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setIsConnecting(false);
|
||||||
return pk;
|
return pk;
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
setError(err.message);
|
setError(e.message);
|
||||||
setIsConnected(false);
|
setIsConnecting(false);
|
||||||
throw err;
|
throw e;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SIGN EVENT
|
||||||
|
// ============================================================
|
||||||
const signEvent = useCallback(async (event) => {
|
const signEvent = useCallback(async (event) => {
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
throw new Error('No Nostr extension connected');
|
throw new Error('No Nostr extension connected');
|
||||||
}
|
}
|
||||||
return await extension.signEvent(event);
|
try {
|
||||||
|
console.log('📱 Signing event...');
|
||||||
|
const signed = await extension.signEvent(event);
|
||||||
|
console.log('✅ Event signed');
|
||||||
|
return signed;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Signing failed:', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}, [extension]);
|
}, [extension]);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PUBLISH HELPERS
|
||||||
|
// ============================================================
|
||||||
const publishToSingleRelay = useCallback((url, event) => {
|
const publishToSingleRelay = useCallback((url, event) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let ws = null;
|
const ws = new WebSocket(url);
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
let timeoutIds = [];
|
|
||||||
|
|
||||||
try {
|
const timeout = setTimeout(() => {
|
||||||
ws = new WebSocket(url);
|
|
||||||
|
|
||||||
const connectTimeout = setTimeout(() => {
|
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
||||||
try { ws.close(); } catch (e) {}
|
try { ws.close(); } catch (e) {}
|
||||||
}
|
resolve({ url, success: false, error: 'Timeout' });
|
||||||
resolve({ url, success: false, error: 'Connection timeout' });
|
|
||||||
}
|
}
|
||||||
}, 10000);
|
}, 10000);
|
||||||
timeoutIds.push(connectTimeout);
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
const message = JSON.stringify(['EVENT', event]);
|
ws.send(JSON.stringify(['EVENT', event]));
|
||||||
ws.send(message);
|
const pubTimeout = setTimeout(() => {
|
||||||
|
|
||||||
const publishTimeout = setTimeout(() => {
|
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
console.log(`⏰ Publish timeout for ${url}`);
|
|
||||||
try { ws.close(); } catch (e) {}
|
try { ws.close(); } catch (e) {}
|
||||||
resolve({ url, success: false, error: 'Publish timeout' });
|
resolve({ url, success: false, error: 'Publish timeout' });
|
||||||
}
|
}
|
||||||
}, 15000);
|
}, 15000);
|
||||||
timeoutIds.push(publishTimeout);
|
|
||||||
|
|
||||||
ws.onmessage = (msg) => {
|
ws.onmessage = (msg) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(msg.data);
|
const data = JSON.parse(msg.data);
|
||||||
if (Array.isArray(data) && data[0] === 'OK' && data[1] === event.id) {
|
if (data[0] === 'OK' && data[1] === event.id) {
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
console.log(`✅ Published to ${url}`);
|
clearTimeout(timeout);
|
||||||
timeoutIds.forEach(id => clearTimeout(id));
|
clearTimeout(pubTimeout);
|
||||||
try { ws.close(); } catch (e) {}
|
try { ws.close(); } catch (e) {}
|
||||||
resolve({ url, success: true });
|
resolve({ url, success: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
timeoutIds.forEach(id => clearTimeout(id));
|
clearTimeout(timeout);
|
||||||
try { ws.close(); } catch (e) {}
|
try { ws.close(); } catch (e) {}
|
||||||
resolve({ url, success: false, error: 'WebSocket error' });
|
resolve({ url, success: false, error: 'WebSocket error' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true;
|
|
||||||
timeoutIds.forEach(id => clearTimeout(id));
|
|
||||||
resolve({ url, success: false, error: 'Connection closed' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = () => {
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true;
|
|
||||||
timeoutIds.forEach(id => clearTimeout(id));
|
|
||||||
resolve({ url, success: false, error: 'Connection error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true;
|
|
||||||
timeoutIds.forEach(id => clearTimeout(id));
|
|
||||||
if (ws) {
|
|
||||||
try { ws.close(); } catch (e) {}
|
|
||||||
}
|
|
||||||
resolve({ url, success: false, error: err.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
|
||||||
* Publish to multiple relays with detailed results
|
|
||||||
*/
|
|
||||||
const publishToRelays = useCallback(async (event, relayUrls, label = '') => {
|
const publishToRelays = useCallback(async (event, relayUrls, label = '') => {
|
||||||
console.log(`Publishing ${label} to ${relayUrls.length} relays...`);
|
console.log(`Publishing ${label} to ${relayUrls.length} relays...`);
|
||||||
|
|
||||||
|
|
@ -169,8 +170,9 @@ export const useNostr = () => {
|
||||||
return [{ url: 'none', success: false, error: 'No valid relay URLs' }];
|
return [{ url: 'none', success: false, error: 'No valid relay URLs' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishPromises = validUrls.map(url => publishToSingleRelay(url, event));
|
const results = await Promise.all(
|
||||||
const results = await Promise.all(publishPromises);
|
validUrls.map(url => publishToSingleRelay(url, event))
|
||||||
|
);
|
||||||
|
|
||||||
const successCount = results.filter(r => r.success).length;
|
const successCount = results.filter(r => r.success).length;
|
||||||
console.log(`Published ${label} to ${successCount}/${results.length} relays`);
|
console.log(`Published ${label} to ${successCount}/${results.length} relays`);
|
||||||
|
|
@ -178,8 +180,16 @@ export const useNostr = () => {
|
||||||
return results;
|
return results;
|
||||||
}, [publishToSingleRelay]);
|
}, [publishToSingleRelay]);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PUBLISH KIND FUNCTIONS
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic publish function that publishes to BOTH blast relays AND user relays
|
* Generic publish function that publishes to ALL relays
|
||||||
|
* @param {number} kind - Event kind
|
||||||
|
* @param {object} data - Event data with tags and content
|
||||||
|
* @param {string} pubkey - User's pubkey
|
||||||
|
* @param {string[]} userRelayUrls - User's relay URLs (all of them, not just for this kind)
|
||||||
*/
|
*/
|
||||||
const publishKind = useCallback(async (kind, data, pubkey, userRelayUrls = []) => {
|
const publishKind = useCallback(async (kind, data, pubkey, userRelayUrls = []) => {
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
|
|
@ -187,104 +197,99 @@ export const useNostr = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = {
|
const event = {
|
||||||
kind: kind,
|
kind,
|
||||||
pubkey: pubkey,
|
pubkey,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
tags: data.tags || [],
|
tags: data.tags || [],
|
||||||
content: data.content || ''
|
content: data.content || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`Signing kind ${kind} event...`);
|
console.log(`Signing kind ${kind} event...`);
|
||||||
const signedEvent = await extension.signEvent(event);
|
const signedEvent = await signEvent(event);
|
||||||
console.log(`Kind ${kind} event signed successfully`);
|
console.log(`Kind ${kind} event signed successfully`);
|
||||||
|
|
||||||
// Combine blast relays and user relays (deduplicated)
|
// ✅ Combine ALL relays: blast + ALL user relays
|
||||||
const allRelays = [...new Set([...BLAST_RELAYS, ...userRelayUrls])];
|
const allRelays = [...new Set([...BLAST_RELAYS, ...userRelayUrls])];
|
||||||
console.log(`Publishing kind ${kind} to ${allRelays.length} relays (${BLAST_RELAYS.length} blast + ${userRelayUrls.length} user)`);
|
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}`);
|
const results = await publishToRelays(signedEvent, allRelays, `kind ${kind}`);
|
||||||
|
|
||||||
// Separate results for better reporting
|
|
||||||
const blastResults = results.filter(r => BLAST_RELAYS.includes(r.url));
|
const blastResults = results.filter(r => BLAST_RELAYS.includes(r.url));
|
||||||
const userResults = results.filter(r => userRelayUrls.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 {
|
return {
|
||||||
event: signedEvent,
|
event: signedEvent,
|
||||||
results: results,
|
results,
|
||||||
blastResults: blastResults,
|
blastResults,
|
||||||
userResults: userResults
|
userResults
|
||||||
};
|
};
|
||||||
}, [extension, publishToRelays]);
|
}, [extension, signEvent, publishToRelays]);
|
||||||
|
|
||||||
// Kind 10002 - Relays
|
// Kind 10002 - Relays
|
||||||
const publishKind10002 = useCallback(async (relays, pubkey) => {
|
const publishKind10002 = useCallback(async (relays, pubkey) => {
|
||||||
const validRelays = relays.filter(r => r.url && r.url.trim() !== '');
|
const validRelays = relays.filter(r => r.url && r.url.trim() !== '');
|
||||||
|
|
||||||
if (validRelays.length === 0) {
|
if (validRelays.length === 0) {
|
||||||
throw new Error('No valid relays to publish');
|
throw new Error('No valid relays to publish');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = validRelays.map(relay => {
|
const tags = validRelays.map(relay => {
|
||||||
const params = [];
|
const params = [];
|
||||||
if (!relay.read) params.push('read');
|
if (!relay.read) params.push('read');
|
||||||
if (!relay.write) params.push('write');
|
if (!relay.write) params.push('write');
|
||||||
return ['r', relay.url, ...params];
|
return ['r', relay.url, ...params];
|
||||||
});
|
});
|
||||||
|
// ✅ Pass ALL user relay URLs (they're the same as the relays being published)
|
||||||
// Extract user relay URLs for combined publishing
|
|
||||||
const userRelayUrls = validRelays.map(r => r.url);
|
const userRelayUrls = validRelays.map(r => r.url);
|
||||||
|
|
||||||
return await publishKind(10002, { tags }, pubkey, userRelayUrls);
|
return await publishKind(10002, { tags }, pubkey, userRelayUrls);
|
||||||
}, [publishKind]);
|
}, [publishKind]);
|
||||||
|
|
||||||
// Kind 10063 - Blossom Servers
|
// Kind 10063 - Blossom Servers
|
||||||
const publishKind10063 = useCallback(async (servers, pubkey) => {
|
const publishKind10063 = useCallback(async (servers, pubkey, allUserRelays = []) => {
|
||||||
const validServers = servers.filter(s => s.url && s.url.trim() !== '');
|
const validServers = servers.filter(s => s.url && s.url.trim() !== '');
|
||||||
|
|
||||||
if (validServers.length === 0) {
|
if (validServers.length === 0) {
|
||||||
throw new Error('No valid blossom servers');
|
throw new Error('No valid blossom servers');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = validServers.map(server => ['server', server.url]);
|
const tags = validServers.map(server => ['server', server.url]);
|
||||||
|
// ✅ Pass ALL user relays (main relays, not just blossom servers)
|
||||||
// For blossom servers, we want to publish to blast relays
|
return await publishKind(10063, { tags }, pubkey, allUserRelays);
|
||||||
// The user's main relays are used for kind 10002, not kind 10063
|
|
||||||
return await publishKind(10063, { tags }, pubkey, []);
|
|
||||||
}, [publishKind]);
|
}, [publishKind]);
|
||||||
|
|
||||||
// Kind 10050 - DM Relays
|
// Kind 10050 - DM Relays
|
||||||
const publishKind10050 = useCallback(async (relays, pubkey) => {
|
const publishKind10050 = useCallback(async (relays, pubkey, allUserRelays = []) => {
|
||||||
const validRelays = relays.filter(r => r.url && r.url.trim() !== '');
|
const validRelays = relays.filter(r => r.url && r.url.trim() !== '');
|
||||||
|
|
||||||
if (validRelays.length === 0) {
|
if (validRelays.length === 0) {
|
||||||
throw new Error('No valid DM relays');
|
throw new Error('No valid DM relays');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = validRelays.map(relay => ['relay', relay.url]);
|
const tags = validRelays.map(relay => ['relay', relay.url]);
|
||||||
|
// ✅ Pass ALL user relays (main relays, not just DM relays)
|
||||||
// For DM relays, we want to publish to blast relays
|
return await publishKind(10050, { tags }, pubkey, allUserRelays);
|
||||||
// The user's main relays are used for kind 10002, not kind 10050
|
|
||||||
return await publishKind(10050, { tags }, pubkey, []);
|
|
||||||
}, [publishKind]);
|
}, [publishKind]);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// DISCONNECT
|
||||||
|
// ============================================================
|
||||||
|
const disconnect = useCallback(() => {
|
||||||
|
setPubkey(null);
|
||||||
|
setIsConnected(false);
|
||||||
|
setExtension(null);
|
||||||
|
console.log('👋 Disconnected');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// RETURN
|
||||||
|
// ============================================================
|
||||||
return {
|
return {
|
||||||
pubkey,
|
pubkey,
|
||||||
isConnected,
|
isConnected,
|
||||||
error,
|
error,
|
||||||
extension,
|
isConnecting,
|
||||||
connect,
|
connect,
|
||||||
signEvent,
|
signEvent,
|
||||||
|
disconnect,
|
||||||
publishToRelays,
|
publishToRelays,
|
||||||
publishKind,
|
publishKind,
|
||||||
publishKind10002,
|
publishKind10002,
|
||||||
publishKind10063,
|
publishKind10063,
|
||||||
publishKind10050
|
publishKind10050,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue