2019-02-19 23:17:03 +07:00
|
|
|
import { EventEmitter } from "events";
|
2019-01-12 02:33:44 +07:00
|
|
|
import * as vm from "vm";
|
2019-02-19 23:17:03 +07:00
|
|
|
import { logger, field } from "@coder/logger";
|
|
|
|
import { NewEvalMessage, EvalFailedMessage, EvalDoneMessage, ServerMessage, EvalEventMessage } from "../proto";
|
2019-01-12 02:33:44 +07:00
|
|
|
import { SendableConnection } from "../common/connection";
|
2019-02-19 23:17:03 +07:00
|
|
|
import { stringify, parse } from "../common/util";
|
2019-01-30 07:48:02 +07:00
|
|
|
|
|
|
|
export interface ActiveEvaluation {
|
|
|
|
onEvent(msg: EvalEventMessage): void;
|
2019-02-19 23:17:03 +07:00
|
|
|
dispose(): void;
|
2019-01-30 07:48:02 +07:00
|
|
|
}
|
2019-01-12 02:33:44 +07:00
|
|
|
|
2019-01-16 01:36:09 +07:00
|
|
|
declare var __non_webpack_require__: typeof require;
|
2019-01-30 07:48:02 +07:00
|
|
|
export const evaluate = (connection: SendableConnection, message: NewEvalMessage, onDispose: () => void): ActiveEvaluation | void => {
|
2019-01-12 02:58:31 +07:00
|
|
|
const argStr: string[] = [];
|
|
|
|
message.getArgsList().forEach((value) => {
|
|
|
|
argStr.push(value);
|
|
|
|
});
|
2019-02-19 23:17:03 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Send the response and call onDispose.
|
|
|
|
*/
|
|
|
|
// tslint:disable-next-line no-any
|
2019-01-12 02:58:31 +07:00
|
|
|
const sendResp = (resp: any): void => {
|
|
|
|
const evalDone = new EvalDoneMessage();
|
|
|
|
evalDone.setId(message.getId());
|
2019-02-19 23:17:03 +07:00
|
|
|
evalDone.setResponse(stringify(resp));
|
2019-01-12 02:33:44 +07:00
|
|
|
|
2019-01-12 02:58:31 +07:00
|
|
|
const serverMsg = new ServerMessage();
|
|
|
|
serverMsg.setEvalDone(evalDone);
|
|
|
|
connection.send(serverMsg.serializeBinary());
|
2019-02-19 23:17:03 +07:00
|
|
|
|
|
|
|
onDispose();
|
2019-01-12 02:58:31 +07:00
|
|
|
};
|
2019-02-19 23:17:03 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Send an exception and call onDispose.
|
|
|
|
*/
|
|
|
|
const sendException = (error: Error): void => {
|
2019-01-12 02:58:31 +07:00
|
|
|
const evalFailed = new EvalFailedMessage();
|
|
|
|
evalFailed.setId(message.getId());
|
2019-02-19 23:17:03 +07:00
|
|
|
evalFailed.setReason(EvalFailedMessage.Reason.EXCEPTION);
|
|
|
|
evalFailed.setMessage(error.toString() + " " + error.stack);
|
2019-01-12 02:33:44 +07:00
|
|
|
|
2019-01-12 02:58:31 +07:00
|
|
|
const serverMsg = new ServerMessage();
|
|
|
|
serverMsg.setEvalFailed(evalFailed);
|
|
|
|
connection.send(serverMsg.serializeBinary());
|
2019-02-19 23:17:03 +07:00
|
|
|
|
|
|
|
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, (...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.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,
|
|
|
|
setTimeout,
|
|
|
|
setInterval,
|
|
|
|
clearTimeout,
|
|
|
|
process: {
|
|
|
|
env: process.env,
|
|
|
|
},
|
2019-01-12 02:58:31 +07:00
|
|
|
};
|
2019-01-30 07:48:02 +07:00
|
|
|
|
2019-02-19 23:17:03 +07:00
|
|
|
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.
|
2019-02-08 00:05:17 +07:00
|
|
|
timeout: message.getTimeout() || 15000,
|
|
|
|
});
|
2019-01-12 02:58:31 +07:00
|
|
|
} catch (ex) {
|
2019-02-19 23:17:03 +07:00
|
|
|
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);
|
2019-01-12 02:58:31 +07:00
|
|
|
}
|
2019-01-30 07:48:02 +07:00
|
|
|
|
|
|
|
return eventEmitter ? {
|
|
|
|
onEvent: (eventMsg: EvalEventMessage): void => {
|
2019-02-19 23:17:03 +07:00
|
|
|
eventEmitter!.emit(eventMsg.getEvent(), ...eventMsg.getArgsList().map(parse));
|
|
|
|
},
|
|
|
|
dispose: (): void => {
|
|
|
|
if (eventEmitter) {
|
|
|
|
if (value && value.dispose) {
|
|
|
|
value.dispose();
|
|
|
|
}
|
|
|
|
eventEmitter.removeAllListeners();
|
|
|
|
eventEmitter = undefined;
|
|
|
|
}
|
2019-01-30 07:48:02 +07:00
|
|
|
},
|
|
|
|
} : undefined;
|
2019-01-19 04:46:40 +07:00
|
|
|
};
|