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:
Asher
2019-02-19 10:17:03 -06:00
committed by GitHub
parent 73762017c8
commit 4a80bcb42c
39 changed files with 1694 additions and 8731 deletions

View File

@@ -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;
}
d1.dispose();
d2.dispose();
const resp = doneMsg.getResponse();
if (!resp) {
return res();
}
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) {
const completed = new Promise<R>((resolve, reject): void => {
const dispose = (): void => {
d1.dispose();
d2.dispose();
};
rej(new Error(failedMsg.getMessage()));
}
const d1 = this.evalDoneEmitter.event((doneMsg) => {
if (doneMsg.getId() === id) {
const resp = doneMsg.getResponse();
dispose();
resolve(parse(resp));
}
});
const d2 = this.evalFailedEmitter.event((failedMsg) => {
if (failedMsg.getId() === id) {
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);
}
}

View File

@@ -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
}