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
JavaScript
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