File size: 1,501 Bytes
f500658
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { spawn } from "node:child_process";

/** Undefined means the command failed; an empty buffer is a successful result. */
export function runClipboardCommand(
	command: string,
	args: readonly string[],
	options?: { input?: string; timeoutMs?: number; maxBufferBytes?: number },
): Promise<Buffer | undefined> {
	return new Promise((resolve) => {
		// Clipboard writers can daemonize. Do not give them output pipes to retain.
		const child = spawn(command, args, {
			stdio: ["pipe", options?.input === undefined ? "pipe" : "ignore", "ignore"],
			windowsHide: true,
		});
		const chunks: Buffer[] = [];
		let length = 0;
		let settled = false;
		const finish = (result: Buffer | undefined): void => {
			if (settled) return;
			settled = true;
			clearTimeout(timer);
			resolve(result);
		};
		const abort = (): void => {
			child.kill("SIGKILL");
			child.stdout?.destroy();
			child.stdin?.destroy();
			finish(undefined);
		};
		const timer = setTimeout(abort, options?.timeoutMs ?? 3000);
		child.on("error", () => finish(undefined));
		child.on("close", (code) => {
			if (!settled) finish(code === 0 ? Buffer.concat(chunks, length) : undefined);
		});
		child.stdout?.on("data", (chunk: Buffer) => {
			if (settled) return;
			length += chunk.length;
			if (length > (options?.maxBufferBytes ?? 50 * 1024 * 1024)) abort();
			else chunks.push(chunk);
		});
		child.stdin?.on("error", () => {}); // A writer may exit before consuming all input.
		child.stdin?.end(options?.input);
	});
}