Make everything use active evals (#30)
* Add trace log level * Use active eval to implement spdlog * Split server/client active eval interfaces Since all properties are *not* valid on both sides * +200% fire resistance * Implement exec using active evaluations * Fully implement child process streams * Watch impl, move child_process back to explicitly adding events Automatically forwarding all events might be the right move, but wanna think/discuss it a bit more because it didn't come out very cleanly. * Would you like some args with that callback? * Implement the rest of child_process using active evals * Rampant memory leaks Emit "kill" to active evaluations when client disconnects in order to kill processes. Most likely won't be the final solution. * Resolve some minor issues with output panel * Implement node-pty with active evals * Provide clearTimeout to vm sandbox * Implement socket with active evals * Extract some callback logic Also remove some eval interfaces, need to re-think those. * Implement net.Server and remainder of net.Socket using active evals * Implement dispose for active evaluations * Use trace for express requests * Handle sending buffers through evaluation events * Make event logging a bit more clear * Fix some errors due to us not actually instantiating until connect/listen * is this a commit message? * We can just create the evaluator in the ctor Not sure what I was thinking. * memory leak for you, memory leak for everyone * it's a ternary now * Don't dispose automatically on close or error The code may or may not be disposable at that point. * Handle parsing buffers on the client side as well * Remove unused protobuf * Remove TypedValue * Remove unused forkProvider and test * Improve dispose pattern for active evals * Socket calls close after error; no need to bind both * Improve comment * Comment is no longer wishy washy due to explicit boolean * Simplify check for sendHandle and options * Replace _require with __non_webpack_require__ Webpack will then replace this with `require` which we then provide to the vm sandbox. * Provide path.parse * Prevent original-fs from loading * Start with a pid of -1 vscode immediately checks the PID to see if the debug process launch correctly, but of course we don't get the pid synchronously. * Pass arguments to bootstrap-fork * Fully implement streams Was causing errors because internally the stream would set this.writing to true and it would never become false, so subsequent messages would never send. * Fix serializing errors and streams emitting errors multiple times * Was emitting close to data * Fix missing path for spawned processes * Move evaluation onDispose call Now it's accurate and runs when the active evaluation has actually disposed. * Fix promisifying fs.exists * Fix some active eval callback issues * Patch existsSync in debug adapter
This commit is contained in:
parent
73762017c8
commit
4a80bcb42c
@ -28,6 +28,7 @@
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"path-browserify": "^1.0.0",
|
||||
"preload-webpack-plugin": "^3.0.0-beta.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"string-replace-loader": "^2.1.1",
|
||||
@ -48,6 +49,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"node-loader": "^0.6.0",
|
||||
"spdlog": "^0.7.2",
|
||||
"trash": "^4.3.0",
|
||||
"webpack-merge": "^4.2.1"
|
||||
}
|
||||
|
@ -1,85 +1,194 @@
|
||||
import * as cp from "child_process";
|
||||
import { Client, useBuffer } from "@coder/protocol";
|
||||
import * as net from "net";
|
||||
import * as stream from "stream";
|
||||
import { CallbackEmitter, ActiveEvalReadable, ActiveEvalWritable, createUniqueEval } from "./evaluation";
|
||||
import { client } from "./client";
|
||||
import { promisify } from "./util";
|
||||
import { promisify } from "util";
|
||||
|
||||
declare var __non_webpack_require__: typeof require;
|
||||
|
||||
class ChildProcess extends CallbackEmitter implements cp.ChildProcess {
|
||||
private _connected: boolean = false;
|
||||
private _killed: boolean = false;
|
||||
private _pid = -1;
|
||||
public readonly stdin: stream.Writable;
|
||||
public readonly stdout: stream.Readable;
|
||||
public readonly stderr: stream.Readable;
|
||||
// We need the explicit type otherwise TypeScript thinks it is (Writable | Readable)[].
|
||||
public readonly stdio: [stream.Writable, stream.Readable, stream.Readable] = [this.stdin, this.stdout, this.stderr];
|
||||
|
||||
// tslint:disable no-any
|
||||
public constructor(method: "exec", command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: (...args: any[]) => void);
|
||||
public constructor(method: "fork", modulePath: string, options?: cp.ForkOptions, args?: string[]);
|
||||
public constructor(method: "spawn", command: string, options?: cp.SpawnOptions, args?: string[]);
|
||||
public constructor(method: "exec" | "spawn" | "fork", command: string, options: object = {}, callback?: string[] | ((...args: any[]) => void)) {
|
||||
// tslint:enable no-any
|
||||
super();
|
||||
|
||||
let args: string[] = [];
|
||||
if (Array.isArray(callback)) {
|
||||
args = callback;
|
||||
callback = undefined;
|
||||
}
|
||||
|
||||
this.ae = client.run((ae, command, method, args, options, callbackId) => {
|
||||
const cp = __non_webpack_require__("child_process") as typeof import("child_process");
|
||||
const { maybeCallback, createUniqueEval, bindWritable, bindReadable, preserveEnv } = __non_webpack_require__("@coder/ide/src/fill/evaluation") as typeof import("@coder/ide/src/fill/evaluation");
|
||||
|
||||
preserveEnv(options);
|
||||
|
||||
let childProcess: cp.ChildProcess;
|
||||
switch (method) {
|
||||
case "exec":
|
||||
childProcess = cp.exec(command, options, maybeCallback(ae, callbackId));
|
||||
break;
|
||||
case "spawn":
|
||||
childProcess = cp.spawn(command, args, options);
|
||||
break;
|
||||
case "fork":
|
||||
const forkOptions = options as cp.ForkOptions;
|
||||
if (forkOptions && forkOptions.env && forkOptions.env.AMD_ENTRYPOINT) {
|
||||
// TODO: This is vscode-specific and should be abstracted.
|
||||
const { forkModule } = __non_webpack_require__("@coder/server/src/vscode/bootstrapFork") as typeof import ("@coder/server/src/vscode/bootstrapFork");
|
||||
childProcess = forkModule(forkOptions.env.AMD_ENTRYPOINT, args, forkOptions);
|
||||
} else {
|
||||
childProcess = cp.fork(command, args, options);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`invalid method ${method}`);
|
||||
}
|
||||
|
||||
ae.on("disconnect", () => childProcess.disconnect());
|
||||
ae.on("kill", (signal) => childProcess.kill(signal));
|
||||
ae.on("ref", () => childProcess.ref());
|
||||
ae.on("send", (message, callbackId) => childProcess.send(message, maybeCallback(ae, callbackId)));
|
||||
ae.on("unref", () => childProcess.unref());
|
||||
|
||||
ae.emit("pid", childProcess.pid);
|
||||
childProcess.on("close", (code, signal) => ae.emit("close", code, signal));
|
||||
childProcess.on("disconnect", () => ae.emit("disconnect"));
|
||||
childProcess.on("error", (error) => ae.emit("error", error));
|
||||
childProcess.on("exit", (code, signal) => ae.emit("exit", code, signal));
|
||||
childProcess.on("message", (message) => ae.emit("message", message));
|
||||
|
||||
bindWritable(createUniqueEval(ae, "stdin"), childProcess.stdin);
|
||||
bindReadable(createUniqueEval(ae, "stdout"), childProcess.stdout);
|
||||
bindReadable(createUniqueEval(ae, "stderr"), childProcess.stderr);
|
||||
|
||||
return {
|
||||
onDidDispose: (cb): cp.ChildProcess => childProcess.on("close", cb),
|
||||
dispose: (): void => {
|
||||
childProcess.kill();
|
||||
setTimeout(() => childProcess.kill("SIGKILL"), 5000); // Double tap.
|
||||
},
|
||||
};
|
||||
}, command, method, args, options, this.storeCallback(callback));
|
||||
|
||||
this.ae.on("pid", (pid) => {
|
||||
this._pid = pid;
|
||||
this._connected = true;
|
||||
});
|
||||
|
||||
this.stdin = new ActiveEvalWritable(createUniqueEval(this.ae, "stdin"));
|
||||
this.stdout = new ActiveEvalReadable(createUniqueEval(this.ae, "stdout"));
|
||||
this.stderr = new ActiveEvalReadable(createUniqueEval(this.ae, "stderr"));
|
||||
|
||||
this.ae.on("close", (code, signal) => this.emit("close", code, signal));
|
||||
this.ae.on("disconnect", () => this.emit("disconnect"));
|
||||
this.ae.on("error", (error) => this.emit("error", error));
|
||||
this.ae.on("exit", (code, signal) => {
|
||||
this._connected = false;
|
||||
this._killed = true;
|
||||
this.emit("exit", code, signal);
|
||||
});
|
||||
this.ae.on("message", (message) => this.emit("message", message));
|
||||
}
|
||||
|
||||
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.ae.emit("kill"); }
|
||||
public disconnect(): void { this.ae.emit("disconnect"); }
|
||||
public ref(): void { this.ae.emit("ref"); }
|
||||
public unref(): void { this.ae.emit("unref"); }
|
||||
|
||||
public send(
|
||||
message: any, // tslint:disable-line no-any to match spec
|
||||
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");
|
||||
}
|
||||
this.ae.emit("send", message, this.storeCallback(callback));
|
||||
|
||||
// Unfortunately this will always have to be true since we can't retrieve
|
||||
// the actual response synchronously.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class CP {
|
||||
public constructor(
|
||||
private readonly client: Client,
|
||||
) { }
|
||||
public readonly ChildProcess = ChildProcess;
|
||||
|
||||
public exec = (
|
||||
command: string,
|
||||
options?: { encoding?: BufferEncoding | string | "buffer" | null } & cp.ExecOptions | null | ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
callback?: ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
options?: { encoding?: string | null } & cp.ExecOptions | null | ((error: cp.ExecException | null, stdout: string, stderr: string) => void) | ((error: cp.ExecException | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
callback?: ((error: cp.ExecException | null, stdout: string, stderr: string) => void) | ((error: cp.ExecException | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
): cp.ChildProcess => {
|
||||
// TODO: Probably should add an `exec` instead of using `spawn`, especially
|
||||
// since bash might not be available.
|
||||
const childProcess = this.client.spawn("bash", ["-c", command.replace(/"/g, "\\\"")]);
|
||||
|
||||
let stdout = "";
|
||||
childProcess.stdout.on("data", (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
let stderr = "";
|
||||
childProcess.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
childProcess.on("exit", (exitCode) => {
|
||||
const error = exitCode !== 0 ? new Error(stderr) : null;
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
options = undefined;
|
||||
}
|
||||
if (callback) {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
callback(
|
||||
error,
|
||||
useBuffer(options) ? Buffer.from(stdout) : stdout,
|
||||
useBuffer(options) ? Buffer.from(stderr) : stderr,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore TODO: not fully implemented
|
||||
return childProcess;
|
||||
return new ChildProcess("exec", command, options, callback);
|
||||
}
|
||||
|
||||
public fork = (modulePath: string, args?: string[] | cp.ForkOptions, options?: cp.ForkOptions): cp.ChildProcess => {
|
||||
if (options && options.env && options.env.AMD_ENTRYPOINT) {
|
||||
// @ts-ignore TODO: not fully implemented
|
||||
return this.client.bootstrapFork(
|
||||
options.env.AMD_ENTRYPOINT,
|
||||
Array.isArray(args) ? args : [],
|
||||
// @ts-ignore TODO: env is a different type
|
||||
Array.isArray(args) || !args ? options : args,
|
||||
);
|
||||
if (args && !Array.isArray(args)) {
|
||||
options = args;
|
||||
args = undefined;
|
||||
}
|
||||
|
||||
// @ts-ignore TODO: not fully implemented
|
||||
return this.client.fork(
|
||||
modulePath,
|
||||
Array.isArray(args) ? args : [],
|
||||
// @ts-ignore TODO: env is a different type
|
||||
Array.isArray(args) || !args ? options : args,
|
||||
);
|
||||
return new ChildProcess("fork", modulePath, options, args);
|
||||
}
|
||||
|
||||
public spawn = (command: string, args?: string[] | cp.SpawnOptions, options?: cp.SpawnOptions): cp.ChildProcess => {
|
||||
// @ts-ignore TODO: not fully implemented
|
||||
return this.client.spawn(
|
||||
command,
|
||||
Array.isArray(args) ? args : [],
|
||||
// @ts-ignore TODO: env is a different type
|
||||
Array.isArray(args) || !args ? options : args,
|
||||
);
|
||||
if (args && !Array.isArray(args)) {
|
||||
options = args;
|
||||
args = undefined;
|
||||
}
|
||||
|
||||
return new ChildProcess("spawn", command, options, args);
|
||||
}
|
||||
}
|
||||
|
||||
const fillCp = new CP(client);
|
||||
|
||||
// tslint:disable-next-line no-any makes util.promisify return an object
|
||||
(fillCp as any).exec[promisify.customPromisifyArgs] = ["stdout", "stderr"];
|
||||
|
||||
const fillCp = new CP();
|
||||
// Methods that don't follow the standard callback pattern (an error followed
|
||||
// by a single result) need to provide a custom promisify function.
|
||||
Object.defineProperty(fillCp.exec, promisify.custom, {
|
||||
value: (
|
||||
command: string,
|
||||
options?: { encoding?: string | null } & cp.ExecOptions | null,
|
||||
): Promise<{ stdout: string | Buffer, stderr: string | Buffer }> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fillCp.exec(command, options, (error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve({ stdout, stderr });
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
export = fillCp;
|
||||
|
@ -6,8 +6,7 @@ import { IKey, Dialog as DialogBox } from "./dialog";
|
||||
import { clipboard } from "./clipboard";
|
||||
import { client } from "./client";
|
||||
|
||||
// Use this to get around Webpack inserting our fills.
|
||||
declare var _require: typeof require;
|
||||
declare var __non_webpack_require__: typeof require;
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
(global as any).getOpenUrls = (): string[] => {
|
||||
@ -99,7 +98,7 @@ class Clipboard {
|
||||
class Shell {
|
||||
public async moveItemToTrash(path: string): Promise<void> {
|
||||
await client.evaluate((path) => {
|
||||
const trash = _require("trash") as typeof import("trash");
|
||||
const trash = __non_webpack_require__("trash") as typeof import("trash");
|
||||
|
||||
return trash(path);
|
||||
}, path);
|
||||
|
338
packages/ide/src/fill/evaluation.ts
Normal file
338
packages/ide/src/fill/evaluation.ts
Normal file
@ -0,0 +1,338 @@
|
||||
import { SpawnOptions, ForkOptions } from "child_process";
|
||||
import { EventEmitter } from "events";
|
||||
import { Socket } from "net";
|
||||
import { Duplex, Readable, Writable } from "stream";
|
||||
import { logger } from "@coder/logger";
|
||||
import { ActiveEval, Disposer } from "@coder/protocol";
|
||||
|
||||
// tslint:disable no-any
|
||||
/**
|
||||
* If there is a callback ID, return a function that emits the callback event on
|
||||
* the active evaluation with that ID and all arguments passed to it. Otherwise,
|
||||
* return undefined.
|
||||
*/
|
||||
export const maybeCallback = (ae: ActiveEval, callbackId?: number): ((...args: any[]) => void) | undefined => {
|
||||
return typeof callbackId !== "undefined" ? (...args: any[]): void => {
|
||||
ae.emit("callback", callbackId, ...args);
|
||||
} : undefined;
|
||||
};
|
||||
|
||||
// Some spawn code tries to preserve the env (the debug adapter for
|
||||
// instance) but the env is mostly blank (since we're in the browser), so
|
||||
// we'll just always preserve the main process.env here, otherwise it
|
||||
// won't have access to PATH, etc.
|
||||
// TODO: An alternative solution would be to send the env to the browser?
|
||||
export const preserveEnv = (options: SpawnOptions | ForkOptions): void => {
|
||||
if (options && options.env) {
|
||||
options.env = { ...process.env, ...options.env };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind a socket to an active evaluation.
|
||||
*/
|
||||
export const bindSocket = (ae: ActiveEval, socket: Socket): Disposer => {
|
||||
socket.on("connect", () => ae.emit("connect"));
|
||||
socket.on("lookup", (error, address, family, host) => ae.emit("lookup", error, address, family, host));
|
||||
socket.on("timeout", () => ae.emit("timeout"));
|
||||
|
||||
ae.on("connect", (options, callbackId) => socket.connect(options, maybeCallback(ae, callbackId)));
|
||||
ae.on("ref", () => socket.ref());
|
||||
ae.on("setKeepAlive", (enable, initialDelay) => socket.setKeepAlive(enable, initialDelay));
|
||||
ae.on("setNoDelay", (noDelay) => socket.setNoDelay(noDelay));
|
||||
ae.on("setTimeout", (timeout, callbackId) => socket.setTimeout(timeout, maybeCallback(ae, callbackId)));
|
||||
ae.on("unref", () => socket.unref());
|
||||
|
||||
bindReadable(ae, socket);
|
||||
bindWritable(ae, socket);
|
||||
|
||||
return {
|
||||
onDidDispose: (cb): Socket => socket.on("close", cb),
|
||||
dispose: (): void => {
|
||||
socket.removeAllListeners();
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
socket.unref();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind a writable stream to an active evaluation.
|
||||
*/
|
||||
export const bindWritable = (ae: ActiveEval, writable: Writable | Duplex): void => {
|
||||
if (!((writable as Readable).read)) { // To avoid binding twice.
|
||||
writable.on("close", () => ae.emit("close"));
|
||||
writable.on("error", (error) => ae.emit("error", error));
|
||||
|
||||
ae.on("destroy", () => writable.destroy());
|
||||
}
|
||||
|
||||
writable.on("drain", () => ae.emit("drain"));
|
||||
writable.on("finish", () => ae.emit("finish"));
|
||||
writable.on("pipe", () => ae.emit("pipe"));
|
||||
writable.on("unpipe", () => ae.emit("unpipe"));
|
||||
|
||||
ae.on("cork", () => writable.cork());
|
||||
ae.on("end", (chunk, encoding, callbackId) => writable.end(chunk, encoding, maybeCallback(ae, callbackId)));
|
||||
ae.on("setDefaultEncoding", (encoding) => writable.setDefaultEncoding(encoding));
|
||||
ae.on("uncork", () => writable.uncork());
|
||||
// Sockets can pass an fd instead of a callback but streams cannot.
|
||||
ae.on("write", (chunk, encoding, fd, callbackId) => writable.write(chunk, encoding, maybeCallback(ae, callbackId) || fd));
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind a readable stream to an active evaluation.
|
||||
*/
|
||||
export const bindReadable = (ae: ActiveEval, readable: Readable): void => {
|
||||
// Streams don't have an argument on close but sockets do.
|
||||
readable.on("close", (...args: any[]) => ae.emit("close", ...args));
|
||||
readable.on("data", (data) => ae.emit("data", data));
|
||||
readable.on("end", () => ae.emit("end"));
|
||||
readable.on("error", (error) => ae.emit("error", error));
|
||||
readable.on("readable", () => ae.emit("readable"));
|
||||
|
||||
ae.on("destroy", () => readable.destroy());
|
||||
ae.on("pause", () => readable.pause());
|
||||
ae.on("push", (chunk, encoding) => readable.push(chunk, encoding));
|
||||
ae.on("resume", () => readable.resume());
|
||||
ae.on("setEncoding", (encoding) => readable.setEncoding(encoding));
|
||||
ae.on("unshift", (chunk) => readable.unshift(chunk));
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap an evaluation emitter to make unique events for an item to prevent
|
||||
* conflicts when it shares that emitter with other items.
|
||||
*/
|
||||
export const createUniqueEval = (ae: ActiveEval, id: number | "stdout" | "stderr" | "stdin"): ActiveEval => {
|
||||
let events = <string[]>[];
|
||||
|
||||
return {
|
||||
removeAllListeners: (event?: string): void => {
|
||||
if (!event) {
|
||||
events.forEach((e) => ae.removeAllListeners(e));
|
||||
events = [];
|
||||
} else {
|
||||
const index = events.indexOf(event);
|
||||
if (index !== -1) {
|
||||
events.splice(index, 1);
|
||||
ae.removeAllListeners(`${event}:${id}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
emit: (event: string, ...args: any[]): void => {
|
||||
ae.emit(`${event}:${id}`, ...args);
|
||||
},
|
||||
on: (event: string, cb: (...args: any[]) => void): void => {
|
||||
if (!events.includes(event)) {
|
||||
events.push(event);
|
||||
}
|
||||
ae.on(`${event}:${id}`, cb);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* An event emitter that can store callbacks with IDs in a map so we can pass
|
||||
* them back and forth through an active evaluation using those IDs.
|
||||
*/
|
||||
export class CallbackEmitter extends EventEmitter {
|
||||
private _ae: ActiveEval | undefined;
|
||||
private callbackId = 0;
|
||||
private readonly callbacks = new Map<number, Function>();
|
||||
|
||||
public constructor(ae?: ActiveEval) {
|
||||
super();
|
||||
if (ae) {
|
||||
this.ae = ae;
|
||||
}
|
||||
}
|
||||
|
||||
protected get ae(): ActiveEval {
|
||||
if (!this._ae) {
|
||||
throw new Error("trying to access active evaluation before it has been set");
|
||||
}
|
||||
|
||||
return this._ae;
|
||||
}
|
||||
|
||||
protected set ae(ae: ActiveEval) {
|
||||
if (this._ae) {
|
||||
throw new Error("cannot override active evaluation");
|
||||
}
|
||||
this._ae = ae;
|
||||
this.ae.on("callback", (callbackId, ...args: any[]) => this.runCallback(callbackId, ...args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the callback and return and ID referencing its location in the map.
|
||||
*/
|
||||
protected storeCallback(callback?: Function): number | undefined {
|
||||
if (!callback) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const callbackId = this.callbackId++;
|
||||
this.callbacks.set(callbackId, callback);
|
||||
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the function with the specified ID and delete it from the map.
|
||||
* If the ID is undefined or doesn't exist, nothing happens.
|
||||
*/
|
||||
private runCallback(callbackId?: number, ...args: any[]): void {
|
||||
const callback = typeof callbackId !== "undefined" && this.callbacks.get(callbackId);
|
||||
if (callback && typeof callbackId !== "undefined") {
|
||||
this.callbacks.delete(callbackId);
|
||||
callback(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A writable stream over an active evaluation.
|
||||
*/
|
||||
export class ActiveEvalWritable extends CallbackEmitter implements Writable {
|
||||
public constructor(ae: ActiveEval) {
|
||||
super(ae);
|
||||
// Streams don't have an argument on close but sockets do.
|
||||
this.ae.on("close", (...args: any[]) => this.emit("close", ...args));
|
||||
this.ae.on("drain", () => this.emit("drain"));
|
||||
this.ae.on("error", (error) => this.emit("error", error));
|
||||
this.ae.on("finish", () => this.emit("finish"));
|
||||
this.ae.on("pipe", () => logger.warn("pipe is not supported"));
|
||||
this.ae.on("unpipe", () => logger.warn("unpipe is not supported"));
|
||||
}
|
||||
|
||||
public get writable(): boolean { throw new Error("not implemented"); }
|
||||
public get writableHighWaterMark(): number { throw new Error("not implemented"); }
|
||||
public get writableLength(): number { throw new Error("not implemented"); }
|
||||
public _write(): void { throw new Error("not implemented"); }
|
||||
public _destroy(): void { throw new Error("not implemented"); }
|
||||
public _final(): void { throw new Error("not implemented"); }
|
||||
public pipe<T>(): T { throw new Error("not implemented"); }
|
||||
|
||||
public cork(): void { this.ae.emit("cork"); }
|
||||
public destroy(): void { this.ae.emit("destroy"); }
|
||||
public setDefaultEncoding(encoding: string): this {
|
||||
this.ae.emit("setDefaultEncoding", encoding);
|
||||
|
||||
return this;
|
||||
}
|
||||
public uncork(): void { this.ae.emit("uncork"); }
|
||||
|
||||
public write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
|
||||
if (typeof encoding === "function") {
|
||||
callback = encoding;
|
||||
encoding = undefined;
|
||||
}
|
||||
|
||||
// Sockets can pass an fd instead of a callback but streams cannot..
|
||||
this.ae.emit("write", chunk, encoding, undefined, this.storeCallback(callback));
|
||||
|
||||
// Always true since we can't get this synchronously.
|
||||
return true;
|
||||
}
|
||||
|
||||
public end(data?: any, encoding?: string | Function, callback?: Function): void {
|
||||
if (typeof encoding === "function") {
|
||||
callback = encoding;
|
||||
encoding = undefined;
|
||||
}
|
||||
this.ae.emit("end", data, encoding, this.storeCallback(callback));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A readable stream over an active evaluation.
|
||||
*/
|
||||
export class ActiveEvalReadable extends CallbackEmitter implements Readable {
|
||||
public constructor(ae: ActiveEval) {
|
||||
super(ae);
|
||||
this.ae.on("close", () => this.emit("close"));
|
||||
this.ae.on("data", (data) => this.emit("data", data));
|
||||
this.ae.on("end", () => this.emit("end"));
|
||||
this.ae.on("error", (error) => this.emit("error", error));
|
||||
this.ae.on("readable", () => this.emit("readable"));
|
||||
}
|
||||
|
||||
public get readable(): boolean { throw new Error("not implemented"); }
|
||||
public get readableHighWaterMark(): number { throw new Error("not implemented"); }
|
||||
public get readableLength(): number { throw new Error("not implemented"); }
|
||||
public _read(): void { throw new Error("not implemented"); }
|
||||
public read(): any { throw new Error("not implemented"); }
|
||||
public isPaused(): boolean { throw new Error("not implemented"); }
|
||||
public pipe<T>(): T { throw new Error("not implemented"); }
|
||||
public unpipe(): this { throw new Error("not implemented"); }
|
||||
public unshift(): this { throw new Error("not implemented"); }
|
||||
public wrap(): this { throw new Error("not implemented"); }
|
||||
public push(): boolean { throw new Error("not implemented"); }
|
||||
public _destroy(): void { throw new Error("not implemented"); }
|
||||
public [Symbol.asyncIterator](): AsyncIterableIterator<any> { throw new Error("not implemented"); }
|
||||
|
||||
public destroy(): void { this.ae.emit("destroy"); }
|
||||
public pause(): this { return this.emitReturnThis("pause"); }
|
||||
public resume(): this { return this.emitReturnThis("resume"); }
|
||||
public setEncoding(encoding?: string): this { return this.emitReturnThis("setEncoding", encoding); }
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
protected emitReturnThis(event: string, ...args: any[]): this {
|
||||
this.ae.emit(event, ...args);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An duplex stream over an active evaluation.
|
||||
*/
|
||||
export class ActiveEvalDuplex extends ActiveEvalReadable implements Duplex {
|
||||
// Some unfortunate duplication here since we can't have multiple extends.
|
||||
public constructor(ae: ActiveEval) {
|
||||
super(ae);
|
||||
this.ae.on("drain", () => this.emit("drain"));
|
||||
this.ae.on("finish", () => this.emit("finish"));
|
||||
this.ae.on("pipe", () => logger.warn("pipe is not supported"));
|
||||
this.ae.on("unpipe", () => logger.warn("unpipe is not supported"));
|
||||
}
|
||||
|
||||
public get writable(): boolean { throw new Error("not implemented"); }
|
||||
public get writableHighWaterMark(): number { throw new Error("not implemented"); }
|
||||
public get writableLength(): number { throw new Error("not implemented"); }
|
||||
public _write(): void { throw new Error("not implemented"); }
|
||||
public _destroy(): void { throw new Error("not implemented"); }
|
||||
public _final(): void { throw new Error("not implemented"); }
|
||||
public pipe<T>(): T { throw new Error("not implemented"); }
|
||||
|
||||
public cork(): void { this.ae.emit("cork"); }
|
||||
public destroy(): void { this.ae.emit("destroy"); }
|
||||
public setDefaultEncoding(encoding: string): this {
|
||||
this.ae.emit("setDefaultEncoding", encoding);
|
||||
|
||||
return this;
|
||||
}
|
||||
public uncork(): void { this.ae.emit("uncork"); }
|
||||
|
||||
public write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
|
||||
if (typeof encoding === "function") {
|
||||
callback = encoding;
|
||||
encoding = undefined;
|
||||
}
|
||||
|
||||
// Sockets can pass an fd instead of a callback but streams cannot..
|
||||
this.ae.emit("write", chunk, encoding, undefined, this.storeCallback(callback));
|
||||
|
||||
// Always true since we can't get this synchronously.
|
||||
return true;
|
||||
}
|
||||
|
||||
public end(data?: any, encoding?: string | Function, callback?: Function): void {
|
||||
if (typeof encoding === "function") {
|
||||
callback = encoding;
|
||||
encoding = undefined;
|
||||
}
|
||||
this.ae.emit("end", data, encoding, this.storeCallback(callback));
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
import { exec, ChildProcess } from "child_process";
|
||||
import { EventEmitter } from "events";
|
||||
import * as fs from "fs";
|
||||
import * as stream from "stream";
|
||||
import { Client, IEncodingOptions, IEncodingOptionsCallback, escapePath, useBuffer } from "@coder/protocol";
|
||||
import { Client, IEncodingOptions, IEncodingOptionsCallback } from "@coder/protocol";
|
||||
import { client } from "./client";
|
||||
import { promisify } from "util";
|
||||
|
||||
// Use this to get around Webpack inserting our fills.
|
||||
// TODO: is there a better way?
|
||||
declare var _require: typeof require;
|
||||
declare var __non_webpack_require__: typeof require;
|
||||
declare var _Buffer: typeof Buffer;
|
||||
|
||||
/**
|
||||
@ -29,8 +27,8 @@ class FS {
|
||||
mode = undefined;
|
||||
}
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.access)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
@ -47,8 +45,8 @@ class FS {
|
||||
options = undefined;
|
||||
}
|
||||
this.client.evaluate((path, data, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.appendFile)(path, data, options);
|
||||
}, file, data, options).then(() => {
|
||||
@ -60,8 +58,8 @@ class FS {
|
||||
|
||||
public chmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.chmod)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
@ -73,8 +71,8 @@ class FS {
|
||||
|
||||
public chown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((path, uid, gid) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.chown)(path, uid, gid);
|
||||
}, path, uid, gid).then(() => {
|
||||
@ -86,8 +84,8 @@ class FS {
|
||||
|
||||
public close = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((fd) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.close)(fd);
|
||||
}, fd).then(() => {
|
||||
@ -102,8 +100,8 @@ class FS {
|
||||
callback = flags;
|
||||
}
|
||||
this.client.evaluate((src, dest, flags) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.copyFile)(src, dest, flags);
|
||||
}, src, dest, typeof flags !== "function" ? flags : undefined).then(() => {
|
||||
@ -113,19 +111,21 @@ class FS {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This should NOT be used for long-term writes.
|
||||
* The runnable will be killed after the timeout specified in evaluate.ts
|
||||
*/
|
||||
// tslint:disable-next-line no-any
|
||||
public createWriteStream = (path: fs.PathLike, options?: any): fs.WriteStream => {
|
||||
const ae = this.client.run((ae, path, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const str = fs.createWriteStream(path, options);
|
||||
ae.on("write", (d, e) => str.write(_Buffer.from(d, "utf8")));
|
||||
ae.on("write", (d) => str.write(_Buffer.from(d, "utf8")));
|
||||
ae.on("close", () => str.close());
|
||||
str.on("close", () => ae.emit("close"));
|
||||
str.on("open", (fd) => ae.emit("open", fd));
|
||||
str.on("error", (err) => ae.emit(err));
|
||||
|
||||
return {
|
||||
onDidDispose: (cb): fs.WriteStream => str.on("close", cb),
|
||||
dispose: (): void => str.close(),
|
||||
};
|
||||
}, path, options);
|
||||
|
||||
return new (class WriteStream extends stream.Writable implements fs.WriteStream {
|
||||
@ -134,7 +134,7 @@ class FS {
|
||||
|
||||
public constructor() {
|
||||
super({
|
||||
write: (data, encoding, cb) => {
|
||||
write: (data, encoding, cb): void => {
|
||||
this._bytesWritten += data.length;
|
||||
ae.emit("write", Buffer.from(data, encoding), encoding);
|
||||
cb();
|
||||
@ -162,8 +162,8 @@ class FS {
|
||||
|
||||
public exists = (path: fs.PathLike, callback: (exists: boolean) => void): void => {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.exists)(path);
|
||||
}, path).then((r) => {
|
||||
@ -175,8 +175,8 @@ class FS {
|
||||
|
||||
public fchmod = (fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((fd, mode) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.fchmod)(fd, mode);
|
||||
}, fd, mode).then(() => {
|
||||
@ -188,8 +188,8 @@ class FS {
|
||||
|
||||
public fchown = (fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((fd, uid, gid) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.fchown)(fd, uid, gid);
|
||||
}, fd, uid, gid).then(() => {
|
||||
@ -201,8 +201,8 @@ class FS {
|
||||
|
||||
public fdatasync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((fd) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.fdatasync)(fd);
|
||||
}, fd).then(() => {
|
||||
@ -214,9 +214,9 @@ class FS {
|
||||
|
||||
public fstat = (fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
||||
this.client.evaluate((fd) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const tslib = _require("tslib") as typeof import("tslib");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
const tslib = __non_webpack_require__("tslib") as typeof import("tslib");
|
||||
|
||||
return util.promisify(fs.fstat)(fd).then((stats) => {
|
||||
return tslib.__assign(stats, {
|
||||
@ -238,8 +238,8 @@ class FS {
|
||||
|
||||
public fsync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((fd) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.fsync)(fd);
|
||||
}, fd).then(() => {
|
||||
@ -255,8 +255,8 @@ class FS {
|
||||
len = undefined;
|
||||
}
|
||||
this.client.evaluate((fd, len) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.ftruncate)(fd, len);
|
||||
}, fd, len).then(() => {
|
||||
@ -268,8 +268,8 @@ class FS {
|
||||
|
||||
public futimes = (fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((fd, atime, mtime) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.futimes)(fd, atime, mtime);
|
||||
}, fd, atime, mtime).then(() => {
|
||||
@ -281,8 +281,8 @@ class FS {
|
||||
|
||||
public lchmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.lchmod)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
@ -294,8 +294,8 @@ class FS {
|
||||
|
||||
public lchown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((path, uid, gid) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.lchown)(path, uid, gid);
|
||||
}, path, uid, gid).then(() => {
|
||||
@ -307,8 +307,8 @@ class FS {
|
||||
|
||||
public link = (existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((existingPath, newPath) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.link)(existingPath, newPath);
|
||||
}, existingPath, newPath).then(() => {
|
||||
@ -320,9 +320,9 @@ class FS {
|
||||
|
||||
public lstat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const tslib = _require("tslib") as typeof import("tslib");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
const tslib = __non_webpack_require__("tslib") as typeof import("tslib");
|
||||
|
||||
return util.promisify(fs.lstat)(path).then((stats) => {
|
||||
return tslib.__assign(stats, {
|
||||
@ -348,8 +348,8 @@ class FS {
|
||||
mode = undefined;
|
||||
}
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.mkdir)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
@ -365,8 +365,8 @@ class FS {
|
||||
options = undefined;
|
||||
}
|
||||
this.client.evaluate((prefix, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.mkdtemp)(prefix, options);
|
||||
}, prefix, options).then((folder) => {
|
||||
@ -382,8 +382,8 @@ class FS {
|
||||
mode = undefined;
|
||||
}
|
||||
this.client.evaluate((path, flags, mode) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.open)(path, flags, mode);
|
||||
}, path, flags, mode).then((fd) => {
|
||||
@ -395,8 +395,8 @@ class FS {
|
||||
|
||||
public read = <TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: TBuffer) => void): void => {
|
||||
this.client.evaluate((fd, length, position) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
const buffer = new _Buffer(length);
|
||||
|
||||
return util.promisify(fs.read)(fd, buffer, 0, length, position).then((resp) => {
|
||||
@ -420,8 +420,8 @@ class FS {
|
||||
options = undefined;
|
||||
}
|
||||
this.client.evaluate((path, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.readFile)(path, options).then((value) => value.toString());
|
||||
}, path, options).then((buffer) => {
|
||||
@ -438,8 +438,8 @@ class FS {
|
||||
}
|
||||
// TODO: options can also take `withFileTypes` but the types aren't working.
|
||||
this.client.evaluate((path, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.readdir)(path, options);
|
||||
}, path, options).then((files) => {
|
||||
@ -455,8 +455,8 @@ class FS {
|
||||
options = undefined;
|
||||
}
|
||||
this.client.evaluate((path, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.readlink)(path, options);
|
||||
}, path, options).then((linkString) => {
|
||||
@ -472,8 +472,8 @@ class FS {
|
||||
options = undefined;
|
||||
}
|
||||
this.client.evaluate((path, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.realpath)(path, options);
|
||||
}, path, options).then((resolvedPath) => {
|
||||
@ -485,8 +485,8 @@ class FS {
|
||||
|
||||
public rename = (oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((oldPath, newPath) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.rename)(oldPath, newPath);
|
||||
}, oldPath, newPath).then(() => {
|
||||
@ -498,8 +498,8 @@ class FS {
|
||||
|
||||
public rmdir = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.rmdir)(path);
|
||||
}, path).then(() => {
|
||||
@ -511,9 +511,9 @@ class FS {
|
||||
|
||||
public stat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const tslib = _require("tslib") as typeof import("tslib");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
const tslib = __non_webpack_require__("tslib") as typeof import("tslib");
|
||||
|
||||
return util.promisify(fs.stat)(path).then((stats) => {
|
||||
return tslib.__assign(stats, {
|
||||
@ -543,8 +543,8 @@ class FS {
|
||||
type = undefined;
|
||||
}
|
||||
this.client.evaluate((target, path, type) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.symlink)(target, path, type);
|
||||
}, target, path, type).then(() => {
|
||||
@ -560,8 +560,8 @@ class FS {
|
||||
len = undefined;
|
||||
}
|
||||
this.client.evaluate((path, len) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.truncate)(path, len);
|
||||
}, path, len).then(() => {
|
||||
@ -573,8 +573,8 @@ class FS {
|
||||
|
||||
public unlink = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.unlink)(path);
|
||||
}, path).then(() => {
|
||||
@ -586,8 +586,8 @@ class FS {
|
||||
|
||||
public utimes = (path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||
this.client.evaluate((path, atime, mtime) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.utimes)(path, atime, mtime);
|
||||
}, path, atime, mtime).then(() => {
|
||||
@ -603,8 +603,8 @@ class FS {
|
||||
position = undefined;
|
||||
}
|
||||
this.client.evaluate((fd, buffer, offset, length, position) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.write)(fd, _Buffer.from(buffer, "utf8"), offset, length, position).then((resp) => {
|
||||
return {
|
||||
@ -626,8 +626,8 @@ class FS {
|
||||
options = undefined;
|
||||
}
|
||||
this.client.evaluate((path, data, options) => {
|
||||
const fs = _require("fs") as typeof import("fs");
|
||||
const util = _require("util") as typeof import("util");
|
||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||
const util = __non_webpack_require__("util") as typeof import("util");
|
||||
|
||||
return util.promisify(fs.writeFile)(path, data, options);
|
||||
}, path, data, options).then(() => {
|
||||
@ -637,63 +637,40 @@ class FS {
|
||||
});
|
||||
}
|
||||
|
||||
public watch = (filename: fs.PathLike, options: IEncodingOptions, listener?: ((event: string, filename: string) => void) | ((event: string, filename: Buffer) => void)): fs.FSWatcher => {
|
||||
// TODO: can we modify `evaluate` for long-running processes like watch?
|
||||
// Especially since inotifywait might not be available.
|
||||
const buffer = new NewlineInputBuffer((msg): void => {
|
||||
msg = msg.trim();
|
||||
const index = msg.lastIndexOf(":");
|
||||
const events = msg.substring(index + 1).split(",");
|
||||
const baseFilename = msg.substring(0, index).split("/").pop();
|
||||
events.forEach((event) => {
|
||||
switch (event) {
|
||||
// Rename is emitted when a file appears or disappears in the directory.
|
||||
case "CREATE":
|
||||
case "DELETE":
|
||||
case "MOVED_FROM":
|
||||
case "MOVED_TO":
|
||||
watcher.emit("rename", baseFilename);
|
||||
break;
|
||||
case "CLOSE_WRITE":
|
||||
watcher.emit("change", baseFilename);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: `exec` is undefined for some reason.
|
||||
const process = exec(`inotifywait ${escapePath(filename.toString())} -m --format "%w%f:%e"`);
|
||||
process.on("exit", (exitCode) => {
|
||||
watcher.emit("error", new Error(`process terminated unexpectedly with code ${exitCode}`));
|
||||
});
|
||||
process.stdout.on("data", (data) => {
|
||||
buffer.push(data);
|
||||
});
|
||||
|
||||
const watcher = new Watcher(process);
|
||||
if (listener) {
|
||||
const l = listener;
|
||||
watcher.on("change", (filename) => {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
l("change", useBuffer(options) ? Buffer.from(filename) : filename);
|
||||
});
|
||||
watcher.on("rename", (filename) => {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
l("rename", useBuffer(options) ? Buffer.from(filename) : filename);
|
||||
});
|
||||
public watch = (filename: fs.PathLike, options?: IEncodingOptions | ((event: string, filename: string | Buffer) => void), listener?: ((event: string, filename: string | Buffer) => void)): fs.FSWatcher => {
|
||||
if (typeof options === "function") {
|
||||
listener = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
return watcher;
|
||||
}
|
||||
}
|
||||
const ae = this.client.run((ae, filename, hasListener, options) => {
|
||||
const fs = __non_webpack_require__("fs") as typeof import ("fs");
|
||||
// tslint:disable-next-line no-any
|
||||
const watcher = fs.watch(filename, options as any, hasListener ? (event, filename): void => {
|
||||
ae.emit("listener", event, filename);
|
||||
} : undefined);
|
||||
watcher.on("change", (event, filename) => ae.emit("change", event, filename));
|
||||
watcher.on("error", (error) => ae.emit("error", error));
|
||||
ae.on("close", () => watcher.close());
|
||||
|
||||
class Watcher extends EventEmitter implements fs.FSWatcher {
|
||||
public constructor(private readonly process: ChildProcess) {
|
||||
return {
|
||||
onDidDispose: (cb): void => ae.on("close", cb),
|
||||
dispose: (): void => watcher.close(),
|
||||
};
|
||||
}, filename.toString(), !!listener, options);
|
||||
|
||||
return new class Watcher extends EventEmitter implements fs.FSWatcher {
|
||||
public constructor() {
|
||||
super();
|
||||
ae.on("change", (event, filename) => this.emit("change", event, filename));
|
||||
ae.on("error", (error) => this.emit("error", error));
|
||||
ae.on("listener", (event, filename) => listener && listener(event, filename));
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.process.kill();
|
||||
ae.emit("close");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -765,38 +742,10 @@ class Stats implements fs.Stats {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for safely taking input and turning it into separate messages.
|
||||
* Assumes that messages are split by newlines.
|
||||
*/
|
||||
class NewlineInputBuffer {
|
||||
private callback: (msg: string) => void;
|
||||
private buffer: string | undefined;
|
||||
|
||||
public constructor(callback: (msg: string) => void) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data to be buffered.
|
||||
*/
|
||||
public push(data: string | Uint8Array): void {
|
||||
let input = typeof data === "string" ? data : data.toString();
|
||||
if (this.buffer) {
|
||||
input = this.buffer + input;
|
||||
this.buffer = undefined;
|
||||
}
|
||||
const lines = input.split("\n");
|
||||
const length = lines.length - 1;
|
||||
const lastLine = lines[length];
|
||||
if (lastLine.length > 0) {
|
||||
this.buffer = lastLine;
|
||||
}
|
||||
lines.pop(); // This is either the line we buffered or an empty string.
|
||||
for (let i = 0; i < length; ++i) {
|
||||
this.callback(lines[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export = new FS(client);
|
||||
const fillFs = new FS(client);
|
||||
// Methods that don't follow the standard callback pattern (an error followed
|
||||
// by a single result) need to provide a custom promisify function.
|
||||
Object.defineProperty(fillFs.exists, promisify.custom, {
|
||||
value: (path: fs.PathLike): Promise<boolean> => new Promise((resolve): void => fillFs.exists(path, resolve)),
|
||||
});
|
||||
export = fillFs;
|
||||
|
@ -1,55 +1,256 @@
|
||||
import * as net from "net";
|
||||
import { Client } from "@coder/protocol";
|
||||
import { ActiveEval } from "@coder/protocol";
|
||||
import { CallbackEmitter, ActiveEvalDuplex, createUniqueEval } from "./evaluation";
|
||||
import { client } from "./client";
|
||||
|
||||
declare var __non_webpack_require__: typeof require;
|
||||
|
||||
class Socket extends ActiveEvalDuplex implements net.Socket {
|
||||
private _connecting: boolean = false;
|
||||
private _destroyed: boolean = false;
|
||||
|
||||
public constructor(options?: net.SocketConstructorOpts, ae?: ActiveEval) {
|
||||
super(ae || client.run((ae, options) => {
|
||||
const net = __non_webpack_require__("net") as typeof import("net");
|
||||
const { bindSocket } = __non_webpack_require__("@coder/ide/src/fill/evaluation") as typeof import("@coder/ide/src/fill/evaluation");
|
||||
|
||||
return bindSocket(ae, new net.Socket(options));
|
||||
}, options));
|
||||
|
||||
this.ae.on("connect", () => {
|
||||
this._connecting = false;
|
||||
this.emit("connect");
|
||||
});
|
||||
this.ae.on("error", () => {
|
||||
this._connecting = false;
|
||||
this._destroyed = true;
|
||||
});
|
||||
this.ae.on("lookup", (error, address, family, host) => this.emit("lookup", error, address, family, host));
|
||||
this.ae.on("timeout", () => this.emit("timeout"));
|
||||
}
|
||||
|
||||
public connect(options: net.SocketConnectOpts | number | string, host?: string | Function, connectionListener?: Function): this {
|
||||
// This is to get around type issues with socket.connect as well as extract
|
||||
// the function wherever it might be.
|
||||
switch (typeof options) {
|
||||
case "string": options = { path: options }; break;
|
||||
case "number": options = { port: options }; break;
|
||||
}
|
||||
switch (typeof host) {
|
||||
case "function": connectionListener = host; break;
|
||||
case "string": (options as net.TcpSocketConnectOpts).host = host; break;
|
||||
}
|
||||
|
||||
this._connecting = true;
|
||||
this.ae.emit("connect", options, this.storeCallback(connectionListener));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
public write(data: any, encoding?: string | Function, fd?: string | Function): boolean {
|
||||
let callback: Function | undefined;
|
||||
if (typeof encoding === "function") {
|
||||
callback = encoding;
|
||||
encoding = undefined;
|
||||
}
|
||||
if (typeof fd === "function") {
|
||||
callback = fd;
|
||||
fd = undefined;
|
||||
}
|
||||
this.ae.emit("write", data, encoding, fd, this.storeCallback(callback));
|
||||
|
||||
return true; // Always true since we can't get this synchronously.
|
||||
}
|
||||
|
||||
public get connecting(): boolean { return this._connecting; }
|
||||
public get destroyed(): boolean { return this._destroyed; }
|
||||
|
||||
public get bufferSize(): number { throw new Error("not implemented"); }
|
||||
public get bytesRead(): number { throw new Error("not implemented"); }
|
||||
public get bytesWritten(): number { throw new Error("not implemented"); }
|
||||
public get localAddress(): string { throw new Error("not implemented"); }
|
||||
public get localPort(): number { throw new Error("not implemented"); }
|
||||
public address(): net.AddressInfo | string { throw new Error("not implemented"); }
|
||||
|
||||
public setTimeout(timeout: number, callback?: Function): this { return this.emitReturnThis("setTimeout", timeout, this.storeCallback(callback)); }
|
||||
public setNoDelay(noDelay?: boolean): this { return this.emitReturnThis("setNoDelay", noDelay); }
|
||||
public setKeepAlive(enable?: boolean, initialDelay?: number): this { return this.emitReturnThis("setKeepAlive", enable, initialDelay); }
|
||||
public unref(): void { this.ae.emit("unref"); }
|
||||
public ref(): void { this.ae.emit("ref"); }
|
||||
}
|
||||
|
||||
class Server extends CallbackEmitter implements net.Server {
|
||||
private readonly sockets = new Map<number, Socket>();
|
||||
private _listening: boolean = false;
|
||||
|
||||
public constructor(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: Socket) => void), connectionListener?: (socket: Socket) => void) {
|
||||
super();
|
||||
|
||||
if (typeof options === "function") {
|
||||
connectionListener = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
this.ae = client.run((ae, options, callbackId) => {
|
||||
const net = __non_webpack_require__("net") as typeof import("net");
|
||||
const { maybeCallback, bindSocket, createUniqueEval } = __non_webpack_require__("@coder/ide/src/fill/evaluation") as typeof import("@coder/ide/src/fill/evaluation");
|
||||
|
||||
let connectionId = 0;
|
||||
let server = new net.Server(options, maybeCallback(ae, callbackId));
|
||||
const sockets = new Map<number, net.Socket>();
|
||||
|
||||
const storeSocket = (socket: net.Socket): number => {
|
||||
const socketId = connectionId++;
|
||||
sockets.set(socketId, socket);
|
||||
const disposer = bindSocket(createUniqueEval(ae, socketId), socket);
|
||||
socket.on("close", () => {
|
||||
disposer.dispose();
|
||||
sockets.delete(socketId);
|
||||
});
|
||||
|
||||
return socketId;
|
||||
};
|
||||
|
||||
server.on("close", () => ae.emit("close"));
|
||||
server.on("connection", (socket) => ae.emit("connection", storeSocket(socket)));
|
||||
server.on("error", (error) => ae.emit("error", error));
|
||||
server.on("listening", () => ae.emit("listening"));
|
||||
|
||||
ae.on("close", (callbackId) => server.close(maybeCallback(ae, callbackId)));
|
||||
ae.on("listen", (handle) => server.listen(handle));
|
||||
ae.on("ref", () => server.ref());
|
||||
ae.on("unref", () => server.unref());
|
||||
|
||||
return {
|
||||
onDidDispose: (cb): net.Server => server.on("close", cb),
|
||||
dispose: (): void => {
|
||||
server.removeAllListeners();
|
||||
server.close();
|
||||
sockets.forEach((socket) => {
|
||||
socket.removeAllListeners();
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
socket.unref();
|
||||
});
|
||||
sockets.clear();
|
||||
},
|
||||
};
|
||||
}, options, this.storeCallback(connectionListener));
|
||||
|
||||
this.ae.on("close", () => {
|
||||
this._listening = false;
|
||||
this.emit("close");
|
||||
});
|
||||
|
||||
this.ae.on("connection", (socketId) => {
|
||||
const socket = new Socket(undefined, createUniqueEval(this.ae, socketId));
|
||||
this.sockets.set(socketId, socket);
|
||||
socket.on("close", () => this.sockets.delete(socketId));
|
||||
if (connectionListener) {
|
||||
connectionListener(socket);
|
||||
}
|
||||
this.emit("connection", socket);
|
||||
});
|
||||
|
||||
this.ae.on("error", (error) => {
|
||||
this._listening = false;
|
||||
this.emit("error", error);
|
||||
});
|
||||
|
||||
this.ae.on("listening", () => {
|
||||
this._listening = true;
|
||||
this.emit("listening");
|
||||
});
|
||||
}
|
||||
|
||||
public listen(handle?: net.ListenOptions | number | string, hostname?: string | number | Function, backlog?: number | Function, listeningListener?: Function): this {
|
||||
if (typeof handle === "undefined") {
|
||||
throw new Error("no handle");
|
||||
}
|
||||
|
||||
switch (typeof handle) {
|
||||
case "number": handle = { port: handle }; break;
|
||||
case "string": handle = { path: handle }; break;
|
||||
}
|
||||
switch (typeof hostname) {
|
||||
case "function": listeningListener = hostname; break;
|
||||
case "string": handle.host = hostname; break;
|
||||
case "number": handle.backlog = hostname; break;
|
||||
}
|
||||
switch (typeof backlog) {
|
||||
case "function": listeningListener = backlog; break;
|
||||
case "number": handle.backlog = backlog; break;
|
||||
}
|
||||
|
||||
if (listeningListener) {
|
||||
this.ae.on("listening", () => {
|
||||
listeningListener!();
|
||||
});
|
||||
}
|
||||
|
||||
this.ae.emit("listen", handle);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public close(callback?: Function): this {
|
||||
// close() doesn't fire the close event until all connections are also
|
||||
// closed, but it does prevent new connections.
|
||||
this._listening = false;
|
||||
this.ae.emit("close", this.storeCallback(callback));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public get connections(): number { return this.sockets.size; }
|
||||
public get listening(): boolean { return this._listening; }
|
||||
|
||||
public get maxConnections(): number { throw new Error("not implemented"); }
|
||||
public address(): net.AddressInfo | string { throw new Error("not implemented"); }
|
||||
|
||||
public ref(): this { return this.emitReturnThis("ref"); }
|
||||
public unref(): this { return this.emitReturnThis("unref"); }
|
||||
public getConnections(cb: (error: Error | null, count: number) => void): void { cb(null, this.sockets.size); }
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
private emitReturnThis(event: string, ...args: any[]): this {
|
||||
this.ae.emit(event, ...args);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
type NodeNet = typeof net;
|
||||
|
||||
/**
|
||||
* Implementation of net for the browser.
|
||||
*/
|
||||
class Net implements NodeNet {
|
||||
public constructor(
|
||||
private readonly client: Client,
|
||||
) {}
|
||||
|
||||
public get Socket(): typeof net.Socket {
|
||||
return this.client.Socket;
|
||||
}
|
||||
|
||||
public get Server(): typeof net.Server {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public connect(): net.Socket {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
// @ts-ignore this is because Socket is missing things from the Stream
|
||||
// namespace but I'm unsure how best to provide them (finished,
|
||||
// finished.__promisify__, pipeline, and some others) or if it even matters.
|
||||
public readonly Socket = Socket;
|
||||
public readonly Server = Server;
|
||||
|
||||
public createConnection(target: string | number | net.NetConnectOpts, host?: string | Function, callback?: Function): net.Socket {
|
||||
if (typeof target === "object") {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
const socket = new Socket();
|
||||
socket.connect(target, host, callback);
|
||||
|
||||
return this.client.createConnection(target, typeof host === "function" ? host : callback) as net.Socket;
|
||||
}
|
||||
|
||||
public isIP(_input: string): number {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public isIPv4(_input: string): boolean {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public isIPv6(_input: string): boolean {
|
||||
throw new Error("not implemented");
|
||||
return socket;
|
||||
}
|
||||
|
||||
public createServer(
|
||||
_options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
|
||||
_connectionListener?: (socket: net.Socket) => void,
|
||||
options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
|
||||
connectionListener?: (socket: net.Socket) => void,
|
||||
): net.Server {
|
||||
return this.client.createServer() as net.Server;
|
||||
}
|
||||
return new Server(options, connectionListener);
|
||||
}
|
||||
|
||||
export = new Net(client);
|
||||
public connect(): net.Socket { throw new Error("not implemented"); }
|
||||
public isIP(_input: string): number { throw new Error("not implemented"); }
|
||||
public isIPv4(_input: string): boolean { throw new Error("not implemented"); }
|
||||
public isIPv6(_input: string): boolean { throw new Error("not implemented"); }
|
||||
}
|
||||
|
||||
export = new Net();
|
||||
|
@ -228,6 +228,7 @@ export class Logger {
|
||||
const envLevel = typeof global !== "undefined" && typeof global.process !== "undefined" ? global.process.env.LOG_LEVEL : process.env.LOG_LEVEL;
|
||||
if (envLevel) {
|
||||
switch (envLevel) {
|
||||
case "trace": this.level = Level.Trace; break;
|
||||
case "debug": this.level = Level.Debug; break;
|
||||
case "info": this.level = Level.Info; break;
|
||||
case "warn": this.level = Level.Warning; break;
|
||||
@ -277,6 +278,21 @@ export class Logger {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a trace message.
|
||||
*/
|
||||
public trace(fn: LogCallback): void;
|
||||
public trace(message: string, ...fields: FieldArray): void;
|
||||
public trace(message: LogCallback | string, ...fields: FieldArray): void {
|
||||
this.handle({
|
||||
type: "trace",
|
||||
message,
|
||||
fields,
|
||||
tagColor: "#888888",
|
||||
level: Level.Trace,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a debug message.
|
||||
*/
|
||||
@ -324,7 +340,7 @@ export class Logger {
|
||||
* Outputs a message.
|
||||
*/
|
||||
private handle(options: {
|
||||
type: "info" | "warn" | "debug" | "error";
|
||||
type: "trace" | "info" | "warn" | "debug" | "error";
|
||||
message: string | LogCallback;
|
||||
fields?: FieldArray;
|
||||
level: Level;
|
||||
|
@ -1,31 +1,20 @@
|
||||
import { ReadWriteConnection, InitData, OperatingSystem, SharedProcessData } from "../common/connection";
|
||||
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage, WorkingInitMessage, EvalEventMessage } from "../proto";
|
||||
import { EventEmitter } from "events";
|
||||
import { Emitter } from "@coder/events";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import { ChildProcess, SpawnOptions, ForkOptions, ServerProcess, ServerSocket, Socket, ServerListener, Server, ActiveEval } from "./command";
|
||||
import { EventEmitter } from "events";
|
||||
import { Socket as NetSocket } from "net";
|
||||
import { ReadWriteConnection, InitData, OperatingSystem, SharedProcessData } from "../common/connection";
|
||||
import { Disposer, stringify, parse } from "../common/util";
|
||||
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, ClientMessage, WorkingInitMessage, EvalEventMessage } from "../proto";
|
||||
import { ActiveEval } from "./command";
|
||||
|
||||
/**
|
||||
* Client accepts an arbitrary connection intended to communicate with the Server.
|
||||
*/
|
||||
export class Client {
|
||||
public readonly Socket: typeof NetSocket;
|
||||
|
||||
private evalId = 0;
|
||||
private readonly evalDoneEmitter = new Emitter<EvalDoneMessage>();
|
||||
private readonly evalFailedEmitter = new Emitter<EvalFailedMessage>();
|
||||
private readonly evalEventEmitter = new Emitter<EvalEventMessage>();
|
||||
|
||||
private sessionId = 0;
|
||||
private readonly sessions = new Map<number, ServerProcess>();
|
||||
|
||||
private connectionId = 0;
|
||||
private readonly connections = new Map<number, ServerSocket>();
|
||||
|
||||
private serverId = 0;
|
||||
private readonly servers = new Map<number, ServerListener>();
|
||||
|
||||
private _initData: InitData | undefined;
|
||||
private readonly initDataEmitter = new Emitter<InitData>();
|
||||
private readonly initDataPromise: Promise<InitData>;
|
||||
@ -40,21 +29,20 @@ export class Client {
|
||||
private readonly connection: ReadWriteConnection,
|
||||
) {
|
||||
connection.onMessage((data) => {
|
||||
let message: ServerMessage | undefined;
|
||||
try {
|
||||
this.handleMessage(ServerMessage.deserializeBinary(data));
|
||||
} catch (ex) {
|
||||
logger.error("Failed to handle server message", field("length", data.byteLength), field("exception", ex));
|
||||
message = ServerMessage.deserializeBinary(data);
|
||||
this.handleMessage(message);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"Failed to handle server message",
|
||||
field("id", message && message.hasEvalEvent() ? message.getEvalEvent()!.getId() : undefined),
|
||||
field("length", data.byteLength),
|
||||
field("error", error.message),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const that = this;
|
||||
// @ts-ignore NOTE: this doesn't fully implement net.Socket.
|
||||
this.Socket = class extends ServerSocket {
|
||||
public constructor() {
|
||||
super(that.connection, that.connectionId++, that.registerConnection);
|
||||
}
|
||||
};
|
||||
|
||||
this.initDataPromise = new Promise((resolve): void => {
|
||||
this.initDataEmitter.event(resolve);
|
||||
});
|
||||
@ -64,44 +52,55 @@ export class Client {
|
||||
return this.initDataPromise;
|
||||
}
|
||||
|
||||
public run(func: (ae: ActiveEval) => void | Promise<void>): ActiveEval;
|
||||
public run<T1>(func: (ae: ActiveEval, a1: T1) => void | Promise<void>, a1: T1): ActiveEval;
|
||||
public run<T1, T2>(func: (ae: ActiveEval, a1: T1, a2: T2) => void | Promise<void>, a1: T1, a2: T2): ActiveEval;
|
||||
public run<T1, T2, T3>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3) => void | Promise<void>, a1: T1, a2: T2, a3: T3): ActiveEval;
|
||||
public run<T1, T2, T3, T4>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3, a4: T4) => void | Promise<void>, a1: T1, a2: T2, a3: T3, a4: T4): ActiveEval;
|
||||
public run<T1, T2, T3, T4, T5>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => void | Promise<void>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): ActiveEval;
|
||||
public run<T1, T2, T3, T4, T5, T6>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => void | Promise<void>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): ActiveEval;
|
||||
|
||||
public run<T1, T2, T3, T4, T5, T6>(func: (ae: ActiveEval, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6) => void | Promise<void>, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6): ActiveEval {
|
||||
public run(func: (ae: ActiveEval) => Disposer): ActiveEval;
|
||||
public run<T1>(func: (ae: ActiveEval, a1: T1) => Disposer, a1: T1): ActiveEval;
|
||||
public run<T1, T2>(func: (ae: ActiveEval, a1: T1, a2: T2) => Disposer, a1: T1, a2: T2): ActiveEval;
|
||||
public run<T1, T2, T3>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3) => Disposer, a1: T1, a2: T2, a3: T3): ActiveEval;
|
||||
public run<T1, T2, T3, T4>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3, a4: T4) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4): ActiveEval;
|
||||
public run<T1, T2, T3, T4, T5>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): ActiveEval;
|
||||
public run<T1, T2, T3, T4, T5, T6>(func: (ae: ActiveEval, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): ActiveEval;
|
||||
/**
|
||||
* Run a function on the server and provide an event emitter which allows
|
||||
* listening and emitting to the emitter provided to that function. The
|
||||
* function should return a disposer for cleaning up when the client
|
||||
* disconnects and for notifying when disposal has happened outside manual
|
||||
* activation.
|
||||
*/
|
||||
public run<T1, T2, T3, T4, T5, T6>(func: (ae: ActiveEval, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6) => Disposer, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6): ActiveEval {
|
||||
const doEval = this.doEvaluate(func, a1, a2, a3, a4, a5, a6, true);
|
||||
|
||||
// This takes server events and emits them to the client's emitter.
|
||||
const eventEmitter = new EventEmitter();
|
||||
const d1 = this.evalEventEmitter.event((msg) => {
|
||||
if (msg.getId() !== doEval.id) {
|
||||
return;
|
||||
if (msg.getId() === doEval.id) {
|
||||
eventEmitter.emit(msg.getEvent(), ...msg.getArgsList().map(parse));
|
||||
}
|
||||
|
||||
eventEmitter.emit(msg.getEvent(), ...msg.getArgsList().filter(a => a).map(s => JSON.parse(s)));
|
||||
});
|
||||
|
||||
doEval.completed.then(() => {
|
||||
d1.dispose();
|
||||
eventEmitter.emit("close");
|
||||
}).catch((ex) => {
|
||||
d1.dispose();
|
||||
// This error event is only received by the client.
|
||||
eventEmitter.emit("error", ex);
|
||||
});
|
||||
|
||||
// This takes client events and emits them to the server's emitter and
|
||||
// listens to events received from the server (via the event hook above).
|
||||
return {
|
||||
// tslint:disable no-any
|
||||
on: (event: string, cb: (...args: any[]) => void): EventEmitter => eventEmitter.on(event, cb),
|
||||
emit: (event: string, ...args: any[]): void => {
|
||||
const eventsMsg = new EvalEventMessage();
|
||||
eventsMsg.setId(doEval.id);
|
||||
eventsMsg.setEvent(event);
|
||||
eventsMsg.setArgsList(args.filter(a => a).map(a => JSON.stringify(a)));
|
||||
eventsMsg.setArgsList(args.map(stringify));
|
||||
const clientMsg = new ClientMessage();
|
||||
clientMsg.setEvalEvent(eventsMsg);
|
||||
this.connection.send(clientMsg.serializeBinary());
|
||||
},
|
||||
removeAllListeners: (event: string): EventEmitter => eventEmitter.removeAllListeners(event),
|
||||
// tslint:enable no-any
|
||||
};
|
||||
}
|
||||
|
||||
@ -128,6 +127,7 @@ export class Client {
|
||||
return this.doEvaluate(func, a1, a2, a3, a4, a5, a6, false).completed;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
private doEvaluate<R, T1, T2, T3, T4, T5, T6>(func: (...args: any[]) => void | Promise<void> | R | Promise<R>, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6, active: boolean = false): {
|
||||
readonly completed: Promise<R>;
|
||||
readonly id: number;
|
||||
@ -136,163 +136,36 @@ export class Client {
|
||||
const id = this.evalId++;
|
||||
newEval.setId(id);
|
||||
newEval.setActive(active);
|
||||
newEval.setArgsList([a1, a2, a3, a4, a5, a6].filter(a => typeof a !== "undefined").map(a => JSON.stringify(a)));
|
||||
newEval.setArgsList([a1, a2, a3, a4, a5, a6].map(stringify));
|
||||
newEval.setFunction(func.toString());
|
||||
|
||||
const clientMsg = new ClientMessage();
|
||||
clientMsg.setNewEval(newEval);
|
||||
this.connection.send(clientMsg.serializeBinary());
|
||||
|
||||
let res: (value?: R) => void;
|
||||
let rej: (err?: Error) => void;
|
||||
const prom = new Promise<R>((r, e): void => {
|
||||
res = r;
|
||||
rej = e;
|
||||
});
|
||||
|
||||
const d1 = this.evalDoneEmitter.event((doneMsg) => {
|
||||
if (doneMsg.getId() !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const completed = new Promise<R>((resolve, reject): void => {
|
||||
const dispose = (): void => {
|
||||
d1.dispose();
|
||||
d2.dispose();
|
||||
};
|
||||
|
||||
const d1 = this.evalDoneEmitter.event((doneMsg) => {
|
||||
if (doneMsg.getId() === id) {
|
||||
const resp = doneMsg.getResponse();
|
||||
if (!resp) {
|
||||
return res();
|
||||
dispose();
|
||||
resolve(parse(resp));
|
||||
}
|
||||
|
||||
const rt = resp.getType();
|
||||
// tslint:disable-next-line no-any
|
||||
let val: any;
|
||||
switch (rt) {
|
||||
case TypedValue.Type.BOOLEAN:
|
||||
val = resp.getValue() === "true";
|
||||
break;
|
||||
case TypedValue.Type.NUMBER:
|
||||
val = parseInt(resp.getValue(), 10);
|
||||
break;
|
||||
case TypedValue.Type.OBJECT:
|
||||
val = JSON.parse(resp.getValue());
|
||||
break;
|
||||
case TypedValue.Type.STRING:
|
||||
val = resp.getValue();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`unsupported typed value ${rt}`);
|
||||
}
|
||||
|
||||
res(val);
|
||||
});
|
||||
|
||||
const d2 = this.evalFailedEmitter.event((failedMsg) => {
|
||||
if (failedMsg.getId() === id) {
|
||||
d1.dispose();
|
||||
d2.dispose();
|
||||
|
||||
rej(new Error(failedMsg.getMessage()));
|
||||
dispose();
|
||||
reject(new Error(failedMsg.getMessage()));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
completed: prom,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns a process from a command. _Somewhat_ reflects the "child_process" API.
|
||||
* @example
|
||||
* const cp = this.client.spawn("echo", ["test"]);
|
||||
* cp.stdout.on("data", (data) => console.log(data.toString()));
|
||||
* cp.on("exit", (code) => console.log("exited with", code));
|
||||
* @param args Arguments
|
||||
* @param options Options to execute for the command
|
||||
*/
|
||||
public spawn(command: string, args: string[] = [], options?: SpawnOptions): ChildProcess {
|
||||
return this.doSpawn(command, args, options, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fork a module.
|
||||
* @param modulePath Path of the module
|
||||
* @param args Args to add for the module
|
||||
* @param options Options to execute
|
||||
*/
|
||||
public fork(modulePath: string, args: string[] = [], options?: ForkOptions): ChildProcess {
|
||||
return this.doSpawn(modulePath, args, options, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* VS Code specific.
|
||||
* Forks a module from bootstrap-fork
|
||||
* @param modulePath Path of the module
|
||||
*/
|
||||
public bootstrapFork(modulePath: string, args: string[] = [], options?: ForkOptions): ChildProcess {
|
||||
return this.doSpawn(modulePath, args, options, true, true);
|
||||
}
|
||||
|
||||
public createConnection(path: string, callback?: Function): Socket;
|
||||
public createConnection(port: number, callback?: Function): Socket;
|
||||
public createConnection(target: string | number, callback?: Function): Socket;
|
||||
public createConnection(target: string | number, callback?: Function): Socket {
|
||||
const id = this.connectionId++;
|
||||
const socket = new ServerSocket(this.connection, id, this.registerConnection);
|
||||
socket.connect(target, callback);
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
public createServer(callback?: () => void): Server {
|
||||
const id = this.serverId++;
|
||||
const server = new ServerListener(this.connection, id, callback);
|
||||
this.servers.set(id, server);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
private doSpawn(command: string, args: string[] = [], options?: SpawnOptions, isFork: boolean = false, isBootstrapFork: boolean = true): ChildProcess {
|
||||
const id = this.sessionId++;
|
||||
const newSess = new NewSessionMessage();
|
||||
newSess.setId(id);
|
||||
newSess.setCommand(command);
|
||||
newSess.setArgsList(args);
|
||||
newSess.setIsFork(isFork);
|
||||
newSess.setIsBootstrapFork(isBootstrapFork);
|
||||
if (options) {
|
||||
if (options.cwd) {
|
||||
newSess.setCwd(options.cwd);
|
||||
}
|
||||
if (options.env) {
|
||||
Object.keys(options.env).forEach((envKey) => {
|
||||
if (options.env![envKey]) {
|
||||
newSess.getEnvMap().set(envKey, options.env![envKey].toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
if (options.tty) {
|
||||
const tty = new TTYDimensions();
|
||||
tty.setHeight(options.tty.rows);
|
||||
tty.setWidth(options.tty.columns);
|
||||
newSess.setTtyDimensions(tty);
|
||||
}
|
||||
}
|
||||
const clientMsg = new ClientMessage();
|
||||
clientMsg.setNewSession(newSess);
|
||||
this.connection.send(clientMsg.serializeBinary());
|
||||
|
||||
const serverProc = new ServerProcess(this.connection, id, options ? options.tty !== undefined : false, isBootstrapFork);
|
||||
serverProc.stdin.on("close", () => {
|
||||
const c = new CloseSessionInputMessage();
|
||||
c.setId(id);
|
||||
const cm = new ClientMessage();
|
||||
cm.setCloseSessionInput(c);
|
||||
this.connection.send(cm.serializeBinary());
|
||||
});
|
||||
this.sessions.set(id, serverProc);
|
||||
|
||||
return serverProc;
|
||||
return { completed, id };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -332,121 +205,12 @@ export class Client {
|
||||
this.evalFailedEmitter.emit(message.getEvalFailed()!);
|
||||
} else if (message.hasEvalEvent()) {
|
||||
this.evalEventEmitter.emit(message.getEvalEvent()!);
|
||||
} else if (message.hasNewSessionFailure()) {
|
||||
const s = this.sessions.get(message.getNewSessionFailure()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.emit("error", new Error(message.getNewSessionFailure()!.getMessage()));
|
||||
this.sessions.delete(message.getNewSessionFailure()!.getId());
|
||||
} else if (message.hasSessionDone()) {
|
||||
const s = this.sessions.get(message.getSessionDone()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.emit("exit", message.getSessionDone()!.getExitStatus());
|
||||
this.sessions.delete(message.getSessionDone()!.getId());
|
||||
} else if (message.hasSessionOutput()) {
|
||||
const output = message.getSessionOutput()!;
|
||||
const s = this.sessions.get(output.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
const data = new TextDecoder().decode(output.getData_asU8());
|
||||
const source = output.getSource();
|
||||
switch (source) {
|
||||
case SessionOutputMessage.Source.STDOUT:
|
||||
case SessionOutputMessage.Source.STDERR:
|
||||
(source === SessionOutputMessage.Source.STDOUT ? s.stdout : s.stderr).emit("data", data);
|
||||
break;
|
||||
case SessionOutputMessage.Source.IPC:
|
||||
s.emit("message", JSON.parse(data));
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown source ${source}`);
|
||||
}
|
||||
} else if (message.hasIdentifySession()) {
|
||||
const s = this.sessions.get(message.getIdentifySession()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
const pid = message.getIdentifySession()!.getPid();
|
||||
if (typeof pid !== "undefined") {
|
||||
s.pid = pid;
|
||||
}
|
||||
const title = message.getIdentifySession()!.getTitle();
|
||||
if (typeof title !== "undefined") {
|
||||
s.title = title;
|
||||
}
|
||||
} else if (message.hasConnectionEstablished()) {
|
||||
const c = this.connections.get(message.getConnectionEstablished()!.getId());
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
c.emit("connect");
|
||||
} else if (message.hasConnectionOutput()) {
|
||||
const c = this.connections.get(message.getConnectionOutput()!.getId());
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
c.emit("data", Buffer.from(message.getConnectionOutput()!.getData_asU8()));
|
||||
} else if (message.hasConnectionClose()) {
|
||||
const c = this.connections.get(message.getConnectionClose()!.getId());
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
c.emit("close");
|
||||
c.emit("end");
|
||||
this.connections.delete(message.getConnectionClose()!.getId());
|
||||
} else if (message.hasConnectionFailure()) {
|
||||
const c = this.connections.get(message.getConnectionFailure()!.getId());
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
c.emit("end");
|
||||
this.connections.delete(message.getConnectionFailure()!.getId());
|
||||
} else if (message.hasSharedProcessActive()) {
|
||||
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
|
||||
this.sharedProcessActiveEmitter.emit({
|
||||
socketPath: message.getSharedProcessActive()!.getSocketPath(),
|
||||
logPath: message.getSharedProcessActive()!.getLogPath(),
|
||||
socketPath: sharedProcessActiveMessage.getSocketPath(),
|
||||
logPath: sharedProcessActiveMessage.getLogPath(),
|
||||
});
|
||||
} else if (message.hasServerEstablished()) {
|
||||
const s = this.servers.get(message.getServerEstablished()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.emit("connect");
|
||||
} else if (message.hasServerConnectionEstablished()) {
|
||||
const s = this.servers.get(message.getServerConnectionEstablished()!.getServerId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
const conId = message.getServerConnectionEstablished()!.getConnectionId();
|
||||
const serverSocket = new ServerSocket(this.connection, conId, this.registerConnection);
|
||||
this.registerConnection(conId, serverSocket);
|
||||
serverSocket.emit("connect");
|
||||
s.emit("connection", serverSocket);
|
||||
} else if (message.getServerFailure()) {
|
||||
const s = this.servers.get(message.getServerFailure()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.emit("error", new Error(message.getNewSessionFailure()!.getReason().toString()));
|
||||
this.servers.delete(message.getNewSessionFailure()!.getId());
|
||||
} else if (message.hasServerClose()) {
|
||||
const s = this.servers.get(message.getServerClose()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.emit("close");
|
||||
this.servers.delete(message.getServerClose()!.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private registerConnection = (id: number, socket: ServerSocket): void => {
|
||||
if (this.connections.has(id)) {
|
||||
throw new Error(`${id} is already registered`);
|
||||
}
|
||||
this.connections.set(id, socket);
|
||||
}
|
||||
}
|
||||
|
@ -1,366 +1,8 @@
|
||||
import * as events from "events";
|
||||
import * as stream from "stream";
|
||||
import { ReadWriteConnection } from "../common/connection";
|
||||
import { NewConnectionMessage, ShutdownSessionMessage, ClientMessage, WriteToSessionMessage, ResizeSessionTTYMessage, TTYDimensions as ProtoTTYDimensions, ConnectionOutputMessage, ConnectionCloseMessage, ServerCloseMessage, NewServerMessage } from "../proto";
|
||||
|
||||
export interface TTYDimensions {
|
||||
readonly columns: number;
|
||||
readonly rows: number;
|
||||
}
|
||||
|
||||
export interface SpawnOptions {
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
tty?: TTYDimensions;
|
||||
}
|
||||
|
||||
export interface ForkOptions {
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface ChildProcess {
|
||||
readonly stdin: stream.Writable;
|
||||
readonly stdout: stream.Readable;
|
||||
readonly stderr: stream.Readable;
|
||||
|
||||
readonly killed?: boolean;
|
||||
readonly pid: number | undefined;
|
||||
readonly title?: string;
|
||||
|
||||
kill(signal?: string): void;
|
||||
|
||||
send(message: string | Uint8Array, callback?: () => void, ipc?: false): void;
|
||||
send(message: any, callback: undefined | (() => void), ipc: true): void;
|
||||
|
||||
on(event: "message", listener: (data: any) => void): void;
|
||||
on(event: "error", listener: (err: Error) => void): void;
|
||||
on(event: "exit", listener: (code: number, signal: string) => void): void;
|
||||
|
||||
resize?(dimensions: TTYDimensions): void;
|
||||
}
|
||||
|
||||
export class ServerProcess extends events.EventEmitter implements ChildProcess {
|
||||
public readonly stdin = new stream.Writable();
|
||||
public readonly stdout = new stream.Readable({ read: (): boolean => true });
|
||||
public readonly stderr = new stream.Readable({ read: (): boolean => true });
|
||||
|
||||
private _pid: number | undefined;
|
||||
private _title: string | undefined;
|
||||
private _killed: boolean = false;
|
||||
private _connected: boolean = false;
|
||||
|
||||
public constructor(
|
||||
private readonly connection: ReadWriteConnection,
|
||||
private readonly id: number,
|
||||
private readonly hasTty: boolean = false,
|
||||
private readonly ipc: boolean = false,
|
||||
) {
|
||||
super();
|
||||
if (!this.hasTty) {
|
||||
delete this.resize;
|
||||
}
|
||||
}
|
||||
|
||||
public get pid(): number | undefined {
|
||||
return this._pid;
|
||||
}
|
||||
|
||||
public set pid(pid: number | undefined) {
|
||||
this._pid = pid;
|
||||
this._connected = true;
|
||||
}
|
||||
|
||||
public get title(): string | undefined {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
public set title(title: string | undefined) {
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
public get connected(): boolean {
|
||||
return this._connected;
|
||||
}
|
||||
|
||||
public get killed(): boolean {
|
||||
return this._killed;
|
||||
}
|
||||
|
||||
public kill(signal?: string): void {
|
||||
const kill = new ShutdownSessionMessage();
|
||||
kill.setId(this.id);
|
||||
if (signal) {
|
||||
kill.setSignal(signal);
|
||||
}
|
||||
const client = new ClientMessage();
|
||||
client.setShutdownSession(kill);
|
||||
this.connection.send(client.serializeBinary());
|
||||
|
||||
this._killed = true;
|
||||
this._connected = false;
|
||||
}
|
||||
|
||||
public send(message: string | Uint8Array | any, callback?: (error: Error | null) => void, ipc: boolean = this.ipc): boolean {
|
||||
const send = new WriteToSessionMessage();
|
||||
send.setId(this.id);
|
||||
send.setSource(ipc ? WriteToSessionMessage.Source.IPC : WriteToSessionMessage.Source.STDIN);
|
||||
if (ipc) {
|
||||
send.setData(new TextEncoder().encode(JSON.stringify(message)));
|
||||
} else {
|
||||
send.setData(typeof message === "string" ? new TextEncoder().encode(message) : message);
|
||||
}
|
||||
const client = new ClientMessage();
|
||||
client.setWriteToSession(send);
|
||||
this.connection.send(client.serializeBinary());
|
||||
// TODO: properly implement?
|
||||
if (callback) {
|
||||
callback(null);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public resize(dimensions: TTYDimensions): void {
|
||||
const resize = new ResizeSessionTTYMessage();
|
||||
resize.setId(this.id);
|
||||
const tty = new ProtoTTYDimensions();
|
||||
tty.setHeight(dimensions.rows);
|
||||
tty.setWidth(dimensions.columns);
|
||||
resize.setTtyDimensions(tty);
|
||||
const client = new ClientMessage();
|
||||
client.setResizeSessionTty(resize);
|
||||
this.connection.send(client.serializeBinary());
|
||||
}
|
||||
}
|
||||
|
||||
export interface Socket {
|
||||
readonly destroyed: boolean;
|
||||
readonly connecting: boolean;
|
||||
write(buffer: Buffer): void;
|
||||
end(): void;
|
||||
|
||||
connect(path: string, callback?: () => void): void;
|
||||
connect(port: number, callback?: () => void): void;
|
||||
|
||||
addListener(event: "data", listener: (data: Buffer) => void): this;
|
||||
addListener(event: "close", listener: (hasError: boolean) => void): this;
|
||||
addListener(event: "connect", listener: () => void): this;
|
||||
addListener(event: "end", listener: () => void): this;
|
||||
|
||||
on(event: "data", listener: (data: Buffer) => void): this;
|
||||
on(event: "close", listener: (hasError: boolean) => void): this;
|
||||
on(event: "connect", listener: () => void): this;
|
||||
on(event: "end", listener: () => void): this;
|
||||
|
||||
once(event: "data", listener: (data: Buffer) => void): this;
|
||||
once(event: "close", listener: (hasError: boolean) => void): this;
|
||||
once(event: "connect", listener: () => void): this;
|
||||
once(event: "end", listener: () => void): this;
|
||||
|
||||
removeListener(event: "data", listener: (data: Buffer) => void): this;
|
||||
removeListener(event: "close", listener: (hasError: boolean) => void): this;
|
||||
removeListener(event: "connect", listener: () => void): this;
|
||||
removeListener(event: "end", listener: () => void): this;
|
||||
|
||||
emit(event: "data", data: Buffer): boolean;
|
||||
emit(event: "close"): boolean;
|
||||
emit(event: "connect"): boolean;
|
||||
emit(event: "end"): boolean;
|
||||
}
|
||||
|
||||
export class ServerSocket extends events.EventEmitter implements Socket {
|
||||
public writable: boolean = true;
|
||||
public readable: boolean = true;
|
||||
|
||||
private _destroyed: boolean = false;
|
||||
private _connecting: boolean = false;
|
||||
|
||||
public constructor(
|
||||
private readonly connection: ReadWriteConnection,
|
||||
private readonly id: number,
|
||||
private readonly beforeConnect: (id: number, socket: ServerSocket) => void,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public connect(target: string | number, callback?: Function): void {
|
||||
this._connecting = true;
|
||||
this.beforeConnect(this.id, this);
|
||||
|
||||
this.once("connect", () => {
|
||||
this._connecting = false;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
const newCon = new NewConnectionMessage();
|
||||
newCon.setId(this.id);
|
||||
if (typeof target === "string") {
|
||||
newCon.setPath(target);
|
||||
} else {
|
||||
newCon.setPort(target);
|
||||
}
|
||||
const clientMsg = new ClientMessage();
|
||||
clientMsg.setNewConnection(newCon);
|
||||
this.connection.send(clientMsg.serializeBinary());
|
||||
}
|
||||
|
||||
public get destroyed(): boolean {
|
||||
return this._destroyed;
|
||||
}
|
||||
|
||||
public get connecting(): boolean {
|
||||
return this._connecting;
|
||||
}
|
||||
|
||||
public write(buffer: Buffer): void {
|
||||
const sendData = new ConnectionOutputMessage();
|
||||
sendData.setId(this.id);
|
||||
sendData.setData(buffer);
|
||||
const client = new ClientMessage();
|
||||
client.setConnectionOutput(sendData);
|
||||
this.connection.send(client.serializeBinary());
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
const closeMsg = new ConnectionCloseMessage();
|
||||
closeMsg.setId(this.id);
|
||||
const client = new ClientMessage();
|
||||
client.setConnectionClose(closeMsg);
|
||||
this.connection.send(client.serializeBinary());
|
||||
}
|
||||
|
||||
public addListener(event: "data", listener: (data: Buffer) => void): this;
|
||||
public addListener(event: "close", listener: (hasError: boolean) => void): this;
|
||||
public addListener(event: "connect", listener: () => void): this;
|
||||
public addListener(event: "end", listener: () => void): this;
|
||||
public addListener(event: string, listener: any): this {
|
||||
return super.addListener(event, listener);
|
||||
}
|
||||
|
||||
public removeListener(event: "data", listener: (data: Buffer) => void): this;
|
||||
public removeListener(event: "close", listener: (hasError: boolean) => void): this;
|
||||
public removeListener(event: "connect", listener: () => void): this;
|
||||
public removeListener(event: "end", listener: () => void): this;
|
||||
public removeListener(event: string, listener: any): this {
|
||||
return super.removeListener(event, listener);
|
||||
}
|
||||
|
||||
public on(event: "data", listener: (data: Buffer) => void): this;
|
||||
public on(event: "close", listener: (hasError: boolean) => void): this;
|
||||
public on(event: "connect", listener: () => void): this;
|
||||
public on(event: "end", listener: () => void): this;
|
||||
public on(event: string, listener: any): this {
|
||||
return super.on(event, listener);
|
||||
}
|
||||
|
||||
public once(event: "data", listener: (data: Buffer) => void): this;
|
||||
public once(event: "close", listener: (hasError: boolean) => void): this;
|
||||
public once(event: "connect", listener: () => void): this;
|
||||
public once(event: "end", listener: () => void): this;
|
||||
public once(event: string, listener: any): this {
|
||||
return super.once(event, listener);
|
||||
}
|
||||
|
||||
public emit(event: "data", data: Buffer): boolean;
|
||||
public emit(event: "close"): boolean;
|
||||
public emit(event: "connect"): boolean;
|
||||
public emit(event: "end"): boolean;
|
||||
public emit(event: string, ...args: any[]): boolean {
|
||||
return super.emit(event, ...args);
|
||||
}
|
||||
|
||||
public setDefaultEncoding(encoding: string): this {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
addListener(event: "close", listener: () => void): this;
|
||||
addListener(event: "connect", listener: (socket: Socket) => void): this;
|
||||
addListener(event: "error", listener: (err: Error) => void): this;
|
||||
|
||||
on(event: "close", listener: () => void): this;
|
||||
on(event: "connection", listener: (socket: Socket) => void): this;
|
||||
on(event: "error", listener: (err: Error) => void): this;
|
||||
|
||||
once(event: "close", listener: () => void): this;
|
||||
once(event: "connection", listener: (socket: Socket) => void): this;
|
||||
once(event: "error", listener: (err: Error) => void): this;
|
||||
|
||||
removeListener(event: "close", listener: () => void): this;
|
||||
removeListener(event: "connection", listener: (socket: Socket) => void): this;
|
||||
removeListener(event: "error", listener: (err: Error) => void): this;
|
||||
|
||||
emit(event: "close"): boolean;
|
||||
emit(event: "connection"): boolean;
|
||||
emit(event: "error"): boolean;
|
||||
|
||||
listen(path: string, listeningListener?: () => void): this;
|
||||
close(callback?: () => void): this;
|
||||
|
||||
readonly listening: boolean;
|
||||
}
|
||||
|
||||
export class ServerListener extends events.EventEmitter implements Server {
|
||||
private _listening: boolean = false;
|
||||
|
||||
public constructor(
|
||||
private readonly connection: ReadWriteConnection,
|
||||
private readonly id: number,
|
||||
connectCallback?: () => void,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.on("connect", () => {
|
||||
this._listening = true;
|
||||
if (connectCallback) {
|
||||
connectCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get listening(): boolean {
|
||||
return this._listening;
|
||||
}
|
||||
|
||||
public listen(path: string, listener?: () => void): this {
|
||||
const ns = new NewServerMessage();
|
||||
ns.setId(this.id);
|
||||
ns.setPath(path!);
|
||||
const cm = new ClientMessage();
|
||||
cm.setNewServer(ns);
|
||||
this.connection.send(cm.serializeBinary());
|
||||
|
||||
if (typeof listener !== "undefined") {
|
||||
this.once("connect", listener);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public close(callback?: Function | undefined): this {
|
||||
const closeMsg = new ServerCloseMessage();
|
||||
closeMsg.setId(this.id);
|
||||
closeMsg.setReason("Manually closed");
|
||||
const clientMsg = new ClientMessage();
|
||||
clientMsg.setServerClose(closeMsg);
|
||||
this.connection.send(clientMsg.serializeBinary());
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ActiveEval {
|
||||
emit(event: string, ...args: any[]): void;
|
||||
removeAllListeners(event?: string): void;
|
||||
|
||||
on(event: "close", cb: () => void): void;
|
||||
on(event: "error", cb: (err: Error) => void): void;
|
||||
// tslint:disable no-any
|
||||
emit(event: string, ...args: any[]): void;
|
||||
on(event: string, cb: (...args: any[]) => void): void;
|
||||
// tslint:disable no-any
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { IDisposable } from "@coder/disposable";
|
||||
|
||||
/**
|
||||
* Return true if we're in a browser environment (including web workers).
|
||||
*/
|
||||
@ -25,10 +27,54 @@ export type IEncodingOptions = {
|
||||
export type IEncodingOptionsCallback = IEncodingOptions | ((err: NodeJS.ErrnoException, ...args: any[]) => void);
|
||||
|
||||
/**
|
||||
* Return true if the options specify to use a Buffer instead of string.
|
||||
* Stringify an event argument.
|
||||
*/
|
||||
export const useBuffer = (options: IEncodingOptionsCallback): boolean => {
|
||||
return options === "buffer"
|
||||
|| (!!options && typeof options !== "string" && typeof options !== "function"
|
||||
&& (options.encoding === "buffer" || options.encoding === null));
|
||||
export const stringify = (arg: any): string => { // tslint:disable-line no-any
|
||||
if (arg instanceof Error) {
|
||||
return JSON.stringify({
|
||||
type: "Error",
|
||||
data: {
|
||||
message: arg.message,
|
||||
name: arg.name,
|
||||
stack: arg.stack,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(arg);
|
||||
};
|
||||
/**
|
||||
* Parse an event argument.
|
||||
*/
|
||||
export const parse = (arg: string): any => { // tslint:disable-line no-any
|
||||
if (!arg) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
const result = JSON.parse(arg);
|
||||
|
||||
if (result && result.data && result.type) {
|
||||
switch (result.type) {
|
||||
// JSON.stringify turns a Buffer into an object but JSON.parse doesn't
|
||||
// turn it back, it just remains an object.
|
||||
case "Buffer":
|
||||
if (Array.isArray(result.data)) {
|
||||
return Buffer.from(result);
|
||||
}
|
||||
break;
|
||||
// Errors apparently can't be stringified, so we do something similar to
|
||||
// what happens to buffers and stringify them as regular objects.
|
||||
case "Error":
|
||||
if (result.data.message) {
|
||||
return new Error(result.data.message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export interface Disposer extends IDisposable {
|
||||
onDidDispose: (cb: () => void) => void;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./browser/client";
|
||||
export { ActiveEval } from "./browser/command";
|
||||
export * from "./common/connection";
|
||||
export * from "./common/util";
|
||||
|
@ -1,349 +0,0 @@
|
||||
import * as cp from "child_process";
|
||||
import * as net from "net";
|
||||
import * as stream from "stream";
|
||||
import { TextEncoder } from "text-encoding";
|
||||
import { Logger, logger, field } from "@coder/logger";
|
||||
import { NewSessionMessage, ServerMessage, SessionDoneMessage, SessionOutputMessage, IdentifySessionMessage, NewConnectionMessage, ConnectionEstablishedMessage, NewConnectionFailureMessage, ConnectionCloseMessage, ConnectionOutputMessage, NewServerMessage, ServerEstablishedMessage, NewServerFailureMessage, ServerCloseMessage, ServerConnectionEstablishedMessage } from "../proto";
|
||||
import { SendableConnection } from "../common/connection";
|
||||
import { ServerOptions } from "./server";
|
||||
|
||||
export interface Process {
|
||||
stdio?: Array<stream.Readable | stream.Writable>;
|
||||
stdin?: stream.Writable;
|
||||
stdout?: stream.Readable;
|
||||
stderr?: stream.Readable;
|
||||
send?: (message: string) => void;
|
||||
|
||||
pid: number;
|
||||
killed?: boolean;
|
||||
|
||||
on(event: "data" | "message", cb: (data: string) => void): void;
|
||||
on(event: "exit", listener: (exitCode: number, signal?: number) => void): void;
|
||||
write(data: string | Uint8Array): void;
|
||||
resize?(cols: number, rows: number): void;
|
||||
kill(signal?: string): void;
|
||||
title?: number;
|
||||
}
|
||||
|
||||
export const handleNewSession = (connection: SendableConnection, newSession: NewSessionMessage, serverOptions: ServerOptions | undefined, onExit: () => void): Process => {
|
||||
const childLogger = getChildLogger(newSession.getCommand());
|
||||
childLogger.debug(() => [
|
||||
newSession.getIsFork() ? "Forking" : "Spawning",
|
||||
field("command", newSession.getCommand()),
|
||||
field("args", newSession.getArgsList()),
|
||||
field("env", newSession.getEnvMap().toObject()),
|
||||
]);
|
||||
|
||||
let process: Process;
|
||||
let processTitle: string | undefined;
|
||||
|
||||
const env: { [key: string]: string } = {};
|
||||
newSession.getEnvMap().forEach((value, key) => {
|
||||
env[key] = value;
|
||||
});
|
||||
if (newSession.getTtyDimensions()) {
|
||||
// Spawn with node-pty
|
||||
const nodePty = require("node-pty") as typeof import("node-pty");
|
||||
const ptyProc = nodePty.spawn(newSession.getCommand(), newSession.getArgsList(), {
|
||||
cols: newSession.getTtyDimensions()!.getWidth(),
|
||||
rows: newSession.getTtyDimensions()!.getHeight(),
|
||||
cwd: newSession.getCwd(),
|
||||
env,
|
||||
});
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (ptyProc.process !== processTitle) {
|
||||
processTitle = ptyProc.process;
|
||||
const id = new IdentifySessionMessage();
|
||||
id.setId(newSession.getId());
|
||||
id.setTitle(processTitle!);
|
||||
const sm = new ServerMessage();
|
||||
sm.setIdentifySession(id);
|
||||
connection.send(sm.serializeBinary());
|
||||
}
|
||||
}, 200);
|
||||
|
||||
ptyProc.on("exit", () => {
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
process = ptyProc;
|
||||
processTitle = ptyProc.process;
|
||||
} else {
|
||||
const options = {
|
||||
cwd: newSession.getCwd(),
|
||||
env,
|
||||
};
|
||||
let proc: cp.ChildProcess;
|
||||
if (newSession.getIsFork()) {
|
||||
if (!serverOptions) {
|
||||
throw new Error("No forkProvider set for bootstrap-fork request");
|
||||
}
|
||||
|
||||
if (!serverOptions.forkProvider) {
|
||||
throw new Error("No forkProvider set for server options");
|
||||
}
|
||||
|
||||
proc = serverOptions.forkProvider(newSession);
|
||||
} else {
|
||||
proc = cp.spawn(newSession.getCommand(), newSession.getArgsList(), options);
|
||||
}
|
||||
|
||||
process = {
|
||||
stdin: proc.stdin,
|
||||
stderr: proc.stderr,
|
||||
stdout: proc.stdout,
|
||||
stdio: proc.stdio,
|
||||
send: (message): void => {
|
||||
proc.send(message);
|
||||
},
|
||||
on: (...args: any[]): void => ((proc as any).on)(...args), // tslint:disable-line no-any
|
||||
write: (d): boolean => proc.stdin.write(d),
|
||||
kill: (s): void => proc.kill(s || "SIGTERM"),
|
||||
pid: proc.pid,
|
||||
};
|
||||
}
|
||||
|
||||
const sendOutput = (_source: SessionOutputMessage.Source, msg: string | Uint8Array): void => {
|
||||
childLogger.debug(() => {
|
||||
|
||||
let data = msg.toString();
|
||||
if (_source === SessionOutputMessage.Source.IPC) {
|
||||
// data = Buffer.from(msg.toString(), "base64").toString();
|
||||
}
|
||||
|
||||
return [
|
||||
_source === SessionOutputMessage.Source.STDOUT
|
||||
? "stdout"
|
||||
: (_source === SessionOutputMessage.Source.STDERR ? "stderr" : "ipc"),
|
||||
field("id", newSession.getId()),
|
||||
field("data", data),
|
||||
];
|
||||
});
|
||||
const serverMsg = new ServerMessage();
|
||||
const d = new SessionOutputMessage();
|
||||
d.setId(newSession.getId());
|
||||
d.setData(typeof msg === "string" ? new TextEncoder().encode(msg) : msg);
|
||||
d.setSource(_source);
|
||||
serverMsg.setSessionOutput(d);
|
||||
connection.send(serverMsg.serializeBinary());
|
||||
};
|
||||
|
||||
if (process.stdout && process.stderr) {
|
||||
process.stdout.on("data", (data) => {
|
||||
sendOutput(SessionOutputMessage.Source.STDOUT, data);
|
||||
});
|
||||
|
||||
process.stderr.on("data", (data) => {
|
||||
sendOutput(SessionOutputMessage.Source.STDERR, data);
|
||||
});
|
||||
} else {
|
||||
process.on("data", (data) => {
|
||||
sendOutput(SessionOutputMessage.Source.STDOUT, Buffer.from(data));
|
||||
});
|
||||
}
|
||||
|
||||
// IPC.
|
||||
if (process.send) {
|
||||
process.on("message", (data) => {
|
||||
sendOutput(SessionOutputMessage.Source.IPC, JSON.stringify(data));
|
||||
});
|
||||
}
|
||||
|
||||
const id = new IdentifySessionMessage();
|
||||
id.setId(newSession.getId());
|
||||
id.setPid(process.pid);
|
||||
if (processTitle) {
|
||||
id.setTitle(processTitle);
|
||||
}
|
||||
const sm = new ServerMessage();
|
||||
sm.setIdentifySession(id);
|
||||
connection.send(sm.serializeBinary());
|
||||
|
||||
process.on("exit", (code) => {
|
||||
childLogger.debug(() => [
|
||||
"Exited",
|
||||
field("id", newSession.getId()),
|
||||
field("command", newSession.getCommand()),
|
||||
field("args", newSession.getArgsList()),
|
||||
]);
|
||||
const serverMsg = new ServerMessage();
|
||||
const exit = new SessionDoneMessage();
|
||||
exit.setId(newSession.getId());
|
||||
exit.setExitStatus(code);
|
||||
serverMsg.setSessionDone(exit);
|
||||
connection.send(serverMsg.serializeBinary());
|
||||
|
||||
onExit();
|
||||
});
|
||||
|
||||
return process;
|
||||
};
|
||||
|
||||
export const handleNewConnection = (connection: SendableConnection, newConnection: NewConnectionMessage, onExit: () => void): net.Socket => {
|
||||
const target = newConnection.getPath() || `${newConnection.getPort()}`;
|
||||
const childLogger = getChildLogger(target, ">");
|
||||
|
||||
const id = newConnection.getId();
|
||||
let socket: net.Socket;
|
||||
let didConnect = false;
|
||||
const connectCallback = (): void => {
|
||||
childLogger.debug("Connected", field("id", newConnection.getId()), field("target", target));
|
||||
didConnect = true;
|
||||
const estab = new ConnectionEstablishedMessage();
|
||||
estab.setId(id);
|
||||
const servMsg = new ServerMessage();
|
||||
servMsg.setConnectionEstablished(estab);
|
||||
connection.send(servMsg.serializeBinary());
|
||||
};
|
||||
|
||||
if (newConnection.getPath()) {
|
||||
socket = net.createConnection(newConnection.getPath(), connectCallback);
|
||||
} else if (newConnection.getPort()) {
|
||||
socket = net.createConnection(newConnection.getPort(), undefined, connectCallback);
|
||||
} else {
|
||||
throw new Error("No path or port provided for new connection");
|
||||
}
|
||||
|
||||
socket.addListener("error", (err) => {
|
||||
childLogger.debug("Error", field("id", newConnection.getId()), field("error", err));
|
||||
if (!didConnect) {
|
||||
const errMsg = new NewConnectionFailureMessage();
|
||||
errMsg.setId(id);
|
||||
errMsg.setMessage(err.message);
|
||||
const servMsg = new ServerMessage();
|
||||
servMsg.setConnectionFailure(errMsg);
|
||||
connection.send(servMsg.serializeBinary());
|
||||
|
||||
onExit();
|
||||
}
|
||||
});
|
||||
|
||||
socket.addListener("close", () => {
|
||||
childLogger.debug("Closed", field("id", newConnection.getId()));
|
||||
if (didConnect) {
|
||||
const closed = new ConnectionCloseMessage();
|
||||
closed.setId(id);
|
||||
const servMsg = new ServerMessage();
|
||||
servMsg.setConnectionClose(closed);
|
||||
connection.send(servMsg.serializeBinary());
|
||||
|
||||
onExit();
|
||||
}
|
||||
});
|
||||
|
||||
socket.addListener("data", (data) => {
|
||||
childLogger.debug(() => [
|
||||
"ipc",
|
||||
field("id", newConnection.getId()),
|
||||
field("data", data),
|
||||
]);
|
||||
const dataMsg = new ConnectionOutputMessage();
|
||||
dataMsg.setId(id);
|
||||
dataMsg.setData(data);
|
||||
const servMsg = new ServerMessage();
|
||||
servMsg.setConnectionOutput(dataMsg);
|
||||
connection.send(servMsg.serializeBinary());
|
||||
});
|
||||
|
||||
return socket;
|
||||
};
|
||||
|
||||
export const handleNewServer = (connection: SendableConnection, newServer: NewServerMessage, addSocket: (socket: net.Socket) => number, onExit: () => void, onSocketExit: (id: number) => void): net.Server => {
|
||||
const target = newServer.getPath() || `${newServer.getPort()}`;
|
||||
const childLogger = getChildLogger(target, "|");
|
||||
|
||||
const s = net.createServer();
|
||||
|
||||
try {
|
||||
s.listen(newServer.getPath() ? newServer.getPath() : newServer.getPort(), () => {
|
||||
childLogger.debug("Listening", field("id", newServer.getId()), field("target", target));
|
||||
const se = new ServerEstablishedMessage();
|
||||
se.setId(newServer.getId());
|
||||
const sm = new ServerMessage();
|
||||
sm.setServerEstablished(se);
|
||||
connection.send(sm.serializeBinary());
|
||||
});
|
||||
} catch (ex) {
|
||||
childLogger.debug("Failed to listen", field("id", newServer.getId()), field("target", target));
|
||||
const sf = new NewServerFailureMessage();
|
||||
sf.setId(newServer.getId());
|
||||
const sm = new ServerMessage();
|
||||
sm.setServerFailure(sf);
|
||||
connection.send(sm.serializeBinary());
|
||||
|
||||
onExit();
|
||||
}
|
||||
|
||||
s.on("close", () => {
|
||||
childLogger.debug("Stopped listening", field("id", newServer.getId()), field("target", target));
|
||||
const sc = new ServerCloseMessage();
|
||||
sc.setId(newServer.getId());
|
||||
const sm = new ServerMessage();
|
||||
sm.setServerClose(sc);
|
||||
connection.send(sm.serializeBinary());
|
||||
|
||||
onExit();
|
||||
});
|
||||
|
||||
s.on("connection", (socket) => {
|
||||
const socketId = addSocket(socket);
|
||||
childLogger.debug("Got connection", field("id", newServer.getId()), field("socketId", socketId));
|
||||
|
||||
const sock = new ServerConnectionEstablishedMessage();
|
||||
sock.setServerId(newServer.getId());
|
||||
sock.setConnectionId(socketId);
|
||||
const sm = new ServerMessage();
|
||||
sm.setServerConnectionEstablished(sock);
|
||||
connection.send(sm.serializeBinary());
|
||||
|
||||
socket.addListener("data", (data) => {
|
||||
childLogger.debug(() => [
|
||||
"ipc",
|
||||
field("id", newServer.getId()),
|
||||
field("socketId", socketId),
|
||||
field("data", data),
|
||||
]);
|
||||
const dataMsg = new ConnectionOutputMessage();
|
||||
dataMsg.setId(socketId);
|
||||
dataMsg.setData(data);
|
||||
const servMsg = new ServerMessage();
|
||||
servMsg.setConnectionOutput(dataMsg);
|
||||
connection.send(servMsg.serializeBinary());
|
||||
});
|
||||
|
||||
socket.on("error", (error) => {
|
||||
childLogger.debug("Error", field("id", newServer.getId()), field("socketId", socketId), field("error", error));
|
||||
onSocketExit(socketId);
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
childLogger.debug("Closed", field("id", newServer.getId()), field("socketId", socketId));
|
||||
onSocketExit(socketId);
|
||||
});
|
||||
});
|
||||
|
||||
return s;
|
||||
};
|
||||
|
||||
const getChildLogger = (command: string, prefix: string = ""): Logger => {
|
||||
// TODO: Temporary, for debugging. Should probably ask for a name?
|
||||
let name: string;
|
||||
if (command.includes("vscode-ipc") || command.includes("extensionHost")) {
|
||||
name = "exthost";
|
||||
} else if (command.includes("vscode-remote")) {
|
||||
name = "shared";
|
||||
} else {
|
||||
const basename = command.split("/").pop()!;
|
||||
let i = 0;
|
||||
for (; i < basename.length; i++) {
|
||||
const character = basename.charAt(i);
|
||||
if (isNaN(+character) && character === character.toUpperCase()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
name = basename.substring(0, i);
|
||||
}
|
||||
|
||||
return logger.named(prefix + name);
|
||||
};
|
@ -1,10 +1,13 @@
|
||||
import * as vm from "vm";
|
||||
import { NewEvalMessage, TypedValue, EvalFailedMessage, EvalDoneMessage, ServerMessage, EvalEventMessage } from "../proto";
|
||||
import { SendableConnection } from "../common/connection";
|
||||
import { EventEmitter } from "events";
|
||||
import * as vm from "vm";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import { NewEvalMessage, EvalFailedMessage, EvalDoneMessage, ServerMessage, EvalEventMessage } from "../proto";
|
||||
import { SendableConnection } from "../common/connection";
|
||||
import { stringify, parse } from "../common/util";
|
||||
|
||||
export interface ActiveEvaluation {
|
||||
onEvent(msg: EvalEventMessage): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
declare var __non_webpack_require__: typeof require;
|
||||
@ -13,96 +16,117 @@ export const evaluate = (connection: SendableConnection, message: NewEvalMessage
|
||||
message.getArgsList().forEach((value) => {
|
||||
argStr.push(value);
|
||||
});
|
||||
|
||||
/**
|
||||
* Send the response and call onDispose.
|
||||
*/
|
||||
// tslint:disable-next-line no-any
|
||||
const sendResp = (resp: any): void => {
|
||||
const evalDone = new EvalDoneMessage();
|
||||
evalDone.setId(message.getId());
|
||||
const tof = typeof resp;
|
||||
if (tof !== "undefined") {
|
||||
const tv = new TypedValue();
|
||||
let t: TypedValue.Type;
|
||||
switch (tof) {
|
||||
case "string":
|
||||
t = TypedValue.Type.STRING;
|
||||
break;
|
||||
case "boolean":
|
||||
t = TypedValue.Type.BOOLEAN;
|
||||
break;
|
||||
case "object":
|
||||
t = TypedValue.Type.OBJECT;
|
||||
break;
|
||||
case "number":
|
||||
t = TypedValue.Type.NUMBER;
|
||||
break;
|
||||
default:
|
||||
return sendErr(EvalFailedMessage.Reason.EXCEPTION, `unsupported response type ${tof}`);
|
||||
}
|
||||
tv.setValue(tof === "string" ? resp : JSON.stringify(resp));
|
||||
tv.setType(t);
|
||||
evalDone.setResponse(tv);
|
||||
}
|
||||
evalDone.setResponse(stringify(resp));
|
||||
|
||||
const serverMsg = new ServerMessage();
|
||||
serverMsg.setEvalDone(evalDone);
|
||||
connection.send(serverMsg.serializeBinary());
|
||||
|
||||
onDispose();
|
||||
};
|
||||
const sendErr = (reason: EvalFailedMessage.Reason, msg: string): void => {
|
||||
|
||||
/**
|
||||
* Send an exception and call onDispose.
|
||||
*/
|
||||
const sendException = (error: Error): void => {
|
||||
const evalFailed = new EvalFailedMessage();
|
||||
evalFailed.setId(message.getId());
|
||||
evalFailed.setReason(reason);
|
||||
evalFailed.setMessage(msg);
|
||||
evalFailed.setReason(EvalFailedMessage.Reason.EXCEPTION);
|
||||
evalFailed.setMessage(error.toString() + " " + error.stack);
|
||||
|
||||
const serverMsg = new ServerMessage();
|
||||
serverMsg.setEvalFailed(evalFailed);
|
||||
connection.send(serverMsg.serializeBinary());
|
||||
};
|
||||
let eventEmitter: EventEmitter | undefined;
|
||||
try {
|
||||
if (message.getActive()) {
|
||||
eventEmitter = new EventEmitter();
|
||||
}
|
||||
|
||||
const value = vm.runInNewContext(`(${message.getFunction()})(${eventEmitter ? `eventEmitter, ` : ""}${argStr.join(",")})`, {
|
||||
onDispose();
|
||||
};
|
||||
|
||||
let eventEmitter = message.getActive() ? new EventEmitter(): undefined;
|
||||
const sandbox = {
|
||||
eventEmitter: eventEmitter ? {
|
||||
// tslint:disable no-any
|
||||
on: (event: string, cb: (...args: any[]) => void): void => {
|
||||
eventEmitter!.on(event, cb);
|
||||
eventEmitter!.on(event, (...args: any[]) => {
|
||||
logger.trace(() => [
|
||||
`${event}`,
|
||||
field("id", message.getId()),
|
||||
field("args", args.map(stringify)),
|
||||
]);
|
||||
cb(...args);
|
||||
});
|
||||
},
|
||||
emit: (event: string, ...args: any[]): void => {
|
||||
logger.trace(() => [
|
||||
`emit ${event}`,
|
||||
field("id", message.getId()),
|
||||
field("args", args.map(stringify)),
|
||||
]);
|
||||
const eventMsg = new EvalEventMessage();
|
||||
eventMsg.setEvent(event);
|
||||
eventMsg.setArgsList(args.filter(a => a).map(a => JSON.stringify(a)));
|
||||
eventMsg.setArgsList(args.map(stringify));
|
||||
eventMsg.setId(message.getId());
|
||||
const serverMsg = new ServerMessage();
|
||||
serverMsg.setEvalEvent(eventMsg);
|
||||
connection.send(serverMsg.serializeBinary());
|
||||
},
|
||||
// tslint:enable no-any
|
||||
} : undefined,
|
||||
_Buffer: Buffer,
|
||||
require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
|
||||
_require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
|
||||
setTimeout,
|
||||
}, {
|
||||
setInterval,
|
||||
clearTimeout,
|
||||
process: {
|
||||
env: process.env,
|
||||
},
|
||||
};
|
||||
|
||||
let value: any; // tslint:disable-line no-any
|
||||
try {
|
||||
const code = `(${message.getFunction()})(${eventEmitter ? "eventEmitter, " : ""}${argStr.join(",")});`;
|
||||
value = vm.runInNewContext(code, sandbox, {
|
||||
// If the code takes longer than this to return, it is killed and throws.
|
||||
timeout: message.getTimeout() || 15000,
|
||||
});
|
||||
if (eventEmitter) {
|
||||
// Is an active evaluation and should NOT be ended
|
||||
eventEmitter.on("close", () => onDispose());
|
||||
eventEmitter.on("error", () => onDispose());
|
||||
} else {
|
||||
if ((value as Promise<void>).then) {
|
||||
// Is promise
|
||||
(value as Promise<void>).then(r => sendResp(r)).catch(ex => sendErr(EvalFailedMessage.Reason.EXCEPTION, ex.toString()));
|
||||
} catch (ex) {
|
||||
sendException(ex);
|
||||
}
|
||||
|
||||
// An evaluation completes when the value it returns resolves. An active
|
||||
// evaluation completes when it is disposed. Active evaluations are required
|
||||
// to return disposers so we can know both when it has ended (so we can clean
|
||||
// up on our end) and how to force end it (for example when the client
|
||||
// disconnects).
|
||||
// tslint:disable-next-line no-any
|
||||
const promise = !eventEmitter ? value as Promise<any> : new Promise((resolve): void => {
|
||||
value.onDidDispose(resolve);
|
||||
});
|
||||
if (promise && promise.then) {
|
||||
promise.then(sendResp).catch(sendException);
|
||||
} else {
|
||||
sendResp(value);
|
||||
}
|
||||
onDispose();
|
||||
}
|
||||
} catch (ex) {
|
||||
sendErr(EvalFailedMessage.Reason.EXCEPTION, ex.toString() + " " + ex.stack);
|
||||
}
|
||||
|
||||
return eventEmitter ? {
|
||||
onEvent: (eventMsg: EvalEventMessage): void => {
|
||||
eventEmitter!.emit(eventMsg.getEvent(), ...eventMsg.getArgsList().map(a => JSON.parse(a)));
|
||||
eventEmitter!.emit(eventMsg.getEvent(), ...eventMsg.getArgsList().map(parse));
|
||||
},
|
||||
dispose: (): void => {
|
||||
if (eventEmitter) {
|
||||
if (value && value.dispose) {
|
||||
value.dispose();
|
||||
}
|
||||
eventEmitter.removeAllListeners();
|
||||
eventEmitter = undefined;
|
||||
}
|
||||
},
|
||||
} : undefined;
|
||||
};
|
||||
|
@ -1,32 +1,21 @@
|
||||
import * as os from "os";
|
||||
import * as cp from "child_process";
|
||||
import * as path from "path";
|
||||
import { mkdir } from "fs";
|
||||
import { promisify } from "util";
|
||||
import { TextDecoder } from "text-encoding";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import { ClientMessage, WorkingInitMessage, ServerMessage, NewSessionMessage, WriteToSessionMessage } from "../proto";
|
||||
import { ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
|
||||
import { evaluate, ActiveEvaluation } from "./evaluate";
|
||||
import { ReadWriteConnection } from "../common/connection";
|
||||
import { Process, handleNewSession, handleNewConnection, handleNewServer } from "./command";
|
||||
import * as net from "net";
|
||||
|
||||
export interface ServerOptions {
|
||||
readonly workingDirectory: string;
|
||||
readonly dataDirectory: string;
|
||||
readonly builtInExtensionsDirectory: string;
|
||||
|
||||
forkProvider?(message: NewSessionMessage): cp.ChildProcess;
|
||||
}
|
||||
|
||||
export class Server {
|
||||
private readonly sessions = new Map<number, Process>();
|
||||
private readonly connections = new Map<number, net.Socket>();
|
||||
private readonly servers = new Map<number, net.Server>();
|
||||
private readonly evals = new Map<number, ActiveEvaluation>();
|
||||
|
||||
private connectionId = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
public constructor(
|
||||
private readonly connection: ReadWriteConnection,
|
||||
private readonly options?: ServerOptions,
|
||||
@ -42,18 +31,10 @@ export class Server {
|
||||
}
|
||||
});
|
||||
connection.onClose(() => {
|
||||
this.sessions.forEach((s) => {
|
||||
s.kill();
|
||||
});
|
||||
this.connections.forEach((c) => {
|
||||
c.destroy();
|
||||
});
|
||||
this.servers.forEach((s) => {
|
||||
s.close();
|
||||
});
|
||||
this.evals.forEach((e) => e.dispose());
|
||||
});
|
||||
|
||||
if (!options) {
|
||||
if (!this.options) {
|
||||
logger.warn("No server options provided. InitMessage will not be sent.");
|
||||
|
||||
return;
|
||||
@ -74,16 +55,16 @@ export class Server {
|
||||
}
|
||||
}
|
||||
};
|
||||
Promise.all([ mkdirP(path.join(options.dataDirectory, "User", "workspaceStorage")) ]).then(() => {
|
||||
Promise.all([ mkdirP(path.join(this.options.dataDirectory, "User", "workspaceStorage")) ]).then(() => {
|
||||
logger.info("Created data directory");
|
||||
}).catch((error) => {
|
||||
logger.error(error.message, field("error", error));
|
||||
});
|
||||
|
||||
const initMsg = new WorkingInitMessage();
|
||||
initMsg.setDataDirectory(options.dataDirectory);
|
||||
initMsg.setWorkingDirectory(options.workingDirectory);
|
||||
initMsg.setBuiltinExtensionsDir(options.builtInExtensionsDirectory);
|
||||
initMsg.setDataDirectory(this.options.dataDirectory);
|
||||
initMsg.setWorkingDirectory(this.options.workingDirectory);
|
||||
initMsg.setBuiltinExtensionsDir(this.options.builtInExtensionsDirectory);
|
||||
initMsg.setHomeDirectory(os.homedir());
|
||||
initMsg.setTmpDirectory(os.tmpdir());
|
||||
const platform = os.platform();
|
||||
@ -113,7 +94,7 @@ export class Server {
|
||||
private handleMessage(message: ClientMessage): void {
|
||||
if (message.hasNewEval()) {
|
||||
const evalMessage = message.getNewEval()!;
|
||||
logger.debug(() => [
|
||||
logger.trace(() => [
|
||||
"EvalMessage",
|
||||
field("id", evalMessage.getId()),
|
||||
field("args", evalMessage.getArgsList()),
|
||||
@ -121,132 +102,22 @@ export class Server {
|
||||
]);
|
||||
const resp = evaluate(this.connection, evalMessage, () => {
|
||||
this.evals.delete(evalMessage.getId());
|
||||
logger.trace(() => [
|
||||
`dispose ${evalMessage.getId()}, ${this.evals.size} left`,
|
||||
]);
|
||||
});
|
||||
if (resp) {
|
||||
this.evals.set(evalMessage.getId(), resp);
|
||||
}
|
||||
} else if (message.hasEvalEvent()) {
|
||||
const evalEventMessage = message.getEvalEvent()!;
|
||||
logger.debug("EvalEventMessage", field("id", evalEventMessage.getId()));
|
||||
const e = this.evals.get(evalEventMessage.getId());
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
e.onEvent(evalEventMessage);
|
||||
} else if (message.hasNewSession()) {
|
||||
const sessionMessage = message.getNewSession()!;
|
||||
logger.debug("NewSession", field("id", sessionMessage.getId()));
|
||||
const session = handleNewSession(this.connection, sessionMessage, this.options, () => {
|
||||
this.sessions.delete(sessionMessage.getId());
|
||||
});
|
||||
this.sessions.set(sessionMessage.getId(), session);
|
||||
} else if (message.hasCloseSessionInput()) {
|
||||
const closeSessionMessage = message.getCloseSessionInput()!;
|
||||
logger.debug("CloseSessionInput", field("id", closeSessionMessage.getId()));
|
||||
const s = this.getSession(closeSessionMessage.getId());
|
||||
if (!s || !s.stdin) {
|
||||
return;
|
||||
}
|
||||
s.stdin.end();
|
||||
} else if (message.hasResizeSessionTty()) {
|
||||
const resizeSessionTtyMessage = message.getResizeSessionTty()!;
|
||||
logger.debug("ResizeSessionTty", field("id", resizeSessionTtyMessage.getId()));
|
||||
const s = this.getSession(resizeSessionTtyMessage.getId());
|
||||
if (!s || !s.resize) {
|
||||
return;
|
||||
}
|
||||
const tty = resizeSessionTtyMessage.getTtyDimensions()!;
|
||||
s.resize(tty.getWidth(), tty.getHeight());
|
||||
} else if (message.hasShutdownSession()) {
|
||||
const shutdownSessionMessage = message.getShutdownSession()!;
|
||||
logger.debug("ShutdownSession", field("id", shutdownSessionMessage.getId()));
|
||||
const s = this.getSession(shutdownSessionMessage.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.kill(shutdownSessionMessage.getSignal());
|
||||
} else if (message.hasWriteToSession()) {
|
||||
const writeToSessionMessage = message.getWriteToSession()!;
|
||||
logger.debug("WriteToSession", field("id", writeToSessionMessage.getId()));
|
||||
const s = this.getSession(writeToSessionMessage.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
const data = new TextDecoder().decode(writeToSessionMessage.getData_asU8());
|
||||
const source = writeToSessionMessage.getSource();
|
||||
if (source === WriteToSessionMessage.Source.IPC) {
|
||||
if (!s.send) {
|
||||
throw new Error("Cannot send message via IPC to process without IPC");
|
||||
}
|
||||
s.send(JSON.parse(data));
|
||||
} else {
|
||||
s.write(data);
|
||||
}
|
||||
} else if (message.hasNewConnection()) {
|
||||
const connectionMessage = message.getNewConnection()!;
|
||||
logger.debug("NewConnection", field("id", connectionMessage.getId()));
|
||||
if (this.connections.has(connectionMessage.getId())) {
|
||||
throw new Error(`connect EISCONN ${connectionMessage.getPath() || connectionMessage.getPort()}`);
|
||||
}
|
||||
const socket = handleNewConnection(this.connection, connectionMessage, () => {
|
||||
this.connections.delete(connectionMessage.getId());
|
||||
});
|
||||
this.connections.set(connectionMessage.getId(), socket);
|
||||
} else if (message.hasConnectionOutput()) {
|
||||
const connectionOutputMessage = message.getConnectionOutput()!;
|
||||
logger.debug("ConnectionOuput", field("id", connectionOutputMessage.getId()));
|
||||
const c = this.getConnection(connectionOutputMessage.getId());
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
c.write(Buffer.from(connectionOutputMessage.getData_asU8()));
|
||||
} else if (message.hasConnectionClose()) {
|
||||
const connectionCloseMessage = message.getConnectionClose()!;
|
||||
logger.debug("ConnectionClose", field("id", connectionCloseMessage.getId()));
|
||||
const c = this.getConnection(connectionCloseMessage.getId());
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
c.end();
|
||||
} else if (message.hasNewServer()) {
|
||||
const serverMessage = message.getNewServer()!;
|
||||
logger.debug("NewServer", field("id", serverMessage.getId()));
|
||||
if (this.servers.has(serverMessage.getId())) {
|
||||
throw new Error("multiple listeners not supported");
|
||||
}
|
||||
const s = handleNewServer(this.connection, serverMessage, (socket) => {
|
||||
const id = this.connectionId--;
|
||||
this.connections.set(id, socket);
|
||||
|
||||
return id;
|
||||
}, () => {
|
||||
this.connections.delete(serverMessage.getId());
|
||||
}, (id) => {
|
||||
this.connections.delete(id);
|
||||
});
|
||||
this.servers.set(serverMessage.getId(), s);
|
||||
} else if (message.hasServerClose()) {
|
||||
const serverCloseMessage = message.getServerClose()!;
|
||||
logger.debug("ServerClose", field("id", serverCloseMessage.getId()));
|
||||
const s = this.getServer(serverCloseMessage.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.close();
|
||||
} else {
|
||||
logger.debug("Received unknown message type");
|
||||
throw new Error("unknown message type");
|
||||
}
|
||||
}
|
||||
|
||||
private getServer(id: number): net.Server | undefined {
|
||||
return this.servers.get(id);
|
||||
}
|
||||
|
||||
private getConnection(id: number): net.Socket | undefined {
|
||||
return this.connections.get(id);
|
||||
}
|
||||
|
||||
private getSession(id: number): Process | undefined {
|
||||
return this.sessions.get(id);
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,9 @@
|
||||
syntax = "proto3";
|
||||
import "command.proto";
|
||||
import "node.proto";
|
||||
import "vscode.proto";
|
||||
|
||||
message ClientMessage {
|
||||
oneof msg {
|
||||
// command.proto
|
||||
NewSessionMessage new_session = 1;
|
||||
ShutdownSessionMessage shutdown_session = 2;
|
||||
WriteToSessionMessage write_to_session = 3;
|
||||
CloseSessionInputMessage close_session_input = 4;
|
||||
ResizeSessionTTYMessage resize_session_tty = 5;
|
||||
NewConnectionMessage new_connection = 6;
|
||||
ConnectionOutputMessage connection_output = 7;
|
||||
ConnectionCloseMessage connection_close = 8;
|
||||
NewServerMessage new_server = 9;
|
||||
ServerCloseMessage server_close = 10;
|
||||
|
||||
// node.proto
|
||||
NewEvalMessage new_eval = 11;
|
||||
EvalEventMessage eval_event = 12;
|
||||
@ -25,20 +12,6 @@ message ClientMessage {
|
||||
|
||||
message ServerMessage {
|
||||
oneof msg {
|
||||
// command.proto
|
||||
NewSessionFailureMessage new_session_failure = 1;
|
||||
SessionDoneMessage session_done = 2;
|
||||
SessionOutputMessage session_output = 3;
|
||||
IdentifySessionMessage identify_session = 4;
|
||||
NewConnectionFailureMessage connection_failure = 5;
|
||||
ConnectionOutputMessage connection_output = 6;
|
||||
ConnectionCloseMessage connection_close = 7;
|
||||
ConnectionEstablishedMessage connection_established = 8;
|
||||
NewServerFailureMessage server_failure = 9;
|
||||
ServerEstablishedMessage server_established = 10;
|
||||
ServerCloseMessage server_close = 11;
|
||||
ServerConnectionEstablishedMessage server_connection_established = 12;
|
||||
|
||||
// node.proto
|
||||
EvalFailedMessage eval_failed = 13;
|
||||
EvalDoneMessage eval_done = 14;
|
||||
|
155
packages/protocol/src/proto/client_pb.d.ts
vendored
155
packages/protocol/src/proto/client_pb.d.ts
vendored
@ -2,61 +2,10 @@
|
||||
// file: client.proto
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as command_pb from "./command_pb";
|
||||
import * as node_pb from "./node_pb";
|
||||
import * as vscode_pb from "./vscode_pb";
|
||||
|
||||
export class ClientMessage extends jspb.Message {
|
||||
hasNewSession(): boolean;
|
||||
clearNewSession(): void;
|
||||
getNewSession(): command_pb.NewSessionMessage | undefined;
|
||||
setNewSession(value?: command_pb.NewSessionMessage): void;
|
||||
|
||||
hasShutdownSession(): boolean;
|
||||
clearShutdownSession(): void;
|
||||
getShutdownSession(): command_pb.ShutdownSessionMessage | undefined;
|
||||
setShutdownSession(value?: command_pb.ShutdownSessionMessage): void;
|
||||
|
||||
hasWriteToSession(): boolean;
|
||||
clearWriteToSession(): void;
|
||||
getWriteToSession(): command_pb.WriteToSessionMessage | undefined;
|
||||
setWriteToSession(value?: command_pb.WriteToSessionMessage): void;
|
||||
|
||||
hasCloseSessionInput(): boolean;
|
||||
clearCloseSessionInput(): void;
|
||||
getCloseSessionInput(): command_pb.CloseSessionInputMessage | undefined;
|
||||
setCloseSessionInput(value?: command_pb.CloseSessionInputMessage): void;
|
||||
|
||||
hasResizeSessionTty(): boolean;
|
||||
clearResizeSessionTty(): void;
|
||||
getResizeSessionTty(): command_pb.ResizeSessionTTYMessage | undefined;
|
||||
setResizeSessionTty(value?: command_pb.ResizeSessionTTYMessage): void;
|
||||
|
||||
hasNewConnection(): boolean;
|
||||
clearNewConnection(): void;
|
||||
getNewConnection(): command_pb.NewConnectionMessage | undefined;
|
||||
setNewConnection(value?: command_pb.NewConnectionMessage): void;
|
||||
|
||||
hasConnectionOutput(): boolean;
|
||||
clearConnectionOutput(): void;
|
||||
getConnectionOutput(): command_pb.ConnectionOutputMessage | undefined;
|
||||
setConnectionOutput(value?: command_pb.ConnectionOutputMessage): void;
|
||||
|
||||
hasConnectionClose(): boolean;
|
||||
clearConnectionClose(): void;
|
||||
getConnectionClose(): command_pb.ConnectionCloseMessage | undefined;
|
||||
setConnectionClose(value?: command_pb.ConnectionCloseMessage): void;
|
||||
|
||||
hasNewServer(): boolean;
|
||||
clearNewServer(): void;
|
||||
getNewServer(): command_pb.NewServerMessage | undefined;
|
||||
setNewServer(value?: command_pb.NewServerMessage): void;
|
||||
|
||||
hasServerClose(): boolean;
|
||||
clearServerClose(): void;
|
||||
getServerClose(): command_pb.ServerCloseMessage | undefined;
|
||||
setServerClose(value?: command_pb.ServerCloseMessage): void;
|
||||
|
||||
hasNewEval(): boolean;
|
||||
clearNewEval(): void;
|
||||
getNewEval(): node_pb.NewEvalMessage | undefined;
|
||||
@ -80,98 +29,18 @@ export class ClientMessage extends jspb.Message {
|
||||
|
||||
export namespace ClientMessage {
|
||||
export type AsObject = {
|
||||
newSession?: command_pb.NewSessionMessage.AsObject,
|
||||
shutdownSession?: command_pb.ShutdownSessionMessage.AsObject,
|
||||
writeToSession?: command_pb.WriteToSessionMessage.AsObject,
|
||||
closeSessionInput?: command_pb.CloseSessionInputMessage.AsObject,
|
||||
resizeSessionTty?: command_pb.ResizeSessionTTYMessage.AsObject,
|
||||
newConnection?: command_pb.NewConnectionMessage.AsObject,
|
||||
connectionOutput?: command_pb.ConnectionOutputMessage.AsObject,
|
||||
connectionClose?: command_pb.ConnectionCloseMessage.AsObject,
|
||||
newServer?: command_pb.NewServerMessage.AsObject,
|
||||
serverClose?: command_pb.ServerCloseMessage.AsObject,
|
||||
newEval?: node_pb.NewEvalMessage.AsObject,
|
||||
evalEvent?: node_pb.EvalEventMessage.AsObject,
|
||||
}
|
||||
|
||||
export enum MsgCase {
|
||||
MSG_NOT_SET = 0,
|
||||
NEW_SESSION = 1,
|
||||
SHUTDOWN_SESSION = 2,
|
||||
WRITE_TO_SESSION = 3,
|
||||
CLOSE_SESSION_INPUT = 4,
|
||||
RESIZE_SESSION_TTY = 5,
|
||||
NEW_CONNECTION = 6,
|
||||
CONNECTION_OUTPUT = 7,
|
||||
CONNECTION_CLOSE = 8,
|
||||
NEW_SERVER = 9,
|
||||
SERVER_CLOSE = 10,
|
||||
NEW_EVAL = 11,
|
||||
EVAL_EVENT = 12,
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerMessage extends jspb.Message {
|
||||
hasNewSessionFailure(): boolean;
|
||||
clearNewSessionFailure(): void;
|
||||
getNewSessionFailure(): command_pb.NewSessionFailureMessage | undefined;
|
||||
setNewSessionFailure(value?: command_pb.NewSessionFailureMessage): void;
|
||||
|
||||
hasSessionDone(): boolean;
|
||||
clearSessionDone(): void;
|
||||
getSessionDone(): command_pb.SessionDoneMessage | undefined;
|
||||
setSessionDone(value?: command_pb.SessionDoneMessage): void;
|
||||
|
||||
hasSessionOutput(): boolean;
|
||||
clearSessionOutput(): void;
|
||||
getSessionOutput(): command_pb.SessionOutputMessage | undefined;
|
||||
setSessionOutput(value?: command_pb.SessionOutputMessage): void;
|
||||
|
||||
hasIdentifySession(): boolean;
|
||||
clearIdentifySession(): void;
|
||||
getIdentifySession(): command_pb.IdentifySessionMessage | undefined;
|
||||
setIdentifySession(value?: command_pb.IdentifySessionMessage): void;
|
||||
|
||||
hasConnectionFailure(): boolean;
|
||||
clearConnectionFailure(): void;
|
||||
getConnectionFailure(): command_pb.NewConnectionFailureMessage | undefined;
|
||||
setConnectionFailure(value?: command_pb.NewConnectionFailureMessage): void;
|
||||
|
||||
hasConnectionOutput(): boolean;
|
||||
clearConnectionOutput(): void;
|
||||
getConnectionOutput(): command_pb.ConnectionOutputMessage | undefined;
|
||||
setConnectionOutput(value?: command_pb.ConnectionOutputMessage): void;
|
||||
|
||||
hasConnectionClose(): boolean;
|
||||
clearConnectionClose(): void;
|
||||
getConnectionClose(): command_pb.ConnectionCloseMessage | undefined;
|
||||
setConnectionClose(value?: command_pb.ConnectionCloseMessage): void;
|
||||
|
||||
hasConnectionEstablished(): boolean;
|
||||
clearConnectionEstablished(): void;
|
||||
getConnectionEstablished(): command_pb.ConnectionEstablishedMessage | undefined;
|
||||
setConnectionEstablished(value?: command_pb.ConnectionEstablishedMessage): void;
|
||||
|
||||
hasServerFailure(): boolean;
|
||||
clearServerFailure(): void;
|
||||
getServerFailure(): command_pb.NewServerFailureMessage | undefined;
|
||||
setServerFailure(value?: command_pb.NewServerFailureMessage): void;
|
||||
|
||||
hasServerEstablished(): boolean;
|
||||
clearServerEstablished(): void;
|
||||
getServerEstablished(): command_pb.ServerEstablishedMessage | undefined;
|
||||
setServerEstablished(value?: command_pb.ServerEstablishedMessage): void;
|
||||
|
||||
hasServerClose(): boolean;
|
||||
clearServerClose(): void;
|
||||
getServerClose(): command_pb.ServerCloseMessage | undefined;
|
||||
setServerClose(value?: command_pb.ServerCloseMessage): void;
|
||||
|
||||
hasServerConnectionEstablished(): boolean;
|
||||
clearServerConnectionEstablished(): void;
|
||||
getServerConnectionEstablished(): command_pb.ServerConnectionEstablishedMessage | undefined;
|
||||
setServerConnectionEstablished(value?: command_pb.ServerConnectionEstablishedMessage): void;
|
||||
|
||||
hasEvalFailed(): boolean;
|
||||
clearEvalFailed(): void;
|
||||
getEvalFailed(): node_pb.EvalFailedMessage | undefined;
|
||||
@ -210,18 +79,6 @@ export class ServerMessage extends jspb.Message {
|
||||
|
||||
export namespace ServerMessage {
|
||||
export type AsObject = {
|
||||
newSessionFailure?: command_pb.NewSessionFailureMessage.AsObject,
|
||||
sessionDone?: command_pb.SessionDoneMessage.AsObject,
|
||||
sessionOutput?: command_pb.SessionOutputMessage.AsObject,
|
||||
identifySession?: command_pb.IdentifySessionMessage.AsObject,
|
||||
connectionFailure?: command_pb.NewConnectionFailureMessage.AsObject,
|
||||
connectionOutput?: command_pb.ConnectionOutputMessage.AsObject,
|
||||
connectionClose?: command_pb.ConnectionCloseMessage.AsObject,
|
||||
connectionEstablished?: command_pb.ConnectionEstablishedMessage.AsObject,
|
||||
serverFailure?: command_pb.NewServerFailureMessage.AsObject,
|
||||
serverEstablished?: command_pb.ServerEstablishedMessage.AsObject,
|
||||
serverClose?: command_pb.ServerCloseMessage.AsObject,
|
||||
serverConnectionEstablished?: command_pb.ServerConnectionEstablishedMessage.AsObject,
|
||||
evalFailed?: node_pb.EvalFailedMessage.AsObject,
|
||||
evalDone?: node_pb.EvalDoneMessage.AsObject,
|
||||
evalEvent?: node_pb.EvalEventMessage.AsObject,
|
||||
@ -231,18 +88,6 @@ export namespace ServerMessage {
|
||||
|
||||
export enum MsgCase {
|
||||
MSG_NOT_SET = 0,
|
||||
NEW_SESSION_FAILURE = 1,
|
||||
SESSION_DONE = 2,
|
||||
SESSION_OUTPUT = 3,
|
||||
IDENTIFY_SESSION = 4,
|
||||
CONNECTION_FAILURE = 5,
|
||||
CONNECTION_OUTPUT = 6,
|
||||
CONNECTION_CLOSE = 7,
|
||||
CONNECTION_ESTABLISHED = 8,
|
||||
SERVER_FAILURE = 9,
|
||||
SERVER_ESTABLISHED = 10,
|
||||
SERVER_CLOSE = 11,
|
||||
SERVER_CONNECTION_ESTABLISHED = 12,
|
||||
EVAL_FAILED = 13,
|
||||
EVAL_DONE = 14,
|
||||
EVAL_EVENT = 15,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,143 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// Executes a command.
|
||||
// Ensure the id field is unique for each new session. If a client reuses the id of an existing
|
||||
// session, the connection will be closed.
|
||||
// If env is provided, the environment variables will be set.
|
||||
// If tty_dimensions is included, we will spawn a tty for the command using the given dimensions.
|
||||
message NewSessionMessage {
|
||||
uint64 id = 1;
|
||||
string command = 2;
|
||||
repeated string args = 3;
|
||||
map<string, string> env = 4;
|
||||
string cwd = 5;
|
||||
TTYDimensions tty_dimensions = 6;
|
||||
bool is_fork = 7;
|
||||
|
||||
// Janky, but required for having custom handling of the bootstrap fork
|
||||
bool is_bootstrap_fork = 8;
|
||||
}
|
||||
|
||||
// Sent when starting a session failed.
|
||||
message NewSessionFailureMessage {
|
||||
uint64 id = 1;
|
||||
enum Reason {
|
||||
Prohibited = 0;
|
||||
ResourceShortage = 1;
|
||||
}
|
||||
Reason reason = 2;
|
||||
string message = 3;
|
||||
}
|
||||
|
||||
// Sent when a session has completed
|
||||
message SessionDoneMessage {
|
||||
uint64 id = 1;
|
||||
int64 exit_status = 2;
|
||||
}
|
||||
|
||||
// Identifies a session with a PID and a title.
|
||||
// Can be sent multiple times when title changes.
|
||||
message IdentifySessionMessage {
|
||||
uint64 id = 1;
|
||||
uint64 pid = 2;
|
||||
string title = 3;
|
||||
}
|
||||
|
||||
// Writes data to a session.
|
||||
message WriteToSessionMessage {
|
||||
uint64 id = 1;
|
||||
bytes data = 2;
|
||||
enum Source {
|
||||
Stdin = 0;
|
||||
Ipc = 1;
|
||||
}
|
||||
Source source = 3;
|
||||
}
|
||||
|
||||
// Resizes the TTY of the session identified by the id.
|
||||
// The connection will be closed if a TTY was not requested when the session was created.
|
||||
message ResizeSessionTTYMessage {
|
||||
uint64 id = 1;
|
||||
TTYDimensions tty_dimensions = 2;
|
||||
}
|
||||
|
||||
// CloseSessionInputMessage closes the stdin of the session by the ID.
|
||||
message CloseSessionInputMessage {
|
||||
uint64 id = 1;
|
||||
}
|
||||
|
||||
message ShutdownSessionMessage {
|
||||
uint64 id = 1;
|
||||
string signal = 2;
|
||||
}
|
||||
|
||||
// SessionOutputMessage carries data read from the stdout or stderr of the session identified by the id.
|
||||
message SessionOutputMessage {
|
||||
uint64 id = 1;
|
||||
enum Source {
|
||||
Stdout = 0;
|
||||
Stderr = 1;
|
||||
Ipc = 2;
|
||||
}
|
||||
Source source = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message TTYDimensions {
|
||||
uint32 height = 1;
|
||||
uint32 width = 2;
|
||||
}
|
||||
|
||||
// Initializes a new connection to a port or path
|
||||
message NewConnectionMessage {
|
||||
uint64 id = 1;
|
||||
uint64 port = 2;
|
||||
string path = 3;
|
||||
}
|
||||
|
||||
// Sent when a connection has successfully established
|
||||
message ConnectionEstablishedMessage {
|
||||
uint64 id = 1;
|
||||
}
|
||||
|
||||
// Sent when a connection fails
|
||||
message NewConnectionFailureMessage {
|
||||
uint64 id = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
// Sent for connection output
|
||||
message ConnectionOutputMessage {
|
||||
uint64 id = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// Sent to close a connection
|
||||
message ConnectionCloseMessage {
|
||||
uint64 id = 1;
|
||||
}
|
||||
|
||||
message NewServerMessage {
|
||||
uint64 id = 1;
|
||||
uint64 port = 2;
|
||||
string path = 3;
|
||||
}
|
||||
|
||||
message NewServerFailureMessage {
|
||||
uint64 id = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message ServerEstablishedMessage {
|
||||
uint64 id = 1;
|
||||
}
|
||||
|
||||
message ServerCloseMessage {
|
||||
uint64 id = 1;
|
||||
string reason = 2;
|
||||
}
|
||||
|
||||
message ServerConnectionEstablishedMessage {
|
||||
uint64 server_id = 1;
|
||||
uint64 connection_id = 2;
|
||||
}
|
544
packages/protocol/src/proto/command_pb.d.ts
vendored
544
packages/protocol/src/proto/command_pb.d.ts
vendored
@ -1,544 +0,0 @@
|
||||
// package:
|
||||
// file: command.proto
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
|
||||
export class NewSessionMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getCommand(): string;
|
||||
setCommand(value: string): void;
|
||||
|
||||
clearArgsList(): void;
|
||||
getArgsList(): Array<string>;
|
||||
setArgsList(value: Array<string>): void;
|
||||
addArgs(value: string, index?: number): string;
|
||||
|
||||
getEnvMap(): jspb.Map<string, string>;
|
||||
clearEnvMap(): void;
|
||||
getCwd(): string;
|
||||
setCwd(value: string): void;
|
||||
|
||||
hasTtyDimensions(): boolean;
|
||||
clearTtyDimensions(): void;
|
||||
getTtyDimensions(): TTYDimensions | undefined;
|
||||
setTtyDimensions(value?: TTYDimensions): void;
|
||||
|
||||
getIsFork(): boolean;
|
||||
setIsFork(value: boolean): void;
|
||||
|
||||
getIsBootstrapFork(): boolean;
|
||||
setIsBootstrapFork(value: boolean): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): NewSessionMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: NewSessionMessage): NewSessionMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: NewSessionMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): NewSessionMessage;
|
||||
static deserializeBinaryFromReader(message: NewSessionMessage, reader: jspb.BinaryReader): NewSessionMessage;
|
||||
}
|
||||
|
||||
export namespace NewSessionMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
command: string,
|
||||
argsList: Array<string>,
|
||||
envMap: Array<[string, string]>,
|
||||
cwd: string,
|
||||
ttyDimensions?: TTYDimensions.AsObject,
|
||||
isFork: boolean,
|
||||
isBootstrapFork: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
export class NewSessionFailureMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getReason(): NewSessionFailureMessage.Reason;
|
||||
setReason(value: NewSessionFailureMessage.Reason): void;
|
||||
|
||||
getMessage(): string;
|
||||
setMessage(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): NewSessionFailureMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: NewSessionFailureMessage): NewSessionFailureMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: NewSessionFailureMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): NewSessionFailureMessage;
|
||||
static deserializeBinaryFromReader(message: NewSessionFailureMessage, reader: jspb.BinaryReader): NewSessionFailureMessage;
|
||||
}
|
||||
|
||||
export namespace NewSessionFailureMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
reason: NewSessionFailureMessage.Reason,
|
||||
message: string,
|
||||
}
|
||||
|
||||
export enum Reason {
|
||||
PROHIBITED = 0,
|
||||
RESOURCESHORTAGE = 1,
|
||||
}
|
||||
}
|
||||
|
||||
export class SessionDoneMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getExitStatus(): number;
|
||||
setExitStatus(value: number): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): SessionDoneMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: SessionDoneMessage): SessionDoneMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: SessionDoneMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): SessionDoneMessage;
|
||||
static deserializeBinaryFromReader(message: SessionDoneMessage, reader: jspb.BinaryReader): SessionDoneMessage;
|
||||
}
|
||||
|
||||
export namespace SessionDoneMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
exitStatus: number,
|
||||
}
|
||||
}
|
||||
|
||||
export class IdentifySessionMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getPid(): number;
|
||||
setPid(value: number): void;
|
||||
|
||||
getTitle(): string;
|
||||
setTitle(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): IdentifySessionMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: IdentifySessionMessage): IdentifySessionMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: IdentifySessionMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): IdentifySessionMessage;
|
||||
static deserializeBinaryFromReader(message: IdentifySessionMessage, reader: jspb.BinaryReader): IdentifySessionMessage;
|
||||
}
|
||||
|
||||
export namespace IdentifySessionMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
pid: number,
|
||||
title: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteToSessionMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getData(): Uint8Array | string;
|
||||
getData_asU8(): Uint8Array;
|
||||
getData_asB64(): string;
|
||||
setData(value: Uint8Array | string): void;
|
||||
|
||||
getSource(): WriteToSessionMessage.Source;
|
||||
setSource(value: WriteToSessionMessage.Source): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): WriteToSessionMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: WriteToSessionMessage): WriteToSessionMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: WriteToSessionMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): WriteToSessionMessage;
|
||||
static deserializeBinaryFromReader(message: WriteToSessionMessage, reader: jspb.BinaryReader): WriteToSessionMessage;
|
||||
}
|
||||
|
||||
export namespace WriteToSessionMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
data: Uint8Array | string,
|
||||
source: WriteToSessionMessage.Source,
|
||||
}
|
||||
|
||||
export enum Source {
|
||||
STDIN = 0,
|
||||
IPC = 1,
|
||||
}
|
||||
}
|
||||
|
||||
export class ResizeSessionTTYMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
hasTtyDimensions(): boolean;
|
||||
clearTtyDimensions(): void;
|
||||
getTtyDimensions(): TTYDimensions | undefined;
|
||||
setTtyDimensions(value?: TTYDimensions): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ResizeSessionTTYMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ResizeSessionTTYMessage): ResizeSessionTTYMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ResizeSessionTTYMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ResizeSessionTTYMessage;
|
||||
static deserializeBinaryFromReader(message: ResizeSessionTTYMessage, reader: jspb.BinaryReader): ResizeSessionTTYMessage;
|
||||
}
|
||||
|
||||
export namespace ResizeSessionTTYMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
ttyDimensions?: TTYDimensions.AsObject,
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseSessionInputMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): CloseSessionInputMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: CloseSessionInputMessage): CloseSessionInputMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: CloseSessionInputMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): CloseSessionInputMessage;
|
||||
static deserializeBinaryFromReader(message: CloseSessionInputMessage, reader: jspb.BinaryReader): CloseSessionInputMessage;
|
||||
}
|
||||
|
||||
export namespace CloseSessionInputMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
}
|
||||
}
|
||||
|
||||
export class ShutdownSessionMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getSignal(): string;
|
||||
setSignal(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ShutdownSessionMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ShutdownSessionMessage): ShutdownSessionMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ShutdownSessionMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ShutdownSessionMessage;
|
||||
static deserializeBinaryFromReader(message: ShutdownSessionMessage, reader: jspb.BinaryReader): ShutdownSessionMessage;
|
||||
}
|
||||
|
||||
export namespace ShutdownSessionMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
signal: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class SessionOutputMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getSource(): SessionOutputMessage.Source;
|
||||
setSource(value: SessionOutputMessage.Source): void;
|
||||
|
||||
getData(): Uint8Array | string;
|
||||
getData_asU8(): Uint8Array;
|
||||
getData_asB64(): string;
|
||||
setData(value: Uint8Array | string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): SessionOutputMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: SessionOutputMessage): SessionOutputMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: SessionOutputMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): SessionOutputMessage;
|
||||
static deserializeBinaryFromReader(message: SessionOutputMessage, reader: jspb.BinaryReader): SessionOutputMessage;
|
||||
}
|
||||
|
||||
export namespace SessionOutputMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
source: SessionOutputMessage.Source,
|
||||
data: Uint8Array | string,
|
||||
}
|
||||
|
||||
export enum Source {
|
||||
STDOUT = 0,
|
||||
STDERR = 1,
|
||||
IPC = 2,
|
||||
}
|
||||
}
|
||||
|
||||
export class TTYDimensions extends jspb.Message {
|
||||
getHeight(): number;
|
||||
setHeight(value: number): void;
|
||||
|
||||
getWidth(): number;
|
||||
setWidth(value: number): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): TTYDimensions.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: TTYDimensions): TTYDimensions.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: TTYDimensions, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): TTYDimensions;
|
||||
static deserializeBinaryFromReader(message: TTYDimensions, reader: jspb.BinaryReader): TTYDimensions;
|
||||
}
|
||||
|
||||
export namespace TTYDimensions {
|
||||
export type AsObject = {
|
||||
height: number,
|
||||
width: number,
|
||||
}
|
||||
}
|
||||
|
||||
export class NewConnectionMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getPort(): number;
|
||||
setPort(value: number): void;
|
||||
|
||||
getPath(): string;
|
||||
setPath(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): NewConnectionMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: NewConnectionMessage): NewConnectionMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: NewConnectionMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): NewConnectionMessage;
|
||||
static deserializeBinaryFromReader(message: NewConnectionMessage, reader: jspb.BinaryReader): NewConnectionMessage;
|
||||
}
|
||||
|
||||
export namespace NewConnectionMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
port: number,
|
||||
path: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionEstablishedMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ConnectionEstablishedMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ConnectionEstablishedMessage): ConnectionEstablishedMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ConnectionEstablishedMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ConnectionEstablishedMessage;
|
||||
static deserializeBinaryFromReader(message: ConnectionEstablishedMessage, reader: jspb.BinaryReader): ConnectionEstablishedMessage;
|
||||
}
|
||||
|
||||
export namespace ConnectionEstablishedMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
}
|
||||
}
|
||||
|
||||
export class NewConnectionFailureMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getMessage(): string;
|
||||
setMessage(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): NewConnectionFailureMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: NewConnectionFailureMessage): NewConnectionFailureMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: NewConnectionFailureMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): NewConnectionFailureMessage;
|
||||
static deserializeBinaryFromReader(message: NewConnectionFailureMessage, reader: jspb.BinaryReader): NewConnectionFailureMessage;
|
||||
}
|
||||
|
||||
export namespace NewConnectionFailureMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
message: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionOutputMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getData(): Uint8Array | string;
|
||||
getData_asU8(): Uint8Array;
|
||||
getData_asB64(): string;
|
||||
setData(value: Uint8Array | string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ConnectionOutputMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ConnectionOutputMessage): ConnectionOutputMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ConnectionOutputMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ConnectionOutputMessage;
|
||||
static deserializeBinaryFromReader(message: ConnectionOutputMessage, reader: jspb.BinaryReader): ConnectionOutputMessage;
|
||||
}
|
||||
|
||||
export namespace ConnectionOutputMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
data: Uint8Array | string,
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionCloseMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ConnectionCloseMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ConnectionCloseMessage): ConnectionCloseMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ConnectionCloseMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ConnectionCloseMessage;
|
||||
static deserializeBinaryFromReader(message: ConnectionCloseMessage, reader: jspb.BinaryReader): ConnectionCloseMessage;
|
||||
}
|
||||
|
||||
export namespace ConnectionCloseMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
}
|
||||
}
|
||||
|
||||
export class NewServerMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getPort(): number;
|
||||
setPort(value: number): void;
|
||||
|
||||
getPath(): string;
|
||||
setPath(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): NewServerMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: NewServerMessage): NewServerMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: NewServerMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): NewServerMessage;
|
||||
static deserializeBinaryFromReader(message: NewServerMessage, reader: jspb.BinaryReader): NewServerMessage;
|
||||
}
|
||||
|
||||
export namespace NewServerMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
port: number,
|
||||
path: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class NewServerFailureMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getMessage(): string;
|
||||
setMessage(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): NewServerFailureMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: NewServerFailureMessage): NewServerFailureMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: NewServerFailureMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): NewServerFailureMessage;
|
||||
static deserializeBinaryFromReader(message: NewServerFailureMessage, reader: jspb.BinaryReader): NewServerFailureMessage;
|
||||
}
|
||||
|
||||
export namespace NewServerFailureMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
message: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerEstablishedMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ServerEstablishedMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ServerEstablishedMessage): ServerEstablishedMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ServerEstablishedMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ServerEstablishedMessage;
|
||||
static deserializeBinaryFromReader(message: ServerEstablishedMessage, reader: jspb.BinaryReader): ServerEstablishedMessage;
|
||||
}
|
||||
|
||||
export namespace ServerEstablishedMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerCloseMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
getReason(): string;
|
||||
setReason(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ServerCloseMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ServerCloseMessage): ServerCloseMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ServerCloseMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ServerCloseMessage;
|
||||
static deserializeBinaryFromReader(message: ServerCloseMessage, reader: jspb.BinaryReader): ServerCloseMessage;
|
||||
}
|
||||
|
||||
export namespace ServerCloseMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
reason: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerConnectionEstablishedMessage extends jspb.Message {
|
||||
getServerId(): number;
|
||||
setServerId(value: number): void;
|
||||
|
||||
getConnectionId(): number;
|
||||
setConnectionId(value: number): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ServerConnectionEstablishedMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: ServerConnectionEstablishedMessage): ServerConnectionEstablishedMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: ServerConnectionEstablishedMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): ServerConnectionEstablishedMessage;
|
||||
static deserializeBinaryFromReader(message: ServerConnectionEstablishedMessage, reader: jspb.BinaryReader): ServerConnectionEstablishedMessage;
|
||||
}
|
||||
|
||||
export namespace ServerConnectionEstablishedMessage {
|
||||
export type AsObject = {
|
||||
serverId: number,
|
||||
connectionId: number,
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,3 @@
|
||||
export * from "./client_pb";
|
||||
export * from "./command_pb";
|
||||
export * from "./node_pb";
|
||||
export * from "./vscode_pb";
|
||||
|
@ -1,16 +1,5 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message TypedValue {
|
||||
enum Type {
|
||||
String = 0;
|
||||
Number = 1;
|
||||
Object = 2;
|
||||
Boolean = 3;
|
||||
}
|
||||
Type type = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message NewEvalMessage {
|
||||
uint64 id = 1;
|
||||
string function = 2;
|
||||
@ -41,5 +30,5 @@ message EvalFailedMessage {
|
||||
|
||||
message EvalDoneMessage {
|
||||
uint64 id = 1;
|
||||
TypedValue response = 2;
|
||||
string response = 2;
|
||||
}
|
||||
|
39
packages/protocol/src/proto/node_pb.d.ts
vendored
39
packages/protocol/src/proto/node_pb.d.ts
vendored
@ -3,37 +3,6 @@
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
|
||||
export class TypedValue extends jspb.Message {
|
||||
getType(): TypedValue.Type;
|
||||
setType(value: TypedValue.Type): void;
|
||||
|
||||
getValue(): string;
|
||||
setValue(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): TypedValue.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: TypedValue): TypedValue.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: TypedValue, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): TypedValue;
|
||||
static deserializeBinaryFromReader(message: TypedValue, reader: jspb.BinaryReader): TypedValue;
|
||||
}
|
||||
|
||||
export namespace TypedValue {
|
||||
export type AsObject = {
|
||||
type: TypedValue.Type,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export enum Type {
|
||||
STRING = 0,
|
||||
NUMBER = 1,
|
||||
OBJECT = 2,
|
||||
BOOLEAN = 3,
|
||||
}
|
||||
}
|
||||
|
||||
export class NewEvalMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
@ -140,10 +109,8 @@ export class EvalDoneMessage extends jspb.Message {
|
||||
getId(): number;
|
||||
setId(value: number): void;
|
||||
|
||||
hasResponse(): boolean;
|
||||
clearResponse(): void;
|
||||
getResponse(): TypedValue | undefined;
|
||||
setResponse(value?: TypedValue): void;
|
||||
getResponse(): string;
|
||||
setResponse(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): EvalDoneMessage.AsObject;
|
||||
@ -158,7 +125,7 @@ export class EvalDoneMessage extends jspb.Message {
|
||||
export namespace EvalDoneMessage {
|
||||
export type AsObject = {
|
||||
id: number,
|
||||
response?: TypedValue.AsObject,
|
||||
response: string,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* @enhanceable
|
||||
* @suppress {messageConventions} JS Compiler reports an error if a variable or
|
||||
* field starts with 'MSG_' and isn't a translatable message.
|
||||
* @public
|
||||
*/
|
||||
// GENERATED CODE -- DO NOT EDIT!
|
||||
@ -14,204 +16,6 @@ goog.exportSymbol('proto.EvalEventMessage', null, global);
|
||||
goog.exportSymbol('proto.EvalFailedMessage', null, global);
|
||||
goog.exportSymbol('proto.EvalFailedMessage.Reason', null, global);
|
||||
goog.exportSymbol('proto.NewEvalMessage', null, global);
|
||||
goog.exportSymbol('proto.TypedValue', null, global);
|
||||
goog.exportSymbol('proto.TypedValue.Type', null, global);
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.TypedValue = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.TypedValue, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
proto.TypedValue.displayName = 'proto.TypedValue';
|
||||
}
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto suitable for use in Soy templates.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
|
||||
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
|
||||
* for transitional soy proto support: http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.TypedValue.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.TypedValue.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Whether to include the JSPB
|
||||
* instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.TypedValue} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.TypedValue.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
type: msg.getType(),
|
||||
value: msg.getValue()
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.TypedValue}
|
||||
*/
|
||||
proto.TypedValue.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.TypedValue;
|
||||
return proto.TypedValue.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.TypedValue} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.TypedValue}
|
||||
*/
|
||||
proto.TypedValue.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {!proto.TypedValue.Type} */ (reader.readEnum());
|
||||
msg.setType(value);
|
||||
break;
|
||||
case 2:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setValue(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class method variant: serializes the given message to binary data
|
||||
* (in protobuf wire format), writing to the given BinaryWriter.
|
||||
* @param {!proto.TypedValue} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.TypedValue.serializeBinaryToWriter = function(message, writer) {
|
||||
message.serializeBinaryToWriter(writer);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.TypedValue.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
this.serializeBinaryToWriter(writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format),
|
||||
* writing to the given BinaryWriter.
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.TypedValue.prototype.serializeBinaryToWriter = function (writer) {
|
||||
var f = undefined;
|
||||
f = this.getType();
|
||||
if (f !== 0.0) {
|
||||
writer.writeEnum(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getValue();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
2,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a deep clone of this proto. No data is shared with the original.
|
||||
* @return {!proto.TypedValue} The clone.
|
||||
*/
|
||||
proto.TypedValue.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.TypedValue} */ (jspb.Message.cloneMessage(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional Type type = 1;
|
||||
* @return {!proto.TypedValue.Type}
|
||||
*/
|
||||
proto.TypedValue.prototype.getType = function() {
|
||||
return /** @type {!proto.TypedValue.Type} */ (jspb.Message.getFieldProto3(this, 1, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {!proto.TypedValue.Type} value */
|
||||
proto.TypedValue.prototype.setType = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string value = 2;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.TypedValue.prototype.getValue = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.TypedValue.prototype.setValue = function(value) {
|
||||
jspb.Message.setField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
proto.TypedValue.Type = {
|
||||
STRING: 0,
|
||||
NUMBER: 1,
|
||||
OBJECT: 2,
|
||||
BOOLEAN: 3
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
@ -262,14 +66,15 @@ proto.NewEvalMessage.prototype.toObject = function(opt_includeInstance) {
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.NewEvalMessage} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.NewEvalMessage.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
id: msg.getId(),
|
||||
pb_function: msg.getFunction(),
|
||||
argsList: jspb.Message.getField(msg, 3),
|
||||
timeout: msg.getTimeout(),
|
||||
active: msg.getActive()
|
||||
id: jspb.Message.getFieldWithDefault(msg, 1, 0),
|
||||
pb_function: jspb.Message.getFieldWithDefault(msg, 2, ""),
|
||||
argsList: jspb.Message.getRepeatedField(msg, 3),
|
||||
timeout: jspb.Message.getFieldWithDefault(msg, 4, 0),
|
||||
active: jspb.Message.getFieldWithDefault(msg, 5, false)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -316,8 +121,7 @@ proto.NewEvalMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
break;
|
||||
case 3:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.getArgsList().push(value);
|
||||
msg.setArgsList(msg.getArgsList());
|
||||
msg.addArgs(value);
|
||||
break;
|
||||
case 4:
|
||||
var value = /** @type {number} */ (reader.readUint32());
|
||||
@ -336,64 +140,55 @@ proto.NewEvalMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class method variant: serializes the given message to binary data
|
||||
* (in protobuf wire format), writing to the given BinaryWriter.
|
||||
* @param {!proto.NewEvalMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.NewEvalMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
message.serializeBinaryToWriter(writer);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
this.serializeBinaryToWriter(writer);
|
||||
proto.NewEvalMessage.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format),
|
||||
* writing to the given BinaryWriter.
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.NewEvalMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
proto.NewEvalMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = this.getId();
|
||||
f = message.getId();
|
||||
if (f !== 0) {
|
||||
writer.writeUint64(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getFunction();
|
||||
f = message.getFunction();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
2,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getArgsList();
|
||||
f = message.getArgsList();
|
||||
if (f.length > 0) {
|
||||
writer.writeRepeatedString(
|
||||
3,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getTimeout();
|
||||
f = message.getTimeout();
|
||||
if (f !== 0) {
|
||||
writer.writeUint32(
|
||||
4,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getActive();
|
||||
f = message.getActive();
|
||||
if (f) {
|
||||
writer.writeBool(
|
||||
5,
|
||||
@ -403,27 +198,18 @@ proto.NewEvalMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a deep clone of this proto. No data is shared with the original.
|
||||
* @return {!proto.NewEvalMessage} The clone.
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.NewEvalMessage} */ (jspb.Message.cloneMessage(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional uint64 id = 1;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.getId = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
|
||||
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.NewEvalMessage.prototype.setId = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
jspb.Message.setProto3IntField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
@ -432,35 +218,42 @@ proto.NewEvalMessage.prototype.setId = function(value) {
|
||||
* @return {string}
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.getFunction = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.NewEvalMessage.prototype.setFunction = function(value) {
|
||||
jspb.Message.setField(this, 2, value);
|
||||
jspb.Message.setProto3StringField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* repeated string args = 3;
|
||||
* If you change this array by adding, removing or replacing elements, or if you
|
||||
* replace the array itself, then you must call the setter to update it.
|
||||
* @return {!Array.<string>}
|
||||
* @return {!Array<string>}
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.getArgsList = function() {
|
||||
return /** @type {!Array.<string>} */ (jspb.Message.getField(this, 3));
|
||||
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 3));
|
||||
};
|
||||
|
||||
|
||||
/** @param {Array.<string>} value */
|
||||
/** @param {!Array<string>} value */
|
||||
proto.NewEvalMessage.prototype.setArgsList = function(value) {
|
||||
jspb.Message.setField(this, 3, value || []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!string} value
|
||||
* @param {number=} opt_index
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.addArgs = function(value, opt_index) {
|
||||
jspb.Message.addToRepeatedField(this, 3, value, opt_index);
|
||||
};
|
||||
|
||||
|
||||
proto.NewEvalMessage.prototype.clearArgsList = function() {
|
||||
jspb.Message.setField(this, 3, []);
|
||||
this.setArgsList([]);
|
||||
};
|
||||
|
||||
|
||||
@ -469,13 +262,13 @@ proto.NewEvalMessage.prototype.clearArgsList = function() {
|
||||
* @return {number}
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.getTimeout = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 4, 0));
|
||||
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.NewEvalMessage.prototype.setTimeout = function(value) {
|
||||
jspb.Message.setField(this, 4, value);
|
||||
jspb.Message.setProto3IntField(this, 4, value);
|
||||
};
|
||||
|
||||
|
||||
@ -486,13 +279,13 @@ proto.NewEvalMessage.prototype.setTimeout = function(value) {
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.NewEvalMessage.prototype.getActive = function() {
|
||||
return /** @type {boolean} */ (jspb.Message.getFieldProto3(this, 5, false));
|
||||
return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 5, false));
|
||||
};
|
||||
|
||||
|
||||
/** @param {boolean} value */
|
||||
proto.NewEvalMessage.prototype.setActive = function(value) {
|
||||
jspb.Message.setField(this, 5, value);
|
||||
jspb.Message.setProto3BooleanField(this, 5, value);
|
||||
};
|
||||
|
||||
|
||||
@ -546,12 +339,13 @@ proto.EvalEventMessage.prototype.toObject = function(opt_includeInstance) {
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.EvalEventMessage} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.EvalEventMessage.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
id: msg.getId(),
|
||||
event: msg.getEvent(),
|
||||
argsList: jspb.Message.getField(msg, 3)
|
||||
id: jspb.Message.getFieldWithDefault(msg, 1, 0),
|
||||
event: jspb.Message.getFieldWithDefault(msg, 2, ""),
|
||||
argsList: jspb.Message.getRepeatedField(msg, 3)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -598,8 +392,7 @@ proto.EvalEventMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
break;
|
||||
case 3:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.getArgsList().push(value);
|
||||
msg.setArgsList(msg.getArgsList());
|
||||
msg.addArgs(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
@ -610,50 +403,41 @@ proto.EvalEventMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class method variant: serializes the given message to binary data
|
||||
* (in protobuf wire format), writing to the given BinaryWriter.
|
||||
* @param {!proto.EvalEventMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.EvalEventMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
message.serializeBinaryToWriter(writer);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.EvalEventMessage.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
this.serializeBinaryToWriter(writer);
|
||||
proto.EvalEventMessage.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format),
|
||||
* writing to the given BinaryWriter.
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.EvalEventMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.EvalEventMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
proto.EvalEventMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = this.getId();
|
||||
f = message.getId();
|
||||
if (f !== 0) {
|
||||
writer.writeUint64(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getEvent();
|
||||
f = message.getEvent();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
2,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getArgsList();
|
||||
f = message.getArgsList();
|
||||
if (f.length > 0) {
|
||||
writer.writeRepeatedString(
|
||||
3,
|
||||
@ -663,27 +447,18 @@ proto.EvalEventMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a deep clone of this proto. No data is shared with the original.
|
||||
* @return {!proto.EvalEventMessage} The clone.
|
||||
*/
|
||||
proto.EvalEventMessage.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.EvalEventMessage} */ (jspb.Message.cloneMessage(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional uint64 id = 1;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.EvalEventMessage.prototype.getId = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
|
||||
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.EvalEventMessage.prototype.setId = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
jspb.Message.setProto3IntField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
@ -692,35 +467,42 @@ proto.EvalEventMessage.prototype.setId = function(value) {
|
||||
* @return {string}
|
||||
*/
|
||||
proto.EvalEventMessage.prototype.getEvent = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.EvalEventMessage.prototype.setEvent = function(value) {
|
||||
jspb.Message.setField(this, 2, value);
|
||||
jspb.Message.setProto3StringField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* repeated string args = 3;
|
||||
* If you change this array by adding, removing or replacing elements, or if you
|
||||
* replace the array itself, then you must call the setter to update it.
|
||||
* @return {!Array.<string>}
|
||||
* @return {!Array<string>}
|
||||
*/
|
||||
proto.EvalEventMessage.prototype.getArgsList = function() {
|
||||
return /** @type {!Array.<string>} */ (jspb.Message.getField(this, 3));
|
||||
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 3));
|
||||
};
|
||||
|
||||
|
||||
/** @param {Array.<string>} value */
|
||||
/** @param {!Array<string>} value */
|
||||
proto.EvalEventMessage.prototype.setArgsList = function(value) {
|
||||
jspb.Message.setField(this, 3, value || []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!string} value
|
||||
* @param {number=} opt_index
|
||||
*/
|
||||
proto.EvalEventMessage.prototype.addArgs = function(value, opt_index) {
|
||||
jspb.Message.addToRepeatedField(this, 3, value, opt_index);
|
||||
};
|
||||
|
||||
|
||||
proto.EvalEventMessage.prototype.clearArgsList = function() {
|
||||
jspb.Message.setField(this, 3, []);
|
||||
this.setArgsList([]);
|
||||
};
|
||||
|
||||
|
||||
@ -767,12 +549,13 @@ proto.EvalFailedMessage.prototype.toObject = function(opt_includeInstance) {
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.EvalFailedMessage} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.EvalFailedMessage.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
id: msg.getId(),
|
||||
reason: msg.getReason(),
|
||||
message: msg.getMessage()
|
||||
id: jspb.Message.getFieldWithDefault(msg, 1, 0),
|
||||
reason: jspb.Message.getFieldWithDefault(msg, 2, 0),
|
||||
message: jspb.Message.getFieldWithDefault(msg, 3, "")
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -830,50 +613,41 @@ proto.EvalFailedMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class method variant: serializes the given message to binary data
|
||||
* (in protobuf wire format), writing to the given BinaryWriter.
|
||||
* @param {!proto.EvalFailedMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.EvalFailedMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
message.serializeBinaryToWriter(writer);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
this.serializeBinaryToWriter(writer);
|
||||
proto.EvalFailedMessage.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format),
|
||||
* writing to the given BinaryWriter.
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.EvalFailedMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
proto.EvalFailedMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = this.getId();
|
||||
f = message.getId();
|
||||
if (f !== 0) {
|
||||
writer.writeUint64(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getReason();
|
||||
f = message.getReason();
|
||||
if (f !== 0.0) {
|
||||
writer.writeEnum(
|
||||
2,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getMessage();
|
||||
f = message.getMessage();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
3,
|
||||
@ -883,60 +657,6 @@ proto.EvalFailedMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a deep clone of this proto. No data is shared with the original.
|
||||
* @return {!proto.EvalFailedMessage} The clone.
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.EvalFailedMessage} */ (jspb.Message.cloneMessage(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional uint64 id = 1;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.getId = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.EvalFailedMessage.prototype.setId = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional Reason reason = 2;
|
||||
* @return {!proto.EvalFailedMessage.Reason}
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.getReason = function() {
|
||||
return /** @type {!proto.EvalFailedMessage.Reason} */ (jspb.Message.getFieldProto3(this, 2, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {!proto.EvalFailedMessage.Reason} value */
|
||||
proto.EvalFailedMessage.prototype.setReason = function(value) {
|
||||
jspb.Message.setField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string message = 3;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.getMessage = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 3, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.EvalFailedMessage.prototype.setMessage = function(value) {
|
||||
jspb.Message.setField(this, 3, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
@ -946,6 +666,51 @@ proto.EvalFailedMessage.Reason = {
|
||||
CONFLICT: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* optional uint64 id = 1;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.getId = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.EvalFailedMessage.prototype.setId = function(value) {
|
||||
jspb.Message.setProto3IntField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional Reason reason = 2;
|
||||
* @return {!proto.EvalFailedMessage.Reason}
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.getReason = function() {
|
||||
return /** @type {!proto.EvalFailedMessage.Reason} */ (jspb.Message.getFieldWithDefault(this, 2, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {!proto.EvalFailedMessage.Reason} value */
|
||||
proto.EvalFailedMessage.prototype.setReason = function(value) {
|
||||
jspb.Message.setProto3EnumField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string message = 3;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.EvalFailedMessage.prototype.getMessage = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.EvalFailedMessage.prototype.setMessage = function(value) {
|
||||
jspb.Message.setProto3StringField(this, 3, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
@ -989,11 +754,12 @@ proto.EvalDoneMessage.prototype.toObject = function(opt_includeInstance) {
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.EvalDoneMessage} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.EvalDoneMessage.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
id: msg.getId(),
|
||||
response: (f = msg.getResponse()) && proto.TypedValue.toObject(includeInstance, f)
|
||||
id: jspb.Message.getFieldWithDefault(msg, 1, 0),
|
||||
response: jspb.Message.getFieldWithDefault(msg, 2, "")
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -1035,8 +801,7 @@ proto.EvalDoneMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
msg.setId(value);
|
||||
break;
|
||||
case 2:
|
||||
var value = new proto.TypedValue;
|
||||
reader.readMessage(value,proto.TypedValue.deserializeBinaryFromReader);
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setResponse(value);
|
||||
break;
|
||||
default:
|
||||
@ -1048,104 +813,70 @@ proto.EvalDoneMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class method variant: serializes the given message to binary data
|
||||
* (in protobuf wire format), writing to the given BinaryWriter.
|
||||
* @param {!proto.EvalDoneMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.EvalDoneMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
message.serializeBinaryToWriter(writer);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.EvalDoneMessage.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
this.serializeBinaryToWriter(writer);
|
||||
proto.EvalDoneMessage.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format),
|
||||
* writing to the given BinaryWriter.
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.EvalDoneMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.EvalDoneMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
proto.EvalDoneMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = this.getId();
|
||||
f = message.getId();
|
||||
if (f !== 0) {
|
||||
writer.writeUint64(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getResponse();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
f = message.getResponse();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
2,
|
||||
f,
|
||||
proto.TypedValue.serializeBinaryToWriter
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a deep clone of this proto. No data is shared with the original.
|
||||
* @return {!proto.EvalDoneMessage} The clone.
|
||||
*/
|
||||
proto.EvalDoneMessage.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.EvalDoneMessage} */ (jspb.Message.cloneMessage(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional uint64 id = 1;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.EvalDoneMessage.prototype.getId = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
|
||||
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.EvalDoneMessage.prototype.setId = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
jspb.Message.setProto3IntField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional TypedValue response = 2;
|
||||
* @return {proto.TypedValue}
|
||||
* optional string response = 2;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.EvalDoneMessage.prototype.getResponse = function() {
|
||||
return /** @type{proto.TypedValue} */ (
|
||||
jspb.Message.getWrapperField(this, proto.TypedValue, 2));
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {proto.TypedValue|undefined} value */
|
||||
/** @param {string} value */
|
||||
proto.EvalDoneMessage.prototype.setResponse = function(value) {
|
||||
jspb.Message.setWrapperField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
proto.EvalDoneMessage.prototype.clearResponse = function() {
|
||||
this.setResponse(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return{!boolean}
|
||||
*/
|
||||
proto.EvalDoneMessage.prototype.hasResponse = function() {
|
||||
return jspb.Message.getField(this, 2) != null;
|
||||
jspb.Message.setProto3StringField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* @enhanceable
|
||||
* @suppress {messageConventions} JS Compiler reports an error if a variable or
|
||||
* field starts with 'MSG_' and isn't a translatable message.
|
||||
* @public
|
||||
*/
|
||||
// GENERATED CODE -- DO NOT EDIT!
|
||||
@ -53,11 +55,12 @@ proto.SharedProcessActiveMessage.prototype.toObject = function(opt_includeInstan
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.SharedProcessActiveMessage} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.SharedProcessActiveMessage.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
socketPath: msg.getSocketPath(),
|
||||
logPath: msg.getLogPath()
|
||||
socketPath: jspb.Message.getFieldWithDefault(msg, 1, ""),
|
||||
logPath: jspb.Message.getFieldWithDefault(msg, 2, "")
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -111,43 +114,34 @@ proto.SharedProcessActiveMessage.deserializeBinaryFromReader = function(msg, rea
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class method variant: serializes the given message to binary data
|
||||
* (in protobuf wire format), writing to the given BinaryWriter.
|
||||
* @param {!proto.SharedProcessActiveMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.SharedProcessActiveMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
message.serializeBinaryToWriter(writer);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.SharedProcessActiveMessage.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
this.serializeBinaryToWriter(writer);
|
||||
proto.SharedProcessActiveMessage.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format),
|
||||
* writing to the given BinaryWriter.
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.SharedProcessActiveMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.SharedProcessActiveMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
proto.SharedProcessActiveMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = this.getSocketPath();
|
||||
f = message.getSocketPath();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getLogPath();
|
||||
f = message.getLogPath();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
2,
|
||||
@ -157,27 +151,18 @@ proto.SharedProcessActiveMessage.prototype.serializeBinaryToWriter = function (w
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a deep clone of this proto. No data is shared with the original.
|
||||
* @return {!proto.SharedProcessActiveMessage} The clone.
|
||||
*/
|
||||
proto.SharedProcessActiveMessage.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.SharedProcessActiveMessage} */ (jspb.Message.cloneMessage(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string socket_path = 1;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.SharedProcessActiveMessage.prototype.getSocketPath = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.SharedProcessActiveMessage.prototype.setSocketPath = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
jspb.Message.setProto3StringField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
@ -186,13 +171,13 @@ proto.SharedProcessActiveMessage.prototype.setSocketPath = function(value) {
|
||||
* @return {string}
|
||||
*/
|
||||
proto.SharedProcessActiveMessage.prototype.getLogPath = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.SharedProcessActiveMessage.prototype.setLogPath = function(value) {
|
||||
jspb.Message.setField(this, 2, value);
|
||||
jspb.Message.setProto3StringField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,268 +0,0 @@
|
||||
import * as cp from "child_process";
|
||||
import * as net from "net";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { TextEncoder, TextDecoder } from "text-encoding";
|
||||
import { createClient } from "./helpers";
|
||||
import { ChildProcess } from "../src/browser/command";
|
||||
|
||||
(global as any).TextDecoder = TextDecoder; // tslint:disable-line no-any
|
||||
(global as any).TextEncoder = TextEncoder; // tslint:disable-line no-any
|
||||
|
||||
describe("spawn", () => {
|
||||
const client = createClient({
|
||||
dataDirectory: "",
|
||||
workingDirectory: "",
|
||||
builtInExtensionsDirectory: "",
|
||||
forkProvider: (msg): cp.ChildProcess => {
|
||||
return cp.spawn(msg.getCommand(), msg.getArgsList(), {
|
||||
stdio: [null, null, null, "ipc"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns a function that when called returns a promise that resolves with
|
||||
* the next chunk of data from the process.
|
||||
*/
|
||||
const promisifyData = (proc: ChildProcess): (() => Promise<string>) => {
|
||||
// Use a persistent callback instead of creating it in the promise since
|
||||
// otherwise we could lose data that comes in while no promise is listening.
|
||||
let onData: (() => void) | undefined;
|
||||
let buffer: string | undefined;
|
||||
proc.stdout.on("data", (data) => {
|
||||
// Remove everything that isn't a letter, number, or $ to avoid issues
|
||||
// with ANSI escape codes printing inside the test output.
|
||||
buffer = (buffer || "") + data.toString().replace(/[^a-zA-Z0-9$]/g, "");
|
||||
if (onData) {
|
||||
onData();
|
||||
}
|
||||
});
|
||||
|
||||
return (): Promise<string> => new Promise((resolve): void => {
|
||||
onData = (): void => {
|
||||
if (typeof buffer !== "undefined") {
|
||||
const data = buffer;
|
||||
buffer = undefined;
|
||||
onData = undefined;
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
onData();
|
||||
});
|
||||
};
|
||||
|
||||
it("should execute command and return output", (done) => {
|
||||
const proc = client.spawn("echo", ["test"]);
|
||||
proc.stdout.on("data", (data) => {
|
||||
expect(data).toEqual("test\n");
|
||||
});
|
||||
proc.on("exit", (): void => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should create shell", async () => {
|
||||
// Setting the config file to something that shouldn't exist so the test
|
||||
// isn't affected by custom configuration.
|
||||
const proc = client.spawn("/bin/bash", ["--rcfile", "/tmp/test/nope/should/not/exist"], {
|
||||
tty: {
|
||||
columns: 100,
|
||||
rows: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const getData = promisifyData(proc);
|
||||
|
||||
// First it outputs @hostname:cwd
|
||||
expect((await getData()).length).toBeGreaterThan(1);
|
||||
|
||||
// Then it seems to overwrite that with a shorter prompt in the format of
|
||||
// [hostname@user]$
|
||||
expect((await getData())).toContain("$");
|
||||
|
||||
proc.kill();
|
||||
|
||||
await new Promise((resolve): void => {
|
||||
proc.on("exit", resolve);
|
||||
});
|
||||
});
|
||||
|
||||
it("should cat", (done) => {
|
||||
const proc = client.spawn("cat", []);
|
||||
expect(proc.pid).toBeUndefined();
|
||||
proc.stdout.on("data", (data) => {
|
||||
expect(data).toEqual("banana");
|
||||
expect(proc.pid).toBeDefined();
|
||||
proc.kill();
|
||||
});
|
||||
proc.on("exit", () => done());
|
||||
proc.send("banana");
|
||||
proc.stdin.end();
|
||||
});
|
||||
|
||||
it("should print env variable", (done) => {
|
||||
const proc = client.spawn("env", [], {
|
||||
env: { hi: "donkey" },
|
||||
});
|
||||
proc.stdout.on("data", (data) => {
|
||||
expect(data).toEqual("hi=donkey\n");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should resize", async () => {
|
||||
// Requires the `tput lines` cmd to be available.
|
||||
// Setting the config file to something that shouldn't exist so the test
|
||||
// isn't affected by custom configuration.
|
||||
const proc = client.spawn("/bin/bash", ["--rcfile", "/tmp/test/nope/should/not/exist"], {
|
||||
tty: {
|
||||
columns: 10,
|
||||
rows: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const getData = promisifyData(proc);
|
||||
|
||||
// We've already tested these first two bits of output; see shell test.
|
||||
await getData();
|
||||
await getData();
|
||||
|
||||
proc.send("tput lines\n");
|
||||
expect(await getData()).toContain("tput");
|
||||
|
||||
expect((await getData()).trim()).toContain("10");
|
||||
proc.resize!({
|
||||
columns: 10,
|
||||
rows: 50,
|
||||
});
|
||||
|
||||
// The prompt again.
|
||||
await getData();
|
||||
await getData();
|
||||
|
||||
proc.send("tput lines\n");
|
||||
expect(await getData()).toContain("tput");
|
||||
|
||||
expect((await getData())).toContain("50");
|
||||
|
||||
proc.kill();
|
||||
expect(proc.killed).toBeTruthy();
|
||||
await new Promise((resolve): void => {
|
||||
proc.on("exit", resolve);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fork and echo messages", (done) => {
|
||||
const proc = client.fork(path.join(__dirname, "forker.js"));
|
||||
proc.on("message", (msg) => {
|
||||
expect(msg.bananas).toBeTruthy();
|
||||
proc.kill();
|
||||
});
|
||||
proc.send({ bananas: true }, undefined, true);
|
||||
proc.on("exit", () => done());
|
||||
});
|
||||
});
|
||||
|
||||
describe("createConnection", () => {
|
||||
const client = createClient();
|
||||
const tmpPath = path.join(os.tmpdir(), Math.random().toString());
|
||||
let server: net.Server;
|
||||
beforeAll(async () => {
|
||||
await new Promise((r): void => {
|
||||
server = net.createServer().listen(tmpPath, () => {
|
||||
r();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
it("should connect to socket", async () => {
|
||||
await new Promise((resolve): void => {
|
||||
const socket = client.createConnection(tmpPath, () => {
|
||||
socket.end();
|
||||
socket.addListener("close", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve): void => {
|
||||
const socket = new client.Socket();
|
||||
socket.connect(tmpPath, () => {
|
||||
socket.end();
|
||||
socket.addListener("close", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should get data from server", (done) => {
|
||||
server.once("connection", (socket: net.Socket) => {
|
||||
socket.write("hi how r u");
|
||||
});
|
||||
|
||||
const socket = client.createConnection(tmpPath);
|
||||
|
||||
socket.addListener("data", (data) => {
|
||||
expect(data.toString()).toEqual("hi how r u");
|
||||
socket.end();
|
||||
socket.addListener("close", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send data to server", (done) => {
|
||||
const clientSocket = client.createConnection(tmpPath);
|
||||
clientSocket.write(Buffer.from("bananas"));
|
||||
server.once("connection", (socket: net.Socket) => {
|
||||
socket.addListener("data", (data) => {
|
||||
expect(data.toString()).toEqual("bananas");
|
||||
socket.end();
|
||||
clientSocket.addListener("end", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createServer", () => {
|
||||
const client = createClient();
|
||||
const tmpPath = path.join(os.tmpdir(), Math.random().toString());
|
||||
|
||||
it("should connect to server", (done) => {
|
||||
const s = client.createServer(() => {
|
||||
s.close();
|
||||
});
|
||||
s.on("close", () => {
|
||||
done();
|
||||
});
|
||||
s.listen(tmpPath);
|
||||
});
|
||||
|
||||
it("should connect to server and get socket connection", (done) => {
|
||||
const s = client.createServer();
|
||||
s.listen(tmpPath, () => {
|
||||
net.createConnection(tmpPath, () => {
|
||||
checks++;
|
||||
s.close();
|
||||
});
|
||||
});
|
||||
let checks = 0;
|
||||
s.on("connection", (con) => {
|
||||
expect(checks).toEqual(1);
|
||||
con.end();
|
||||
checks++;
|
||||
});
|
||||
s.on("close", () => {
|
||||
expect(checks).toEqual(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
@ -48,7 +48,7 @@ describe("Evaluate", () => {
|
||||
|
||||
it("should resolve with promise", async () => {
|
||||
const value = await client.evaluate(async () => {
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
await new Promise((r): number => setTimeout(r, 100));
|
||||
|
||||
return "donkey";
|
||||
});
|
||||
@ -64,6 +64,11 @@ describe("Evaluate", () => {
|
||||
ae.emit("close");
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
onDidDispose: (): void => undefined,
|
||||
dispose: (): void => undefined,
|
||||
};
|
||||
});
|
||||
runner.emit("1");
|
||||
runner.on("2", () => runner.emit("3"));
|
||||
|
@ -27,6 +27,7 @@ export class Entry extends Command {
|
||||
// Dev flags
|
||||
"bootstrap-fork": flags.string({ hidden: true }),
|
||||
env: flags.string({ hidden: true }),
|
||||
args: flags.string({ hidden: true }),
|
||||
};
|
||||
public static args = [{
|
||||
name: "workdir",
|
||||
@ -57,10 +58,6 @@ export class Entry extends Command {
|
||||
|
||||
const { args, flags } = this.parse(Entry);
|
||||
|
||||
if (flags.env) {
|
||||
Object.assign(process.env, JSON.parse(flags.env));
|
||||
}
|
||||
|
||||
const builtInExtensionsDir = path.join(buildDir || path.join(__dirname, ".."), "build/extensions");
|
||||
if (flags["bootstrap-fork"]) {
|
||||
const modulePath = flags["bootstrap-fork"];
|
||||
@ -69,6 +66,13 @@ export class Entry extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Object.assign(process.env, flags.env ? JSON.parse(flags.env) : {});
|
||||
((flags.args ? JSON.parse(flags.args) : []) as string[]).forEach((arg, i) => {
|
||||
// [0] contains the binary running the script (`node` for example) and
|
||||
// [1] contains the script name, so the arguments come after that.
|
||||
process.argv[i + 2] = arg;
|
||||
});
|
||||
|
||||
return requireModule(modulePath, builtInExtensionsDir);
|
||||
}
|
||||
|
||||
@ -110,7 +114,7 @@ export class Entry extends Command {
|
||||
const app = createApp((app) => {
|
||||
app.use((req, res, next) => {
|
||||
res.on("finish", () => {
|
||||
logger.debug(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.url}`, field("host", req.hostname), field("ip", req.ip));
|
||||
logger.trace(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.url}`, field("host", req.hostname), field("ip", req.ip));
|
||||
});
|
||||
|
||||
next();
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { logger } from "@coder/logger";
|
||||
import { ReadWriteConnection } from "@coder/protocol";
|
||||
import { Server, ServerOptions } from "@coder/protocol/src/node/server";
|
||||
import { NewSessionMessage } from "@coder/protocol/src/proto";
|
||||
import { ChildProcess } from "child_process";
|
||||
import * as express from "express";
|
||||
//@ts-ignore
|
||||
import * as expressStaticGzip from "express-static-gzip";
|
||||
@ -12,7 +10,6 @@ import * as mime from "mime-types";
|
||||
import * as path from "path";
|
||||
import * as util from "util";
|
||||
import * as ws from "ws";
|
||||
import { forkModule } from "./vscode/bootstrapFork";
|
||||
import { isCli, buildDir } from "./constants";
|
||||
|
||||
export const createApp = (registerMiddleware?: (app: express.Application) => void, options?: ServerOptions): {
|
||||
@ -51,23 +48,7 @@ export const createApp = (registerMiddleware?: (app: express.Application) => voi
|
||||
onClose: (cb): void => ws.addEventListener("close", () => cb()),
|
||||
};
|
||||
|
||||
const server = new Server(connection, options ? {
|
||||
...options,
|
||||
forkProvider: (message: NewSessionMessage): ChildProcess => {
|
||||
let proc: ChildProcess;
|
||||
if (message.getIsBootstrapFork()) {
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
message.getEnvMap().forEach((value, key) => {
|
||||
env[key] = value;
|
||||
});
|
||||
proc = forkModule(message.getCommand(), env);
|
||||
} else {
|
||||
throw new Error("No support for non bootstrap-forking yet");
|
||||
}
|
||||
|
||||
return proc;
|
||||
},
|
||||
} : undefined);
|
||||
const server = new Server(connection, options);
|
||||
});
|
||||
|
||||
const baseDir = buildDir || path.join(__dirname, "..");
|
||||
|
@ -16,10 +16,11 @@ export const requireModule = (modulePath: string, builtInExtensionsDir: string):
|
||||
* Used for loading extensions. Using __non_webpack_require__ didn't work
|
||||
* as it was not resolving to the FS.
|
||||
*/
|
||||
(global as any).nativeNodeRequire = (id: string) => {
|
||||
(global as any).nativeNodeRequire = (id: string): any => {// tslint:disable-line no-any
|
||||
const customMod = new mod.Module(id);
|
||||
customMod.filename = id;
|
||||
customMod.paths = (<any>mod)._nodeModulePaths(path.dirname(id));
|
||||
// tslint:disable-next-line no-any
|
||||
customMod.paths = (mod as any)._nodeModulePaths(path.dirname(id));
|
||||
|
||||
if (id.startsWith(builtInExtensionsDir)) {
|
||||
customMod.loaded = true;
|
||||
@ -28,6 +29,7 @@ export const requireModule = (modulePath: string, builtInExtensionsDir: string):
|
||||
filename: id + ".js",
|
||||
});
|
||||
req(customMod.exports, customMod.require.bind(customMod), customMod, __filename, path.dirname(id));
|
||||
|
||||
return customMod.exports;
|
||||
}
|
||||
|
||||
@ -54,20 +56,24 @@ export const requireModule = (modulePath: string, builtInExtensionsDir: string):
|
||||
* cp.stderr.on("data", (data) => console.log(data.toString("utf8")));
|
||||
* @param modulePath Path of the VS Code module to load.
|
||||
*/
|
||||
export const forkModule = (modulePath: string, env?: NodeJS.ProcessEnv): cp.ChildProcess => {
|
||||
let proc: cp.ChildProcess | undefined;
|
||||
|
||||
const args = ["--bootstrap-fork", modulePath];
|
||||
if (env) {
|
||||
args.push("--env", JSON.stringify(env));
|
||||
export const forkModule = (modulePath: string, args: string[], options: cp.ForkOptions): cp.ChildProcess => {
|
||||
let proc: cp.ChildProcess;
|
||||
const forkArgs = ["--bootstrap-fork", modulePath];
|
||||
if (args) {
|
||||
forkArgs.push("--args", JSON.stringify(args));
|
||||
}
|
||||
const options: cp.SpawnOptions = {
|
||||
if (options.env) {
|
||||
// This prevents vscode from trying to load original-fs from electron.
|
||||
delete options.env.ELECTRON_RUN_AS_NODE;
|
||||
forkArgs.push("--env", JSON.stringify(options.env));
|
||||
}
|
||||
const forkOptions: cp.ForkOptions = {
|
||||
stdio: [null, null, null, "ipc"],
|
||||
};
|
||||
if (isCli) {
|
||||
proc = cp.execFile(process.execPath, args, options);
|
||||
proc = cp.execFile(process.execPath, forkArgs, forkOptions);
|
||||
} else {
|
||||
proc = cp.spawn(process.execPath, ["--require", "ts-node/register", "--require", "tsconfig-paths/register", process.argv[1], ...args], options);
|
||||
proc = cp.spawn(process.execPath, ["--require", "ts-node/register", "--require", "tsconfig-paths/register", process.argv[1], ...forkArgs], forkOptions);
|
||||
}
|
||||
|
||||
return proc;
|
||||
|
@ -65,9 +65,11 @@ export class SharedProcess {
|
||||
state: SharedProcessState.Starting,
|
||||
});
|
||||
let resolved: boolean = false;
|
||||
this.activeProcess = forkModule("vs/code/electron-browser/sharedProcess/sharedProcessMain", {
|
||||
this.activeProcess = forkModule("vs/code/electron-browser/sharedProcess/sharedProcessMain", [], {
|
||||
env: {
|
||||
VSCODE_ALLOW_IO: "true",
|
||||
VSCODE_LOGS: process.env.VSCODE_LOGS,
|
||||
},
|
||||
});
|
||||
this.activeProcess.on("exit", (err) => {
|
||||
if (this._state !== SharedProcessState.Stopped) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "./fill/require";
|
||||
import * as paths from "./fill/paths";
|
||||
import "./fill/platform";
|
||||
import "./fill/storageDatabase";
|
||||
|
@ -1,40 +1,79 @@
|
||||
import { client } from "@coder/ide/src/fill/client";
|
||||
import { EventEmitter } from "events";
|
||||
import * as nodePty from "node-pty";
|
||||
import { ChildProcess } from "@coder/protocol/src/browser/command";
|
||||
import { ActiveEval } from "@coder/protocol";
|
||||
|
||||
type nodePtyType = typeof nodePty;
|
||||
// Use this to prevent Webpack from hijacking require.
|
||||
declare var __non_webpack_require__: typeof require;
|
||||
|
||||
/**
|
||||
* Implementation of nodePty for the browser.
|
||||
*/
|
||||
class Pty implements nodePty.IPty {
|
||||
private readonly emitter = new EventEmitter();
|
||||
private readonly cp: ChildProcess;
|
||||
private readonly ae: ActiveEval;
|
||||
private _pid = -1;
|
||||
private _process = "";
|
||||
|
||||
public constructor(file: string, args: string[] | string, options: nodePty.IPtyForkOptions) {
|
||||
this.cp = client.spawn(file, Array.isArray(args) ? args : [args], {
|
||||
this.ae = client.run((ae, file, args, options) => {
|
||||
const nodePty = __non_webpack_require__("node-pty") as typeof import("node-pty");
|
||||
const { preserveEnv } = __non_webpack_require__("@coder/ide/src/fill/evaluation") as typeof import("@coder/ide/src/fill/evaluation");
|
||||
|
||||
preserveEnv(options);
|
||||
|
||||
const ptyProc = nodePty.spawn(file, args, options);
|
||||
|
||||
let process = ptyProc.process;
|
||||
ae.emit("process", process);
|
||||
ae.emit("pid", ptyProc.pid);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (ptyProc.process !== process) {
|
||||
process = ptyProc.process;
|
||||
ae.emit("process", process);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
ptyProc.on("exit", (code, signal) => {
|
||||
clearTimeout(timer);
|
||||
ae.emit("exit", code, signal);
|
||||
});
|
||||
|
||||
ptyProc.on("data", (data) => ae.emit("data", data));
|
||||
|
||||
ae.on("resize", (cols, rows) => ptyProc.resize(cols, rows));
|
||||
ae.on("write", (data) => ptyProc.write(data));
|
||||
ae.on("kill", (signal) => ptyProc.kill(signal));
|
||||
|
||||
return {
|
||||
onDidDispose: (cb): void => ptyProc.on("exit", cb),
|
||||
dispose: (): void => {
|
||||
ptyProc.kill();
|
||||
setTimeout(() => ptyProc.kill("SIGKILL"), 5000); // Double tap.
|
||||
},
|
||||
};
|
||||
}, file, Array.isArray(args) ? args : [args], {
|
||||
...options,
|
||||
tty: {
|
||||
columns: options.cols || 100,
|
||||
rows: options.rows || 100,
|
||||
},
|
||||
});
|
||||
this.on("write", (d) => this.cp.send(d));
|
||||
this.on("kill", (exitCode) => this.cp.kill(exitCode));
|
||||
this.on("resize", (cols, rows) => this.cp.resize!({ columns: cols, rows }));
|
||||
}, file, args, options);
|
||||
|
||||
this.cp.stdout.on("data", (data) => this.emitter.emit("data", data));
|
||||
this.cp.stderr.on("data", (data) => this.emitter.emit("data", data));
|
||||
this.cp.on("exit", (code) => this.emitter.emit("exit", code));
|
||||
this.ae.on("pid", (pid) => this._pid = pid);
|
||||
this.ae.on("process", (process) => this._process = process);
|
||||
|
||||
this.ae.on("exit", (code, signal) => this.emitter.emit("exit", code, signal));
|
||||
this.ae.on("data", (data) => this.emitter.emit("data", data));
|
||||
}
|
||||
|
||||
public get pid(): number {
|
||||
return this.cp.pid!;
|
||||
return this._pid;
|
||||
}
|
||||
|
||||
public get process(): string {
|
||||
return this.cp.title!;
|
||||
return this._process;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
@ -43,19 +82,19 @@ class Pty implements nodePty.IPty {
|
||||
}
|
||||
|
||||
public resize(columns: number, rows: number): void {
|
||||
this.emitter.emit("resize", columns, rows);
|
||||
this.ae.emit("resize", columns, rows);
|
||||
}
|
||||
|
||||
public write(data: string): void {
|
||||
this.emitter.emit("write", data);
|
||||
this.ae.emit("write", data);
|
||||
}
|
||||
|
||||
public kill(signal?: string): void {
|
||||
this.emitter.emit("kill", signal);
|
||||
this.ae.emit("kill", signal);
|
||||
}
|
||||
}
|
||||
|
||||
const ptyType: nodePtyType = {
|
||||
const ptyType: typeof nodePty = {
|
||||
spawn: (file: string, args: string[] | string, options: nodePty.IPtyForkOptions): nodePty.IPty => {
|
||||
return new Pty(file, args, options);
|
||||
},
|
||||
|
@ -1 +0,0 @@
|
||||
require("path").posix = require("path");
|
@ -1,184 +1,67 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { appendFile, stat, readdir } from "fs";
|
||||
/// <reference path="../../../../lib/vscode/src/typings/spdlog.d.ts" />
|
||||
import { RotatingLogger as NodeRotatingLogger } from "spdlog";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import { escapePath } from "@coder/protocol";
|
||||
import { logger } from "@coder/logger";
|
||||
import { client } from "@coder/ide/src/fill/client";
|
||||
|
||||
// TODO: It would be better to spawn an actual spdlog instance on the server and
|
||||
// use that for the logging. Or maybe create an instance when the server starts,
|
||||
// and just always use that one (make it part of the protocol).
|
||||
declare var __non_webpack_require__: typeof require;
|
||||
|
||||
const ae = client.run((ae) => {
|
||||
const spdlog = __non_webpack_require__("spdlog") as typeof import("spdlog");
|
||||
const loggers = new Map<number, NodeRotatingLogger>();
|
||||
|
||||
ae.on("new", (id, name, filePath, fileSize, fileCount) => {
|
||||
const logger = new spdlog.RotatingLogger(name, filePath, fileSize, fileCount);
|
||||
loggers.set(id, logger);
|
||||
});
|
||||
|
||||
ae.on("clearFormatters", (id) => loggers.get(id)!.clearFormatters());
|
||||
ae.on("critical", (id, message) => loggers.get(id)!.critical(message));
|
||||
ae.on("debug", (id, message) => loggers.get(id)!.debug(message));
|
||||
ae.on("drop", (id) => loggers.get(id)!.drop());
|
||||
ae.on("errorLog", (id, message) => loggers.get(id)!.error(message));
|
||||
ae.on("flush", (id) => loggers.get(id)!.flush());
|
||||
ae.on("info", (id, message) => loggers.get(id)!.info(message));
|
||||
ae.on("setAsyncMode", (bufferSize, flushInterval) => spdlog.setAsyncMode(bufferSize, flushInterval));
|
||||
ae.on("setLevel", (id, level) => loggers.get(id)!.setLevel(level));
|
||||
ae.on("trace", (id, message) => loggers.get(id)!.trace(message));
|
||||
ae.on("warn", (id, message) => loggers.get(id)!.warn(message));
|
||||
|
||||
const disposeCallbacks = <Array<() => void>>[];
|
||||
|
||||
return {
|
||||
onDidDispose: (cb): number => disposeCallbacks.push(cb),
|
||||
dispose: (): void => {
|
||||
loggers.forEach((logger) => logger.flush());
|
||||
loggers.clear();
|
||||
disposeCallbacks.forEach((cb) => cb());
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const spdLogger = logger.named("spdlog");
|
||||
ae.on("close", () => spdLogger.error("session closed prematurely"));
|
||||
ae.on("error", (error) => spdLogger.error(error.message));
|
||||
|
||||
let id = 0;
|
||||
export class RotatingLogger implements NodeRotatingLogger {
|
||||
private format = true;
|
||||
private buffer = "";
|
||||
private flushPromise: Promise<void> | undefined;
|
||||
private name: string;
|
||||
private logDirectory: string;
|
||||
private escapedLogDirectory: string;
|
||||
private fullFilePath: string;
|
||||
private fileName: string;
|
||||
private fileExt: string | undefined;
|
||||
private escapedFilePath: string;
|
||||
private filesize: number;
|
||||
private filecount: number;
|
||||
private readonly id = id++;
|
||||
|
||||
public constructor(name: string, filePath: string, filesize: number, filecount: number) {
|
||||
this.name = name;
|
||||
this.filesize = filesize;
|
||||
this.filecount = filecount;
|
||||
|
||||
this.fullFilePath = filePath;
|
||||
const slashIndex = filePath.lastIndexOf("/");
|
||||
const dotIndex = filePath.lastIndexOf(".");
|
||||
this.logDirectory = slashIndex !== -1 ? filePath.substring(0, slashIndex) : "/";
|
||||
this.fileName = filePath.substring(slashIndex + 1, dotIndex !== -1 ? dotIndex : undefined);
|
||||
this.fileExt = dotIndex !== -1 ? filePath.substring(dotIndex + 1) : undefined;
|
||||
|
||||
this.escapedLogDirectory = escapePath(this.logDirectory);
|
||||
this.escapedFilePath = escapePath(filePath);
|
||||
|
||||
this.flushPromise = new Promise((resolve): void => {
|
||||
exec(`mkdir -p ${this.escapedLogDirectory}; touch ${this.escapedFilePath}`, async (error) => {
|
||||
if (!error) {
|
||||
try {
|
||||
await this.doFlush();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
logger.error(error.message, field("error", error));
|
||||
}
|
||||
this.flushPromise = undefined;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
public constructor(name: string, filePath: string, fileSize: number, fileCount: number) {
|
||||
ae.emit("new", this.id, name, filePath, fileSize, fileCount);
|
||||
}
|
||||
|
||||
public trace(message: string): void {
|
||||
this.write("trace", message);
|
||||
public trace(message: string): void { ae.emit("trace", this.id, message); }
|
||||
public debug(message: string): void { ae.emit("debug", this.id, message); }
|
||||
public info(message: string): void { ae.emit("info", this.id, message); }
|
||||
public warn(message: string): void { ae.emit("warn", this.id, message); }
|
||||
public error(message: string): void { ae.emit("errorLog", this.id, message); }
|
||||
public critical(message: string): void { ae.emit("critical", this.id, message); }
|
||||
public setLevel(level: number): void { ae.emit("setLevel", this.id, level); }
|
||||
public clearFormatters(): void { ae.emit("clearFormatters", this.id); }
|
||||
public flush(): void { ae.emit("flush", this.id); }
|
||||
public drop(): void { ae.emit("drop", this.id); }
|
||||
}
|
||||
|
||||
public debug(message: string): void {
|
||||
this.write("debug", message);
|
||||
}
|
||||
|
||||
public info(message: string): void {
|
||||
this.write("info", message);
|
||||
}
|
||||
|
||||
public warn(message: string): void {
|
||||
this.write("warn", message);
|
||||
}
|
||||
|
||||
public error(message: string): void {
|
||||
this.write("error", message);
|
||||
}
|
||||
|
||||
public critical(message: string): void {
|
||||
this.write("critical", message);
|
||||
}
|
||||
|
||||
public setLevel(): void {
|
||||
// Should output everything.
|
||||
}
|
||||
|
||||
public clearFormatters(): void {
|
||||
this.format = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the buffer. Only one process runs at a time to prevent race
|
||||
* conditions.
|
||||
*/
|
||||
public flush(): Promise<void> {
|
||||
if (!this.flushPromise) {
|
||||
this.flushPromise = this.doFlush().then(() => {
|
||||
this.flushPromise = undefined;
|
||||
}).catch((error) => {
|
||||
this.flushPromise = undefined;
|
||||
logger.error(error.message, field("error", error));
|
||||
});
|
||||
}
|
||||
|
||||
return this.flushPromise;
|
||||
}
|
||||
|
||||
public drop(): void {
|
||||
this.buffer = "";
|
||||
}
|
||||
|
||||
private pad(num: number, length: number = 2, prefix: string = "0"): string {
|
||||
const str = num.toString();
|
||||
|
||||
return (length > str.length ? prefix.repeat(length - str.length) : "") + str;
|
||||
}
|
||||
|
||||
private write(severity: string, message: string): void {
|
||||
if (this.format) {
|
||||
const date = new Date();
|
||||
const dateStr = `${date.getFullYear()}-${this.pad(date.getMonth() + 1)}-${this.pad(date.getDate())}`
|
||||
+ ` ${this.pad(date.getHours())}:${this.pad(date.getMinutes())}:${this.pad(date.getSeconds())}.${this.pad(date.getMilliseconds(), 3)}`;
|
||||
this.buffer += `[${dateStr}] [${this.name}] [${severity}] `;
|
||||
}
|
||||
this.buffer += message;
|
||||
if (this.format) {
|
||||
this.buffer += "\n";
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
|
||||
private async rotate(): Promise<void> {
|
||||
const stats = await promisify(stat)(this.fullFilePath);
|
||||
if (stats.size < this.filesize) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reExt = typeof this.fileExt !== "undefined" ? `\\.${this.fileExt}` : "";
|
||||
const re = new RegExp(`^${this.fileName}(?:\\.(\\d+))?${reExt}$`);
|
||||
const orderedFiles: string[] = [];
|
||||
(await promisify(readdir)(this.logDirectory)).forEach((file) => {
|
||||
const match = re.exec(file);
|
||||
if (match) {
|
||||
orderedFiles[typeof match[1] !== "undefined" ? parseInt(match[1], 10) : 0] = file;
|
||||
}
|
||||
});
|
||||
|
||||
// Rename in reverse so we don't overwrite before renaming something.
|
||||
let count = 0;
|
||||
const command = orderedFiles.map((file) => {
|
||||
const fileExt = typeof this.fileExt !== "undefined" ? `.${this.fileExt}` : "";
|
||||
const newFile = `${this.logDirectory}/${this.fileName}.${++count}${fileExt}`;
|
||||
|
||||
return count >= this.filecount
|
||||
? `rm ${escapePath(this.logDirectory + "/" + file)}`
|
||||
: `mv ${escapePath(this.logDirectory + "/" + file)} ${escapePath(newFile)}`;
|
||||
}).reverse().concat([
|
||||
`touch ${escapePath(this.fullFilePath)}`,
|
||||
]).join(";");
|
||||
|
||||
await promisify(exec)(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the entire buffer, including anything added in the meantime, and
|
||||
* rotates the log if necessary.
|
||||
*/
|
||||
private async doFlush(): Promise<void> {
|
||||
const writeBuffer = async (): Promise<void> => {
|
||||
const toWrite = this.buffer;
|
||||
this.buffer = "";
|
||||
|
||||
await promisify(appendFile)(this.fullFilePath, toWrite);
|
||||
};
|
||||
|
||||
while (this.buffer.length > 0) {
|
||||
await writeBuffer();
|
||||
await this.rotate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const setAsyncMode = (): void => {
|
||||
// Nothing to do.
|
||||
export const setAsyncMode = (bufferSize: number, flushInterval: number): void => {
|
||||
ae.emit("setAsyncMode", bufferSize, flushInterval);
|
||||
};
|
||||
|
@ -38,6 +38,11 @@ module.exports = merge({
|
||||
"readline": path.join(fills, "empty.ts"),
|
||||
"oniguruma": path.join(fills, "empty.ts"),
|
||||
|
||||
// Webpack includes path-browserify but not the latest version, so
|
||||
// path.posix and path.parse are undefined (among other things possibly).
|
||||
// Also if we don't provide the full path, the code in vscode will import
|
||||
// from vscode's node_modules which is the wrong version.
|
||||
"path": path.join(root, "node_modules", "path-browserify"),
|
||||
"crypto": "crypto-browserify",
|
||||
"http": "http-browserify",
|
||||
|
||||
|
@ -122,12 +122,37 @@ index 8d182d18d9..69d51e1aea 100644
|
||||
@@ -132 +132 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
|
||||
- process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
+ // process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts
|
||||
index 2d798bf2df..9ccadacb3a 100644
|
||||
--- a/src/vs/workbench/parts/debug/node/debugAdapter.ts
|
||||
+++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts
|
||||
@@ -315 +315 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
|
||||
- return new Promise<void>((resolve, reject) => {
|
||||
+ return new Promise<void>(async (resolve, reject) => {
|
||||
@@ -320 +320 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
|
||||
- if (!fs.existsSync(this.adapterExecutable.command)) {
|
||||
+ if (!(await require("util").promisify(fs.exists)(this.adapterExecutable.command))) {
|
||||
diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts
|
||||
index e600fb2f78..1e0dc9a220 100644
|
||||
--- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts
|
||||
+++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts
|
||||
@@ -934,0 +935 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop {
|
||||
+ return (require('vs/../../../../packages/vscode') as typeof import ('vs/../../../../packages/vscode')).client.handleExternalDrop(target, originalEvent);
|
||||
diff --git a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts
|
||||
index 4015c9cd5d..bebdb25f6c 100644
|
||||
--- a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts
|
||||
+++ b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts
|
||||
@@ -31 +31,2 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution {
|
||||
- outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(join(environmentService.logsPath, `main.log`)), log: true });
|
||||
+ // This channel only seems to be used when loading the app and we skip all of that, so it is never actually created or written to.
|
||||
+ // outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(join(environmentService.logsPath, `main.log`)), log: true });
|
||||
diff --git a/src/vs/workbench/parts/output/common/outputLinkProvider.ts b/src/vs/workbench/parts/output/common/outputLinkProvider.ts
|
||||
index 63437034c9..acd82c8375 100644
|
||||
--- a/src/vs/workbench/parts/output/common/outputLinkProvider.ts
|
||||
+++ b/src/vs/workbench/parts/output/common/outputLinkProvider.ts
|
||||
@@ -77,0 +78,2 @@ export class OutputLinkProvider {
|
||||
+ // TODO@coder: get this working.
|
||||
+ return Promise.resolve([]);
|
||||
diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts
|
||||
index 7b4e8721ac..96d612f940 100644
|
||||
--- a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts
|
||||
|
@ -44,7 +44,7 @@ module.exports = (options = {}) => ({
|
||||
// These are meant to run in separate pages, like the issue reporter or
|
||||
// process explorer. Ignoring for now since otherwise their CSS is
|
||||
// included in the main CSS.
|
||||
test: /electron-browser.+\.html$|code\/electron-browser\/.+\.css$/,
|
||||
test: /test|electron-browser.+\.html$|code\/electron-browser\/.+\.css$/,
|
||||
use: [{
|
||||
loader: "ignore-loader",
|
||||
}],
|
||||
|
28
yarn.lock
28
yarn.lock
@ -506,6 +506,13 @@ binary-extensions@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
|
||||
integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==
|
||||
|
||||
bindings@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.4.0.tgz#909efa49f2ebe07ecd3cb136778f665052040127"
|
||||
integrity sha512-7znEVX22Djn+nYjxCWKDne0RRloa9XfYa84yk3s+HkE3LpDYZmhArYr9O9huBoHY3/oXispx5LorIX7Sl2CgSQ==
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
block-stream@*:
|
||||
version "0.0.9"
|
||||
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
|
||||
@ -1775,6 +1782,11 @@ file-loader@^3.0.1:
|
||||
loader-utils "^1.0.2"
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
|
||||
|
||||
filesize@^3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
|
||||
@ -3317,7 +3329,7 @@ multicast-dns@^6.0.1:
|
||||
dns-packet "^1.3.1"
|
||||
thunky "^1.0.2"
|
||||
|
||||
nan@^2.10.0, nan@^2.9.2:
|
||||
nan@^2.10.0, nan@^2.8.0, nan@^2.9.2:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
|
||||
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
|
||||
@ -3803,6 +3815,11 @@ path-browserify@0.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
|
||||
integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=
|
||||
|
||||
path-browserify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.0.tgz#40702a97af46ae00b0ea6fa8998c0b03c0af160d"
|
||||
integrity sha512-Hkavx/nY4/plImrZPHRk2CL9vpOymZLgEbMNX1U0bjcBL7QN9wODxyx0yaMZURSQaUtSEvDrfAvxa9oPb0at9g==
|
||||
|
||||
path-dirname@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
|
||||
@ -4719,6 +4736,15 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
spdlog@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.7.2.tgz#9298753d7694b9ee9bbfd7e01ea1e4c6ace1e64d"
|
||||
integrity sha512-rHfWCaWMD4NindDnql6rc6kn7Bs8JR92jhiUpCl3D6v+jYcQ6GozMLig0RliOOR8st5mU+IHLZnr15fBys5x/Q==
|
||||
dependencies:
|
||||
bindings "^1.3.0"
|
||||
mkdirp "^0.5.1"
|
||||
nan "^2.8.0"
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
|
||||
|
Reference in New Issue
Block a user