fix: rewrite useCVMClient to use Relay.connect() instead of SimplePool
nostr-tools v2 SimplePool.subscribeMany() callbacks (onevent/oneose) no longer fire reliably in browser context, causing the CVM client to always time out. Switch to raw Relay.connect() which uses the v2 API correctly. Race across all CVM relays and resolve on first valid response. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
c772330e74
commit
a9c677b58d
1 changed files with 57 additions and 52 deletions
|
|
@ -23,7 +23,7 @@ import {
|
|||
generateSecretKey,
|
||||
getPublicKey,
|
||||
finalizeEvent,
|
||||
SimplePool,
|
||||
Relay,
|
||||
} from 'nostr-tools';
|
||||
import { nip44 } from 'nostr-tools';
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ export interface CommunityStats {
|
|||
async function callCVMTool(
|
||||
toolName: string,
|
||||
toolArgs: Record<string, unknown>,
|
||||
timeoutMs = 10000
|
||||
timeoutMs = 15000
|
||||
): Promise<unknown> {
|
||||
// Generate ephemeral keypair for this request
|
||||
const sk = generateSecretKey();
|
||||
|
|
@ -138,69 +138,74 @@ async function callCVMTool(
|
|||
conversationKey
|
||||
);
|
||||
|
||||
// Build the Nostr event
|
||||
const eventTemplate = {
|
||||
// Build and sign the request event
|
||||
const signedEvent = finalizeEvent({
|
||||
kind: CVM_REQUEST_KIND,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
['p', CVM_SERVER_PUBKEY],
|
||||
],
|
||||
tags: [['p', CVM_SERVER_PUBKEY]],
|
||||
content: encryptedContent,
|
||||
}, sk);
|
||||
|
||||
// Use raw Relay connections (nostr-tools v2 compatible)
|
||||
// Try each relay in order, race for the first response
|
||||
return new Promise((resolve, reject) => {
|
||||
const relays: InstanceType<typeof Relay>[] = [];
|
||||
let settled = false;
|
||||
|
||||
const cleanup = () => {
|
||||
relays.forEach(r => { try { r.close(); } catch { /* ignore */ } });
|
||||
};
|
||||
|
||||
const signedEvent = finalizeEvent(eventTemplate, sk);
|
||||
|
||||
const pool = new SimplePool();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
sub.close();
|
||||
pool.close(CVM_RELAYS);
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(new Error(`CVM tool call timed out after ${timeoutMs}ms`));
|
||||
}
|
||||
}, timeoutMs);
|
||||
|
||||
// Subscribe for the response before publishing
|
||||
const sub = pool.subscribeMany(
|
||||
CVM_RELAYS,
|
||||
[
|
||||
{
|
||||
kinds: [CVM_RESPONSE_KIND],
|
||||
'#p': [pk], // response tagged to our ephemeral pubkey
|
||||
since: Math.floor(Date.now() / 1000) - 5,
|
||||
},
|
||||
],
|
||||
const since = Math.floor(Date.now() / 1000) - 5;
|
||||
|
||||
CVM_RELAYS.forEach(async (url) => {
|
||||
try {
|
||||
const relay = await Relay.connect(url);
|
||||
relays.push(relay);
|
||||
|
||||
// Subscribe for response BEFORE publishing
|
||||
const sub = relay.subscribe(
|
||||
[{ kinds: [CVM_RESPONSE_KIND], '#p': [pk], since }],
|
||||
{
|
||||
onevent(responseEvent) {
|
||||
if (settled) return;
|
||||
try {
|
||||
// Decrypt the response
|
||||
const convKey = nip44.v2.utils.getConversationKey(sk, responseEvent.pubkey);
|
||||
const decrypted = nip44.v2.decrypt(responseEvent.content, convKey);
|
||||
const mcpResponse = JSON.parse(decrypted);
|
||||
|
||||
// Match by request ID
|
||||
if (mcpResponse.id !== requestId) return;
|
||||
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
sub.close();
|
||||
pool.close(CVM_RELAYS);
|
||||
cleanup();
|
||||
|
||||
if (mcpResponse.error) {
|
||||
reject(new Error(mcpResponse.error.message ?? 'CVM error'));
|
||||
} else {
|
||||
resolve(mcpResponse.result);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore events we can't decrypt (not for us)
|
||||
} catch {
|
||||
// Not for us — ignore
|
||||
}
|
||||
},
|
||||
oneose() {
|
||||
// Keep listening even after EOSE — response may come after
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Publish the request after subscribing
|
||||
pool.publish(CVM_RELAYS, signedEvent).catch(() => {});
|
||||
// Now publish the request
|
||||
await relay.publish(signedEvent);
|
||||
} catch {
|
||||
// This relay failed — others may still succeed
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue