* 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
152 lines
4.6 KiB
TypeScript
152 lines
4.6 KiB
TypeScript
import * as cp from "child_process";
|
|
import * as net from "net";
|
|
import * as stream from "stream";
|
|
import { callbackify } from "util";
|
|
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
|
|
import { ChildProcessModuleProxy, ChildProcessProxy } from "../../node/modules/child_process";
|
|
import { ClientWritableProxy, ClientReadableProxy, Readable, Writable } from "./stream";
|
|
|
|
// tslint:disable completed-docs
|
|
|
|
export interface ClientChildProcessProxy extends ChildProcessProxy, ClientServerProxy<cp.ChildProcess> {}
|
|
|
|
export interface ClientChildProcessProxies {
|
|
childProcess: ClientChildProcessProxy;
|
|
stdin?: ClientWritableProxy | null;
|
|
stdout?: ClientReadableProxy | null;
|
|
stderr?: ClientReadableProxy | null;
|
|
}
|
|
|
|
export class ChildProcess extends ClientProxy<ClientChildProcessProxy> implements cp.ChildProcess {
|
|
public readonly stdin: stream.Writable;
|
|
public readonly stdout: stream.Readable;
|
|
public readonly stderr: stream.Readable;
|
|
public readonly stdio: [stream.Writable, stream.Readable, stream.Readable];
|
|
|
|
private _connected: boolean = false;
|
|
private _killed: boolean = false;
|
|
private _pid = -1;
|
|
|
|
public constructor(proxyPromises: Promise<ClientChildProcessProxies>) {
|
|
super(proxyPromises.then((p) => p.childProcess));
|
|
this.stdin = new Writable(proxyPromises.then((p) => p.stdin!));
|
|
this.stdout = new Readable(proxyPromises.then((p) => p.stdout!));
|
|
this.stderr = new Readable(proxyPromises.then((p) => p.stderr!));
|
|
this.stdio = [this.stdin, this.stdout, this.stderr];
|
|
|
|
this.catch(this.proxy.getPid().then((pid) => {
|
|
this._pid = pid;
|
|
this._connected = true;
|
|
}));
|
|
this.on("disconnect", () => this._connected = false);
|
|
this.on("exit", () => {
|
|
this._connected = false;
|
|
this._killed = true;
|
|
});
|
|
}
|
|
|
|
public get pid(): number {
|
|
return this._pid;
|
|
}
|
|
|
|
public get connected(): boolean {
|
|
return this._connected;
|
|
}
|
|
|
|
public get killed(): boolean {
|
|
return this._killed;
|
|
}
|
|
|
|
public kill(): void {
|
|
this._killed = true;
|
|
this.catch(this.proxy.kill());
|
|
}
|
|
|
|
public disconnect(): void {
|
|
this.catch(this.proxy.disconnect());
|
|
}
|
|
|
|
public ref(): void {
|
|
this.catch(this.proxy.ref());
|
|
}
|
|
|
|
public unref(): void {
|
|
this.catch(this.proxy.unref());
|
|
}
|
|
|
|
public send(
|
|
message: any, // tslint:disable-line no-any
|
|
sendHandle?: net.Socket | net.Server | ((error: Error) => void),
|
|
options?: cp.MessageOptions | ((error: Error) => void),
|
|
callback?: (error: Error) => void): boolean {
|
|
if (typeof sendHandle === "function") {
|
|
callback = sendHandle;
|
|
sendHandle = undefined;
|
|
} else if (typeof options === "function") {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
if (sendHandle || options) {
|
|
throw new Error("sendHandle and options are not supported");
|
|
}
|
|
|
|
callbackify(this.proxy.send)(message, (error) => {
|
|
if (callback) {
|
|
callback(error);
|
|
}
|
|
});
|
|
|
|
return true; // Always true since we can't get this synchronously.
|
|
}
|
|
|
|
/**
|
|
* Exit and close the process when disconnected.
|
|
*/
|
|
protected handleDisconnect(): void {
|
|
this.emit("exit", 1);
|
|
this.emit("close");
|
|
}
|
|
}
|
|
|
|
interface ClientChildProcessModuleProxy extends ChildProcessModuleProxy, ClientServerProxy {
|
|
exec(command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void)): Promise<ClientChildProcessProxies>;
|
|
fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ClientChildProcessProxies>;
|
|
spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ClientChildProcessProxies>;
|
|
}
|
|
|
|
export class ChildProcessModule {
|
|
public constructor(private readonly proxy: ClientChildProcessModuleProxy) {}
|
|
|
|
public exec = (
|
|
command: string,
|
|
options?: { encoding?: string | null } & cp.ExecOptions | null
|
|
| ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
|
|
callback?: ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
|
|
): cp.ChildProcess => {
|
|
if (typeof options === "function") {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
|
|
return new ChildProcess(this.proxy.exec(command, options, callback));
|
|
}
|
|
|
|
public fork = (modulePath: string, args?: string[] | cp.ForkOptions, options?: cp.ForkOptions): cp.ChildProcess => {
|
|
if (!Array.isArray(args)) {
|
|
options = args;
|
|
args = undefined;
|
|
}
|
|
|
|
return new ChildProcess(this.proxy.fork(modulePath, args, options));
|
|
}
|
|
|
|
public spawn = (command: string, args?: string[] | cp.SpawnOptions, options?: cp.SpawnOptions): cp.ChildProcess => {
|
|
if (!Array.isArray(args)) {
|
|
options = args;
|
|
args = undefined;
|
|
}
|
|
|
|
return new ChildProcess(this.proxy.spawn(command, args, options));
|
|
}
|
|
}
|