UNPKG

askexperts

Version:

AskExperts SDK: build and use AI experts - ask them questions and pay with bitcoin on an open protocol

133 lines 5.76 kB
import { SimplePool, getPublicKey } from "nostr-tools"; import { StreamReader } from "../../../stream/StreamReader.js"; import { debugError, enableAllDebug, } from "../../../common/debug.js"; import { parseStreamMetadataEvent } from "../../../stream/metadata.js"; import { hexToBytes } from "nostr-tools/utils"; export async function executeStreamReceiveCommand(options) { try { let metadataJson; // Get metadata from options or stdin if (options.metadata) { metadataJson = options.metadata; } else { // Read metadata from stdin metadataJson = await new Promise((resolve, reject) => { let data = ""; process.stdin.on("data", (chunk) => { data += chunk; }); process.stdin.on("end", () => { resolve(data); }); process.stdin.on("error", (err) => { reject(err); }); }); } // Parse metadata event let metadataEvent; try { metadataEvent = JSON.parse(metadataJson); } catch (err) { throw new Error(`Invalid metadata event JSON: ${err instanceof Error ? err.message : String(err)}`); } // Parse and validate the metadata event const metadata = parseStreamMetadataEvent(metadataEvent); // If encryption is used and receiver_privkey is provided via command line, use it if (metadata.encryption !== "none" && options.receiverPrivkey) { metadata.receiver_privkey = hexToBytes(options.receiverPrivkey); // Validate that the provided private key matches the public key in metadata if (metadata.receiver_pubkey) { const derivedPubkey = getPublicKey(metadata.receiver_privkey); if (derivedPubkey !== metadata.receiver_pubkey) { throw new Error("Provided receiver private key does not match the public key in metadata"); } } } // Create a SimplePool for relay communication const pool = new SimplePool(); // Parse reader options const ttl = options.ttl ? parseInt(options.ttl, 10) : undefined; const maxChunks = options.maxChunks ? parseInt(options.maxChunks, 10) : undefined; const maxResultSize = options.maxSize ? parseInt(options.maxSize, 10) : undefined; // Create reader config const readerConfig = { ttl, maxChunks, maxResultSize, }; // Create stream reader const reader = new StreamReader(metadata, pool, readerConfig); console.error(`[${new Date().toISOString()}] Stream receive started`); let startTime = 0; let totalBytes = 0; let chunkCount = 0; // Read data from stream and write to stdout try { for await (const chunk of reader) { if (!startTime) startTime = Date.now(); totalBytes += chunk.length; // Log chunk info to stderr console.error(`[${new Date().toISOString()}] Received chunk ${chunkCount}, size: ${chunk.length} bytes`); chunkCount++; // Write chunk to stdout process.stdout.write(chunk); } const endTime = Date.now(); const duration = endTime - startTime; console.error(`[${new Date().toISOString()}] Stream completed`); console.error(`Total chunks: ${chunkCount}`); console.error(`Total bytes: ${totalBytes}`); console.error(`Total time: ${duration}ms`); // Clean up resources pool.destroy(); process.exit(0); } catch (err) { const endTime = Date.now(); const duration = endTime - startTime; console.error(`[${new Date().toISOString()}] Stream error: ${err instanceof Error ? err.message : String(err)}`); console.error(`Partial chunks: ${chunkCount}`); console.error(`Partial bytes: ${totalBytes}`); console.error(`Time until error: ${duration}ms`); // Clean up resources pool.destroy(); process.exit(1); } } catch (err) { debugError("Failed to execute stream receive command:", err); console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); process.exit(1); } } /** * Register the receive command * * @param streamCommand The parent stream command * @param addCommonOptions Function to add common options to command */ export function registerReceiveCommand(streamCommand, addCommonOptions) { const receiveCommand = streamCommand .command("receive") .description("Receive stream data and output to stdout") .option("-m, --metadata <json>", "Stream metadata JSON (if not provided, read from stdin)") .option("--ttl <ms>", "Time-to-live in milliseconds for waiting for the next chunk", "60000") .option("--max-chunks <number>", "Maximum number of chunks to process", "1000") .option("--max-size <bytes>", "Maximum total size of all chunks in bytes", "10485760") .option("-k, --receiver-privkey <key>", "Receiver private key for decryption (required for encrypted streams, provided out-of-band)") .action((options) => { if (options.debug) enableAllDebug(); executeStreamReceiveCommand(options); }); addCommonOptions(receiveCommand); } //# sourceMappingURL=receive.js.map