From 3c1227e5be3cf64d914536cb9def1cb03e178222 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:21:01 +0000 Subject: [PATCH] feat(ai): Implement scripts/agent/interop-test.mjs Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- scripts/agent/interop-test.mjs | 158 ++++++++++++++++++++++++++------- 1 file changed, 127 insertions(+), 31 deletions(-) diff --git a/scripts/agent/interop-test.mjs b/scripts/agent/interop-test.mjs index b900466a..b6319911 100644 --- a/scripts/agent/interop-test.mjs +++ b/scripts/agent/interop-test.mjs @@ -1,20 +1,46 @@ import './setup-test-env.js'; +import { indexedDB, IDBKeyRange } from "fake-indexeddb"; +import { WebSocket } from 'ws'; +import * as NostrTools from 'nostr-tools'; +import { webcrypto } from 'node:crypto'; + +// --- Polyfills --- +if (!global.indexedDB) { + global.indexedDB = indexedDB; +} +if (!global.IDBKeyRange) { + global.IDBKeyRange = IDBKeyRange; +} +if (!global.NostrTools) { + global.NostrTools = NostrTools; +} +// Ensure crypto is available globally for NostrClient (it might be used in some paths) +if (!global.crypto) { + global.crypto = webcrypto; +} + +// --- Imports from Codebase --- import { NostrClient } from '../../js/nostr/client.js'; import { buildVideoPostEvent, buildViewEvent } from '../../js/nostrEventSchemas.js'; -import * as NostrTools from 'nostr-tools'; +import { decryptDM } from '../../js/dmDecryptor.js'; import { bytesToHex } from '../../vendor/crypto-helpers.bundle.min.js'; -// Setup global NostrTools for toolkit.js fallback -global.NostrTools = NostrTools; +// --- Configuration --- +const TEST_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"]; +const TIMEOUT_MS = 15000; async function waitForEvent(pool, relays, id, timeoutMs = 10000) { const start = Date.now(); while (Date.now() - start < timeoutMs) { - const event = await pool.get(relays, { ids: [id] }); - if (event) return event; + try { + const event = await pool.get(relays, { ids: [id] }); + if (event) return event; + } catch (e) { + // ignore network errors during polling + } await new Promise(r => setTimeout(r, 1000)); } return null; @@ -22,29 +48,34 @@ async function waitForEvent(pool, relays, id, timeoutMs = 10000) { async function run() { console.log("Starting Interop Test..."); + console.log(`Relays: ${TEST_RELAYS.join(', ')}`); // 1. Setup Client const client = new NostrClient(); - - // Test relays - using reputable public relays - const TEST_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"]; client.relays = [...TEST_RELAYS]; - - console.log(`Using relays: ${TEST_RELAYS.join(', ')}`); + client.readRelays = [...TEST_RELAYS]; + client.writeRelays = [...TEST_RELAYS]; try { // 2. Init (connects to relays) await client.init(); + console.log("Client initialized."); - // 3. Generate Ephemeral Keys + // 3. Generate Ephemeral Keys (Sender) console.log("Generating ephemeral keys..."); const sk = NostrTools.generateSecretKey(); const skHex = bytesToHex(sk); const pk = NostrTools.getPublicKey(sk); - console.log(`Ephemeral Pubkey: ${pk}`); + console.log(`Ephemeral Sender Pubkey: ${pk}`); // 4. Register Signer await client.registerPrivateKeySigner({ privateKey: skHex, pubkey: pk }); + // Manually set pubkey as registerPrivateKeySigner doesn't always set client.pubkey depending on implementation details in some versions, + // though the code read showed it sets sessionActor. client.pubkey is usually set on login. + // The client.registerPrivateKeySigner implementation sets sessionActor but assumes the user calls init or similar to set client.pubkey if full login is desired? + // Looking at NostrClient.js, registerPrivateKeySigner does NOT set this.pubkey. It sets sessionActor. + // So we manually set it to simulate "logged in" state for high-level methods. + client.pubkey = pk; console.log("Signer registered."); // 5. Test A: Video Post @@ -62,9 +93,10 @@ async function run() { url: "https://example.com/video.mp4", mimeType: "video/mp4", mode: "live", - deleted: false + deleted: false, + magnet: `magnet:?xt=urn:btih:${Math.random().toString(16).slice(2)}`, // Add magnet as per schema requirements usually }, - // 's' tag is required by schema (storage pointer) + // 's' tag is recommended for storage pointer additionalTags: [['s', `url:https://example.com/video.mp4`]] }); @@ -78,8 +110,8 @@ async function run() { console.log(`Published Video Event ID: ${publishedEvent.id}`); // Verify - console.log("Verifying Video Post..."); - const rawRelayEvent = await waitForEvent(client.pool, TEST_RELAYS, publishedEvent.id); + console.log("Verifying Video Post (fetch by ID)..."); + const rawRelayEvent = await waitForEvent(client.pool, TEST_RELAYS, publishedEvent.id, TIMEOUT_MS); if (!rawRelayEvent) throw new Error("Failed to fetch published video event from relays"); if (rawRelayEvent.id !== publishedEvent.id) throw new Error("Fetched event ID mismatch"); @@ -96,35 +128,99 @@ async function run() { }); const viewPublishResult = await client.signAndPublishEvent(viewEvent); - console.log(`Published View Event ID: ${viewPublishResult.signedEvent.id}`); + const viewEventId = viewPublishResult.signedEvent.id; + console.log(`Published View Event ID: ${viewEventId}`); // Ensure it is visible to subscriber clients (simulate). - const rawViewEvent = await waitForEvent(client.pool, TEST_RELAYS, viewPublishResult.signedEvent.id); + const rawViewEvent = await waitForEvent(client.pool, TEST_RELAYS, viewEventId, TIMEOUT_MS); if (!rawViewEvent) throw new Error("Failed to fetch published view event"); console.log("View Event Verified."); // 7. Test C: Direct Message console.log("\n[Test C] Testing Direct Message..."); - const msg = `Test DM ${Date.now()}`; - const npub = NostrTools.nip19.npubEncode(pk); - console.log(`Sending DM to self (${npub})...`); - const dmResult = await client.sendDirectMessage(npub, msg); + // Generate Recipient + const recipientSk = NostrTools.generateSecretKey(); + const recipientSkHex = bytesToHex(recipientSk); + const recipientPk = NostrTools.getPublicKey(recipientSk); + const recipientNpub = NostrTools.nip19.npubEncode(recipientPk); + console.log(`Recipient Pubkey: ${recipientPk}`); + + const msg = `Test DM ${Date.now()}`; + console.log(`Sending DM to recipient (${recipientNpub})...`); + + // We send using the client (Sender) + const dmResult = await client.sendDirectMessage(recipientNpub, msg); if (!dmResult.ok) throw new Error(`DM Send failed: ${dmResult.error}`); console.log("DM Sent."); - // Verify decryption using the appropriate decryptor helper - console.log("Verifying DM (waiting 2s)..."); - await new Promise(resolve => setTimeout(resolve, 2000)); - const dms = await client.listDirectMessages(pk, { limit: 10 }); - const found = dms.find(d => d.plaintext === msg); + // Fetch the raw DM event from relays (Recipient perspective) + console.log("Fetching DM event from relays..."); - if (!found) { - console.log("Fetched DMs:", dms.map(d => d.plaintext)); - throw new Error("Failed to find sent DM"); + // Filter for DMs to recipient + const filter = { + kinds: [4, 1059], // 4 (legacy) or 1059 (nip17) + '#p': [recipientPk], + limit: 1, + since: Math.floor(Date.now() / 1000) - 60 // last 60 seconds + }; + + // Wait for propagation + await new Promise(r => setTimeout(r, 2000)); + + let events = []; + const fetchStart = Date.now(); + while (Date.now() - fetchStart < TIMEOUT_MS) { + events = await client.pool.list(TEST_RELAYS, [filter]); + if (events.length > 0) break; + await new Promise(r => setTimeout(r, 1000)); } - console.log("DM Verified."); + + if (events.length === 0) { + throw new Error("Failed to find sent DM on relays."); + } + + const dmEvent = events[0]; + console.log(`Found DM Event (Kind ${dmEvent.kind}, ID: ${dmEvent.id})`); + + // Decrypt using decryptDM helper and Recipient Keys + console.log("Attempting decryption using decryptDM helper..."); + + const decryptors = []; + // NIP-04 Decryptor + decryptors.push({ + scheme: 'nip04', + decrypt: async (remotePubkey, ciphertext) => { + return await NostrTools.nip04.decrypt(recipientSkHex, remotePubkey, ciphertext); + } + }); + // NIP-44 Decryptor + decryptors.push({ + scheme: 'nip44', + supportsGiftWrap: true, + decrypt: (remotePubkey, ciphertext) => { + const conversationKey = NostrTools.nip44.v2.utils.getConversationKey(recipientSk, remotePubkey); + return NostrTools.nip44.v2.decrypt(ciphertext, conversationKey); + } + }); + + const context = { + actorPubkey: recipientPk, + decryptors + }; + + const decryptResult = await decryptDM(dmEvent, context); + + if (!decryptResult.ok) { + console.error("Decryption errors:", decryptResult.errors); + throw new Error("DM decryption failed"); + } + + if (decryptResult.plaintext !== msg) { + throw new Error(`DM plaintext mismatch. Expected "${msg}", got "${decryptResult.plaintext}"`); + } + console.log("DM Verified (Decryption successful)."); console.log("\nAll Interop Tests Passed!"); process.exit(0);