Refactor evaluations (#285)

* Replace evaluations with proxies and messages

* Return proxies synchronously

Otherwise events can be lost.

* Ensure events cannot be missed

* Refactor remaining fills

* Use more up-to-date version of util

For callbackify.

* Wait for dispose to come back before removing

This prevents issues with the "done" event not always being the last
event fired. For example a socket might close and then end, but only
if the caller called end.

* Remove old node-pty tests

* Fix emitting events twice on duplex streams

* Preserve environment when spawning processes

* Throw a better error if the proxy doesn't exist

* Remove rimraf dependency from ide

* Update net.Server.listening

* Use exit event instead of killed

Doesn't look like killed is even a thing.

* Add response timeout to server

* Fix trash

* Require node-pty & spdlog after they get unpackaged

This fixes an error when running in the binary.

* Fix errors in down emitter preventing reconnecting

* Fix disposing proxies when nothing listens to "error" event

* Refactor event tests to use jest.fn()

* Reject proxy call when disconnected

Otherwise it'll wait for the timeout which is a waste of time since we
already know the connection is dead.

* Use nbin for binary packaging

* Remove additional module requires

* Attempt to remove require for local bootstrap-fork

* Externalize fsevents
This commit is contained in:
Asher
2019-03-26 13:01:25 -05:00
committed by Kyle Carberry
parent d16c6aeb30
commit dc2253e718
75 changed files with 5866 additions and 6181 deletions

View File

@@ -1,19 +1,32 @@
import { EventEmitter } from "events";
import { PathLike } from "fs";
import { ExecException, ExecOptions } from "child_process";
import { promisify } from "util";
import { Emitter } from "@coder/events";
import { logger, field } from "@coder/logger";
import { Ping, NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, ClientMessage, WorkingInitMessage, EvalEventMessage } from "../proto";
import { ReadWriteConnection, InitData, OperatingSystem, SharedProcessData } from "../common/connection";
import { ActiveEvalHelper, EvalHelper, Disposer, ServerActiveEvalHelper } from "../common/helpers";
import { stringify, parse } from "../common/util";
import { ReadWriteConnection, InitData, SharedProcessData } from "../common/connection";
import { Module, ServerProxy } from "../common/proxy";
import { stringify, parse, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
import { Ping, ServerMessage, ClientMessage, MethodMessage, NamedProxyMessage, NumberedProxyMessage, SuccessMessage, FailMessage, EventMessage, CallbackMessage } from "../proto";
import { FsModule, ChildProcessModule, NetModule, NodePtyModule, SpdlogModule, TrashModule } from "./modules";
// tslint:disable no-any
interface ProxyData {
promise: Promise<void>;
instance: any;
callbacks: Map<number, (...args: any[]) => void>;
}
/**
* Client accepts an arbitrary connection intended to communicate with the Server.
* Client accepts a connection to communicate with the server.
*/
export class Client {
private evalId = 0;
private readonly evalDoneEmitter = new Emitter<EvalDoneMessage>();
private readonly evalFailedEmitter = new Emitter<EvalFailedMessage>();
private readonly evalEventEmitter = new Emitter<EvalEventMessage>();
private messageId = 0;
private callbackId = 0;
private readonly proxies = new Map<number | Module, ProxyData>();
private readonly successEmitter = new Emitter<SuccessMessage>();
private readonly failEmitter = new Emitter<FailMessage>();
private readonly eventEmitter = new Emitter<{ event: string; args: any[]; }>();
private _initData: InitData | undefined;
private readonly initDataEmitter = new Emitter<InitData>();
@@ -22,37 +35,123 @@ export class Client {
private readonly sharedProcessActiveEmitter = new Emitter<SharedProcessData>();
public readonly onSharedProcessActive = this.sharedProcessActiveEmitter.event;
private disconnected: boolean = false;
// The socket timeout is 60s, so we need to send a ping periodically to
// prevent it from closing.
private pingTimeout: NodeJS.Timer | number | undefined;
private readonly pingTimeoutDelay = 30000;
private readonly responseTimeout = 10000;
public readonly modules: {
[Module.ChildProcess]: ChildProcessModule,
[Module.Fs]: FsModule,
[Module.Net]: NetModule,
[Module.NodePty]: NodePtyModule,
[Module.Spdlog]: SpdlogModule,
[Module.Trash]: TrashModule,
};
/**
* @param connection Established connection to the server
*/
public constructor(
private readonly connection: ReadWriteConnection,
) {
connection.onMessage((data) => {
public constructor(private readonly connection: ReadWriteConnection) {
connection.onMessage(async (data) => {
let message: ServerMessage | undefined;
try {
message = ServerMessage.deserializeBinary(data);
this.handleMessage(message);
await this.handleMessage(message);
} catch (error) {
logger.error(
"Failed to handle server message",
field("id", message && message.hasEvalEvent() ? message.getEvalEvent()!.getId() : undefined),
field("id", message && this.getMessageId(message)),
field("length", data.byteLength),
field("error", error.message),
);
}
});
connection.onClose(() => {
clearTimeout(this.pingTimeout as any); // tslint:disable-line no-any
this.pingTimeout = undefined;
this.createProxy(Module.ChildProcess);
this.createProxy(Module.Fs);
this.createProxy(Module.Net);
this.createProxy(Module.NodePty);
this.createProxy(Module.Spdlog);
this.createProxy(Module.Trash);
this.modules = {
[Module.ChildProcess]: new ChildProcessModule(this.getProxy(Module.ChildProcess).instance),
[Module.Fs]: new FsModule(this.getProxy(Module.Fs).instance),
[Module.Net]: new NetModule(this.getProxy(Module.Net).instance),
[Module.NodePty]: new NodePtyModule(this.getProxy(Module.NodePty).instance),
[Module.Spdlog]: new SpdlogModule(this.getProxy(Module.Spdlog).instance),
[Module.Trash]: new TrashModule(this.getProxy(Module.Trash).instance),
};
// 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(this.modules[Module.Fs].exists, promisify.custom, {
value: (path: PathLike): Promise<boolean> => {
return new Promise((resolve): void => this.modules[Module.Fs].exists(path, resolve));
},
});
Object.defineProperty(this.modules[Module.ChildProcess].exec, promisify.custom, {
value: (
command: string,
options?: { encoding?: string | null } & ExecOptions | null,
): Promise<{ stdout: string | Buffer, stderr: string | Buffer }> => {
return new Promise((resolve, reject): void => {
this.modules[Module.ChildProcess].exec(command, options, (error: ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => {
if (error) {
reject(error);
} else {
resolve({ stdout, stderr });
}
});
});
},
});
/**
* If the connection is interrupted, the calls will neither succeed nor fail
* nor exit so we need to send a failure on all of them as well as trigger
* events so things like child processes can clean up and possibly restart.
*/
const handleDisconnect = (): void => {
this.disconnected = true;
logger.trace(() => [
"disconnected from server",
field("proxies", this.proxies.size),
field("callbacks", Array.from(this.proxies.values()).reduce((count, p) => count + p.callbacks.size, 0)),
field("success listeners", this.successEmitter.counts),
field("fail listeners", this.failEmitter.counts),
field("event listeners", this.eventEmitter.counts),
]);
const message = new FailMessage();
const error = new Error("disconnected");
message.setResponse(stringify(error));
this.failEmitter.emit(message);
this.eventEmitter.emit({ event: "exit", args: [1] });
this.eventEmitter.emit({ event: "close", args: [] });
try {
this.eventEmitter.emit({ event: "error", args: [error] });
} catch (error) {
// If nothing is listening, EventEmitter will throw an error.
}
this.eventEmitter.emit({ event: "done", args: [true] });
};
connection.onDown(() => handleDisconnect());
connection.onClose(() => {
clearTimeout(this.pingTimeout as any);
this.pingTimeout = undefined;
handleDisconnect();
});
connection.onUp(() => this.disconnected = false);
this.initDataPromise = new Promise((resolve): void => {
this.initDataEmitter.event(resolve);
});
@@ -60,6 +159,9 @@ export class Client {
this.startPinging();
}
/**
* Close the connection.
*/
public dispose(): void {
this.connection.close();
}
@@ -68,158 +170,109 @@ export class Client {
return this.initDataPromise;
}
public run(func: (helper: ServerActiveEvalHelper) => Disposer): ActiveEvalHelper;
public run<T1>(func: (helper: ServerActiveEvalHelper, a1: T1) => Disposer, a1: T1): ActiveEvalHelper;
public run<T1, T2>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2) => Disposer, a1: T1, a2: T2): ActiveEvalHelper;
public run<T1, T2, T3>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2, a3: T3) => Disposer, a1: T1, a2: T2, a3: T3): ActiveEvalHelper;
public run<T1, T2, T3, T4>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2, a3: T3, a4: T4) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4): ActiveEvalHelper;
public run<T1, T2, T3, T4, T5>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): ActiveEvalHelper;
public run<T1, T2, T3, T4, T5, T6>(func: (helper: ServerActiveEvalHelper, 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): ActiveEvalHelper;
/**
* 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.
* Make a remote call for a proxy's method using proto.
*/
public run<T1, T2, T3, T4, T5, T6>(func: (helper: ServerActiveEvalHelper, 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): ActiveEvalHelper {
const doEval = this.doEvaluate(func, a1, a2, a3, a4, a5, a6, true);
private remoteCall(proxyId: number | Module, method: string, args: any[]): Promise<any> {
if (this.disconnected) {
return Promise.reject(new Error("disconnected"));
}
// 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) {
eventEmitter.emit(msg.getEvent(), ...msg.getArgsList().map(parse));
}
});
const message = new MethodMessage();
const id = this.messageId++;
let proxyMessage: NamedProxyMessage | NumberedProxyMessage;
if (typeof proxyId === "string") {
proxyMessage = new NamedProxyMessage();
proxyMessage.setModule(moduleToProto(proxyId));
message.setNamedProxy(proxyMessage);
} else {
proxyMessage = new NumberedProxyMessage();
proxyMessage.setProxyId(proxyId);
message.setNumberedProxy(proxyMessage);
}
proxyMessage.setId(id);
proxyMessage.setMethod(method);
doEval.completed.then(() => {
d1.dispose();
}).catch((ex) => {
d1.dispose();
// This error event is only received by the client.
eventEmitter.emit("error", ex);
});
const storeCallback = (cb: (...args: any[]) => void): number => {
const callbackId = this.callbackId++;
logger.trace(() => [
"storing callback",
field("proxyId", proxyId),
field("callbackId", callbackId),
]);
return new ActiveEvalHelper({
// 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).
// 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.map((a) => stringify(a)));
const clientMsg = new ClientMessage();
clientMsg.setEvalEvent(eventsMsg);
this.connection.send(clientMsg.serializeBinary());
},
removeAllListeners: (event: string): EventEmitter => eventEmitter.removeAllListeners(event),
// tslint:enable no-any
});
}
this.getProxy(proxyId).callbacks.set(callbackId, cb);
public evaluate<R>(func: (helper: EvalHelper) => R | Promise<R>): Promise<R>;
public evaluate<R, T1>(func: (helper: EvalHelper, a1: T1) => R | Promise<R>, a1: T1): Promise<R>;
public evaluate<R, T1, T2>(func: (helper: EvalHelper, a1: T1, a2: T2) => R | Promise<R>, a1: T1, a2: T2): Promise<R>;
public evaluate<R, T1, T2, T3>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3) => R | Promise<R>, a1: T1, a2: T2, a3: T3): Promise<R>;
public evaluate<R, T1, T2, T3, T4>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4): Promise<R>;
public evaluate<R, T1, T2, T3, T4, T5>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): Promise<R>;
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): Promise<R>;
/**
* Evaluates a function on the server.
* To pass variables, ensure they are serializable and passed through the included function.
* @example
* const returned = await this.client.evaluate((helper, value) => {
* return value;
* }, "hi");
* console.log(returned);
* // output: "hi"
* @param func Function to evaluate
* @returns Promise rejected or resolved from the evaluated function
*/
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (helper: EvalHelper, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6) => R | Promise<R>, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6): Promise<R> {
return this.doEvaluate(func, a1, a2, a3, a4, a5, a6, false).completed;
}
return callbackId;
};
// 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;
} {
const newEval = new NewEvalMessage();
const id = this.evalId++;
newEval.setId(id);
newEval.setActive(active);
newEval.setArgsList([a1, a2, a3, a4, a5, a6].map((a) => stringify(a)));
newEval.setFunction(func.toString());
const stringifiedArgs = args.map((a) => stringify(a, storeCallback));
logger.trace(() => [
"sending",
field("id", id),
field("proxyId", proxyId),
field("method", method),
field("args", stringifiedArgs),
]);
const clientMsg = new ClientMessage();
clientMsg.setNewEval(newEval);
this.connection.send(clientMsg.serializeBinary());
proxyMessage.setArgsList(stringifiedArgs);
const completed = new Promise<R>((resolve, reject): void => {
const clientMessage = new ClientMessage();
clientMessage.setMethod(message);
this.connection.send(clientMessage.serializeBinary());
// The server will send back a fail or success message when the method
// has completed, so we listen for that based on the message's unique ID.
const promise = new Promise((resolve, reject): void => {
const dispose = (): void => {
d1.dispose();
d2.dispose();
clearTimeout(timeout as any);
};
const d1 = this.evalDoneEmitter.event((doneMsg) => {
if (doneMsg.getId() === id) {
dispose();
resolve(parse(doneMsg.getResponse()));
}
const timeout = setTimeout(() => {
dispose();
reject(new Error("timed out"));
}, this.responseTimeout);
const d1 = this.successEmitter.event(id, (message) => {
dispose();
resolve(this.parse(message.getResponse()));
});
const d2 = this.evalFailedEmitter.event((failedMsg) => {
if (failedMsg.getId() === id) {
dispose();
reject(parse(failedMsg.getResponse()));
}
const d2 = this.failEmitter.event(id, (message) => {
dispose();
reject(parse(message.getResponse()));
});
});
return { completed, id };
return promise;
}
/**
* Handles a message from the server. All incoming server messages should be
* routed through here.
* Handle all messages from the server.
*/
private handleMessage(message: ServerMessage): void {
private async handleMessage(message: ServerMessage): Promise<void> {
if (message.hasInit()) {
const init = message.getInit()!;
let opSys: OperatingSystem;
switch (init.getOperatingSystem()) {
case WorkingInitMessage.OperatingSystem.WINDOWS:
opSys = OperatingSystem.Windows;
break;
case WorkingInitMessage.OperatingSystem.LINUX:
opSys = OperatingSystem.Linux;
break;
case WorkingInitMessage.OperatingSystem.MAC:
opSys = OperatingSystem.Mac;
break;
default:
throw new Error(`unsupported operating system ${init.getOperatingSystem()}`);
}
this._initData = {
dataDirectory: init.getDataDirectory(),
homeDirectory: init.getHomeDirectory(),
tmpDirectory: init.getTmpDirectory(),
workingDirectory: init.getWorkingDirectory(),
os: opSys,
os: protoToOperatingSystem(init.getOperatingSystem()),
shell: init.getShell(),
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
};
this.initDataEmitter.emit(this._initData);
} else if (message.hasEvalDone()) {
this.evalDoneEmitter.emit(message.getEvalDone()!);
} else if (message.hasEvalFailed()) {
this.evalFailedEmitter.emit(message.getEvalFailed()!);
} else if (message.hasEvalEvent()) {
this.evalEventEmitter.emit(message.getEvalEvent()!);
} else if (message.hasSuccess()) {
this.emitSuccess(message.getSuccess()!);
} else if (message.hasFail()) {
this.emitFail(message.getFail()!);
} else if (message.hasEvent()) {
await this.emitEvent(message.getEvent()!);
} else if (message.hasCallback()) {
await this.runCallback(message.getCallback()!);
} else if (message.hasSharedProcessActive()) {
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
this.sharedProcessActiveEmitter.emit({
@@ -227,13 +280,85 @@ export class Client {
logPath: sharedProcessActiveMessage.getLogPath(),
});
} else if (message.hasPong()) {
// Nothing to do since we run the pings on a timer, in case either message
// is dropped which would break the ping cycle.
// Nothing to do since pings are on a timer rather than waiting for the
// next pong in case a message from either the client or server is dropped
// which would break the ping cycle.
} else {
throw new Error("unknown message type");
}
}
private emitSuccess(message: SuccessMessage): void {
logger.trace(() => [
"received resolve",
field("id", message.getId()),
]);
this.successEmitter.emit(message.getId(), message);
}
private emitFail(message: FailMessage): void {
logger.trace(() => [
"received reject",
field("id", message.getId()),
]);
this.failEmitter.emit(message.getId(), message);
}
/**
* Emit an event received from the server. We could send requests for "on" to
* the server and serialize functions using IDs, but doing it that way makes
* it possible to miss events depending on whether the server receives the
* request before it emits. Instead, emit all events from the server so all
* events are always caught on the client.
*/
private async emitEvent(message: EventMessage): Promise<void> {
const eventMessage = message.getNamedEvent()! || message.getNumberedEvent()!;
const proxyId = message.getNamedEvent()
? protoToModule(message.getNamedEvent()!.getModule())
: message.getNumberedEvent()!.getProxyId();
const event = eventMessage.getEvent();
await this.ensureResolved(proxyId);
logger.trace(() => [
"received event",
field("proxyId", proxyId),
field("event", event),
field("args", eventMessage.getArgsList()),
]);
const args = eventMessage.getArgsList().map((a) => this.parse(a));
this.eventEmitter.emit(proxyId, { event, args });
}
/**
* Run a callback as requested by the server. Since we don't know when
* callbacks get garbage collected we dispose them only when the proxy
* disposes. That means they should only be used if they run for the lifetime
* of the proxy (like child_process.exec), otherwise we'll leak. They should
* also only be used when passed together with the method. If they are sent
* afterward, they may never be called due to timing issues.
*/
private async runCallback(message: CallbackMessage): Promise<void> {
const callbackMessage = message.getNamedCallback()! || message.getNumberedCallback()!;
const proxyId = message.getNamedCallback()
? protoToModule(message.getNamedCallback()!.getModule())
: message.getNumberedCallback()!.getProxyId();
const callbackId = callbackMessage.getCallbackId();
await this.ensureResolved(proxyId);
logger.trace(() => [
"running callback",
field("proxyId", proxyId),
field("callbackId", callbackId),
field("args", callbackMessage.getArgsList()),
]);
const args = callbackMessage.getArgsList().map((a) => this.parse(a));
this.getProxy(proxyId).callbacks.get(callbackId)!(...args);
}
/**
* Start the ping loop. Does nothing if already pinging.
*/
private startPinging = (): void => {
if (typeof this.pingTimeout !== "undefined") {
return;
@@ -250,4 +375,136 @@ export class Client {
schedulePing();
}
/**
* Return the message's ID if it has one or a string identifier. For logging
* errors with an ID to make the error more useful.
*/
private getMessageId(message: ServerMessage): number | string | undefined {
if (message.hasInit()) {
return "init";
} else if (message.hasSuccess()) {
return message.getSuccess()!.getId();
} else if (message.hasFail()) {
return message.getFail()!.getId();
} else if (message.hasEvent()) {
const eventMessage = message.getEvent()!.getNamedEvent()!
|| message.getEvent()!.getNumberedEvent()!;
return `event: ${eventMessage.getEvent()}`;
} else if (message.hasCallback()) {
const callbackMessage = message.getCallback()!.getNamedCallback()!
|| message.getCallback()!.getNumberedCallback()!;
return `callback: ${callbackMessage.getCallbackId()}`;
} else if (message.hasSharedProcessActive()) {
return "shared";
} else if (message.hasPong()) {
return "pong";
}
}
/**
* Return a proxy that makes remote calls.
*/
private createProxy<T>(proxyId: number | Module, promise: Promise<any> = Promise.resolve()): T {
logger.trace(() => [
"creating proxy",
field("proxyId", proxyId),
]);
const instance = new Proxy({
proxyId,
onDone: (cb: (...args: any[]) => void): void => {
this.eventEmitter.event(proxyId, (event) => {
if (event.event === "done") {
cb(...event.args);
}
});
},
onEvent: (cb: (event: string, ...args: any[]) => void): void => {
this.eventEmitter.event(proxyId, (event) => {
cb(event.event, ...event.args);
});
},
}, {
get: (target: any, name: string): any => {
// When resolving a promise with a proxy, it will check for "then".
if (name === "then") {
return;
}
if (typeof target[name] === "undefined") {
target[name] = (...args: any[]): Promise<any> | ServerProxy => {
return this.remoteCall(proxyId, name, args);
};
}
return target[name];
},
});
this.proxies.set(proxyId, {
promise,
instance,
callbacks: new Map(),
});
instance.onDone((disconnected: boolean) => {
const log = (): void => {
logger.trace(() => [
typeof proxyId === "number" ? "disposed proxy" : "disposed proxy callbacks",
field("proxyId", proxyId),
field("disconnected", disconnected),
field("callbacks", Array.from(this.proxies.values()).reduce((count, proxy) => count + proxy.callbacks.size, 0)),
field("success listeners", this.successEmitter.counts),
field("fail listeners", this.failEmitter.counts),
field("event listeners", this.eventEmitter.counts),
]);
};
// Uniquely identified items (top-level module proxies) can continue to
// be used so we don't need to delete them.
if (typeof proxyId === "number") {
const dispose = (): void => {
this.proxies.delete(proxyId);
this.eventEmitter.dispose(proxyId);
log();
};
if (!disconnected) {
instance.dispose().then(dispose).catch(dispose);
} else {
dispose();
}
} else {
// The callbacks will still be unusable though.
this.getProxy(proxyId).callbacks.clear();
log();
}
});
return instance;
}
/**
* We aren't guaranteed the promise will call all the `then` callbacks
* synchronously once it resolves, so the event message can come in and fire
* before a caller has been able to attach an event. Waiting for the promise
* ensures it runs after everything else.
*/
private async ensureResolved(proxyId: number | Module): Promise<void> {
await this.getProxy(proxyId).promise;
}
private parse(value?: string, promise?: Promise<any>): any {
return parse(value, undefined, (id) => this.createProxy(id, promise));
}
private getProxy(proxyId: number | Module): ProxyData {
if (!this.proxies.has(proxyId)) {
throw new Error(`proxy ${proxyId} disposed too early`);
}
return this.proxies.get(proxyId)!;
}
}

View File

@@ -0,0 +1,126 @@
import * as cp from "child_process";
import * as net from "net";
import * as stream from "stream";
import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { ChildProcessModuleProxy, ChildProcessProxy, ChildProcessProxies } from "../../node/modules/child_process";
import { Readable, Writable } from "./stream";
export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.ChildProcess {
public readonly stdin: stream.Writable;
public readonly stdout: stream.Readable;
public readonly stderr: stream.Readable;
public readonly stdio: [stream.Writable, stream.Readable, stream.Readable];
private _connected: boolean = false;
private _killed: boolean = false;
private _pid = -1;
public constructor(proxyPromises: Promise<ChildProcessProxies>) {
super(proxyPromises.then((p) => p.childProcess));
this.stdin = new Writable(proxyPromises.then((p) => p.stdin!));
this.stdout = new Readable(proxyPromises.then((p) => p.stdout!));
this.stderr = new Readable(proxyPromises.then((p) => p.stderr!));
this.stdio = [this.stdin, this.stdout, this.stderr];
this.proxy.getPid().then((pid) => {
this._pid = pid;
this._connected = true;
});
this.on("disconnect", () => this._connected = false);
this.on("exit", () => {
this._connected = false;
this._killed = true;
});
}
public get pid(): number {
return this._pid;
}
public get connected(): boolean {
return this._connected;
}
public get killed(): boolean {
return this._killed;
}
public kill(): void {
this._killed = true;
this.proxy.kill();
}
public disconnect(): void {
this.proxy.disconnect();
}
public ref(): void {
this.proxy.ref();
}
public unref(): void {
this.proxy.unref();
}
public send(
message: any, // tslint:disable-line no-any
sendHandle?: net.Socket | net.Server | ((error: Error) => void),
options?: cp.MessageOptions | ((error: Error) => void),
callback?: (error: Error) => void): boolean {
if (typeof sendHandle === "function") {
callback = sendHandle;
sendHandle = undefined;
} else if (typeof options === "function") {
callback = options;
options = undefined;
}
if (sendHandle || options) {
throw new Error("sendHandle and options are not supported");
}
callbackify(this.proxy.send)(message, (error) => {
if (callback) {
callback(error);
}
});
return true; // Always true since we can't get this synchronously.
}
}
export class ChildProcessModule {
public constructor(private readonly proxy: ChildProcessModuleProxy) {}
public exec = (
command: string,
options?: { encoding?: string | null } & cp.ExecOptions | null
| ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
callback?: ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
): cp.ChildProcess => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
return new ChildProcess(this.proxy.exec(command, options, callback));
}
public fork = (modulePath: string, args?: string[] | cp.ForkOptions, options?: cp.ForkOptions): cp.ChildProcess => {
if (!Array.isArray(args)) {
options = args;
args = undefined;
}
return new ChildProcess(this.proxy.fork(modulePath, args, options));
}
public spawn = (command: string, args?: string[] | cp.SpawnOptions, options?: cp.SpawnOptions): cp.ChildProcess => {
if (!Array.isArray(args)) {
options = args;
args = undefined;
}
return new ChildProcess(this.proxy.spawn(command, args, options));
}
}

View File

@@ -0,0 +1,316 @@
import * as fs from "fs";
import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { IEncodingOptions, IEncodingOptionsCallback } from "../../common/util";
import { FsModuleProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
import { Writable } from "./stream";
// tslint:disable no-any
class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
public close(): void {
this.proxy.close();
}
}
class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
public get bytesWritten(): number {
throw new Error("not implemented");
}
public get path(): string | Buffer {
throw new Error("not implemented");
}
public close(): void {
this.proxy.close();
}
}
export class FsModule {
public constructor(private readonly proxy: FsModuleProxy) {}
public access = (path: fs.PathLike, mode: number | undefined | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof mode === "function") {
callback = mode;
mode = undefined;
}
callbackify(this.proxy.access)(path, mode, callback!);
}
public appendFile = (path: fs.PathLike | number, data: any, options?: fs.WriteFileOptions | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.appendFile)(path, data, options, callback!);
}
public chmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.chmod)(path, mode, callback!);
}
public chown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.chown)(path, uid, gid, callback!);
}
public close = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.close)(fd, callback!);
}
public copyFile = (src: fs.PathLike, dest: fs.PathLike, flags: number | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof flags === "function") {
callback = flags;
}
callbackify(this.proxy.copyFile)(
src, dest, typeof flags !== "function" ? flags : undefined, callback!,
);
}
public createWriteStream = (path: fs.PathLike, options?: any): fs.WriteStream => {
return new WriteStream(this.proxy.createWriteStream(path, options));
}
public exists = (path: fs.PathLike, callback: (exists: boolean) => void): void => {
callbackify(this.proxy.exists)(path, (exists) => {
callback!(exists as any);
});
}
public fchmod = (fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fchmod)(fd, mode, callback!);
}
public fchown = (fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fchown)(fd, uid, gid, callback!);
}
public fdatasync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fdatasync)(fd, callback!);
}
public fstat = (fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
callbackify(this.proxy.fstat)(fd, (error, stats) => {
callback(error, stats && new Stats(stats));
});
}
public fsync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fsync)(fd, callback!);
}
public ftruncate = (fd: number, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof len === "function") {
callback = len;
len = undefined;
}
callbackify(this.proxy.ftruncate)(fd, len, callback!);
}
public futimes = (fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.futimes)(fd, atime, mtime, callback!);
}
public lchmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.lchmod)(path, mode, callback!);
}
public lchown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.lchown)(path, uid, gid, callback!);
}
public link = (existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.link)(existingPath, newPath, callback!);
}
public lstat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
callbackify(this.proxy.lstat)(path, (error, stats) => {
callback(error, stats && new Stats(stats));
});
}
public mkdir = (path: fs.PathLike, mode: number | string | fs.MakeDirectoryOptions | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof mode === "function") {
callback = mode;
mode = undefined;
}
callbackify(this.proxy.mkdir)(path, mode, callback!);
}
public mkdtemp = (prefix: string, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, folder: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.mkdtemp)(prefix, options, callback!);
}
public open = (path: fs.PathLike, flags: string | number, mode: string | number | undefined | null | ((err: NodeJS.ErrnoException, fd: number) => void), callback?: (err: NodeJS.ErrnoException, fd: number) => void): void => {
if (typeof mode === "function") {
callback = mode;
mode = undefined;
}
callbackify(this.proxy.open)(path, flags, mode, callback!);
}
public read = (fd: number, buffer: Buffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void => {
this.proxy.read(fd, length, position).then((response) => {
buffer.set(response.buffer, offset);
callback(undefined!, response.bytesRead, response.buffer);
}).catch((error) => {
callback(error, undefined!, undefined!);
});
}
public readFile = (path: fs.PathLike | number, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.readFile)(path, options, callback!);
}
public readdir = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, files: Buffer[] | fs.Dirent[] | string[]) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.readdir)(path, options, callback!);
}
public readlink = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, linkString: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.readlink)(path, options, callback!);
}
public realpath = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, resolvedPath: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.realpath)(path, options, callback!);
}
public rename = (oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.rename)(oldPath, newPath, callback!);
}
public rmdir = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.rmdir)(path, callback!);
}
public stat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
callbackify(this.proxy.stat)(path, (error, stats) => {
callback(error, stats && new Stats(stats));
});
}
public symlink = (target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof type === "function") {
callback = type;
type = undefined;
}
callbackify(this.proxy.symlink)(target, path, type, callback!);
}
public truncate = (path: fs.PathLike, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof len === "function") {
callback = len;
len = undefined;
}
callbackify(this.proxy.truncate)(path, len, callback!);
}
public unlink = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.unlink)(path, callback!);
}
public utimes = (path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.utimes)(path, atime, mtime, callback!);
}
public write = (fd: number, buffer: Buffer, offset: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), length: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), position: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void => {
if (typeof offset === "function") {
callback = offset;
offset = undefined;
}
if (typeof length === "function") {
callback = length;
length = undefined;
}
if (typeof position === "function") {
callback = position;
position = undefined;
}
this.proxy.write(fd, buffer, offset, length, position).then((r) => {
callback!(undefined!, r.bytesWritten, r.buffer);
}).catch((error) => {
callback!(error, undefined!, undefined!);
});
}
public writeFile = (path: fs.PathLike | number, data: any, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.writeFile)(path, data, options, callback!);
}
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;
}
const watcher = new Watcher(this.proxy.watch(filename, options));
if (listener) {
watcher.on("change", listener);
}
return watcher;
}
}
class Stats implements fs.Stats {
public readonly atime: Date;
public readonly mtime: Date;
public readonly ctime: Date;
public readonly birthtime: Date;
public constructor(private readonly stats: IStats) {
this.atime = new Date(stats.atime);
this.mtime = new Date(stats.mtime);
this.ctime = new Date(stats.ctime);
this.birthtime = new Date(stats.birthtime);
}
public get dev(): number { return this.stats.dev; }
public get ino(): number { return this.stats.ino; }
public get mode(): number { return this.stats.mode; }
public get nlink(): number { return this.stats.nlink; }
public get uid(): number { return this.stats.uid; }
public get gid(): number { return this.stats.gid; }
public get rdev(): number { return this.stats.rdev; }
public get size(): number { return this.stats.size; }
public get blksize(): number { return this.stats.blksize; }
public get blocks(): number { return this.stats.blocks; }
public get atimeMs(): number { return this.stats.atimeMs; }
public get mtimeMs(): number { return this.stats.mtimeMs; }
public get ctimeMs(): number { return this.stats.ctimeMs; }
public get birthtimeMs(): number { return this.stats.birthtimeMs; }
public isFile(): boolean { return this.stats._isFile; }
public isDirectory(): boolean { return this.stats._isDirectory; }
public isBlockDevice(): boolean { return this.stats._isBlockDevice; }
public isCharacterDevice(): boolean { return this.stats._isCharacterDevice; }
public isSymbolicLink(): boolean { return this.stats._isSymbolicLink; }
public isFIFO(): boolean { return this.stats._isFIFO; }
public isSocket(): boolean { return this.stats._isSocket; }
public toObject(): object {
return JSON.parse(JSON.stringify(this));
}
}

View File

@@ -0,0 +1,6 @@
export * from "./child_process";
export * from "./fs";
export * from "./net";
export * from "./node-pty";
export * from "./spdlog";
export * from "./trash";

View File

@@ -0,0 +1,280 @@
import * as net from "net";
import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { NetModuleProxy, NetServerProxy, NetSocketProxy } from "../../node/modules/net";
import { Duplex } from "./stream";
export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
private _connecting: boolean = false;
private _destroyed: boolean = false;
public constructor(proxyPromise: Promise<NetSocketProxy> | NetSocketProxy, connecting?: boolean) {
super(proxyPromise);
if (connecting) {
this._connecting = connecting;
}
this.on("close", () => {
this._destroyed = true;
this._connecting = false;
});
this.on("connect", () => this._connecting = false);
}
public connect(options: number | string | net.SocketConnectOpts, host?: string | Function, callback?: Function): this {
if (typeof host === "function") {
callback = host;
host = undefined;
}
this._connecting = true;
if (callback) {
this.on("connect", callback as () => void);
}
this.proxy.connect(options, host);
return this;
}
// tslint:disable-next-line no-any
public end(data?: any, encoding?: string | Function, callback?: Function): void {
if (typeof encoding === "function") {
callback = encoding;
encoding = undefined;
}
callbackify(this.proxy.end)(data, encoding, () => {
if (callback) {
callback();
}
});
}
// tslint:disable-next-line no-any
public write(data: any, encoding?: string | Function, fd?: string | Function): boolean {
let callback: undefined | Function;
if (typeof encoding === "function") {
callback = encoding;
encoding = undefined;
}
if (typeof fd === "function") {
callback = fd;
fd = undefined;
}
if (typeof fd !== "undefined") {
throw new Error("fd argument not supported");
}
callbackify(this.proxy.write)(data, encoding, () => {
if (callback) {
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(): this {
throw new Error("not implemented");
}
public setNoDelay(): this {
throw new Error("not implemented");
}
public setKeepAlive(): this {
throw new Error("not implemented");
}
public unref(): void {
this.proxy.unref();
}
public ref(): void {
this.proxy.ref();
}
}
export class Server extends ClientProxy<NetServerProxy> implements net.Server {
private readonly sockets = new Map<number, net.Socket>();
private _listening: boolean = false;
public constructor(proxyPromise: Promise<NetServerProxy> | NetServerProxy) {
super(proxyPromise);
this.proxy.onConnection((socketProxy) => {
this.emit("connection", new Socket(socketProxy));
});
this.on("listening", () => this._listening = true);
this.on("error", () => this._listening = false);
this.on("close", () => this._listening = false);
}
public listen(handle?: net.ListenOptions | number | string, hostname?: string | number | Function, backlog?: number | Function, callback?: Function): this {
if (typeof hostname === "function") {
callback = hostname;
hostname = undefined;
}
if (typeof backlog === "function") {
callback = backlog;
backlog = undefined;
}
if (callback) {
this.on("listening", callback as () => void);
}
this.proxy.listen(handle, hostname, backlog);
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 close(callback?: () => void): this {
this._listening = false;
if (callback) {
this.on("close", callback);
}
this.proxy.close();
return this;
}
public ref(): this {
this.proxy.ref();
return this;
}
public unref(): this {
this.proxy.unref();
return this;
}
public getConnections(cb: (error: Error | null, count: number) => void): void {
cb(null, this.sockets.size);
}
}
type NodeNet = typeof net;
export class NetModule implements NodeNet {
public readonly Socket: typeof net.Socket;
public readonly Server: typeof net.Server;
public constructor(private readonly proxy: NetModuleProxy) {
// @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.
this.Socket = class extends Socket {
public constructor(options?: net.SocketConstructorOpts) {
super(proxy.createSocket(options));
}
};
this.Server = class extends Server {
public constructor(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: Socket) => void), listener?: (socket: Socket) => void) {
super(proxy.createServer(typeof options !== "function" ? options : undefined));
if (typeof options === "function") {
listener = options;
}
if (listener) {
this.on("connection", listener);
}
}
};
}
public createConnection = (target: string | number | net.NetConnectOpts, host?: string | Function, callback?: Function): net.Socket => {
if (typeof host === "function") {
callback = host;
host = undefined;
}
const socket = new Socket(this.proxy.createConnection(target, host), true);
if (callback) {
socket.on("connect", callback as () => void);
}
return socket;
}
public createServer = (
options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
callback?: (socket: net.Socket) => void,
): net.Server => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
const server = new Server(this.proxy.createServer(options));
if (callback) {
server.on("connection", callback);
}
return server;
}
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");
}
}

View File

@@ -0,0 +1,45 @@
import * as pty from "node-pty";
import { ClientProxy } from "../../common/proxy";
import { NodePtyModuleProxy, NodePtyProcessProxy } from "../../node/modules/node-pty";
export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements pty.IPty {
private _pid = -1;
private _process = "";
public constructor(proxyPromise: Promise<NodePtyProcessProxy>) {
super(proxyPromise);
this.proxy.getPid().then((pid) => this._pid = pid);
this.proxy.getProcess().then((process) => this._process = process);
this.on("process", (process) => this._process = process);
}
public get pid(): number {
return this._pid;
}
public get process(): string {
return this._process;
}
public resize(columns: number, rows: number): void {
this.proxy.resize(columns, rows);
}
public write(data: string): void {
this.proxy.write(data);
}
public kill(signal?: string): void {
this.proxy.kill(signal);
}
}
type NodePty = typeof pty;
export class NodePtyModule implements NodePty {
public constructor(private readonly proxy: NodePtyModuleProxy) {}
public spawn = (file: string, args: string[] | string, options: pty.IPtyForkOptions): pty.IPty => {
return new NodePtyProcess(this.proxy.spawn(file, args, options));
}
}

View File

@@ -0,0 +1,32 @@
import * as spdlog from "spdlog";
import { ClientProxy } from "../../common/proxy";
import { RotatingLoggerProxy, SpdlogModuleProxy } from "../../node/modules/spdlog";
class RotatingLogger extends ClientProxy<RotatingLoggerProxy> implements spdlog.RotatingLogger {
public async trace (message: string): Promise<void> { this.proxy.trace(message); }
public async debug (message: string): Promise<void> { this.proxy.debug(message); }
public async info (message: string): Promise<void> { this.proxy.info(message); }
public async warn (message: string): Promise<void> { this.proxy.warn(message); }
public async error (message: string): Promise<void> { this.proxy.error(message); }
public async critical (message: string): Promise<void> { this.proxy.critical(message); }
public async setLevel (level: number): Promise<void> { this.proxy.setLevel(level); }
public async clearFormatters (): Promise<void> { this.proxy.clearFormatters(); }
public async flush (): Promise<void> { this.proxy.flush(); }
public async drop (): Promise<void> { this.proxy.drop(); }
}
export class SpdlogModule {
public readonly RotatingLogger: typeof spdlog.RotatingLogger;
public constructor(private readonly proxy: SpdlogModuleProxy) {
this.RotatingLogger = class extends RotatingLogger {
public constructor(name: string, filename: string, filesize: number, filecount: number) {
super(proxy.createLogger(name, filename, filesize, filecount));
}
};
}
public setAsyncMode = (bufferSize: number, flushInterval: number): void => {
this.proxy.setAsyncMode(bufferSize, flushInterval);
}
}

View File

@@ -0,0 +1,233 @@
import * as stream from "stream";
import { callbackify } from "util";
import { ClientProxy } from "../../common/proxy";
import { DuplexProxy, IReadableProxy, WritableProxy } from "../../node/modules/stream";
export class Writable<T extends WritableProxy = WritableProxy> extends ClientProxy<T> implements stream.Writable {
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 {
throw new Error("not implemented");
}
public uncork(): void {
throw new Error("not implemented");
}
public destroy(): void {
this.proxy.destroy();
}
public setDefaultEncoding(encoding: string): this {
this.proxy.setDefaultEncoding(encoding);
return this;
}
// tslint:disable-next-line no-any
public write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
if (typeof encoding === "function") {
callback = encoding;
encoding = undefined;
}
callbackify(this.proxy.write)(chunk, encoding, (error) => {
if (callback) {
callback(error);
}
});
return true; // Always true since we can't get this synchronously.
}
// tslint:disable-next-line no-any
public end(data?: any | (() => void), encoding?: string | (() => void), callback?: (() => void)): void {
if (typeof data === "function") {
callback = data;
data = undefined;
}
if (typeof encoding === "function") {
callback = encoding;
encoding = undefined;
}
callbackify(this.proxy.end)(data, encoding, () => {
if (callback) {
callback();
}
});
}
}
export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientProxy<T> implements stream.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(): void {
throw new Error("not implemented");
}
public _destroy(): void {
throw new Error("not implemented");
}
public unpipe(): this {
throw new Error("not implemented");
}
public pause(): this {
throw new Error("not implemented");
}
public resume(): this {
throw new Error("not implemented");
}
public isPaused(): boolean {
throw new Error("not implemented");
}
public wrap(): this {
throw new Error("not implemented");
}
public push(): boolean {
throw new Error("not implemented");
}
public unshift(): void {
throw new Error("not implemented");
}
public pipe<T>(): T {
throw new Error("not implemented");
}
// tslint:disable-next-line no-any
public [Symbol.asyncIterator](): AsyncIterableIterator<any> {
throw new Error("not implemented");
}
public destroy(): void {
this.proxy.destroy();
}
public setEncoding(encoding: string): this {
this.proxy.setEncoding(encoding);
return this;
}
}
export class Duplex<T extends DuplexProxy = DuplexProxy> extends Writable<T> implements stream.Duplex, stream.Readable {
private readonly _readable: Readable;
public constructor(proxyPromise: Promise<T> | T) {
super(proxyPromise);
this._readable = new Readable(proxyPromise, false);
}
public get readable(): boolean {
return this._readable.readable;
}
public get readableHighWaterMark(): number {
return this._readable.readableHighWaterMark;
}
public get readableLength(): number {
return this._readable.readableLength;
}
public _read(): void {
this._readable._read();
}
public read(): void {
this._readable.read();
}
public unpipe(): this {
this._readable.unpipe();
return this;
}
public pause(): this {
this._readable.unpipe();
return this;
}
public resume(): this {
this._readable.resume();
return this;
}
public isPaused(): boolean {
return this._readable.isPaused();
}
public wrap(): this {
this._readable.wrap();
return this;
}
public push(): boolean {
return this._readable.push();
}
public unshift(): void {
this._readable.unshift();
}
// tslint:disable-next-line no-any
public [Symbol.asyncIterator](): AsyncIterableIterator<any> {
return this._readable[Symbol.asyncIterator]();
}
public setEncoding(encoding: string): this {
this.proxy.setEncoding(encoding);
return this;
}
}

View File

@@ -0,0 +1,10 @@
import * as trash from "trash";
import { TrashModuleProxy } from "../../node/modules/trash";
export class TrashModule {
public constructor(private readonly proxy: TrashModuleProxy) {}
public trash = (path: string, options?: trash.Options): Promise<void> => {
return this.proxy.trash(path, options);
}
}