import Router from "@koa/router"; import { hasShape, predicates } from "@sealcode/ts-predicates"; import { spawn } from "child_process"; import Koa from "koa"; import mount from "koa-mount"; import serve from "koa-static"; import qs from "qs"; import { Readable } from "stream"; import { v4 as uuid } from "uuid"; import { DOCKER_ARGS, IMAGE_NAME, VOLUME_MOUNT } from "./docker-args"; import { requests } from "./memory"; import ScreenshotRequest from "./request"; const router = new Router(); // response const app = new Koa(); const Static = new Koa(); Static.use(serve("./static")); app.use(mount("/static", Static)); function attach(docker_id: string, output_stream: Readable) { // to prevent browser timeout const interval = setInterval(() => output_stream.push(""), 500); const task = spawn("docker", ["logs", "-f", docker_id]); task.stdout.on("data", (d: Buffer) => { output_stream.push(d); console.log("DATA!", d.toString()); }); task.stderr.on("data", (d: Buffer) => { /* output_stream.push(d); */ console.log("STDERR!", d.toString()); }); task.stdout.on("error", (d) => { output_stream.push(d); }); task.on("close", () => { output_stream.push(""); output_stream.push(/* HTML */ ``); clearInterval(interval); output_stream.push(null); }); } router.get("/", async (ctx) => { ctx.body = /* HTML */ `
`;
});
router.get("/preview", async (ctx) => {
const response = new Readable({
read() {
/*noop */
},
});
// stream data
ctx.response.set("content-type", "txt/html");
ctx.type = "html"; // <-- THIS is the important step!
ctx.body = response;
response.push("");
const id = uuid();
response.push(
`
`);
let docker_id = "";
if (!params.url) {
ctx.body = "specify url!";
return;
}
const starter = spawn(
"docker",
[
...DOCKER_ARGS,
`{"url": "${params.url}", "third_party_domains": ["hotjar.com", "cookielaw.org", "facebook.com", "gemius.pl"]}`,
id,
],
{ cwd: process.cwd() }
);
starter.stdout.on("data", (data: Buffer) => {
docker_id += data.toString().replace(/\n/g, "");
});
starter.on("close", () => {
response.push("spawned " + docker_id);
attach(docker_id, ctx.body);
});
});
router.post("/api/requests", async (ctx) => {
const params = qs.parse(ctx.querystring);
if (!hasShape({ url: predicates.string }, params)) {
ctx.body = "Specify url";
ctx.status = 422;
return;
}
if (!hasShape({ domains: predicates.array(predicates.string) }, params)) {
ctx.body = "Specify domains as an array of strings";
ctx.status = 422;
return;
}
if (!Array.isArray(params.domains)) {
ctx.body = "'domains' should be an array of strings";
ctx.status = 422;
return;
}
const request = new ScreenshotRequest(params.url, params.domains);
ctx.status = 303;
ctx.redirect(`/api/requests/${request.id}`);
});
router.get("/api/requests/:id", async (ctx) => {
const request = requests[ctx.params.id];
if (!request) {
ctx.status = 404;
return;
}
ctx.body = await request.getJSON();
});
router.get("/api/requests/:id/all-screenshots", async (ctx) => {
const request = requests[ctx.params.id];
if (!request || request.status != "finished") {
ctx.status = 404;
return;
}
ctx.attachment(`${request.url.replace(/\W/g, "_")}_all-screenshots.zip`);
ctx.response.set("content-type", "application/zip");
const process = spawn("docker", [
"run",
"-v",
VOLUME_MOUNT,
IMAGE_NAME,
"zip",
"--recurse-paths",
"--junk-paths",
"-",
`/opt/static/${request.id}`,
]);
ctx.body = process.stdout;
});
app.use(router.routes()).use(router.allowedMethods());
const port = 3000;
app.listen(port);
console.log(`server started on port ${port}`);