mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
- Create `_redirects` file with catch-all rule (`/* /index.html 200`) to fix Netlify "Redirect rules" check failure and support SPA routing. - Remove unused `NOTE_TYPES` import from `scripts/agent/interop-test.mjs`. Co-authored-by: PR0M3TH3AN <[email protected]>
216 lines
8.3 KiB
JavaScript
216 lines
8.3 KiB
JavaScript
|
|
import { WebSocket } from 'ws';
|
|
if (!global.WebSocket) {
|
|
global.WebSocket = WebSocket;
|
|
}
|
|
|
|
import { NostrClient, resolveActiveSigner } from '../../js/nostr/client.js';
|
|
import { startRelay } from './load-test-relay.mjs';
|
|
import {
|
|
buildVideoPostEvent,
|
|
buildViewEvent,
|
|
buildLegacyDirectMessageEvent
|
|
} from '../../js/nostrEventSchemas.js';
|
|
import { decryptDM } from '../../js/dmDecryptor.js';
|
|
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
|
import { bytesToHex } from '../../vendor/crypto-helpers.bundle.min.js';
|
|
|
|
// Configuration
|
|
const RELAY_PORT = 8899;
|
|
const RELAY_URL = `ws://localhost:${RELAY_PORT}`;
|
|
const EPHEMERAL_SK_BYTES = generateSecretKey();
|
|
const EPHEMERAL_SK = bytesToHex(EPHEMERAL_SK_BYTES);
|
|
const EPHEMERAL_PK = getPublicKey(EPHEMERAL_SK_BYTES);
|
|
|
|
console.log(`[Setup] Generated Ephemeral Public Key: ${EPHEMERAL_PK}`);
|
|
|
|
async function runInteropTests() {
|
|
let relayServer;
|
|
let client;
|
|
|
|
try {
|
|
// 1. Start Local Relay
|
|
console.log('[Setup] Starting local test relay...');
|
|
relayServer = startRelay(RELAY_PORT);
|
|
|
|
// 2. Initialize Client
|
|
console.log('[Setup] Initializing NostrClient...');
|
|
client = new NostrClient();
|
|
client.relays = [RELAY_URL];
|
|
client.writeRelays = [RELAY_URL];
|
|
client.readRelays = [RELAY_URL];
|
|
|
|
// Register signer
|
|
await client.registerPrivateKeySigner({ privateKey: EPHEMERAL_SK });
|
|
console.log('[Setup] Registered private key signer.');
|
|
|
|
// Init client (connects to relay)
|
|
await client.init();
|
|
console.log('[Setup] Client initialized and connected.');
|
|
|
|
// --- Test A: Video Post ---
|
|
console.log('\n[Test A] Video Post Event Roundtrip');
|
|
const videoContent = {
|
|
version: 3,
|
|
title: 'Interop Test Video',
|
|
url: 'https://example.com/video.mp4',
|
|
description: 'A test video for interoperability verification.',
|
|
mode: 'live',
|
|
videoRootId: 'interop-test-root-1'
|
|
};
|
|
|
|
const videoEventTemplate = buildVideoPostEvent({
|
|
pubkey: EPHEMERAL_PK,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
dTagValue: 'interop-test-root-1', // Using rootId as d-tag for simplicity
|
|
content: videoContent
|
|
});
|
|
|
|
console.log('[Test A] Publishing Video Post...');
|
|
const { signedEvent: publishedVideo } = await client.signAndPublishEvent(videoEventTemplate, {
|
|
context: 'test-video-post'
|
|
});
|
|
|
|
if (!publishedVideo || !publishedVideo.id) {
|
|
throw new Error('[Test A] Failed to publish video event (no ID returned).');
|
|
}
|
|
console.log(`[Test A] Published Video Event ID: ${publishedVideo.id}`);
|
|
|
|
// Fetch back
|
|
console.log('[Test A] Fetching event back...');
|
|
// Small delay to ensure relay processing
|
|
await new Promise(r => setTimeout(r, 200));
|
|
|
|
const fetchedVideo = await client.getEventById(publishedVideo.id, { includeRaw: true });
|
|
|
|
if (!fetchedVideo) {
|
|
throw new Error('[Test A] Failed to fetch video event back from relay.');
|
|
}
|
|
|
|
const rawVideoEvent = fetchedVideo.rawEvent || fetchedVideo; // handle if getEventById returns raw or object wrapper
|
|
|
|
if (rawVideoEvent.id !== publishedVideo.id) {
|
|
throw new Error(`[Test A] ID mismatch: expected ${publishedVideo.id}, got ${rawVideoEvent.id}`);
|
|
}
|
|
|
|
// Validate content shape (simplified check)
|
|
const parsedContent = typeof rawVideoEvent.content === 'string' ? JSON.parse(rawVideoEvent.content) : rawVideoEvent.content;
|
|
if (parsedContent.title !== videoContent.title) {
|
|
throw new Error(`[Test A] Content mismatch: expected title "${videoContent.title}", got "${parsedContent.title}"`);
|
|
}
|
|
|
|
console.log('[Test A] SUCCESS: Video Post roundtrip verified.');
|
|
|
|
|
|
// --- Test B: View Event ---
|
|
console.log('\n[Test B] View Event Visibility');
|
|
const viewEventTemplate = buildViewEvent({
|
|
pubkey: EPHEMERAL_PK,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
pointerValue: 'interop-test-root-1', // referencing the video we just created
|
|
pointerTag: ['d', 'interop-test-root-1']
|
|
});
|
|
|
|
console.log('[Test B] Publishing View Event...');
|
|
const { signedEvent: publishedView } = await client.signAndPublishEvent(viewEventTemplate, {
|
|
context: 'test-view-event'
|
|
});
|
|
|
|
if (!publishedView || !publishedView.id) {
|
|
throw new Error('[Test B] Failed to publish view event.');
|
|
}
|
|
console.log(`[Test B] Published View Event ID: ${publishedView.id}`);
|
|
|
|
// Fetch back to verify
|
|
await new Promise(r => setTimeout(r, 200));
|
|
const fetchedView = await client.fetchRawEventById(publishedView.id);
|
|
if (!fetchedView) {
|
|
throw new Error('[Test B] Failed to fetch view event back.');
|
|
}
|
|
console.log('[Test B] SUCCESS: View Event verified.');
|
|
|
|
|
|
// --- Test C: Encrypted DM ---
|
|
console.log('\n[Test C] Encrypted DM Roundtrip (Self-DM)');
|
|
const dmMessage = "This is a secret interop message.";
|
|
|
|
// 1. Encrypt
|
|
const signer = resolveActiveSigner(EPHEMERAL_PK);
|
|
if (!signer || typeof signer.nip04Encrypt !== 'function') {
|
|
throw new Error('[Test C] Signer does not support NIP-04 encryption.');
|
|
}
|
|
|
|
console.log('[Test C] Encrypting message...');
|
|
const ciphertext = await signer.nip04Encrypt(EPHEMERAL_PK, dmMessage);
|
|
|
|
// 2. Build Event
|
|
const dmTemplate = buildLegacyDirectMessageEvent({
|
|
pubkey: EPHEMERAL_PK,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
recipientPubkey: EPHEMERAL_PK,
|
|
ciphertext: ciphertext
|
|
});
|
|
|
|
// 3. Publish
|
|
console.log('[Test C] Publishing DM...');
|
|
const { signedEvent: publishedDM } = await client.signAndPublishEvent(dmTemplate, {
|
|
context: 'test-dm'
|
|
});
|
|
console.log(`[Test C] Published DM Event ID: ${publishedDM.id}`);
|
|
|
|
// 4. Fetch
|
|
await new Promise(r => setTimeout(r, 200));
|
|
const fetchedDM = await client.fetchRawEventById(publishedDM.id);
|
|
if (!fetchedDM) {
|
|
throw new Error('[Test C] Failed to fetch DM event.');
|
|
}
|
|
|
|
// 5. Decrypt using dmDecryptor.js helper
|
|
console.log('[Test C] Attempting decryption...');
|
|
|
|
// We need to construct the context expected by decryptDM
|
|
// It expects { actorPubkey, decryptors }
|
|
// We can reuse client.buildDmDecryptContext, but let's do it manually to test the decryptor isolation if possible,
|
|
// or just use the client helper to ensure we are testing the full stack availability.
|
|
// Let's use `client.decryptDirectMessageEvent` which wraps `decryptDM`.
|
|
// But the prompt asked to "attempt decryption using the appropriate decryptor helper (see js/dmDecryptor.js / docs)".
|
|
// So I'll call decryptDM directly to verify strict compliance with that instruction.
|
|
|
|
const decryptContext = await client.buildDmDecryptContext(EPHEMERAL_PK);
|
|
const decryptionResult = await decryptDM(fetchedDM, decryptContext);
|
|
|
|
if (!decryptionResult.ok) {
|
|
console.error('[Test C] Decryption errors:', decryptionResult.errors);
|
|
throw new Error('[Test C] Decryption failed.');
|
|
}
|
|
|
|
if (decryptionResult.plaintext !== dmMessage) {
|
|
throw new Error(`[Test C] Decryption mismatch: expected "${dmMessage}", got "${decryptionResult.plaintext}"`);
|
|
}
|
|
|
|
console.log(`[Test C] Decrypted text: "${decryptionResult.plaintext}"`);
|
|
console.log('[Test C] SUCCESS: Encrypted DM roundtrip verified.');
|
|
|
|
|
|
} catch (err) {
|
|
console.error('\n[Error] Test failed:', err);
|
|
process.exit(1);
|
|
} finally {
|
|
console.log('\n[Teardown] Cleaning up...');
|
|
if (client && client.pool) {
|
|
// Close pool connections to allow exit
|
|
if (typeof client.pool.close === 'function') {
|
|
client.pool.close(client.relays);
|
|
}
|
|
}
|
|
if (relayServer) {
|
|
await relayServer.close();
|
|
console.log('[Teardown] Local relay stopped.');
|
|
}
|
|
console.log('[Teardown] Done.');
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
runInteropTests();
|