import { is, predicates } from "@sealcode/ts-predicates"; import { ChildProcessWithoutNullStreams, spawn } from "child_process"; import { v4 as uuid } from "uuid"; import containerPool from "./container-pool"; import { q, requests } from "./memory"; let queue_order: ScreenshotRequest[] = []; export default class ScreenshotRequest { public id = uuid(); public status = "waiting"; public output = ""; public images: Record[] = []; public request_time: number = Date.now(); public started_time: number | null = null; public finished_time: number | null = null; public processing_took: number | null = null; public waiting_took: number | null = null; public process: ChildProcessWithoutNullStreams; constructor(public url: string, public domains: string[]) { q.push(async () => { return this.exec(); }); requests[this.id] = this; queue_order.push(this); } getJobsAhead(): number { if (this.status != "waiting") { return 0; } let count = 0; for (const request of queue_order) { if (request == this) { break; } count++; } return count; } async getJSON(): Promise<{ url: string; domains: string[]; jobs_ahead: number; id: string; status: string; output: string; images: Record[]; request_time: number; started_time: number | null; finished_time: number | null; processing_took: number | null; waiting_took: number | null; elapsed_time_s: number; }> { return { url: this.url, domains: this.domains, jobs_ahead: this.getJobsAhead(), id: this.id, status: this.status, output: this.output, images: this.images, request_time: this.request_time, started_time: this.started_time, finished_time: this.finished_time, processing_took: this.processing_took, waiting_took: this.waiting_took, elapsed_time_s: Math.round( ((this.status === "finished" ? this.finished_time || -1 : Date.now()) - this.request_time) / 1000 ), }; } setFinished(): void { this.status = "finished"; this.finished_time = Date.now(); if (this.started_time) { this.processing_took = this.finished_time - this.started_time; this.waiting_took = this.started_time - this.request_time; } } async exec(): Promise { this.started_time = Date.now(); this.status = "running"; const container = containerPool.getContainer(); await container.waitReady(); return new Promise((resolve, reject) => { this.process = spawn( "docker", [ "exec", container.id, "/opt/run-analysis.sh", JSON.stringify({ url: this.url, third_party_domains: this.domains, }), this.id, ], { cwd: process.cwd() } ); this.process.on("close", (exitCode) => { this.setFinished(); container.close(); queue_order = queue_order.filter((request) => request != this); if (exitCode === 0) { resolve(); } else { reject(); } }); this.process.stdout.on("data", (d: Buffer) => { try { const parsed = JSON.parse(d.toString()) as unknown; if ( is(parsed, predicates.object) && is(parsed.new_file, predicates.object) ) { this.images.push(parsed.new_file); } } catch (e) { //noop } this.output += d.toString(); /* console.log("DATA!", d.toString()); */ }); this.process.stderr.on("data", (d: Buffer) => { this.output += d.toString(); /* console.log("STDERR!", d.toString()); */ }); }); } }