* Implement write/read buffers in electron fill This makes cutting and copy files from the file tree work. * Implement fs.createReadStream This is used by the file tree to copy files. * Allow passing proxies back from client to server This makes things like piping streams possible. * Synchronously bind to proxy events This eliminates any chance whatsoever of missing events due to binding too late. * Make it possible to bind some events on demand * Add some protocol documentation
102 lines
2.8 KiB
TypeScript
102 lines
2.8 KiB
TypeScript
import * as cp from "child_process";
|
|
import { ServerProxy } from "../../common/proxy";
|
|
import { preserveEnv } from "../../common/util";
|
|
import { WritableProxy, ReadableProxy } from "./stream";
|
|
|
|
// tslint:disable completed-docs
|
|
|
|
export type ForkProvider = (modulePath: string, args?: string[], options?: cp.ForkOptions) => cp.ChildProcess;
|
|
|
|
export class ChildProcessProxy extends ServerProxy<cp.ChildProcess> {
|
|
public constructor(instance: cp.ChildProcess) {
|
|
super({
|
|
bindEvents: ["close", "disconnect", "error", "exit", "message"],
|
|
doneEvents: ["close"],
|
|
instance,
|
|
});
|
|
}
|
|
|
|
public async kill(signal?: string): Promise<void> {
|
|
this.instance.kill(signal);
|
|
}
|
|
|
|
public async disconnect(): Promise<void> {
|
|
this.instance.disconnect();
|
|
}
|
|
|
|
public async ref(): Promise<void> {
|
|
this.instance.ref();
|
|
}
|
|
|
|
public async unref(): Promise<void> {
|
|
this.instance.unref();
|
|
}
|
|
|
|
// tslint:disable-next-line no-any
|
|
public async send(message: any): Promise<void> {
|
|
return new Promise((resolve, reject): void => {
|
|
this.instance.send(message, (error) => {
|
|
if (error) {
|
|
reject(error);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
public async getPid(): Promise<number> {
|
|
return this.instance.pid;
|
|
}
|
|
|
|
public async dispose(): Promise<void> {
|
|
this.instance.kill();
|
|
setTimeout(() => this.instance.kill("SIGKILL"), 5000); // Double tap.
|
|
await super.dispose();
|
|
}
|
|
}
|
|
|
|
export interface ChildProcessProxies {
|
|
childProcess: ChildProcessProxy;
|
|
stdin?: WritableProxy | null;
|
|
stdout?: ReadableProxy | null;
|
|
stderr?: ReadableProxy | null;
|
|
}
|
|
|
|
export class ChildProcessModuleProxy {
|
|
public constructor(private readonly forkProvider?: ForkProvider) {}
|
|
|
|
public async exec(
|
|
command: string,
|
|
options?: { encoding?: string | null } & cp.ExecOptions | null,
|
|
callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void),
|
|
): Promise<ChildProcessProxies> {
|
|
preserveEnv(options);
|
|
|
|
return this.returnProxies(cp.exec(command, options, callback));
|
|
}
|
|
|
|
public async fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ChildProcessProxies> {
|
|
preserveEnv(options);
|
|
|
|
return this.returnProxies((this.forkProvider || cp.fork)(modulePath, args, options));
|
|
}
|
|
|
|
public async spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ChildProcessProxies> {
|
|
preserveEnv(options);
|
|
|
|
return this.returnProxies(cp.spawn(command, args, options));
|
|
}
|
|
|
|
private returnProxies(process: cp.ChildProcess): ChildProcessProxies {
|
|
return {
|
|
childProcess: new ChildProcessProxy(process),
|
|
stdin: process.stdin && new WritableProxy(process.stdin),
|
|
// Child processes streams appear to immediately flow so we need to bind
|
|
// to the data event right away.
|
|
stdout: process.stdout && new ReadableProxy(process.stdout, ["data"]),
|
|
stderr: process.stderr && new ReadableProxy(process.stderr, ["data"]),
|
|
};
|
|
}
|
|
}
|